Commit 26589332 authored by Andreas Traczyk's avatar Andreas Traczyk

video: refactor/separate video rendering logic

- Moves rendering logic from the UI objects to RenderManager.
- RenderManager handles connections to avmodel and provisions
  QImages for UI widgets to manipulate within their paintEvent
  overrides.
- Provides several reusable widget types for displaying previews
  and incoming video.

Change-Id: I090f4866a667ab8538827e4a1d5f1231cb0c26bd
parent 7ce7bd73
......@@ -41,10 +41,8 @@ CallAudioOnlyAvatarOverlay::setAvatarVisible(bool visible)
}
void
CallAudioOnlyAvatarOverlay::writeAvatarOverlay(const std::string& accountId, const lrc::api::conversation::Info& convInfo)
CallAudioOnlyAvatarOverlay::writeAvatarOverlay(const lrc::api::conversation::Info& convInfo)
{
Q_UNUSED(accountId);
auto contact = LRCInstance::getCurrentAccountInfo().contactModel->getContact(convInfo.participants.at(0));
ui->avatarLabel->setPixmap(QPixmap::fromImage(Utils::conversationPhoto(convInfo.uid, LRCInstance::getCurrentAccountInfo())));
......
......@@ -37,7 +37,7 @@ public:
explicit CallAudioOnlyAvatarOverlay(QWidget *parent = nullptr);
~CallAudioOnlyAvatarOverlay();
void setAvatarVisible(bool visible);
void writeAvatarOverlay(const std::string& accountId, const lrc::api::conversation::Info& convInfo);
void writeAvatarOverlay(const lrc::api::conversation::Info& convInfo);
void respondToPauseLabel(bool pauseButtonDisplayed);
private:
......
......@@ -23,15 +23,12 @@
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="ressources.qrc">:/images/jami.ico</pixmap>
</property>
</widget>
</item>
<item alignment="Qt::AlignHCenter|Qt::AlignTop">
<widget class="QLabel" name="nameLabel">
<property name="text">
<string>TextLabel</string>
<string/>
</property>
</widget>
</item>
......
This diff is collapsed.
......@@ -22,19 +22,10 @@
#pragma once
#include <QClipboard>
#include <QItemSelection>
#include <QMenu>
#include <QMovie>
#include <QString>
#include <QVector>
#include <QWidget>
#include "navwidget.h"
#include "smartlistmodel.h"
#include "previewrender.h"
#include "videoview.h"
// new LRC
#include "api/account.h"
#include "api/contact.h"
#include "api/contactmodel.h"
......@@ -43,6 +34,14 @@
#include "api/newcallmodel.h"
#include "globalinstances.h"
#include <QClipboard>
#include <QItemSelection>
#include <QMenu>
#include <QMovie>
#include <QString>
#include <QVector>
#include <QWidget>
class ConversationItemDelegate;
class QPropertyAnimation;
......@@ -58,9 +57,7 @@ public:
explicit CallWidget(QWidget* parent = 0);
~CallWidget();
void restartPreviewWhenSwitchDevice();
int getLeftPanelWidth();
void reconnectRenderingVideoDeviceChanged();
// NavWidget
virtual void navigated(bool to);
......@@ -68,10 +65,8 @@ public:
public slots:
virtual void slotAccountListChanged();
public slots:
private slots:
void on_ringContactLineEdit_returnPressed();
public slots:
void settingsButtonClicked();
void showChatView(const QModelIndex& nodeIdx);
void showChatView(const std::string & accountId, const lrc::api::conversation::Info & convInfo);
......@@ -85,15 +80,14 @@ public slots:
void slotShowCallView(const std::string & accountId, const lrc::api::conversation::Info & convInfo);
void slotShowIncomingCallView(const std::string & accountId, const lrc::api::conversation::Info & convInfo);
void slotShowChatView(const std::string & accountId, const lrc::api::conversation::Info & convInfo);
void slotNewTrustRequest(const std::string& accountId, const std::string& contactUri);
void slotToggleFullScreenClicked();
void slotVideoViewDestroyed(const std::string& callid);
void slotVideoViewTerminating(const std::string& callid);
void update();
void ShowContextMenu(const QPoint& pos);
void Paste();
void Copy();
void CreateCopyPasteContextMenu();
private slots:
void on_acceptButton_clicked();
void on_refuseButton_clicked();
void on_cancelButton_clicked();
......@@ -106,13 +100,11 @@ private slots:
void on_shareButton_clicked();
void on_btnAudioCall_clicked();
void on_btnVideoCall_clicked();
private slots:
void smartListSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
void onIncomingMessage(const std::string & convUid, uint64_t interactionId, const lrc::api::interaction::Info & interaction);
private:
void placeCall();
void onIncomingMessage(const std::string& accountId, const std::string& convUid,
uint64_t interactionId, const lrc::api::interaction::Info& interaction);
void conversationsButtonClicked();
void invitationsButtonClicked();
void setupSmartListContextMenu(const QPoint &pos);
......@@ -142,15 +134,12 @@ private:
QMenu* menu_;
QClipboard* clipboard_;
PreviewRenderWidget* previewRenderer_;
Ui::CallWidget* ui;
QMovie* miniSpinner_;
constexpr static int qrSize_ = 200;
// lrc
Video::Renderer* videoRenderer_;
std::string lastConvUid_ {};
lrc::api::profile::Type currentTypeFilter_{};
std::unique_ptr<SmartListModel> smartListModel_;
......
......@@ -445,12 +445,12 @@
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<attribute name="headerMinimumSectionSize">
<number>0</number>
</attribute>
<attribute name="headerDefaultSectionSize">
<number>42</number>
</attribute>
<attribute name="headerMinimumSectionSize">
<number>0</number>
</attribute>
</widget>
</item>
</layout>
......@@ -1052,14 +1052,7 @@ Copy and share it with your friends!
<number>0</number>
</property>
<item row="0" column="0">
<widget class="VideoView" name="videoWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
<widget class="VideoView" name="videoView" native="true"/>
</item>
</layout>
</widget>
......
......@@ -62,10 +62,11 @@ ContactPicker::accept()
if (idx.isValid()) {
// get current call id and peer uri
auto selectedConvUid = LRCInstance::getSelectedConvUid();
auto convModel = LRCInstance::getCurrentConversationModel();
auto conversation = Utils::getConversationFromUid(selectedConvUid, *convModel);
auto thisCallId = conversation->callId;
auto conversation = LRCInstance::getCurrentConversation();
if (conversation.uid.empty()) {
return;
}
auto thisCallId = conversation.callId;
auto contactUri = idx.data(static_cast<int>(SmartListModel::Role::URI)).value<QString>().toStdString();
// let parent deal with this as this dialog will be destroyed
......@@ -118,14 +119,13 @@ ContactPicker::setType(const Type& type)
[this](const QModelIndex& index, const QRegExp& regexp) {
bool match = regexp.indexIn(index.data(Qt::DisplayRole).toString()) != -1;
auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
auto convModel = LRCInstance::getCurrentConversationModel();
auto conversation = Utils::getConversationFromUid(convUid, *convModel);
if (conversation == convModel->allFilteredConversations().end()) {
auto conversation = LRCInstance::getConversationFromConvUid(convUid);
if (conversation.uid.empty()) {
return false;
}
auto callModel = LRCInstance::getCurrentCallModel();
return match &&
!(callModel->hasCall(conversation->callId) || callModel->hasCall(conversation->confId)) &&
!(callModel->hasCall(conversation.callId) || callModel->hasCall(conversation.confId)) &&
!index.parent().isValid();
});
break;
......
......@@ -81,8 +81,9 @@ ConversationItemDelegate::paint(QPainter* painter
painter->fillRect(option.rect, RingTheme::smartlistHighlight_);
}
auto convUid = index.data(static_cast<int>(SmartListModel::Role::UID)).value<QString>().toStdString();
auto conversation = Utils::getConversationFromUid(convUid, *LRCInstance::getCurrentConversationModel());
if (LRCInstance::getCurrentCallModel()->hasCall(conversation->callId)) {
auto conversation = LRCInstance::getConversationFromConvUid(convUid);
if (conversation.uid.empty()) return;
if (LRCInstance::getCurrentCallModel()->hasCall(conversation.callId)) {
auto color = QColor(RingTheme::blue_.lighter(180));
color.setAlpha(128);
painter->fillRect(option.rect, color);
......
/***************************************************************************
* Copyright (C) 2019 by Savoir-faire Linux *
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
**************************************************************************/
#include "distantwidget.h"
#include "lrcinstance.h"
#include "utils.h"
DistantWidget::DistantWidget(QWidget* parent)
: VideoWidgetBase(Qt::black, parent)
{
}
DistantWidget::~DistantWidget()
{
}
void
DistantWidget::paintEvent(QPaintEvent* e)
{
Q_UNUSED(e);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
auto distantImage = LRCInstance::renderer()->getFrame(id_);
if (distantImage) {
auto scaledDistant = distantImage->scaled(size(), Qt::KeepAspectRatio);
auto xDiff = (width() - scaledDistant.width()) / 2;
auto yDiff = (height() - scaledDistant.height()) / 2;
painter.drawImage(QRect(xDiff, yDiff,
scaledDistant.width(), scaledDistant.height()),
scaledDistant);
}
painter.end();
}
void
DistantWidget::setRendererId(const std::string& id)
{
id_ = id;
}
/***************************************************************************
* Copyright (C) 2015-2019 by Savoir-faire Linux *
* Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
* Copyright (C) 2019 by Savoir-faire Linux *
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> *
* *
* This program is free software; you can redistribute it and/or modify *
......@@ -19,46 +18,24 @@
#pragma once
#include <QWidget>
#include <QPainter>
#include <QMutex>
#include <memory>
#include <array>
#include "videowidgetbase.h"
#include "lrcinstance.h"
using namespace lrc::api;
#include <QPainter>
#include <QWidget>
class VideoWidget : public QWidget
{
class DistantWidget : public VideoWidgetBase {
Q_OBJECT;
public:
explicit VideoWidget(QWidget* parent = 0);
~VideoWidget();
void connectDistantRendering();
void disconnectRendering();
explicit DistantWidget(QWidget* parent = 0);
~DistantWidget();
void setRendererId(const std::string& id);
protected:
void paintEvent(QPaintEvent* e);
public slots:
void slotToggleFullScreenClicked();
void slotDistantRendererStarted(const std::string& id = {});
void slotUpdateDistantView(const std::string& id = {});
void slotStopDistantView(const std::string& id = {});
void renderFrame(const std::string& id);
private:
struct rendererDistantConnections {
QMetaObject::Connection started, stopped, updated;
} rendererDistantConnections_;
video::Renderer* distantRenderer_;
video::Frame distantFrame_;
std::unique_ptr<QImage> distantImage_;
std::vector<uint8_t> frameDistant_;
std::string id_;
QMutex mutex_;
};
......@@ -75,7 +75,10 @@ HEADERS += ./aboutdialog.h \
./callaudioonlyavataroverlay.h \
./overlaybutton.h \
./accountmigrationdialog.h \
./previewrender.h
./previewwidget.h \
./rendermanager.h \
./distantwidget.h \
./videowidgetbase.h
SOURCES += ./aboutdialog.cpp \
./banneditemwidget.cpp \
./conversationsfilterwidget.cpp \
......@@ -132,8 +135,10 @@ SOURCES += ./aboutdialog.cpp \
./callaudioonlyavataroverlay.cpp \
./overlaybutton.cpp \
./accountmigrationdialog.cpp \
./previewrender.cpp \
./lrcinstance.cpp
./previewwidget.cpp \
./rendermanager.cpp \
./distantwidget.cpp \
./videowidgetbase.cpp
FORMS += ./aboutdialog.ui \
./advancedsipsettingwidget.ui \
./callwidget.ui \
......@@ -146,7 +151,6 @@ FORMS += ./aboutdialog.ui \
./contactpicker.ui \
./invitebuttonswidget.ui \
./nameregistrationdialog.ui \
./photoboothdialog.ui \
./settingswidget.ui \
./videooverlay.ui \
./advancedsettingswidget.ui \
......@@ -159,6 +163,5 @@ FORMS += ./aboutdialog.ui \
./videoview.ui \
./sipkeypad.ui \
./callaudioonlyavataroverlay.ui \
./accountmigration.ui \
./previewrender.ui
./accountmigration.ui
RESOURCES += ressources.qrc
/**************************************************************************
| Copyright (C) 2019 by Savoir-faire Linux |
| Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <https://www.gnu.org/licenses/>. |
**************************************************************************/
#include "lrcinstance.h"
FrameWrapper::FrameWrapper(bool isPreview) :
isPreview_(isPreview)
{
}
FrameWrapper::~FrameWrapper()
{
}
void
FrameWrapper::connectPreviewRendering()
{
QObject::disconnect(frameWrapperConnections_.started);
if (isPreview_) {
frameWrapperConnections_.started = connect(
&LRCInstance::avModel(),
&lrc::api::AVModel::rendererStarted,
this,
&FrameWrapper::slotPreviewStarted);
}
}
void
FrameWrapper::slotPreviewStarted(const std::string& id)
{
if (id != lrc::api::video::PREVIEW_RENDERER_ID)
return;
QObject::disconnect(frameWrapperConnections_.started);
QObject::disconnect(frameWrapperConnections_.updated);
frameWrapperConnections_.updated = connect(
&LRCInstance::avModel(),
&lrc::api::AVModel::frameUpdated,
this,
&FrameWrapper::slotPreviewUpdated);
QObject::disconnect(frameWrapperConnections_.stopped);
frameWrapperConnections_.stopped = connect(
&LRCInstance::avModel(),
&lrc::api::AVModel::rendererStopped,
this,
&FrameWrapper::slotPreviewStoped);
}
void
FrameWrapper::slotPreviewUpdated(const std::string& id)
{
if (id != lrc::api::video::PREVIEW_RENDERER_ID)
return;
auto avModel = &LRCInstance::avModel();
auto renderer = &avModel->getRenderer(id);
if (!renderer->isRendering()) {
return;
}
previewRenderer_ = const_cast<lrc::api::video::Renderer*>(renderer);
renderFrame(id);
}
void
FrameWrapper::renderFrame(const std::string& id)
{
auto avModel = &LRCInstance::avModel();
using namespace lrc::api::video;
auto renderer = &avModel->getRenderer(id);
if (renderer && renderer->isRendering()) {
{
QMutexLocker lock(&mutex_);
auto tmp = renderer->currentFrame();
if (tmp.storage.size()) {
previewFrame_ = tmp;
}
}
emit previewRenderReady();
}
}
void
FrameWrapper::slotPreviewStoped(const std::string& id)
{
if (id != lrc::api::video::PREVIEW_RENDERER_ID)
return;
QObject::disconnect(frameWrapperConnections_.updated);
QObject::disconnect(frameWrapperConnections_.stopped);
previewRenderer_ = nullptr;
emit previewRenderStopped();
}
RenderDistributer::RenderDistributer()
{
previewFrameWrapper_ = std::make_unique<FrameWrapper>(true);
connect(previewFrameWrapper_.get(), &FrameWrapper::previewRenderReady,
[this]() {
emit previewRenderReady();
});
connect(previewFrameWrapper_.get(), &FrameWrapper::previewRenderStopped,
[this]() {
emit previewRenderStopped();
});
}
RenderDistributer::~RenderDistributer()
{
previewFrameWrapper_.reset();
}
......@@ -33,6 +33,7 @@
#include "settingskey.h"
#include "accountlistmodel.h"
#include "utils.h"
#include "rendermanager.h"
#include "networkmanager.h"
#include "api/lrc.h"
......@@ -52,68 +53,10 @@
#include <memory>
class FrameWrapper : public QObject
{
Q_OBJECT
public:
FrameWrapper(bool isPreview);
~FrameWrapper();
void connectPreviewRendering();
lrc::api::video::Renderer* getPreviewRenderer() { return previewRenderer_; }
lrc::api::video::Frame getPreviewFrame() { return previewFrame_; }
signals:
void previewRenderReady();
void previewRenderStopped();
private slots:
void slotPreviewStarted(const std::string& id = {});
void slotPreviewUpdated(const std::string& id = {});
void slotPreviewStoped(const std::string& id = {});
private:
bool isPreview_;
lrc::api::video::Renderer* previewRenderer_;
lrc::api::video::Frame previewFrame_;
QMutex mutex_;
struct frameWrapperConnections {
QMetaObject::Connection started, stopped, updated;
} frameWrapperConnections_;
void renderFrame(const std::string& id);
};
class RenderDistributer : public QObject
{
Q_OBJECT
public:
RenderDistributer();
~RenderDistributer();
lrc::api::video::Renderer* getPreviewRenderer() { return previewFrameWrapper_->getPreviewRenderer(); }
lrc::api::video::Frame getPreviewFrame() { return previewFrameWrapper_->getPreviewFrame(); }
void connectPreviewRendering() { previewFrameWrapper_->connectPreviewRendering(); }
signals:
void previewRenderReady();
void previewRenderStopped();
private:
// one preview to rule them all
std::unique_ptr<FrameWrapper> previewFrameWrapper_;
// distant for each call/conf/conversation
//std::map<std::string, std::unique_ptr<FrameWrapper>> distantFrames_;
};
using namespace lrc::api;
using migrateCallback = std::function<void()>;
using getConvPredicate = std::function<bool(const conversation::Info& conv)>;
class LRCInstance : public QObject
{
......@@ -132,7 +75,7 @@ public:
static Lrc& getAPI() {
return *(instance().lrc_);
};
static RenderDistributer* getRenderDistributer() {
static RenderManager* renderer() {
return instance().renderer_.get();
}
static void connectivityChanged() {
......@@ -156,17 +99,102 @@ public:
static std::vector<std::string> getActiveCalls() {
return instance().lrc_->activeCalls();
};
static const account::Info&
getAccountInfo(const std::string& accountId) {
return accountModel().getAccountInfo(accountId);
};
static const account::Info&
getCurrentAccountInfo() {
try {
return accountModel().getAccountInfo(getCurrAccId());
} catch (...) {
static account::Info invalid = {};
qWarning() << "getAccountInfo exception";
return invalid;
return getAccountInfo(getCurrAccId());
};
static bool hasVideoCall() {
auto activeCalls = instance().lrc_->activeCalls();
auto accountList = accountModel().getAccountList();
bool result = false;
for (const auto& callId : activeCalls) {
for (const auto& accountId : accountList) {
auto& accountInfo = accountModel().getAccountInfo(accountId);
if (accountInfo.callModel->hasCall(callId)) {
result |= !accountInfo.callModel->getCall(callId).isAudioOnly;
}
}
}
return result;
};
static const call::Info*
getCallInfoForConversation(const conversation::Info& convInfo) {
try {
auto accountId = convInfo.accountId;
auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId);
if (!accInfo.callModel->hasCall(convInfo.callId)) {
return nullptr;
}
return &accInfo.callModel->getCall(convInfo.callId);
} catch(...) {
return nullptr;
}
}
static const conversation::Info&
getConversation(const std::string& accountId,
getConvPredicate pred = {},
bool filtered=false) {
using namespace lrc::api;
static conversation::Info invalid = {};
try {
auto& accInfo = LRCInstance::getAccountInfo(accountId);
auto& convModel = accInfo.conversationModel;
if (filtered) {
auto& convs = convModel->allFilteredConversations();
auto conv = std::find_if(convs.begin(), convs.end(), pred);
if (conv != convs.end()) {
return *conv;
}
} else {
for (int i = Utils::toUnderlyingValue(profile::Type::RING);
i <= Utils::toUnderlyingValue(profile::Type::TEMPORARY);
++i) {
auto filter = Utils::toEnum<profile::Type>(i);
auto& convs = convModel->getFilteredConversations(filter);
auto conv = std::find_if(convs.begin(), convs.end(), pred);
if (conv != convs.end()) {
return *conv;
}
}
}
} catch(...) {}
return invalid;
}
static const conversation::Info&
getConversationFromCallId(const std::string& callId,
const std::string& accountId = {},
bool filtered = false) {
return getConversation(!accountId.empty() ? accountId : getCurrAccId(),
[&](const conversation::Info& conv) -> bool {
return callId == conv.callId;
}, filtered);
}
static const conversation::Info&
getConversationFromConvUid(const std::string& convUid,
const std::string& accountId = {},
bool filtered = false) {
return getConversation(!accountId.empty() ? accountId : getCurrAccId(),
[&](const conversation::Info& conv) -> bool {
return convUid == conv.uid;
}, filtered);
}
static const conversation::Info&
getConversationFromPeerUri(const std::string& peerUri,
const std::string& accountId = {},
bool filtered = false) {
return getConversation(!accountId.empty() ? accountId : getCurrAccId(),
[&](const conversation::Info& conv) -> bool {
return peerUri == conv.participants[0];
}, filtered);
}
static const conversation::Info&
getCurrentConversation() {
return getConversationFromConvUid(getSelectedConvUid());
}
static ConversationModel*
getCurrentConversationModel() {
......@@ -200,22 +228,13 @@ public:
instance().selectedConvUid_ = convUid;
};
static bool getIfCurrentSelectedCallIsAudioOnly() {
auto isAudioOnly = false;
auto convInfo = Utils::getSelectedConversation();
if (!convInfo.uid.empty()) {
isAudioOnly = LRCInstance::getCurrentCallModel()->getCall(convInfo.callId).isAudioOnly;
}
return isAudioOnly;
};
static void reset(bool newInstance = false) {
if (newInstance) {
instance().renderer_.reset(new RenderManager(avModel()));
instance().lrc_.reset(new Lrc());