Fix video crashes on changing layout.

- Make as readonly the isVideoEnabled from Camera and let it to do its work.
- Use deactivateCamera to temporary deactivate it (preveiw aswell).
- Check audio only from callModel video layout.
- Deactivate Camera Sticker when changing layout.
- Temporize layout changes in order to let time to Qt to shutdown its objects, and to let the SDK to not use/reuse deleted Qt objects.
- Dynamic load some items on Sticker only if needed.
- Forbid to change layout if previous change has not been done.
- Remove old clean layout functions.
This commit is contained in:
Julien Wadel 2022-11-25 17:40:04 +01:00
parent 149fe9ea8e
commit c870daf530
9 changed files with 172 additions and 87 deletions

View file

@ -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";

View file

@ -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{

View file

@ -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

View file

@ -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()
}
}
}

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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{

View file

@ -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 {