From a8154e237e2a83653217061a9d55fabca9fa77f7 Mon Sep 17 00:00:00 2001 From: Gus Grubba Date: Mon, 27 Nov 2017 08:41:28 -0500 Subject: [PATCH] Video Stream Control Widget --- qgroundcontrol.qrc | 1 + src/FlightDisplay/FlightDisplayView.qml | 59 --------- src/FlightDisplay/FlightDisplayViewVideo.qml | 2 +- src/FlightMap/Widgets/VideoPageWidget.qml | 128 +++++++++++++++++++ src/QmlControls/PageView.qml | 2 +- src/VideoStreaming/VideoReceiver.cc | 42 +++--- src/VideoStreaming/VideoReceiver.h | 11 +- src/api/QGCCorePlugin.cc | 14 +- 8 files changed, 176 insertions(+), 83 deletions(-) create mode 100644 src/FlightMap/Widgets/VideoPageWidget.qml diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc index 5a743baf9..40388bb66 100644 --- a/qgroundcontrol.qrc +++ b/qgroundcontrol.qrc @@ -179,6 +179,7 @@ src/VehicleSetup/VehicleSummary.qml src/FlightDisplay/VirtualJoystick.qml src/FlightMap/Widgets/CameraPageWidget.qml + src/FlightMap/Widgets/VideoPageWidget.qml src/FlightMap/Widgets/ValuePageWidget.qml src/FlightMap/Widgets/HealthPageWidget.qml src/FlightMap/Widgets/VibrationPageWidget.qml diff --git a/src/FlightDisplay/FlightDisplayView.qml b/src/FlightDisplay/FlightDisplayView.qml index 3c87dc13a..e2ff0b234 100644 --- a/src/FlightDisplay/FlightDisplayView.qml +++ b/src/FlightDisplay/FlightDisplayView.qml @@ -43,8 +43,6 @@ QGCView { property var _geoFenceController: _planMasterController.geoFenceController property var _rallyPointController: _planMasterController.rallyPointController property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle - property var _videoReceiver: QGroundControl.videoManager.videoReceiver - property bool _recordingVideo: _videoReceiver && _videoReceiver.recording property bool _mainIsMap: QGroundControl.videoManager.hasVideo ? QGroundControl.loadBoolGlobalSetting(_mainIsMapKey, true) : true property bool _isPipVisible: QGroundControl.videoManager.hasVideo ? QGroundControl.loadBoolGlobalSetting(_PIPVisibleKey, true) : false property real _savedZoomLevel: 0 @@ -346,63 +344,6 @@ QGCView { property var qgcView: root } - // Button to start/stop video recording - Item { - z: _flightVideoPipControl.z + 1 - anchors.margins: ScreenTools.defaultFontPixelHeight / 2 - anchors.bottom: _flightVideo.bottom - anchors.right: _flightVideo.right - height: ScreenTools.defaultFontPixelHeight * 2 - width: height - visible: _videoReceiver && _videoReceiver.videoRunning && QGroundControl.settingsManager.videoSettings.showRecControl.rawValue && _flightVideo.visible && !_isCamera && !QGroundControl.videoManager.fullScreen - opacity: 0.75 - - readonly property string recordBtnBackground: "BackgroundName" - - Rectangle { - id: recordBtnBackground - anchors.top: parent.top - anchors.bottom: parent.bottom - width: height - radius: _recordingVideo ? 0 : height - color: "red" - - SequentialAnimation on visible { - running: _recordingVideo - loops: Animation.Infinite - PropertyAnimation { to: false; duration: 1000 } - PropertyAnimation { to: true; duration: 1000 } - } - } - - QGCColoredImage { - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - width: height * 0.625 - sourceSize.width: width - source: "/qmlimages/CameraIcon.svg" - visible: recordBtnBackground.visible - fillMode: Image.PreserveAspectFit - color: "white" - } - - MouseArea { - anchors.fill: parent - onClicked: { - if (_videoReceiver) { - if (_recordingVideo) { - _videoReceiver.stopRecording() - // reset blinking animation - recordBtnBackground.visible = true - } else { - _videoReceiver.startRecording() - } - } - } - } - } - MultiVehicleList { anchors.margins: _margins anchors.top: singleMultiSelector.bottom diff --git a/src/FlightDisplay/FlightDisplayViewVideo.qml b/src/FlightDisplay/FlightDisplayViewVideo.qml index a3d39fd82..f9673ea52 100644 --- a/src/FlightDisplay/FlightDisplayViewVideo.qml +++ b/src/FlightDisplay/FlightDisplayViewVideo.qml @@ -34,7 +34,7 @@ Item { color: Qt.rgba(0,0,0,0.75) visible: !(_videoReceiver && _videoReceiver.videoRunning) QGCLabel { - text: qsTr("WAITING FOR VIDEO") + text: _videoReceiver && _videoReceiver.enabled ? qsTr("WAITING FOR VIDEO") : qsTr("VIDEO DISABLED") font.family: ScreenTools.demiboldFontFamily color: "white" font.pointSize: _mainIsMap ? ScreenTools.smallFontPointSize : ScreenTools.largeFontPointSize diff --git a/src/FlightMap/Widgets/VideoPageWidget.qml b/src/FlightMap/Widgets/VideoPageWidget.qml new file mode 100644 index 000000000..a58fb3414 --- /dev/null +++ b/src/FlightMap/Widgets/VideoPageWidget.qml @@ -0,0 +1,128 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +import QtQuick 2.4 +import QtPositioning 5.2 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 1.4 +import QtQuick.Dialogs 1.2 +import QtQuick.Controls.Styles 1.4 +import QtGraphicalEffects 1.0 + +import QGroundControl 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.Vehicle 1.0 +import QGroundControl.Controllers 1.0 +import QGroundControl.FactSystem 1.0 +import QGroundControl.FactControls 1.0 + +/// Video streaming page for Instrument Panel PageView +Item { + width: pageWidth + height: videoGrid.height + (ScreenTools.defaultFontPixelHeight * 2) + anchors.margins: ScreenTools.defaultFontPixelWidth * 2 + anchors.centerIn: parent + + property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle + property bool _communicationLost: _activeVehicle ? _activeVehicle.connectionLost : false + property var _videoReceiver: QGroundControl.videoManager.videoReceiver + property bool _recordingVideo: _videoReceiver && _videoReceiver.recording + property bool _videoRunning: _videoReceiver && _videoReceiver.videoRunning + + QGCPalette { id:qgcPal; colorGroupEnabled: parent.enabled } + + GridLayout { + id: videoGrid + columns: 2 + columnSpacing: ScreenTools.defaultFontPixelWidth * 2 + rowSpacing: ScreenTools.defaultFontPixelHeight + anchors.centerIn: parent + QGCLabel { + text: qsTr("Enable Stream") + font.pointSize: ScreenTools.smallFontPointSize + } + Switch { + checked: _videoRunning + enabled: _activeVehicle + onClicked: { + if(checked) { + _videoReceiver.start() + } else { + _videoReceiver.stop() + } + } + style: SwitchStyle { + groove: Rectangle { + implicitWidth: ScreenTools.defaultFontPixelWidth * 6 + implicitHeight: ScreenTools.defaultFontPixelHeight + color: control.checked ? qgcPal.colorGreen : qgcPal.colorGrey + radius: 3 + border.color: qgcPal.button + border.width: 1 + } + } + } + QGCLabel { + text: qsTr("Stream Recording") + font.pointSize: ScreenTools.smallFontPointSize + visible: QGroundControl.settingsManager.videoSettings.showRecControl.rawValue + } + // Button to start/stop video recording + Item { + anchors.margins: ScreenTools.defaultFontPixelHeight / 2 + height: ScreenTools.defaultFontPixelHeight * 2 + width: height + Layout.alignment: Qt.AlignHCenter + visible: QGroundControl.settingsManager.videoSettings.showRecControl.rawValue + + Rectangle { + id: recordBtnBackground + anchors.top: parent.top + anchors.bottom: parent.bottom + width: height + radius: _recordingVideo ? 0 : height + color: _videoRunning ? "red" : "gray" + SequentialAnimation on opacity { + running: _recordingVideo + loops: Animation.Infinite + PropertyAnimation { to: 0.5; duration: 500 } + PropertyAnimation { to: 1.0; duration: 500 } + } + } + + QGCColoredImage { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + width: height * 0.625 + sourceSize.width: width + source: "/qmlimages/CameraIcon.svg" + visible: recordBtnBackground.visible + fillMode: Image.PreserveAspectFit + color: "white" + } + + MouseArea { + anchors.fill: parent + enabled: _videoRunning + onClicked: { + if (_recordingVideo) { + _videoReceiver.stopRecording() + // reset blinking animation + recordBtnBackground.opacity = 1 + } else { + _videoReceiver.startRecording() + } + } + } + } + } +} diff --git a/src/QmlControls/PageView.qml b/src/QmlControls/PageView.qml index 80196383c..6dfdc6cb4 100644 --- a/src/QmlControls/PageView.qml +++ b/src/QmlControls/PageView.qml @@ -38,7 +38,7 @@ Rectangle { width: parent.height -(_margins * 2) sourceSize.width: width fillMode: Image.PreserveAspectFit - visible: pageWidgetLoader.item.showSettingsIcon + visible: pageWidgetLoader.item ? (pageWidgetLoader.item.showSettingsIcon ? pageWidgetLoader.item.showSettingsIcon : false) : false QGCMouseArea { fillItem: parent diff --git a/src/VideoStreaming/VideoReceiver.cc b/src/VideoStreaming/VideoReceiver.cc index 5091f215a..7c79b9826 100644 --- a/src/VideoStreaming/VideoReceiver.cc +++ b/src/VideoStreaming/VideoReceiver.cc @@ -67,6 +67,7 @@ VideoReceiver::VideoReceiver(QObject* parent) , _videoSurface(NULL) , _videoRunning(false) , _showFullScreen(false) + , _enabled(true) { _videoSurface = new VideoSurface; #if defined(QGC_GST_STREAMING) @@ -147,8 +148,10 @@ VideoReceiver::_connected() _timer.stop(); _socket->deleteLater(); _socket = NULL; - _serverPresent = true; - start(); + if(_enabled) { + _serverPresent = true; + start(); + } } #endif @@ -161,7 +164,9 @@ VideoReceiver::_socketError(QAbstractSocket::SocketError socketError) _socket->deleteLater(); _socket = NULL; //-- Try again in 5 seconds - _timer.start(5000); + if(_enabled) { + _timer.start(5000); + } } #endif @@ -175,18 +180,19 @@ VideoReceiver::_timeout() delete _socket; _socket = NULL; } - //-- RTSP will try to connect to the server. If it cannot connect, - // it will simply give up and never try again. Instead, we keep - // attempting a connection on this timer. Once a connection is - // found to be working, only then we actually start the stream. - QUrl url(_uri); - _socket = new QTcpSocket; - _socket->setProxy(QNetworkProxy::NoProxy); - connect(_socket, static_cast(&QTcpSocket::error), this, &VideoReceiver::_socketError); - connect(_socket, &QTcpSocket::connected, this, &VideoReceiver::_connected); - //qCDebug(VideoReceiverLog) << "Trying to connect to:" << url.host() << url.port(); - _socket->connectToHost(url.host(), url.port()); - _timer.start(5000); + if(_enabled) { + //-- RTSP will try to connect to the server. If it cannot connect, + // it will simply give up and never try again. Instead, we keep + // attempting a connection on this timer. Once a connection is + // found to be working, only then we actually start the stream. + QUrl url(_uri); + _socket = new QTcpSocket; + _socket->setProxy(QNetworkProxy::NoProxy); + connect(_socket, static_cast(&QTcpSocket::error), this, &VideoReceiver::_socketError); + connect(_socket, &QTcpSocket::connected, this, &VideoReceiver::_connected); + _socket->connectToHost(url.host(), url.port()); + _timer.start(5000); + } } #endif @@ -203,6 +209,8 @@ VideoReceiver::_timeout() void VideoReceiver::start() { + _enabled = true; + emit enabledChanged(); #if defined(QGC_GST_STREAMING) qCDebug(VideoReceiverLog) << "start()"; @@ -416,6 +424,8 @@ VideoReceiver::start() void VideoReceiver::stop() { + _enabled = false; + emit enabledChanged(); #if defined(QGC_GST_STREAMING) qCDebug(VideoReceiverLog) << "stop()"; if(!_streaming) { @@ -814,7 +824,7 @@ VideoReceiver::_updateTimer() stop(); } } else { - if(!running() && !_uri.isEmpty()) { + if(!running() && !_uri.isEmpty() && _enabled) { start(); } } diff --git a/src/VideoStreaming/VideoReceiver.h b/src/VideoStreaming/VideoReceiver.h index 7e7cfaf56..b32cd7b01 100644 --- a/src/VideoStreaming/VideoReceiver.h +++ b/src/VideoStreaming/VideoReceiver.h @@ -38,9 +38,10 @@ public: Q_PROPERTY(bool recording READ recording NOTIFY recordingChanged) #endif Q_PROPERTY(VideoSurface* videoSurface READ videoSurface CONSTANT) - Q_PROPERTY(bool videoRunning READ videoRunning NOTIFY videoRunningChanged) - Q_PROPERTY(QString imageFile READ imageFile NOTIFY imageFileChanged) - Q_PROPERTY(bool showFullScreen READ showFullScreen WRITE setShowFullScreen NOTIFY showFullScreenChanged) + Q_PROPERTY(bool videoRunning READ videoRunning NOTIFY videoRunningChanged) + Q_PROPERTY(QString imageFile READ imageFile NOTIFY imageFileChanged) + Q_PROPERTY(bool showFullScreen READ showFullScreen WRITE setShowFullScreen NOTIFY showFullScreenChanged) + Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged) explicit VideoReceiver(QObject* parent = 0); ~VideoReceiver(); @@ -57,6 +58,8 @@ public: bool videoRunning () { return _videoRunning; } QString imageFile () { return _imageFile; } bool showFullScreen () { return _showFullScreen; } + bool enabled () { return _enabled; } + void grabImage (QString imageFile); void setShowFullScreen (bool show) { _showFullScreen = show; emit showFullScreenChanged(); } @@ -65,6 +68,7 @@ signals: void videoRunningChanged (); void imageFileChanged (); void showFullScreenChanged (); + void enabledChanged (); #if defined(QGC_GST_STREAMING) void recordingChanged (); void msgErrorReceived (); @@ -136,6 +140,7 @@ private: VideoSurface* _videoSurface; bool _videoRunning; bool _showFullScreen; + bool _enabled; }; diff --git a/src/api/QGCCorePlugin.cc b/src/api/QGCCorePlugin.cc index de2d76d00..475b98a67 100644 --- a/src/api/QGCCorePlugin.cc +++ b/src/api/QGCCorePlugin.cc @@ -39,6 +39,7 @@ public: , defaultOptions (NULL) , valuesPageWidgetInfo (NULL) , cameraPageWidgetInfo (NULL) + , videoPageWidgetInfo (NULL) , healthPageWidgetInfo (NULL) , vibrationPageWidgetInfo (NULL) { @@ -80,6 +81,7 @@ public: QmlComponentInfo* valuesPageWidgetInfo; QmlComponentInfo* cameraPageWidgetInfo; + QmlComponentInfo* videoPageWidgetInfo; QmlComponentInfo* healthPageWidgetInfo; QmlComponentInfo* vibrationPageWidgetInfo; QVariantList instrumentPageWidgetList; @@ -148,13 +150,19 @@ QVariantList &QGCCorePlugin::settingsPages() QVariantList& QGCCorePlugin::instrumentPages(void) { if (!_p->valuesPageWidgetInfo) { - _p->valuesPageWidgetInfo = new QmlComponentInfo(tr("Values"), QUrl::fromUserInput("qrc:/qml/ValuePageWidget.qml")); - _p->cameraPageWidgetInfo = new QmlComponentInfo(tr("Camera"), QUrl::fromUserInput("qrc:/qml/CameraPageWidget.qml")); - _p->healthPageWidgetInfo = new QmlComponentInfo(tr("Health"), QUrl::fromUserInput("qrc:/qml/HealthPageWidget.qml")); + _p->valuesPageWidgetInfo = new QmlComponentInfo(tr("Values"), QUrl::fromUserInput("qrc:/qml/ValuePageWidget.qml")); + _p->cameraPageWidgetInfo = new QmlComponentInfo(tr("Camera"), QUrl::fromUserInput("qrc:/qml/CameraPageWidget.qml")); +#if defined(QGC_GST_STREAMING) + _p->videoPageWidgetInfo = new QmlComponentInfo(tr("Video"), QUrl::fromUserInput("qrc:/qml/VideoPageWidget.qml")); +#endif + _p->healthPageWidgetInfo = new QmlComponentInfo(tr("Health"), QUrl::fromUserInput("qrc:/qml/HealthPageWidget.qml")); _p->vibrationPageWidgetInfo = new QmlComponentInfo(tr("Vibration"), QUrl::fromUserInput("qrc:/qml/VibrationPageWidget.qml")); _p->instrumentPageWidgetList.append(QVariant::fromValue(_p->valuesPageWidgetInfo)); _p->instrumentPageWidgetList.append(QVariant::fromValue(_p->cameraPageWidgetInfo)); +#if defined(QGC_GST_STREAMING) + _p->instrumentPageWidgetList.append(QVariant::fromValue(_p->videoPageWidgetInfo)); +#endif _p->instrumentPageWidgetList.append(QVariant::fromValue(_p->healthPageWidgetInfo)); _p->instrumentPageWidgetList.append(QVariant::fromValue(_p->vibrationPageWidgetInfo)); } -- 2.22.0