diff --git a/linphone-app/resources.qrc b/linphone-app/resources.qrc index cb6a18777..a16d12d70 100644 --- a/linphone-app/resources.qrc +++ b/linphone-app/resources.qrc @@ -400,6 +400,7 @@ ui/views/App/Calls/Conference.qml ui/views/App/Calls/VideoConference.qml ui/views/App/Calls/VideoConferenceActiveSpeaker.qml + ui/views/App/Calls/VideoConferenceFullscreen.qml ui/views/App/Calls/VideoConferenceGrid.qml ui/views/App/Calls/VideoConferenceMenu.qml ui/views/App/Calls/Dialogs/CallSipAddress.qml diff --git a/linphone-app/src/components/camera/Camera.cpp b/linphone-app/src/components/camera/Camera.cpp index 0f8225829..02dcd969f 100644 --- a/linphone-app/src/components/camera/Camera.cpp +++ b/linphone-app/src/components/camera/Camera.cpp @@ -43,6 +43,7 @@ int Camera::mPreviewCounter; // ============================================================================= Camera::Camera (QQuickItem *parent) : QQuickFramebufferObject(parent) { + updateWindowIdLocation(); setTextureFollowsItemSize(true); // The fbo content must be y-mirrored because the ms rendering is y-inverted. setMirrorVertically(true); @@ -60,72 +61,109 @@ Camera::Camera (QQuickItem *parent) : QQuickFramebufferObject(parent) { } Camera::~Camera(){ + qWarning() << "Camera destructor" << this; if(mIsPreview) deactivatePreview(); -// else - // resetWindowId(); + setWindowIdLocation(None); } -void Camera::resetWindowId() { - if(mIsPreview) - CoreManager::getInstance()->getCore()->setNativePreviewWindowId(NULL); - else if( mCallModel && mCallModel->getCall()) - mCallModel->getCall()->setNativeVideoWindowId(NULL); - else if(mParticipantDeviceModel){ - if(mParticipantDeviceModel->getDevice()) - mParticipantDeviceModel->getDevice()->setNativeVideoWindowId(NULL); - }else - CoreManager::getInstance()->getCore()->setNativeVideoWindowId(NULL); +void Camera::resetWindowId() const{ + if(mIsWindowIdSet){ + QQuickFramebufferObject::Renderer * oldRenderer = NULL; + if(mWindowIdLocation == CorePreview){ + oldRenderer = (QQuickFramebufferObject::Renderer *)CoreManager::getInstance()->getCore()->getNativePreviewWindowId(); + if(oldRenderer) + CoreManager::getInstance()->getCore()->setNativePreviewWindowId(NULL); + }else if( mWindowIdLocation == Call){ + if(mCallModel){ + auto call = mCallModel->getCall(); + if( call ){ + oldRenderer = (QQuickFramebufferObject::Renderer *) call->getNativeVideoWindowId(); + if(oldRenderer) + call->setNativeVideoWindowId(NULL); + } + } + }else if(mWindowIdLocation == Device){ + if(mParticipantDeviceModel){ + auto device = mParticipantDeviceModel->getDevice(); + if( device ){ + oldRenderer = (QQuickFramebufferObject::Renderer *)device->getNativeVideoWindowId(); + if(oldRenderer) + mParticipantDeviceModel->getDevice()->setNativeVideoWindowId(NULL); + } + } + }else if( mWindowIdLocation == Core){ + oldRenderer = (QQuickFramebufferObject::Renderer *)CoreManager::getInstance()->getCore()->getNativeVideoWindowId(); + if(oldRenderer) + CoreManager::getInstance()->getCore()->setNativeVideoWindowId(NULL); + } + qWarning() << "Removed " << oldRenderer << " at " << mWindowIdLocation << " for " << this; + mIsWindowIdSet = false; + } } -QQuickFramebufferObject::Renderer *Camera::createRenderer () const { - QQuickFramebufferObject::Renderer * renderer = NULL; +void Camera::setWindowIdLocation(const WindowIdLocation& location){ + if( mWindowIdLocation != location){ + resetWindowId();// Location change: Reset old window ID. + mWindowIdLocation = location; + } +} +void Camera::updateWindowIdLocation(){ bool useDefaultWindow = true; - if(mIsPreview){ - qWarning() << "Setting Camera to Preview"; - renderer = (QQuickFramebufferObject::Renderer *)CoreManager::getInstance()->getCore()->getNativePreviewWindowId(); - if(renderer) - CoreManager::getInstance()->getCore()->setNativePreviewWindowId(NULL);// Reset - renderer=(QQuickFramebufferObject::Renderer *)CoreManager::getInstance()->getCore()->createNativePreviewWindowId(); - if(renderer) - CoreManager::getInstance()->getCore()->setNativePreviewWindowId(renderer); - }else{ + if(mIsPreview) + setWindowIdLocation( WindowIdLocation::CorePreview); + else{ if(mCallModel){ auto call = mCallModel->getCall(); if(call){ - qWarning() << "Setting Camera to CallModel"; - renderer = (QQuickFramebufferObject::Renderer *) call->getNativeVideoWindowId(); - if(renderer) - call->setNativeVideoWindowId(NULL);// Reset - renderer = (QQuickFramebufferObject::Renderer *) call->createNativeVideoWindowId(); - if(renderer) - call->setNativeVideoWindowId(renderer); + setWindowIdLocation( WindowIdLocation::Call); useDefaultWindow = false; } }else if( mParticipantDeviceModel){ auto participantDevice = mParticipantDeviceModel->getDevice(); if(participantDevice){ - qWarning() << "Setting Camera to Participant Device"; - renderer = (QQuickFramebufferObject::Renderer *)participantDevice->getNativeVideoWindowId(); - if(renderer) - participantDevice->setNativeVideoWindowId(NULL);// Reset - qWarning() << "Trying to create new window ID for " << participantDevice->getName().c_str() << ", addr=" << participantDevice->getAddress()->asString().c_str(); - renderer = (QQuickFramebufferObject::Renderer *) participantDevice->createNativeVideoWindowId(); - if(renderer) - participantDevice->setNativeVideoWindowId(renderer); + setWindowIdLocation(WindowIdLocation::Device); useDefaultWindow = false; } } if(useDefaultWindow){ - qWarning() << "Setting Camera to Defaul tWindow"; - renderer = (QQuickFramebufferObject::Renderer *)CoreManager::getInstance()->getCore()->getNativeVideoWindowId(); - if(renderer) - CoreManager::getInstance()->getCore()->setNativeVideoWindowId(NULL); - renderer = (QQuickFramebufferObject::Renderer *) CoreManager::getInstance()->getCore()->createNativeVideoWindowId(); - if(renderer) - CoreManager::getInstance()->getCore()->setNativeVideoWindowId(renderer); + setWindowIdLocation(WindowIdLocation::Core); } } +} + +QQuickFramebufferObject::Renderer *Camera::createRenderer () const { + resetWindowId(); + + QQuickFramebufferObject::Renderer * renderer = NULL; + if(mWindowIdLocation == CorePreview){ + qWarning() << "Setting Camera to Preview"; + renderer=(QQuickFramebufferObject::Renderer *)CoreManager::getInstance()->getCore()->createNativePreviewWindowId(); + if(renderer) + CoreManager::getInstance()->getCore()->setNativePreviewWindowId(renderer); + }else if(mWindowIdLocation == Call){ + auto call = mCallModel->getCall(); + if(call){ + qWarning() << "Setting Camera to CallModel"; + renderer = (QQuickFramebufferObject::Renderer *) call->createNativeVideoWindowId(); + if(renderer) + call->setNativeVideoWindowId(renderer); + } + }else if( mWindowIdLocation == Device) { + auto participantDevice = mParticipantDeviceModel->getDevice(); + if(participantDevice){ + qWarning() << "Setting Camera to Participant Device"; + qWarning() << "Trying to create new window ID for " << participantDevice->getName().c_str() << ", addr=" << participantDevice->getAddress()->asString().c_str(); + renderer = (QQuickFramebufferObject::Renderer *) participantDevice->createNativeVideoWindowId(); + if(renderer) + participantDevice->setNativeVideoWindowId(renderer); + } + }else if( mWindowIdLocation == Core){ + qWarning() << "Setting Camera to Default Window"; + renderer = (QQuickFramebufferObject::Renderer *) CoreManager::getInstance()->getCore()->createNativeVideoWindowId(); + if(renderer) + CoreManager::getInstance()->getCore()->setNativeVideoWindowId(renderer); + } if( !renderer){ QTimer::singleShot(1, this, &Camera::isNotReady);// Workaround for const createRenderer qWarning() << "Camera stream couldn't start for Rendering. Retrying in 1s"; @@ -133,6 +171,8 @@ QQuickFramebufferObject::Renderer *Camera::createRenderer () const { QTimer::singleShot(1000, this, &Camera::requestNewRenderer); }else{ + mIsWindowIdSet = true; + qWarning() << "Added " << renderer << " at " << mWindowIdLocation << " for " << this; QTimer::singleShot(1, this, &Camera::isReady);// Workaround for const createRenderer } return renderer; @@ -159,6 +199,7 @@ ParticipantDeviceModel * Camera::getParticipantDeviceModel() const{ void Camera::setCallModel (CallModel *callModel) { if (mCallModel != callModel) { mCallModel = callModel; + updateWindowIdLocation(); update(); emit callChanged(mCallModel); @@ -172,6 +213,7 @@ void Camera::setIsPreview (bool status) { activatePreview(); else deactivatePreview(); + updateWindowIdLocation(); update(); emit isPreviewChanged(status); @@ -188,6 +230,7 @@ void Camera::setIsReady(bool status) { void Camera::setParticipantDeviceModel(ParticipantDeviceModel * participantDeviceModel){ if (mParticipantDeviceModel != participantDeviceModel) { mParticipantDeviceModel = participantDeviceModel; + updateWindowIdLocation(); update(); emit participantDeviceModelChanged(mParticipantDeviceModel); } @@ -214,6 +257,5 @@ void Camera::deactivatePreview(){ if (--mPreviewCounter == 0) core->enableVideoPreview(false); mPreviewCounterMutex.unlock(); - core->setNativePreviewWindowId(NULL); } -} \ No newline at end of file +} diff --git a/linphone-app/src/components/camera/Camera.hpp b/linphone-app/src/components/camera/Camera.hpp index ed5c54de7..6b0d79542 100644 --- a/linphone-app/src/components/camera/Camera.hpp +++ b/linphone-app/src/components/camera/Camera.hpp @@ -44,6 +44,14 @@ class Camera : public QQuickFramebufferObject { Q_PROPERTY(ParticipantDeviceModel * participantDeviceModel READ getParticipantDeviceModel WRITE setParticipantDeviceModel NOTIFY participantDeviceModelChanged) Q_PROPERTY(bool isPreview READ getIsPreview WRITE setIsPreview NOTIFY isPreviewChanged); Q_PROPERTY(bool isReady READ getIsReady WRITE setIsReady NOTIFY isReadyChanged); + + typedef enum{ + None = -1, + CorePreview = 0, + Call, + Device, + Core + }WindowIdLocation; public: Camera (QQuickItem *parent = Q_NULLPTR); @@ -51,7 +59,7 @@ public: QQuickFramebufferObject::Renderer *createRenderer () const override; - Q_INVOKABLE void resetWindowId(); + Q_INVOKABLE void resetWindowId() const; // const to be used from createRenderer() static QMutex mPreviewCounterMutex; static int mPreviewCounter; @@ -76,14 +84,19 @@ private: void setIsPreview (bool status); void setIsReady(bool status); void setParticipantDeviceModel(ParticipantDeviceModel * participantDeviceModel); + void setWindowIdLocation(const WindowIdLocation& location); void activatePreview(); void deactivatePreview(); + void updateWindowIdLocation(); bool mIsPreview = false; bool mIsReady = false; CallModel *mCallModel = nullptr; ParticipantDeviceModel *mParticipantDeviceModel = nullptr; + + WindowIdLocation mWindowIdLocation = None; + mutable bool mIsWindowIdSet = false; QTimer *mRefreshTimer = nullptr; }; diff --git a/linphone-app/ui/modules/Linphone/Camera/CameraItem.qml b/linphone-app/ui/modules/Linphone/Camera/CameraItem.qml index eaecc263c..2bf5c854e 100644 --- a/linphone-app/ui/modules/Linphone/Camera/CameraItem.qml +++ b/linphone-app/ui/modules/Linphone/Camera/CameraItem.qml @@ -78,7 +78,7 @@ Item { isPreview: container.isPreview onRequestNewRenderer: {resetTimer.resetActive()} - Component.onDestruction: {resetWindowId(); console.log("Destroyed Camera [" + isPreview + "] : " + camera)} + Component.onDestruction: {/*resetWindowId();*/ console.log("Destroyed Camera [" + isPreview + "] : " + camera)} Component.onCompleted: console.log("Completed Camera [" + isPreview + "] : " + camera) } } diff --git a/linphone-app/ui/views/App/Calls/Incall.js b/linphone-app/ui/views/App/Calls/Incall.js index 9093e80bd..29143ea97 100644 --- a/linphone-app/ui/views/App/Calls/Incall.js +++ b/linphone-app/ui/views/App/Calls/Incall.js @@ -123,27 +123,28 @@ function openMediaParameters (window, incall) { call: incall.call }) } - -function showFullscreen (position) { - incall.isFullScreen = true - if (incall._fullscreen) { - incall._fullscreen.raise() +// callerId = incall, qmlFile = 'IncallFullscreenWindow.qml' +// callerId need to have : _fullscreen and isFullScreen +function showFullscreen (window, callerId, qmlFile, position) { + callerId.isFullScreen = true + if (callerId._fullscreen) { + callerId._fullscreen.raise() return } DesktopTools.DesktopTools.screenSaverStatus = false var parameters = { - caller: incall, + caller: callerId, x:position.x, y:position.y, width:window.width, height:window.height, window:window } - incall._fullscreen = Utils.openWindow(Qt.resolvedUrl('IncallFullscreenWindow.qml'), parameters.window, { + callerId._fullscreen = Utils.openWindow(Qt.resolvedUrl(qmlFile), parameters.window, { properties: parameters }, true) - if(incall._fullscreen) { - incall._fullscreen.cameraIsReady = Qt.binding(function(){ return !incall.cameraIsReady}) - incall._fullscreen.previewIsReady = Qt.binding(function(){ return !incall.previewIsReady}) + if(callerId._fullscreen) { + callerId._fullscreen.cameraIsReady = Qt.binding(function(){ return !callerId.cameraIsReady}) + callerId._fullscreen.previewIsReady = Qt.binding(function(){ return !callerId.previewIsReady}) } } diff --git a/linphone-app/ui/views/App/Calls/Incall.qml b/linphone-app/ui/views/App/Calls/Incall.qml index e1c6cf78d..cfcfc09c3 100644 --- a/linphone-app/ui/views/App/Calls/Incall.qml +++ b/linphone-app/ui/views/App/Calls/Incall.qml @@ -220,7 +220,7 @@ Rectangle { colorSet: CallStyle.buttons.fullscreen visible: incall.call.videoEnabled - onClicked: Logic.showFullscreen(contactDescription.mapToGlobal(0,0)) + onClicked: Logic.showFullscreen(window, incall, 'IncallFullscreenWindow.qml', contactDescription.mapToGlobal(0,0)) } } } diff --git a/linphone-app/ui/views/App/Calls/VideoConference.qml b/linphone-app/ui/views/App/Calls/VideoConference.qml index a7cce487e..1ff49b029 100644 --- a/linphone-app/ui/views/App/Calls/VideoConference.qml +++ b/linphone-app/ui/views/App/Calls/VideoConference.qml @@ -25,7 +25,12 @@ Rectangle { property CallModel callModel property ConferenceModel conferenceModel: callModel && callModel.getConferenceModel() + property bool cameraIsReady : false + property bool previewIsReady : false + property bool isFullScreen: false // Use this variable to test if we are in fullscreen. Do not test _fullscreen : we need to clean memory before having the window (see .js file) property var _fullscreen: null + on_FullscreenChanged: if( !_fullscreen) isFullScreen = false + property bool listCallsOpened: true signal openListCallsRequest() @@ -115,6 +120,7 @@ Rectangle { } // Title Text{ + id: title Timer{ id: elapsedTimeRefresher running: true @@ -152,7 +158,8 @@ Rectangle { isCustom: true backgroundRadius: width/2 colorSet: VideoConferenceStyle.buttons.fullscreen - visible: false //TODO + visible: conference.callModel.videoEnabled + onClicked: Logic.showFullscreen(window, conference, 'VideoConferenceFullscreen.qml', title.mapToGlobal(0,0)) } } @@ -184,6 +191,7 @@ Rectangle { anchors.leftMargin: 70 anchors.rightMargin: rightMenu.visible ? 15 : 70 callModel: conference.callModel + isFullScreen: conference.isFullScreen } } Component{ @@ -193,6 +201,7 @@ Rectangle { callModel: conference.callModel isRightReducedLayout: rightMenu.visible isLeftReducedLayout: conference.listCallsOpened + isFullScreen: conference.isFullScreen } } RowLayout{ diff --git a/linphone-app/ui/views/App/Calls/VideoConferenceActiveSpeaker.qml b/linphone-app/ui/views/App/Calls/VideoConferenceActiveSpeaker.qml index 13d66c64a..b9f9fc075 100644 --- a/linphone-app/ui/views/App/Calls/VideoConferenceActiveSpeaker.qml +++ b/linphone-app/ui/views/App/Calls/VideoConferenceActiveSpeaker.qml @@ -24,6 +24,7 @@ Item { property alias callModel: allDevices.callModel property bool isRightReducedLayout: false property bool isLeftReducedLayout: false + property bool isFullScreen: false property alias showMe : allDevices.showMe property int participantCount: allDevices.count @@ -39,11 +40,13 @@ Item { CameraView{ id: cameraView callModel: mainItem.callModel + enabled: !mainItem.isFullScreen isCameraFromDevice: false + isPreview: false anchors.fill: parent anchors.leftMargin: isRightReducedLayout || isLeftReducedLayout? 30 : 140 anchors.rightMargin: isRightReducedLayout ? 10 : 140 - isPaused: callModel.pausedByUser || currentDevice && currentDevice.isPaused //callModel.pausedByUser + isPaused: (callModel && callModel.pausedByUser) || (currentDevice && currentDevice.isPaused) //callModel.pausedByUser showCloseButton: false color: 'black' } @@ -71,7 +74,7 @@ Item { anchors.centerIn: parent height: miniViews.cellHeight - 6 width: miniViews.width - 6 - enabled: index >=0 + enabled: index >=0 && !mainItem.isFullScreen currentDevice: modelData callModel: mainItem.callModel isCameraFromDevice: true diff --git a/linphone-app/ui/views/App/Calls/VideoConferenceFullscreen.qml b/linphone-app/ui/views/App/Calls/VideoConferenceFullscreen.qml new file mode 100644 index 000000000..c6b09407d --- /dev/null +++ b/linphone-app/ui/views/App/Calls/VideoConferenceFullscreen.qml @@ -0,0 +1,482 @@ +import QtQuick 2.7 +import QtQuick.Layouts 1.3 +import QtQml.Models 2.12 +import QtGraphicalEffects 1.12 + +import Common 1.0 +import Common.Styles 1.0 +import Linphone 1.0 +import LinphoneUtils 1.0 + +import DesktopTools 1.0 +import LinphoneEnums 1.0 +import UtilsCpp 1.0 + + +import App.Styles 1.0 + + +// Temp +import 'Incall.js' as Logic +import 'qrc:/ui/scripts/Utils/utils.js' as Utils + +Window { + id: window + + // --------------------------------------------------------------------------- + + property alias callModel: conference.callModel + property var caller + property bool hideButtons: !hideButtonsTimer.running + property bool cameraIsReady : false + property bool previewIsReady : false + + // --------------------------------------------------------------------------- + + function exit (cb) { + DesktopTools.screenSaverStatus = true + // `exit` is called by `Incall.qml`. + // The `window` id can be null if the window was closed in this view. + if (!window) { + return + } + if(!window.close() && parent) + parent.close() + if (cb) { + cb() + } + } + + // --------------------------------------------------------------------------- + onCallModelChanged: if(!callModel) window.exit() + Component.onCompleted: { + window.callModel = caller.callModel + } + // --------------------------------------------------------------------------- + + Shortcut { + sequence: StandardKey.Close + onActivated: window.exit() + } + + // --------------------------------------------------------------------------- + // ============================================================================= + + Rectangle { + id: conference + + property CallModel callModel + property ConferenceModel conferenceModel: callModel && callModel.getConferenceModel() + property var _fullscreen: null + property bool listCallsOpened: true + + signal openListCallsRequest() + // --------------------------------------------------------------------------- + anchors.fill: parent + focus: true + + Keys.onEscapePressed: window.exit() + color: VideoConferenceStyle.backgroundColor + + Connections { + target: callModel + + onCameraFirstFrameReceived: Logic.handleCameraFirstFrameReceived(width, height) + onStatusChanged: Logic.handleStatusChanged (status) + onVideoRequested: Logic.handleVideoRequested(callModel) + } + + // --------------------------------------------------------------------------- + Rectangle{ + anchors.fill: parent + visible: callModel.pausedByUser + color: VideoConferenceStyle.pauseArea.backgroundColor + z: 1 + ColumnLayout{ + anchors.fill: parent + spacing: 10 + Item{ + Layout.fillWidth: true + Layout.fillHeight: true + } + ActionButton{ + Layout.alignment: Qt.AlignCenter + isCustom: true + colorSet: VideoConferenceStyle.pauseArea.play + backgroundRadius: width/2 + onClicked: callModel.pausedByUser = !callModel.pausedByUser + } + Text{ + Layout.alignment: Qt.AlignCenter + text: 'Vous ĂȘtes actuellement en dehors de la confĂ©rence.' + font.pointSize: VideoConferenceStyle.pauseArea.title.pointSize + font.weight: VideoConferenceStyle.pauseArea.title.weight + color: VideoConferenceStyle.pauseArea.title.color + } + Text{ + Layout.alignment: Qt.AlignCenter + text: 'Cliquez sur le bouton "play" pour la rejoindre.' + font.pointSize: VideoConferenceStyle.pauseArea.description.pointSize + font.weight: VideoConferenceStyle.pauseArea.description.weight + color: VideoConferenceStyle.pauseArea.description.color + } + Item{ + Layout.fillWidth: true + Layout.preferredHeight: 140 + } + } + } + + // ------------------------------------------------------------------------- + // Conference info. + // ------------------------------------------------------------------------- + RowLayout{ + id: featuresRow + // Aux features + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 10 + anchors.leftMargin: 25 + anchors.rightMargin: 25 + spacing: 10 + + visible: !window.hideButtons + /* + ActionButton{ + isCustom: true + backgroundRadius: width/2 + colorSet: VideoConferenceStyle.buttons.callsList + visible: !listCallsOpened && !window.hideButtons + onClicked: openListCallsRequest() + }*/ + ActionButton{ + id: keypadButton + isCustom: true + backgroundRadius: width/2 + colorSet: VideoConferenceStyle.buttons.dialpad + onClicked: telKeypad.visible = !telKeypad.visible + + visible: !window.hideButtons + } + // Title + Text{ + Timer{ + id: elapsedTimeRefresher + running: true + interval: 1000 + repeat: true + onTriggered: if(conference.conferenceModel) parent.elaspedTime = ' - ' +Utils.formatElapsedTime(conference.conferenceModel.getElapsedSeconds()) + } + property string elaspedTime + horizontalAlignment: Qt.AlignHCenter + Layout.fillWidth: true + text: conference.conferenceModel ? conference.conferenceModel.subject+ elaspedTime : '' + color: VideoConferenceStyle.title.color + font.pointSize: VideoConferenceStyle.title.pointSize + } + // Mode buttons + ActionButton{ + isCustom: true + backgroundRadius: width/2 + colorSet: VideoConferenceStyle.buttons.screenSharing + visible: false //TODO + } + ActionButton{ + isCustom: true + backgroundRadius: width/2 + colorSet: VideoConferenceStyle.buttons.recordOff + visible: false //TODO + } + ActionButton{ + isCustom: true + backgroundRadius: width/2 + colorSet: VideoConferenceStyle.buttons.screenshot + visible: false //TODO + } + ActionButton{ + isCustom: true + backgroundRadius: width/2 + colorSet: VideoConferenceStyle.buttons.fullscreen + onClicked: window.exit() + } + + } + + // ------------------------------------------------------------------------- + // Contacts visual. + // ------------------------------------------------------------------------- + + MouseArea{ + id: mainGrid + anchors.top: featuresRow.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: actionsButtons.top + + anchors.topMargin: 15 + anchors.bottomMargin: 20 + onClicked: { + if(!conference.callModel) + grid.add({color: '#'+ Math.floor(Math.random()*255).toString(16) + +Math.floor(Math.random()*255).toString(16) + +Math.floor(Math.random()*255).toString(16)}) + } + + Component{ + id: gridComponent + VideoConferenceGrid{ + id: grid + anchors.leftMargin: 70 + anchors.rightMargin: rightMenu.visible ? 15 : 70 + callModel: conference.callModel + } + } + Component{ + id: activeSpeakerComponent + VideoConferenceActiveSpeaker{ + id: activeSpeaker + callModel: conference.callModel + isRightReducedLayout: rightMenu.visible + isLeftReducedLayout: conference.listCallsOpened + } + } + RowLayout{ + anchors.fill: parent + Loader{ + id: conferenceLayout + Layout.fillHeight: true + Layout.fillWidth: true + sourceComponent: conference.callModel.conferenceVideoLayout == LinphoneEnums.ConferenceLayoutGrid || !conference.callModel.videoEnabled? gridComponent : activeSpeakerComponent + onSourceComponentChanged: console.log(conference.callModel.conferenceVideoLayout) + active: conference.callModel + ColumnLayout { + anchors.fill: parent + visible: !conference.callModel || !conferenceLayout.item || conferenceLayout.item.participantCount == 0 + BusyIndicator{ + Layout.preferredHeight: 50 + Layout.preferredWidth: 50 + Layout.alignment: Qt.AlignCenter + running: parent.visible + color: VideoConferenceStyle.buzyColor + } + Text{ + Layout.alignment: Qt.AlignCenter + text: "Video conference is not ready. Please Wait..." + color: VideoConferenceStyle.buzyColor + } + } + } + VideoConferenceMenu{ + id: rightMenu + Layout.fillHeight: true + Layout.preferredWidth: 400 + Layout.rightMargin: 30 + callModel: conference.callModel + visible: false + onClose: rightMenu.visible = !rightMenu.visible + } + } + } + // ------------------------------------------------------------------------- + // Action Buttons. + // ------------------------------------------------------------------------- + + // Security + ActionButton{ + visible: false // TODO + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.bottomMargin: 30 + anchors.leftMargin: 25 + height: VideoConferenceStyle.buttons.secure.buttonSize + width: height + isCustom: true + iconIsCustom: false + backgroundRadius: width/2 + colorSet: VideoConferenceStyle.buttons.secure + + icon: 'secure_level_1' + } + // Action buttons + RowLayout{ + id: actionsButtons + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 30 + height: 60 + spacing: 30 + z: 2 + visible: !window.hideButtons + RowLayout{ + spacing: 10 + Row { + spacing: 2 + visible: SettingsModel.muteMicrophoneEnabled + property bool microMuted: callModel.microMuted + + VuMeter { + enabled: !parent.microMuted + Timer { + interval: 50 + repeat: true + running: parent.enabled + + onTriggered: parent.value = callModel.microVu + } + } + ActionSwitch { + id: micro + isCustom: true + backgroundRadius: 90 + colorSet: parent.microMuted ? VideoConferenceStyle.buttons.microOff : VideoConferenceStyle.buttons.microOn + onClicked: callModel.microMuted = !parent.microMuted + } + } + Row { + spacing: 2 + property bool speakerMuted: callModel.speakerMuted + VuMeter { + enabled: !parent.speakerMuted + Timer { + interval: 50 + repeat: true + running: parent.enabled + onTriggered: parent.value = callModel.speakerVu + } + } + ActionSwitch { + id: speaker + isCustom: true + backgroundRadius: 90 + colorSet: parent.speakerMuted ? VideoConferenceStyle.buttons.speakerOff : VideoConferenceStyle.buttons.speakerOn + onClicked: callModel.speakerMuted = !parent.speakerMuted + } + } + ActionSwitch { + id: camera + isCustom: true + backgroundRadius: 90 + colorSet: callModel && callModel.cameraEnabled ? VideoConferenceStyle.buttons.cameraOn : VideoConferenceStyle.buttons.cameraOff + updating: callModel.videoEnabled && callModel.updating + enabled: callModel.videoEnabled + onClicked: if(callModel) callModel.cameraEnabled = !callModel.cameraEnabled + } + } + RowLayout{ + spacing: 10 + ActionButton{ + isCustom: true + backgroundRadius: width/2 + visible: SettingsModel.callPauseEnabled + updating: callModel.updating + colorSet: callModel.pausedByUser ? VideoConferenceStyle.buttons.play : VideoConferenceStyle.buttons.pause + onClicked: callModel.pausedByUser = !callModel.pausedByUser + } + ActionButton{ + isCustom: true + backgroundRadius: width/2 + colorSet: VideoConferenceStyle.buttons.hangup + + onClicked: callModel.terminate() + } + } + } + + // Panel buttons + RowLayout{ + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: 30 + anchors.rightMargin: 25 + height: 60 + visible: !window.hideButtons + ActionButton{ + isCustom: true + backgroundRadius: width/2 + colorSet: VideoConferenceStyle.buttons.chat + visible: false // TODO for next version + } + ActionButton{ + isCustom: true + backgroundRadius: width/2 + colorSet: VideoConferenceStyle.buttons.participants + visible: false // TODO + } + ActionButton { + id: callQuality + + isCustom: true + backgroundRadius: 4 + colorSet: VideoConferenceStyle.buttons.callQuality + percentageDisplayed: 0 + + onClicked: {Logic.openCallStatistics();} + Timer { + interval: 500 + repeat: true + running: true + triggeredOnStart: true + onTriggered: { + // Note: `quality` is in the [0, 5] interval and -1. + var quality = callModel.quality + if(quality >= 0) + callQuality.percentageDisplayed = quality * 100 / 5 + else + callQuality.percentageDisplayed = 0 + } + } + } + ActionButton{ + isCustom: true + backgroundRadius: width/2 + colorSet: VideoConferenceStyle.buttons.options + onClicked: rightMenu.visible = !rightMenu.visible + } + } + + // --------------------------------------------------------------------------- + // TelKeypad. + // --------------------------------------------------------------------------- + CallStatistics { + id: callStatistics + + call: conference.callModel + width: conference.width - 20 + height: conference.height * 2/3 + relativeTo: conference + relativeY: CallStyle.header.stats.relativeY + relativeX: 10 + onClosed: Logic.handleCallStatisticsClosed() + } + } + TelKeypad { + id: telKeypad + + call: callModel + visible: SettingsModel.showTelKeypadAutomatically + } + MouseArea{ + Timer { + id: hideButtonsTimer + + interval: 5000 + running: true + + onTriggered: {console.log("hideButtons");} + } + + anchors.fill: parent + acceptedButtons: Qt.NoButton + propagateComposedEvents: true + cursorShape: Qt.ArrowCursor + + onEntered: hideButtonsTimer.start() + onExited: hideButtonsTimer.stop() + + onPositionChanged: { + hideButtonsTimer.restart() + } + } +} diff --git a/linphone-app/ui/views/App/Calls/VideoConferenceGrid.qml b/linphone-app/ui/views/App/Calls/VideoConferenceGrid.qml index 88e52ee42..9bb32ad0b 100644 --- a/linphone-app/ui/views/App/Calls/VideoConferenceGrid.qml +++ b/linphone-app/ui/views/App/Calls/VideoConferenceGrid.qml @@ -22,6 +22,7 @@ import 'qrc:/ui/scripts/Utils/utils.js' as Utils Mosaic { id: grid property alias callModel: participantDevices.callModel + property bool isFullScreen: false property int participantCount: gridModel.count anchors.fill: parent squaredDisplay: true @@ -70,7 +71,7 @@ Mosaic { CameraView{ id: cameraView - enabled: index >=0 + enabled: index >=0 && !grid.isFullScreen anchors.fill: parent currentDevice: avatarCell.currentDevice callModel: participantDevices.callModel diff --git a/linphone-sdk b/linphone-sdk index dbc795c83..c8d936196 160000 --- a/linphone-sdk +++ b/linphone-sdk @@ -1 +1 @@ -Subproject commit dbc795c83ef288e5fd27eb4a2a8a1c9da127eb95 +Subproject commit c8d936196b84855415e90d41346e7b3a25374077