mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-07-14 20:45:23 +02:00
localvideo: refactor preview component device control
Change-Id: Ibcd88c5a3c73a0e67f94d70bc420845aa7b8c822
This commit is contained in:
parent
afde816b23
commit
ff7acf9932
11 changed files with 318 additions and 306 deletions
|
@ -33,7 +33,7 @@ VideoView {
|
|||
stop();
|
||||
return;
|
||||
}
|
||||
const forceRestart = rendererId === id;
|
||||
const forceRestart = rendererId === id || force;
|
||||
if (!forceRestart) {
|
||||
// Stop previous device
|
||||
VideoDevices.stopDevice(rendererId);
|
||||
|
|
|
@ -223,43 +223,14 @@ CurrentCall::updateCallInfo()
|
|||
set_isGrid(callInfo.layout == call::Layout::GRID);
|
||||
set_isAudioOnly(callInfo.isAudioOnly);
|
||||
|
||||
bool isAudioMuted {};
|
||||
bool isVideoMuted {};
|
||||
bool isSharing {};
|
||||
QString sharingSource {};
|
||||
bool isCapturing {};
|
||||
QString previewId {};
|
||||
using namespace libjami::Media;
|
||||
if (callInfo.status != lrc::api::call::Status::ENDED) {
|
||||
for (const auto& media : callInfo.mediaList) {
|
||||
if (media[MediaAttributeKey::MEDIA_TYPE] == Details::MEDIA_TYPE_VIDEO) {
|
||||
if (media[MediaAttributeKey::SOURCE].startsWith(VideoProtocolPrefix::DISPLAY)
|
||||
|| media[MediaAttributeKey::SOURCE].startsWith(VideoProtocolPrefix::FILE)) {
|
||||
isSharing = true;
|
||||
sharingSource = media[MediaAttributeKey::SOURCE];
|
||||
}
|
||||
if (media[MediaAttributeKey::ENABLED] == TRUE_STR
|
||||
&& media[MediaAttributeKey::MUTED] == FALSE_STR && previewId.isEmpty()) {
|
||||
previewId = media[libjami::Media::MediaAttributeKey::SOURCE];
|
||||
}
|
||||
if (media[libjami::Media::MediaAttributeKey::SOURCE].startsWith(
|
||||
libjami::Media::VideoProtocolPrefix::CAMERA)) {
|
||||
isVideoMuted |= media[MediaAttributeKey::MUTED] == TRUE_STR;
|
||||
isCapturing = media[MediaAttributeKey::MUTED] == FALSE_STR;
|
||||
}
|
||||
} else if (media[MediaAttributeKey::MEDIA_TYPE] == Details::MEDIA_TYPE_AUDIO) {
|
||||
if (media[MediaAttributeKey::LABEL] == "audio_0") {
|
||||
isAudioMuted |= media[libjami::Media::MediaAttributeKey::MUTED] == TRUE_STR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
set_previewId(previewId);
|
||||
set_isAudioMuted(isAudioMuted);
|
||||
set_isVideoMuted(isVideoMuted);
|
||||
set_isSharing(isSharing);
|
||||
set_sharingSource(sharingSource);
|
||||
set_isCapturing(isCapturing);
|
||||
auto callInfoEx = callInfo.getCallInfoEx();
|
||||
set_previewId(callInfoEx["preview_id"].toString());
|
||||
set_isAudioMuted(callInfoEx["is_audio_muted"].toBool());
|
||||
set_isVideoMuted(callInfoEx["is_video_muted"].toBool());
|
||||
set_isSharing(callInfoEx["is_sharing"].toBool());
|
||||
set_sharingSource(isSharing_ ? callInfoEx["preview_id"].toString() : QString());
|
||||
set_isCapturing(callInfoEx["is_capturing"].toBool());
|
||||
|
||||
set_isHandRaised(callModel->isHandRaised(id_));
|
||||
set_isModerator(callModel->isModerator(id_));
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
#include "currentconversation.h"
|
||||
|
||||
#include "global.h"
|
||||
|
||||
#include <api/conversationmodel.h>
|
||||
#include <api/contact.h>
|
||||
|
||||
|
@ -264,51 +266,39 @@ void
|
|||
CurrentConversation::connectModel()
|
||||
{
|
||||
membersModel_->setMembers({}, {}, {});
|
||||
auto convModel = lrcInstance_->getCurrentConversationModel();
|
||||
if (!convModel)
|
||||
|
||||
auto currentConversationModel = lrcInstance_->getCurrentConversationModel();
|
||||
auto currentCallModel = lrcInstance_->getCurrentCallModel();
|
||||
if (!currentConversationModel || !currentCallModel) {
|
||||
C_DBG << "CurrentConversation: can't connect to unavailable models";
|
||||
return;
|
||||
}
|
||||
|
||||
auto connectObjectSignal = [this](auto obj, auto signal, auto slot) {
|
||||
connect(obj, signal, this, slot, Qt::UniqueConnection);
|
||||
};
|
||||
|
||||
connectObjectSignal(convModel,
|
||||
connectObjectSignal(currentConversationModel,
|
||||
&ConversationModel::conversationUpdated,
|
||||
&CurrentConversation::onConversationUpdated);
|
||||
connectObjectSignal(convModel,
|
||||
connectObjectSignal(currentConversationModel,
|
||||
&ConversationModel::profileUpdated,
|
||||
&CurrentConversation::updateProfile);
|
||||
|
||||
connect(lrcInstance_->getCurrentConversationModel(),
|
||||
&ConversationModel::profileUpdated,
|
||||
this,
|
||||
&CurrentConversation::updateProfile,
|
||||
Qt::UniqueConnection);
|
||||
connect(lrcInstance_->getCurrentConversationModel(),
|
||||
&ConversationModel::onConversationErrorsUpdated,
|
||||
this,
|
||||
&CurrentConversation::updateErrors,
|
||||
Qt::UniqueConnection);
|
||||
connect(lrcInstance_->getCurrentConversationModel(),
|
||||
&ConversationModel::activeCallsChanged,
|
||||
this,
|
||||
&CurrentConversation::updateActiveCalls,
|
||||
Qt::UniqueConnection);
|
||||
connect(lrcInstance_->getCurrentConversationModel(),
|
||||
&ConversationModel::conversationPreferencesUpdated,
|
||||
this,
|
||||
&CurrentConversation::updateConversationPreferences,
|
||||
Qt::UniqueConnection);
|
||||
connect(lrcInstance_->getCurrentConversationModel(),
|
||||
&ConversationModel::needsHost,
|
||||
this,
|
||||
&CurrentConversation::onNeedsHost,
|
||||
Qt::UniqueConnection);
|
||||
connect(lrcInstance_->getCurrentCallModel(),
|
||||
&CallModel::callStatusChanged,
|
||||
this,
|
||||
&CurrentConversation::onCallStatusChanged,
|
||||
Qt::UniqueConnection);
|
||||
connectObjectSignal(currentConversationModel,
|
||||
&ConversationModel::conversationErrorsUpdated,
|
||||
&CurrentConversation::updateErrors);
|
||||
connectObjectSignal(currentConversationModel,
|
||||
&ConversationModel::activeCallsChanged,
|
||||
&CurrentConversation::updateActiveCalls);
|
||||
connectObjectSignal(currentConversationModel,
|
||||
&ConversationModel::conversationPreferencesUpdated,
|
||||
&CurrentConversation::updateConversationPreferences);
|
||||
connectObjectSignal(currentConversationModel,
|
||||
&ConversationModel::needsHost,
|
||||
&CurrentConversation::onNeedsHost);
|
||||
connectObjectSignal(currentCallModel,
|
||||
&CallModel::callStatusChanged,
|
||||
&CurrentConversation::onCallStatusChanged);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
211
src/app/mainview/components/InCallLocalVideo.qml
Normal file
211
src/app/mainview/components/InCallLocalVideo.qml
Normal file
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* Copyright (C) 2024 Savoir-faire Linux Inc.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import net.jami.Enums 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
|
||||
import "../../commoncomponents"
|
||||
|
||||
// This component uses anchors and they are set within this component.
|
||||
LocalVideo {
|
||||
id: localPreview
|
||||
|
||||
required property var container
|
||||
required property real opacityModifier
|
||||
|
||||
readonly property int previewMargin: 15
|
||||
readonly property int previewMarginYTop: previewMargin + 42
|
||||
readonly property int previewMarginYBottom: previewMargin + 84
|
||||
|
||||
anchors.bottomMargin: previewMarginYBottom
|
||||
anchors.leftMargin: sideMargin
|
||||
anchors.rightMargin: sideMargin
|
||||
anchors.topMargin: previewMarginYTop
|
||||
|
||||
visibilityCondition: (CurrentCall.isSharing || !CurrentCall.isVideoMuted) &&
|
||||
!CurrentCall.isConference
|
||||
height: width * invAspectRatio
|
||||
width: Math.max(container.width / 5, JamiTheme.minimumPreviewWidth)
|
||||
flip: CurrentCall.flipSelf && !CurrentCall.isSharing
|
||||
blurRadius: hidden ? 25 : 0
|
||||
|
||||
opacity: hidden ? opacityModifier : 1
|
||||
|
||||
// Allow hiding the preview (available when anchored)
|
||||
readonly property bool hovered: hoverHandler.hovered
|
||||
readonly property bool anchored: state !== "unanchored"
|
||||
property bool hidden: false
|
||||
readonly property real hiddenHandleSize: 32
|
||||
// Compute the margin as a function of the preview width in order to
|
||||
// apply a negative margin and expose a constant width handle.
|
||||
// If not hidden, return the previewMargin.
|
||||
property real sideMargin: !hidden ? previewMargin : -(width - hiddenHandleSize)
|
||||
// Animate the hiddenSize with a Behavior.
|
||||
Behavior on sideMargin { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }}
|
||||
readonly property bool onLeft: state.indexOf("left") !== -1
|
||||
PushButton {
|
||||
id: hidePreviewButton
|
||||
objectName: "hidePreviewButton"
|
||||
|
||||
width: localPreview.hiddenHandleSize
|
||||
state: localPreview.onLeft ?
|
||||
(localPreview.hidden ? "right" : "left") :
|
||||
(localPreview.hidden ? "left" : "right")
|
||||
states: [
|
||||
State {
|
||||
name: "left"
|
||||
AnchorChanges {
|
||||
target: hidePreviewButton
|
||||
anchors.left: parent.left
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "right"
|
||||
AnchorChanges {
|
||||
target: hidePreviewButton
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
||||
]
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
opacity: (localPreview.anchored && localPreview.hovered) || localPreview.hidden
|
||||
Behavior on opacity { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }}
|
||||
visible: opacity > 0
|
||||
background: Rectangle {
|
||||
readonly property color normalColor: JamiTheme.mediumGrey
|
||||
color: JamiTheme.mediumGrey
|
||||
opacity: hidePreviewButton.hovered ? 0.7 : 0.5
|
||||
Behavior on opacity { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }}
|
||||
}
|
||||
normalImageSource: hidePreviewButton.state === "left" ?
|
||||
JamiResources.chevron_left_black_24dp_svg :
|
||||
JamiResources.chevron_right_black_24dp_svg
|
||||
imageColor: JamiTheme.darkGreyColor
|
||||
onClicked: localPreview.hidden = !localPreview.hidden
|
||||
toolTipText: localPreview.hidden ?
|
||||
JamiStrings.showLocalVideo :
|
||||
JamiStrings.hideLocalVideo
|
||||
}
|
||||
|
||||
state: "anchor_top_right"
|
||||
states: [
|
||||
State {
|
||||
name: "unanchored"
|
||||
AnchorChanges {
|
||||
target: localPreview
|
||||
anchors.top: undefined
|
||||
anchors.right: undefined
|
||||
anchors.bottom: undefined
|
||||
anchors.left: undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "anchor_top_left"
|
||||
AnchorChanges {
|
||||
target: localPreview
|
||||
anchors.top: localPreview.container.top
|
||||
anchors.left: localPreview.container.left
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "anchor_top_right"
|
||||
AnchorChanges {
|
||||
target: localPreview
|
||||
anchors.top: localPreview.container.top
|
||||
anchors.right: localPreview.container.right
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "anchor_bottom_right"
|
||||
AnchorChanges {
|
||||
target: localPreview
|
||||
anchors.bottom: localPreview.container.bottom
|
||||
anchors.right: localPreview.container.right
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "anchor_bottom_left"
|
||||
AnchorChanges {
|
||||
target: localPreview
|
||||
anchors.bottom: localPreview.container.bottom
|
||||
anchors.left: localPreview.container.left
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: Transition {
|
||||
AnchorAnimation {
|
||||
duration: 250
|
||||
easing.type: Easing.OutBack
|
||||
easing.overshoot: 1.5
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
}
|
||||
|
||||
DragHandler {
|
||||
id: dragHandler
|
||||
readonly property var container: localPreview.container
|
||||
target: parent
|
||||
dragThreshold: 4
|
||||
enabled: !localPreview.hidden
|
||||
xAxis.maximum: container.width - parent.width - previewMargin
|
||||
xAxis.minimum: previewMargin
|
||||
yAxis.maximum: container.height - parent.height - previewMarginYBottom
|
||||
yAxis.minimum: previewMarginYTop
|
||||
onActiveChanged: {
|
||||
if (active) {
|
||||
localPreview.state = "unanchored";
|
||||
} else {
|
||||
const center = Qt.point(target.x + target.width / 2,
|
||||
target.y + target.height / 2);
|
||||
const containerCenter = Qt.point(container.x + container.width / 2,
|
||||
container.y + container.height / 2);
|
||||
if (center.x >= containerCenter.x) {
|
||||
if (center.y >= containerCenter.y) {
|
||||
localPreview.state = "anchor_bottom_right";
|
||||
} else {
|
||||
localPreview.state = "anchor_top_right";
|
||||
}
|
||||
} else {
|
||||
if (center.y >= containerCenter.y) {
|
||||
localPreview.state = "anchor_bottom_left";
|
||||
} else {
|
||||
localPreview.state = "anchor_top_left";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: localPreview.width
|
||||
height: localPreview.height
|
||||
radius: JamiTheme.primaryRadius
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,11 +30,6 @@ import "../../commoncomponents"
|
|||
Rectangle {
|
||||
id: root
|
||||
|
||||
// Constraints for the preview component.
|
||||
property int previewMargin: 15
|
||||
property int previewMarginYTop: previewMargin + 42
|
||||
property int previewMarginYBottom: previewMargin + 84
|
||||
|
||||
property alias chatViewContainer: chatViewContainer
|
||||
property string callPreviewId
|
||||
|
||||
|
@ -166,222 +161,15 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
LocalVideo {
|
||||
// Note: this component should not be used within a layout, as
|
||||
// it implements anchor management itself.
|
||||
InCallLocalVideo {
|
||||
id: localPreview
|
||||
objectName: "localPreview"
|
||||
|
||||
readonly property var container: parent
|
||||
readonly property string callPreviewId: root.callPreviewId
|
||||
|
||||
visibilityCondition: (CurrentCall.isSharing || !CurrentCall.isVideoMuted) &&
|
||||
!CurrentCall.isConference
|
||||
height: width * invAspectRatio
|
||||
|
||||
// Keep the area of the preview a proportion of the screen size plus a
|
||||
// modifier to allow the user to scale it.
|
||||
readonly property real containerArea: container.width * container.height
|
||||
property real scalingFactor: 1
|
||||
width: Math.sqrt(containerArea / 16) * scalingFactor
|
||||
flip: CurrentCall.flipSelf && !CurrentCall.isSharing
|
||||
blurRadius: hidden ? 25 : 0
|
||||
onCallPreviewIdChanged: startWithId(callPreviewId)
|
||||
onVisibleChanged: if (!visible) stop()
|
||||
|
||||
anchors.topMargin: previewMarginYTop
|
||||
anchors.leftMargin: sideMargin
|
||||
anchors.rightMargin: sideMargin
|
||||
anchors.bottomMargin: previewMarginYBottom
|
||||
|
||||
opacity: hidden ? callOverlay.mainOverlayOpacity : 1
|
||||
|
||||
// Allow hiding the preview (available when anchored)
|
||||
readonly property bool hovered: hoverHandler.hovered
|
||||
readonly property bool anchored: state !== "unanchored"
|
||||
property bool hidden: false
|
||||
readonly property real hiddenHandleSize: 32
|
||||
// Compute the margin as a function of the preview width in order to
|
||||
// apply a negative margin and expose a constant width handle.
|
||||
// If not hidden, return the previewMargin.
|
||||
property real sideMargin: !hidden ? previewMargin : -(width - hiddenHandleSize)
|
||||
// Animate the hiddenSize with a Behavior.
|
||||
Behavior on sideMargin { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }}
|
||||
readonly property bool onLeft: state.indexOf("left") !== -1
|
||||
PushButton {
|
||||
id: hidePreviewButton
|
||||
objectName: "hidePreviewButton"
|
||||
|
||||
width: localPreview.hiddenHandleSize
|
||||
state: {
|
||||
if (!localPreview.anchored) {
|
||||
return "none";
|
||||
}
|
||||
return localPreview.onLeft ?
|
||||
(localPreview.hidden ? "right" : "left") :
|
||||
(localPreview.hidden ? "left" : "right")
|
||||
}
|
||||
states: [
|
||||
State {
|
||||
name: "none"
|
||||
// Override visible to false when the localPreview isn't anchored.
|
||||
PropertyChanges {
|
||||
target: hidePreviewButton
|
||||
visible: false
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "left"
|
||||
AnchorChanges {
|
||||
target: hidePreviewButton
|
||||
anchors.left: parent.left
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "right"
|
||||
AnchorChanges {
|
||||
target: hidePreviewButton
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
||||
]
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
opacity: (localPreview.anchored && localPreview.hovered) || localPreview.hidden
|
||||
Behavior on opacity { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }}
|
||||
visible: opacity > 0
|
||||
background: Rectangle {
|
||||
readonly property color normalColor: JamiTheme.mediumGrey
|
||||
color: JamiTheme.mediumGrey
|
||||
opacity: hidePreviewButton.hovered ? 0.7 : 0.5
|
||||
Behavior on opacity { NumberAnimation { duration: 250; easing.type: Easing.OutExpo }}
|
||||
}
|
||||
normalImageSource: hidePreviewButton.state === "left" ?
|
||||
JamiResources.chevron_left_black_24dp_svg :
|
||||
JamiResources.chevron_right_black_24dp_svg
|
||||
imageColor: JamiTheme.darkGreyColor
|
||||
onClicked: localPreview.hidden = !localPreview.hidden
|
||||
toolTipText: localPreview.hidden ?
|
||||
JamiStrings.showLocalVideo :
|
||||
JamiStrings.hideLocalVideo
|
||||
}
|
||||
|
||||
state: "anchor_top_right"
|
||||
states: [
|
||||
State {
|
||||
name: "unanchored"
|
||||
AnchorChanges {
|
||||
target: localPreview
|
||||
anchors.top: undefined
|
||||
anchors.right: undefined
|
||||
anchors.bottom: undefined
|
||||
anchors.left: undefined
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "anchor_top_left"
|
||||
AnchorChanges {
|
||||
target: localPreview
|
||||
anchors.top: localPreview.container.top
|
||||
anchors.left: localPreview.container.left
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "anchor_top_right"
|
||||
AnchorChanges {
|
||||
target: localPreview
|
||||
anchors.top: localPreview.container.top
|
||||
anchors.right: localPreview.container.right
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "anchor_bottom_right"
|
||||
AnchorChanges {
|
||||
target: localPreview
|
||||
anchors.bottom: localPreview.container.bottom
|
||||
anchors.right: localPreview.container.right
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "anchor_bottom_left"
|
||||
AnchorChanges {
|
||||
target: localPreview
|
||||
anchors.bottom: localPreview.container.bottom
|
||||
anchors.left: localPreview.container.left
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: Transition {
|
||||
AnchorAnimation {
|
||||
duration: 250
|
||||
easing.type: Easing.OutBack
|
||||
easing.overshoot: 1.5
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: hoverHandler
|
||||
}
|
||||
|
||||
WheelHandler {
|
||||
onWheel: function(event) {
|
||||
const delta = event.angleDelta.y / 120 * 0.1;
|
||||
parent.opacity = JamiQmlUtils.clamp(parent.opacity + delta, 0.25, 1);
|
||||
}
|
||||
acceptedModifiers: Qt.CTRL
|
||||
}
|
||||
|
||||
WheelHandler {
|
||||
onWheel: function(event) {
|
||||
const delta = event.angleDelta.y / 120 * 0.1;
|
||||
localPreview.scalingFactor = JamiQmlUtils.clamp(localPreview.scalingFactor + delta, 0.5, 4);
|
||||
}
|
||||
acceptedModifiers: Qt.NoModifier
|
||||
enabled: !localPreview.hidden
|
||||
}
|
||||
|
||||
DragHandler {
|
||||
id: dragHandler
|
||||
readonly property var container: localPreview.container
|
||||
target: parent
|
||||
dragThreshold: 4
|
||||
enabled: !localPreview.hidden
|
||||
xAxis.maximum: container.width - parent.width - previewMargin
|
||||
xAxis.minimum: previewMargin
|
||||
yAxis.maximum: container.height - parent.height - previewMarginYBottom
|
||||
yAxis.minimum: previewMarginYTop
|
||||
onActiveChanged: {
|
||||
if (active) {
|
||||
localPreview.state = "unanchored";
|
||||
} else {
|
||||
const center = Qt.point(target.x + target.width / 2,
|
||||
target.y + target.height / 2);
|
||||
const containerCenter = Qt.point(container.x + container.width / 2,
|
||||
container.y + container.height / 2);
|
||||
if (center.x >= containerCenter.x) {
|
||||
if (center.y >= containerCenter.y) {
|
||||
localPreview.state = "anchor_bottom_right";
|
||||
} else {
|
||||
localPreview.state = "anchor_top_right";
|
||||
}
|
||||
} else {
|
||||
if (center.y >= containerCenter.y) {
|
||||
localPreview.state = "anchor_bottom_left";
|
||||
} else {
|
||||
localPreview.state = "anchor_top_left";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
width: localPreview.width
|
||||
height: localPreview.height
|
||||
radius: JamiTheme.primaryRadius
|
||||
}
|
||||
}
|
||||
container: parent
|
||||
rendererId: CurrentCall.previewId
|
||||
opacityModifier: callOverlay.mainOverlayOpacity
|
||||
}
|
||||
|
||||
CallOverlay {
|
||||
|
|
|
@ -80,13 +80,10 @@ SettingsPageBase {
|
|||
Component.onCompleted: {
|
||||
flipControl.checked = UtilsAdapter.getAppValue(Settings.FlipSelf);
|
||||
hardwareAccelControl.checked = AvAdapter.getHardwareAcceleration();
|
||||
if (previewWidget.visible)
|
||||
startPreviewing(true);
|
||||
startPreviewing(true);
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
previewWidget.startWithId("");
|
||||
}
|
||||
Component.onDestruction: previewWidget.stop()
|
||||
|
||||
// video Preview
|
||||
Rectangle {
|
||||
|
|
|
@ -256,13 +256,15 @@ VideoDevices::startDevice(const QString& id, bool force)
|
|||
void
|
||||
VideoDevices::stopDevice(const QString& id)
|
||||
{
|
||||
if (!id.isEmpty()) {
|
||||
qInfo() << "Stopping device" << id;
|
||||
if (lrcInstance_->avModel().stopPreview(id)) {
|
||||
deviceOpen_ = false;
|
||||
} else {
|
||||
qWarning() << "Failed to stop device" << id;
|
||||
}
|
||||
if (id.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
qInfo() << "Stopping device" << id;
|
||||
if (lrcInstance_->avModel().stopPreview(id)) {
|
||||
deviceOpen_ = false;
|
||||
} else {
|
||||
qWarning() << "Failed to stop device" << id;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -149,6 +149,48 @@ struct Info
|
|||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract some common meta data for this call including:
|
||||
// - the video preview ID
|
||||
// - audio/video muted status
|
||||
// - if the call is sharing (indicating that the preview is a screen share)
|
||||
QVariantMap getCallInfoEx() const
|
||||
{
|
||||
bool isAudioMuted = false;
|
||||
bool isVideoMuted = false;
|
||||
QString previewId;
|
||||
QVariantMap callInfo;
|
||||
using namespace libjami::Media;
|
||||
if (status == lrc::api::call::Status::ENDED) {
|
||||
return {};
|
||||
}
|
||||
for (const auto& media : mediaList) {
|
||||
if (media[MediaAttributeKey::MEDIA_TYPE] == Details::MEDIA_TYPE_VIDEO) {
|
||||
if (media[MediaAttributeKey::SOURCE].startsWith(VideoProtocolPrefix::DISPLAY)
|
||||
|| media[MediaAttributeKey::SOURCE].startsWith(VideoProtocolPrefix::FILE)) {
|
||||
callInfo["is_sharing"] = true;
|
||||
callInfo["preview_id"] = media[MediaAttributeKey::SOURCE];
|
||||
}
|
||||
if (media[MediaAttributeKey::ENABLED] == TRUE_STR
|
||||
&& media[MediaAttributeKey::MUTED] == FALSE_STR && previewId.isEmpty()) {
|
||||
previewId = media[libjami::Media::MediaAttributeKey::SOURCE];
|
||||
}
|
||||
if (media[libjami::Media::MediaAttributeKey::SOURCE].startsWith(
|
||||
libjami::Media::VideoProtocolPrefix::CAMERA)) {
|
||||
isVideoMuted |= media[MediaAttributeKey::MUTED] == TRUE_STR;
|
||||
callInfo["is_capturing"] = media[MediaAttributeKey::MUTED] == FALSE_STR;
|
||||
}
|
||||
} else if (media[MediaAttributeKey::MEDIA_TYPE] == Details::MEDIA_TYPE_AUDIO) {
|
||||
if (media[MediaAttributeKey::LABEL] == "audio_0") {
|
||||
isAudioMuted |= media[libjami::Media::MediaAttributeKey::MUTED] == TRUE_STR;
|
||||
}
|
||||
}
|
||||
}
|
||||
callInfo["preview_id"] = previewId;
|
||||
callInfo["is_audio_muted"] = isAudioMuted;
|
||||
callInfo["is_video_muted"] = isVideoMuted;
|
||||
return callInfo;
|
||||
}
|
||||
};
|
||||
|
||||
static inline bool
|
||||
|
|
|
@ -464,7 +464,7 @@ Q_SIGNALS:
|
|||
* Emitted when a conversation detects an error
|
||||
* @param uid
|
||||
*/
|
||||
void onConversationErrorsUpdated(const QString& uid) const;
|
||||
void conversationErrorsUpdated(const QString& uid) const;
|
||||
/**
|
||||
* Emitted when conversation's preferences has been updated
|
||||
* @param uid
|
||||
|
|
|
@ -1089,7 +1089,7 @@ ConversationModel::popFrontError(const QString& conversationId)
|
|||
|
||||
auto& conversation = conversationOpt->get();
|
||||
conversation.errors.pop_front();
|
||||
Q_EMIT onConversationErrorsUpdated(conversationId);
|
||||
Q_EMIT conversationErrorsUpdated(conversationId);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -2706,7 +2706,7 @@ ConversationModelPimpl::slotOnConversationError(const QString& accountId,
|
|||
try {
|
||||
auto& conversation = getConversationForUid(conversationId).get();
|
||||
conversation.errors.push_back({code, what});
|
||||
Q_EMIT linked.onConversationErrorsUpdated(conversationId);
|
||||
Q_EMIT linked.conversationErrorsUpdated(conversationId);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -180,6 +180,17 @@ TestWrapper {
|
|||
compare(localPreview.hidden, false);
|
||||
});
|
||||
}
|
||||
|
||||
function test_localPreviewRemainsVisibleWhenOngoingCallPageIsToggled() {
|
||||
localPreviewTestWrapper(function(localPreview) {
|
||||
// The local preview should remain visible when the OngoingCallPage is toggled.
|
||||
compare(localPreview.visible, true);
|
||||
uut.visible = false;
|
||||
compare(localPreview.visible, false);
|
||||
uut.visible = true;
|
||||
compare(localPreview.visible, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue