- Add Fullscreen mode for video conference.

- Factorization of camera window ID and fix crash from removing GUI while stopping streams.
- Fix layout changing and Active Speaker crash.
This commit is contained in:
Julien Wadel 2022-05-04 20:28:31 +02:00
parent d8b33de489
commit d344701e99
11 changed files with 617 additions and 65 deletions

View file

@ -400,6 +400,7 @@
<file>ui/views/App/Calls/Conference.qml</file>
<file>ui/views/App/Calls/VideoConference.qml</file>
<file>ui/views/App/Calls/VideoConferenceActiveSpeaker.qml</file>
<file>ui/views/App/Calls/VideoConferenceFullscreen.qml</file>
<file>ui/views/App/Calls/VideoConferenceGrid.qml</file>
<file>ui/views/App/Calls/VideoConferenceMenu.qml</file>
<file>ui/views/App/Calls/Dialogs/CallSipAddress.qml</file>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

@ -1 +1 @@
Subproject commit dbc795c83ef288e5fd27eb4a2a8a1c9da127eb95
Subproject commit c8d936196b84855415e90d41346e7b3a25374077