diff --git a/linphone-app/src/components/camera/Camera.cpp b/linphone-app/src/components/camera/Camera.cpp index 5f58d7046..bacbd5afd 100644 --- a/linphone-app/src/components/camera/Camera.cpp +++ b/linphone-app/src/components/camera/Camera.cpp @@ -44,6 +44,7 @@ int Camera::mPreviewCounter; // ============================================================================= Camera::Camera (QQuickItem *parent) : QQuickFramebufferObject(parent) { + qDebug() << "[Camera] Camera constructor" << this; updateWindowIdLocation(); setTextureFollowsItemSize(true); // The fbo content must be y-mirrored because the ms rendering is y-inverted. @@ -66,9 +67,11 @@ Camera::Camera (QQuickItem *parent) : QQuickFramebufferObject(parent) { Camera::~Camera(){ qDebug() << "[Camera] Camera destructor" << this; + mRefreshTimer->stop(); + if(mIsPreview) deactivatePreview(); - setWindowIdLocation(None); + setWindowIdLocation(None);// We need to remove the Qt Buffer from SDK ot avoid to reuse it. } void Camera::resetWindowId() const{ @@ -157,8 +160,6 @@ void Camera::removeParticipantDeviceModel(){ } QQuickFramebufferObject::Renderer *Camera::createRenderer () const { - resetWindowId(); - QQuickFramebufferObject::Renderer * renderer = NULL; if(mWindowIdLocation == CorePreview){ qDebug() << "[Camera] Setting Camera to Preview"; diff --git a/linphone-app/ui/modules/Linphone/Camera/CameraItem.qml b/linphone-app/ui/modules/Linphone/Camera/CameraItem.qml index dcb1ea77d..7e0d04d3b 100644 --- a/linphone-app/ui/modules/Linphone/Camera/CameraItem.qml +++ b/linphone-app/ui/modules/Linphone/Camera/CameraItem.qml @@ -22,10 +22,11 @@ Item { property bool isFullscreen: false property bool hideCamera: false property bool isPaused: false - property bool deactivateCamera: false + property bool deactivateCamera: true property bool isVideoEnabled: !deactivateCamera && (!callModel || callModel.videoEnabled) - && (!container.currentDevice || callModel && (container.currentDevice - && (container.currentDevice.videoEnabled || (container.currentDevice.isMe && callModel.cameraEnabled)))) + && (!container.currentDevice || ( callModel && container.currentDevice && + ( (!container.currentDevice.isMe && container.currentDevice.videoEnabled) + || (container.currentDevice.isMe && callModel.cameraEnabled)))) property bool a : callModel && callModel.videoEnabled property bool b: container.currentDevice && container.currentDevice.videoEnabled @@ -51,6 +52,10 @@ Item { anchors.fill: parent active: !resetActive && container.isVideoEnabled + onActiveChanged: { + console.log("QML Camera status : " + active) + } + sourceComponent: container.isVideoEnabled && !container.isPaused? camera : null Timer{ diff --git a/linphone-app/ui/modules/Linphone/Menus/IncallMenu.qml b/linphone-app/ui/modules/Linphone/Menus/IncallMenu.qml index 5a4a550f7..115b87b49 100644 --- a/linphone-app/ui/modules/Linphone/Menus/IncallMenu.qml +++ b/linphone-app/ui/modules/Linphone/Menus/IncallMenu.qml @@ -50,6 +50,14 @@ Rectangle{ onVisibleChanged: if(!visible && contentsStack.nViews > 1) { contentsStack.pop() } + property bool _activateCamera: false + Connections{// Enable camera only when status is ok + target: mainItem.callModel + onStatusChanged: if( mainItem._activateCamera && (status == LinphoneEnums.CallStatusConnected || status == LinphoneEnums.CallStatusIdle)){ + camera._activateCamera = false + callModel.cameraEnabled = true + } + } ButtonGroup{id: modeGroup} ColumnLayout{ anchors.fill: parent @@ -227,6 +235,7 @@ Rectangle{ bottomWidth: IncallMenuStyle.list.border.width Layout.preferredHeight: Math.max(layoutIcon.height, radio.contentItem.implicitHeight) + 20 Layout.fillWidth: true + enabled: mainItem.callModel && !mainItem.callModel.updating RowLayout{ anchors.fill: parent @@ -238,37 +247,16 @@ Rectangle{ Layout.alignment: Qt.AlignVCenter ButtonGroup.group: modeGroup text: modelData.text - property bool isInternallyChecked: mainItem.callModel ? (mainItem.callModel.localVideoEnabled && modelData.value == mainItem.callModel.conferenceVideoLayout) || (!mainItem.callModel.localVideoEnabled && modelData.value == LinphoneEnums.ConferenceLayoutAudioOnly) : false // break bind. Radiobutton checked itself without taking care of custom binding. This workaound works as long as we don't really need the binding. onIsInternallyCheckedChanged: checked = isInternallyChecked Component.onCompleted: checked = isInternallyChecked - Timer{ - id: changingLayoutDelay - interval: 100 - onTriggered: {if(modelData.value == 2) mainItem.callModel.videoEnabled = false - else { - mainItem.callModel.conferenceVideoLayout = modelData.value - mainItem.callModel.videoEnabled = true - } - mainItem.enabled = true - } - } - onClicked:{ - // Do changes only if we choose a different layout. - if(! ( mainItem.callModel ? (mainItem.callModel.localVideoEnabled && modelData.value == mainItem.callModel.conferenceVideoLayout) - || (!mainItem.callModel.localVideoEnabled && modelData.value == LinphoneEnums.ConferenceLayoutAudioOnly) - : false)){ - mainItem.enabled = false - mainItem.layoutChanging(modelData.value)// Let time to clear cameras - changingLayoutDelay.start() - } - } + onClicked: mainItem.layoutChanging(modelData.value) } Icon{ - id: layoutIcon + id: layoutIcon Layout.minimumWidth: iconWidth Layout.rightMargin: 10 Layout.alignment: Qt.AlignVCenter diff --git a/linphone-app/ui/modules/Linphone/Sticker/DecorationSticker.qml b/linphone-app/ui/modules/Linphone/Sticker/DecorationSticker.qml index 4e7410d7c..c00ed4779 100644 --- a/linphone-app/ui/modules/Linphone/Sticker/DecorationSticker.qml +++ b/linphone-app/ui/modules/Linphone/Sticker/DecorationSticker.qml @@ -101,15 +101,20 @@ Item{ color: "#80000000" source: usernameItem } - ActionButton{ - visible: mainItem._showCloseButton && mainItem._isPreview && mainItem._callModel && mainItem._callModel.videoEnabled + Loader{ + id: closeLoader anchors.right: parent.right anchors.top: parent.top anchors.rightMargin: 5 anchors.topMargin: 5 - isCustom: true - colorSet: DecorationStickerStyle.closePreview - onClicked: mainItem.closeRequested() + active: mainItem._showCloseButton && mainItem._isPreview && mainItem._callModel && mainItem._callModel.videoEnabled + sourceComponent: Component{ + ActionButton{ + isCustom: true + colorSet: DecorationStickerStyle.closePreview + onClicked: mainItem.closeRequested() + } + } } ColumnLayout{ anchors.top: parent.top @@ -137,17 +142,22 @@ Item{ iconSize: DecorationStickerStyle.isMuted.button.iconSize } } - BusyIndicator{// Joining spinner + Loader{ + id: busyLoader + property bool delayed : false Layout.preferredHeight: 20 Layout.preferredWidth: 20 - property bool delayed : false - visible: delayed && mainItem._currentDevice && (mainItem._currentDevice.state == LinphoneEnums.ParticipantDeviceStateJoining || mainItem._currentDevice.state == LinphoneEnums.ParticipantDeviceStateScheduledForJoining || mainItem._currentDevice.state == LinphoneEnums.ParticipantDeviceStateAlerting) - Timer{// Delay starting spinner (Qt bug) - id: indicatorDelay - interval: 100 - onTriggered: parent.delayed = true + active: delayed && mainItem._currentDevice && (mainItem._currentDevice.state == LinphoneEnums.ParticipantDeviceStateJoining || mainItem._currentDevice.state == LinphoneEnums.ParticipantDeviceStateScheduledForJoining || mainItem._currentDevice.state == LinphoneEnums.ParticipantDeviceStateAlerting) + sourceComponent: Component{ + BusyIndicator{// Joining spinner + Timer{// Delay starting spinner (Qt bug) + id: indicatorDelay + interval: 100 + onTriggered: busyLoader.delayed = true + } + Component.onCompleted: indicatorDelay.start() + } } - Component.onCompleted: indicatorDelay.start() } } } diff --git a/linphone-app/ui/modules/Linphone/Sticker/Sticker.qml b/linphone-app/ui/modules/Linphone/Sticker/Sticker.qml index 1cd2bee2f..7b9dae4d5 100644 --- a/linphone-app/ui/modules/Linphone/Sticker/Sticker.qml +++ b/linphone-app/ui/modules/Linphone/Sticker/Sticker.qml @@ -35,7 +35,7 @@ Item{ property alias showActiveSpeakerOverlay: camera.showActiveSpeakerOverlay property alias isCameraFromDevice: camera.isCameraFromDevice property alias deactivateCamera: camera.deactivateCamera - property alias isVideoEnabled: camera.isVideoEnabled + readonly property alias isVideoEnabled: camera.isVideoEnabled property alias image: avatar.image property alias avatarBackgroundColor: avatar.avatarBackgroundColor diff --git a/linphone-app/ui/views/App/Calls/Incall.qml b/linphone-app/ui/views/App/Calls/Incall.qml index 00934308c..94fb64b75 100644 --- a/linphone-app/ui/views/App/Calls/Incall.qml +++ b/linphone-app/ui/views/App/Calls/Incall.qml @@ -27,6 +27,7 @@ Rectangle { 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 bool layoutChanging: false property var _fullscreen: null on_FullscreenChanged: if( !_fullscreen) isFullScreen = false @@ -40,7 +41,7 @@ Rectangle { : conferenceLayout.item ? conferenceLayout.item.participantCount : 2 // States - property bool isAudioOnly: callModel && callModel.isConference && conferenceLayout.sourceComponent == gridComponent && !callModel.videoEnabled + property bool isAudioOnly: callModel && callModel.conferenceVideoLayout == LinphoneEnums.ConferenceLayoutAudioOnly property bool isReady : mainItem.callModel && (!mainItem.callModel.isConference || (mainItem.conferenceModel && mainItem.conferenceModel.isReady) @@ -308,7 +309,7 @@ Rectangle { Layout.leftMargin: 70 Layout.rightMargin: rightMenu.visible ? 15 : 70 callModel: mainItem.callModel - cameraEnabled: !mainItem.isFullScreen + cameraEnabled: !mainItem.isFullScreen && !mainItem.layoutChanging } } Component{ @@ -318,7 +319,7 @@ Rectangle { callModel: mainItem.callModel isRightReducedLayout: rightMenu.visible isLeftReducedLayout: mainItem.listCallsOpened - cameraEnabled: !mainItem.isFullScreen + cameraEnabled: !mainItem.isFullScreen && !mainItem.layoutChanging } } RowLayout{ @@ -328,12 +329,50 @@ Rectangle { Layout.fillWidth: true Loader{ id: conferenceLayout - anchors.fill: parent - sourceComponent: mainItem.conferenceModel - ? mainItem.callModel.conferenceVideoLayout == LinphoneEnums.ConferenceLayoutActiveSpeaker - ? activeSpeakerComponent - : gridComponent - : activeSpeakerComponent + anchors.fill: parent + + Timer{// Avoid Qt crashes when layout changes while videos are on + id: layoutDelay + interval: 100 + property int step : 0 + property var layoutMode + onTriggered: { + switch(step){ + case 2 : step = 0; mainItem.layoutChanging = false; break; + case 1: ++step; conferenceLayout.sourceComponent = conferenceLayout.getLayout(); layoutDelay.restart(); break; + case 0: if( mainItem.callModel.conferenceVideoLayout != layoutMode) + mainItem.callModel.conferenceVideoLayout = layoutMode + else { + ++step; + layoutDelay.restart() + } + break; + } + } + function begin(layoutMode){ + step = 0 + layoutDelay.layoutMode = layoutMode + mainItem.layoutChanging = true + layoutDelay.restart() + } + } + function getLayout(){ + return mainItem.conferenceModel + ? mainItem.callModel.conferenceVideoLayout == LinphoneEnums.ConferenceLayoutActiveSpeaker + ? activeSpeakerComponent + : gridComponent + : activeSpeakerComponent + } + + Connections{ + target: mainItem.callModel + + onConferenceVideoLayoutChanged: { + layoutDelay.layoutMode = mainItem.callModel.conferenceVideoLayout + layoutDelay.restart() + } + } + sourceComponent: getLayout() active: mainItem.callModel && !mainItem.isFullScreen } Rectangle{ @@ -342,12 +381,16 @@ Rectangle { visible: !mainItem.isReady ColumnLayout { anchors.fill: parent - BusyIndicator{ + Loader{ Layout.preferredHeight: 40 Layout.preferredWidth: 40 Layout.alignment: Qt.AlignCenter - running: parent.visible - color: IncallStyle.buzyColor + active: parent.visible + sourceComponent: Component{ + BusyIndicator{ + color: IncallStyle.buzyColor + } + } } Text{ Layout.alignment: Qt.AlignCenter @@ -370,8 +413,11 @@ Rectangle { callModel: mainItem.callModel conferenceModel: mainItem.conferenceModel visible: false + enabled: !mainItem.layoutChanging onClose: rightMenu.visible = !rightMenu.visible - onLayoutChanging: conferenceLayout.item.clearAll(layoutMode) + onLayoutChanging: { + layoutDelay.begin(layoutMode) + } } } } @@ -487,14 +533,14 @@ Rectangle { isCustom: true backgroundRadius: 90 colorSet: callModel && callModel.cameraEnabled ? IncallStyle.buttons.cameraOn : IncallStyle.buttons.cameraOff - updating: callModel.videoEnabled && callModel.updating + updating: callModel.videoEnabled && callModel.updating && !mainItem.layoutChanging property bool _activateCamera: false - onClicked: if(callModel){ + onClicked: if(callModel && !mainItem.layoutChanging){ if( callModel.isConference){// Only deactivate camera in conference. - if(mainItem.isAudioOnly && SettingsModel.videoConferenceLayout != 2) { + if(mainItem.isAudioOnly) { + var layout = SettingsModel.videoConferenceLayout != LinphoneEnums.ConferenceLayoutAudioOnly ? SettingsModel.videoConferenceLayout : LinphoneEnums.ConferenceLayoutGrid + layoutDelay.being(layout) camera._activateCamera = true - conferenceLayout.item.clearAll(SettingsModel.videoConferenceLayout) - callModel.conferenceVideoLayout = SettingsModel.videoConferenceLayout }else callModel.cameraEnabled = !callModel.cameraEnabled }else{// In one-one, we deactivate all videos. diff --git a/linphone-app/ui/views/App/Calls/IncallActiveSpeaker.qml b/linphone-app/ui/views/App/Calls/IncallActiveSpeaker.qml index 47d84a931..3a0e10a68 100644 --- a/linphone-app/ui/views/App/Calls/IncallActiveSpeaker.qml +++ b/linphone-app/ui/views/App/Calls/IncallActiveSpeaker.qml @@ -37,12 +37,6 @@ Item { onConferenceCreated: cameraView.resetCamera() } - function clearAll(layoutMode){ - if( layoutMode != LinphoneEnums.ConferenceLayoutActiveSpeaker){ - mainItem.cameraEnabled = false - miniViews.model = [] - } - } Sticker{ id: cameraView anchors.fill: parent @@ -54,7 +48,7 @@ Item { : callModel.isConference ? allDevices.activeSpeaker : null - deactivateCamera: isPreview && callModel.pausedByUser + deactivateCamera: !mainItem.cameraEnabled || (isPreview && callModel.pausedByUser) ? true : callModel.isConference ? (callModel && (callModel.pausedByUser || callModel.status === CallModel.CallStatusPaused) ) @@ -63,8 +57,6 @@ Item { || !mainItem.isConferenceReady : (callModel && (callModel.pausedByUser || callModel.status === CallModel.CallStatusPaused || !callModel.videoEnabled) ) || currentDevice && !currentDevice.videoEnabled - - isVideoEnabled: !deactivateCamera isPreview: !preview.visible && mainItem.participantCount == 1 onIsPreviewChanged: {cameraView.resetCamera() } isCameraFromDevice: isPreview @@ -101,7 +93,7 @@ Item { sourceComponent: Sticker{ id: previewSticker - deactivateCamera: !mainItem.callModel || callModel.pausedByUser || !mainItem.callModel.cameraEnabled + deactivateCamera: !mainItem.cameraEnabled || !mainItem.callModel || callModel.pausedByUser || !mainItem.callModel.cameraEnabled currentDevice: allDevices.me isPreview: true callModel: mainItem.callModel diff --git a/linphone-app/ui/views/App/Calls/IncallFullscreen.qml b/linphone-app/ui/views/App/Calls/IncallFullscreen.qml index a1b5a8385..081fc1754 100644 --- a/linphone-app/ui/views/App/Calls/IncallFullscreen.qml +++ b/linphone-app/ui/views/App/Calls/IncallFullscreen.qml @@ -68,6 +68,9 @@ Window { property ConferenceModel conferenceModel: callModel && callModel.conferenceModel property var _fullscreen: null property bool listCallsOpened: false + property bool layoutChanging: false + + property bool isAudioOnly: callModel && callModel.conferenceVideoLayout == LinphoneEnums.ConferenceLayoutAudioOnly signal openListCallsRequest() // --------------------------------------------------------------------------- @@ -238,7 +241,7 @@ Window { isCustom: true backgroundRadius: width/2 colorSet: IncallStyle.buttons.screenshot - visible: SettingsModel.incallScreenshotEnabled && conference.callModel && (!conference.callModel.isConference || mainItem.callModel.snapshotEnabled) + visible: SettingsModel.incallScreenshotEnabled && conference.callModel && (!conference.callModel.isConference || window.callModel.snapshotEnabled) onClicked: conference.callModel && conference.callModel.takeSnapshot() //: 'Take Snapshot' : Tooltip for takking snapshot. tooltipText: qsTr('incallSnapshotTooltip') @@ -279,6 +282,7 @@ Window { Layout.leftMargin: window.hideButtons ? 15 : 70 Layout.rightMargin: rightMenu.visible ? 15 : 70 callModel: conference.callModel + cameraEnabled: !conference.layoutChanging } } Component{ @@ -286,6 +290,7 @@ Window { IncallActiveSpeaker{ id: activeSpeaker callModel: conference.callModel + cameraEnabled: !conference.layoutChanging isRightReducedLayout: rightMenu.visible isLeftReducedLayout: conference.listCallsOpened } @@ -297,13 +302,48 @@ Window { Layout.fillHeight: true Layout.fillWidth: true - sourceComponent: conference.callModel - ? conference.conferenceModel - ? conference.callModel.conferenceVideoLayout == LinphoneEnums.ConferenceLayoutActiveSpeaker - ? activeSpeakerComponent - : gridComponent - : activeSpeakerComponent - : null + Timer{// Avoid Qt crashes when layout changes while videos are on + id: layoutDelay + interval: 100 + property int step : 0 + property var layoutMode + onTriggered: { + switch(step){ + case 2 : step = 0; conference.layoutChanging = false; break; + case 1: ++step; conferenceLayout.sourceComponent = conferenceLayout.getLayout(); layoutDelay.restart(); break; + case 0: if( conference.callModel.conferenceVideoLayout != layoutMode) + conference.callModel.conferenceVideoLayout = layoutMode + else { + ++step; + layoutDelay.restart() + } + break; + } + } + function begin(layoutMode){ + step = 0 + layoutDelay.layoutMode = layoutMode + conference.layoutChanging = true + layoutDelay.restart() + } + } + function getLayout(){ + return conference.conferenceModel + ? conference.callModel.conferenceVideoLayout == LinphoneEnums.ConferenceLayoutActiveSpeaker + ? activeSpeakerComponent + : gridComponent + : activeSpeakerComponent + } + + Connections{ + target: conference.callModel + + onConferenceVideoLayoutChanged: { + layoutDelay.layoutMode = conference.callModel.conferenceVideoLayout + layoutDelay.restart() + } + } + sourceComponent: getLayout() active: conference.callModel ColumnLayout { anchors.fill: parent @@ -332,6 +372,9 @@ Window { conferenceModel: conference.conferenceModel visible: false onClose: rightMenu.visible = !rightMenu.visible + onLayoutChanging: { + layoutDelay.begin(layoutMode) + } } } } @@ -450,8 +493,14 @@ Window { backgroundRadius: 90 colorSet: callModel && callModel.cameraEnabled ? IncallStyle.buttons.cameraOn : IncallStyle.buttons.cameraOff updating: callModel && callModel.videoEnabled && callModel.updating - enabled: callModel && callModel.videoEnabled - onClicked: if(callModel) callModel.cameraEnabled = !callModel.cameraEnabled + onClicked: if(callModel && !conference.layoutChanging){ + if( callModel.isConference){// Only deactivate camera in conference. + callModel.cameraEnabled = !callModel.cameraEnabled + }else{// In one-one, we deactivate all videos. + if(callModel.videoEnabled ) Qt.callLater(function(){window.exit()}) + callModel.videoEnabled = !callModel.videoEnabled + } + } } } RowLayout{ diff --git a/linphone-app/ui/views/App/Calls/IncallGrid.qml b/linphone-app/ui/views/App/Calls/IncallGrid.qml index 1369bf7ec..56685c608 100644 --- a/linphone-app/ui/views/App/Calls/IncallGrid.qml +++ b/linphone-app/ui/views/App/Calls/IncallGrid.qml @@ -27,12 +27,6 @@ Mosaic { // On grid view, we limit the quality if there are enough participants// The vga mode has been activated from the factory rc //onParticipantCountChanged: participantCount > ConstantsCpp.maxMosaicParticipants ? SettingsModel.setLimitedMosaicQuality() : SettingsModel.setHighMosaicQuality() - function clearAll(layoutMode){ - if( layoutMode != 2 && layoutMode != LinphoneEnums.ConferenceLayoutGrid){ - clear() - gridModel.model = [] - } - } delegateModel: DelegateModel{ id: gridModel property ParticipantDeviceProxyModel participantDevices : ParticipantDeviceProxyModel {