From 692749e72fb3bcd9d99f326f050ef7859746ca9f Mon Sep 17 00:00:00 2001 From: miruka Date: Tue, 17 Sep 2019 16:30:04 -0400 Subject: [PATCH] Support video events, new media player --- README.md | 2 + TODO.md | 7 + harmonyqml.pro | 2 +- src/icons/thin/{play.svg => play-overlay.svg} | 0 src/icons/thin/player-fullscreen-exit.svg | 3 + src/icons/thin/player-fullscreen.svg | 3 + src/icons/thin/player-loop.svg | 3 + src/icons/thin/player-pause.svg | 6 + src/icons/thin/player-play.svg | 3 + src/icons/thin/player-restart.svg | 3 + src/icons/thin/player-speed.svg | 3 + src/icons/thin/player-track-audio.svg | 3 + src/icons/thin/player-track-subtitle.svg | 3 + src/icons/thin/player-track-video.svg | 3 + src/icons/thin/player-volume-high.svg | 9 + src/icons/thin/player-volume-low.svg | 8 + src/icons/thin/player-volume-mute.svg | 11 + src/python/config_files.py | 12 +- src/qml/Base/HImage.qml | 4 +- src/qml/Base/HProgressBar.qml | 7 +- src/qml/Base/HSlider.qml | 72 +++++ src/qml/Base/HToolTip.qml | 4 +- src/qml/Base/MediaPlayer/OSD.qml | 268 ++++++++++++++++++ src/qml/Base/MediaPlayer/OSDButton.qml | 8 + src/qml/Base/MediaPlayer/OSDLabel.qml | 9 + src/qml/Base/MediaPlayer/VideoPlayer.qml | 74 +++++ src/qml/Chat/Timeline/EventDelegate.qml | 6 - src/qml/Chat/Timeline/EventMediaLoader.qml | 4 + src/qml/Chat/Timeline/EventVideo.qml | 19 ++ src/qml/UI.qml | 18 +- src/qml/utils.js | 20 ++ src/themes/Default.qpl | 30 +- 32 files changed, 605 insertions(+), 22 deletions(-) rename src/icons/thin/{play.svg => play-overlay.svg} (100%) create mode 100644 src/icons/thin/player-fullscreen-exit.svg create mode 100644 src/icons/thin/player-fullscreen.svg create mode 100644 src/icons/thin/player-loop.svg create mode 100644 src/icons/thin/player-pause.svg create mode 100644 src/icons/thin/player-play.svg create mode 100644 src/icons/thin/player-restart.svg create mode 100644 src/icons/thin/player-speed.svg create mode 100644 src/icons/thin/player-track-audio.svg create mode 100644 src/icons/thin/player-track-subtitle.svg create mode 100644 src/icons/thin/player-track-video.svg create mode 100644 src/icons/thin/player-volume-high.svg create mode 100644 src/icons/thin/player-volume-low.svg create mode 100644 src/icons/thin/player-volume-mute.svg create mode 100644 src/qml/Base/HSlider.qml create mode 100644 src/qml/Base/MediaPlayer/OSD.qml create mode 100644 src/qml/Base/MediaPlayer/OSDButton.qml create mode 100644 src/qml/Base/MediaPlayer/OSDLabel.qml create mode 100644 src/qml/Base/MediaPlayer/VideoPlayer.qml create mode 100644 src/qml/Chat/Timeline/EventVideo.qml diff --git a/README.md b/README.md index ad32e032..be78fc75 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ Qt 5.12+, including: - qt5-qmake - qt5-devel +- qtav + - python3 - python3-devel - olm-python3 >= 3.1 diff --git a/TODO.md b/TODO.md index cec8ff50..ca38ce4c 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,7 @@ - Media - Caching + - What effect will it have on GIFs? Can we set `cache:false` on them or get + the frame count once they're cached? - Downloading - Bottom/top bar - Uploading (+local echo) @@ -11,6 +13,11 @@ - GIF thumbnails: load the real animated image - Copy thumbnail URL in context menu? + - GIFs can use the video player + - Video bug: when media is done playing, clicking on progress slider always + bring back to the beginning no matter where + - Video: missing buttons and small size problems + - Refactor EventContent - No background/padding around medias diff --git a/harmonyqml.pro b/harmonyqml.pro index 914280ef..70c21276 100644 --- a/harmonyqml.pro +++ b/harmonyqml.pro @@ -1,5 +1,5 @@ # widgets: Make native file dialogs available to QML (must use QApplication) -QT = quick widgets +QT = quick widgets av DEFINES += QT_DEPRECATED_WARNINGS CONFIG += warn_off c++11 release TEMPLATE = app diff --git a/src/icons/thin/play.svg b/src/icons/thin/play-overlay.svg similarity index 100% rename from src/icons/thin/play.svg rename to src/icons/thin/play-overlay.svg diff --git a/src/icons/thin/player-fullscreen-exit.svg b/src/icons/thin/player-fullscreen-exit.svg new file mode 100644 index 00000000..4291b027 --- /dev/null +++ b/src/icons/thin/player-fullscreen-exit.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/thin/player-fullscreen.svg b/src/icons/thin/player-fullscreen.svg new file mode 100644 index 00000000..89d8a4d4 --- /dev/null +++ b/src/icons/thin/player-fullscreen.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/thin/player-loop.svg b/src/icons/thin/player-loop.svg new file mode 100644 index 00000000..5a5d432e --- /dev/null +++ b/src/icons/thin/player-loop.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/thin/player-pause.svg b/src/icons/thin/player-pause.svg new file mode 100644 index 00000000..85180f87 --- /dev/null +++ b/src/icons/thin/player-pause.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/icons/thin/player-play.svg b/src/icons/thin/player-play.svg new file mode 100644 index 00000000..721df82d --- /dev/null +++ b/src/icons/thin/player-play.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/thin/player-restart.svg b/src/icons/thin/player-restart.svg new file mode 100644 index 00000000..f6671ec8 --- /dev/null +++ b/src/icons/thin/player-restart.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/thin/player-speed.svg b/src/icons/thin/player-speed.svg new file mode 100644 index 00000000..2616133d --- /dev/null +++ b/src/icons/thin/player-speed.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/thin/player-track-audio.svg b/src/icons/thin/player-track-audio.svg new file mode 100644 index 00000000..0f9bc9c8 --- /dev/null +++ b/src/icons/thin/player-track-audio.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/thin/player-track-subtitle.svg b/src/icons/thin/player-track-subtitle.svg new file mode 100644 index 00000000..a2203860 --- /dev/null +++ b/src/icons/thin/player-track-subtitle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/thin/player-track-video.svg b/src/icons/thin/player-track-video.svg new file mode 100644 index 00000000..1ac04c5d --- /dev/null +++ b/src/icons/thin/player-track-video.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/thin/player-volume-high.svg b/src/icons/thin/player-volume-high.svg new file mode 100644 index 00000000..e6fe8840 --- /dev/null +++ b/src/icons/thin/player-volume-high.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/icons/thin/player-volume-low.svg b/src/icons/thin/player-volume-low.svg new file mode 100644 index 00000000..1e906cc2 --- /dev/null +++ b/src/icons/thin/player-volume-low.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/icons/thin/player-volume-mute.svg b/src/icons/thin/player-volume-mute.svg new file mode 100644 index 00000000..1ae1e25d --- /dev/null +++ b/src/icons/thin/player-volume-mute.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/python/config_files.py b/src/python/config_files.py index 5f76d6dd..f67d74c5 100644 --- a/src/python/config_files.py +++ b/src/python/config_files.py @@ -7,8 +7,6 @@ from typing import Any, Dict import aiofiles -import pyotherside - from .backend import Backend from .theme_parser import convert_to_qml from .utils import dict_update_recursive @@ -106,11 +104,19 @@ class UISettings(JSONConfigFile): async def default_data(self) -> JsonData: return { "alertOnMessageForMsec": 4000, - "autoPlayGIF": True, "clearRoomFilterOnEnter": True, "clearRoomFilterOnEscape": True, "theme": "Default.qpl", "writeAliases": {}, + "media": { + "autoLoad": True, + "autoPlay": False, + "autoPlayGIF": True, + "autoHideOSDAfterMsec": 3000, + "defaultVolume": 100, + "startMuted": False, + "hoverPreviewHeight": 192, + }, "keys": { "startPythonDebugger": "Alt+Shift+D", "toggleDebugConsole": "Alt+Shift+C", diff --git a/src/qml/Base/HImage.qml b/src/qml/Base/HImage.qml index 3738dffe..a1e27289 100644 --- a/src/qml/Base/HImage.qml +++ b/src/qml/Base/HImage.qml @@ -34,7 +34,7 @@ Image { cache: true // Needed to allow GIFs to loop paused: ! visible || window.hidden || userPaused - property bool userPaused: ! window.settings.autoPlayGIF + property bool userPaused: ! window.settings.media.autoPlayGIF TapHandler { onTapped: parent.userPaused = ! parent.userPaused @@ -42,7 +42,7 @@ Image { HIcon { anchors.centerIn: parent - svgName: "play" + svgName: "play-overlay" colorize: "transparent" dimension: Math.min( parent.width - theme.spacing * 2, diff --git a/src/qml/Base/HProgressBar.qml b/src/qml/Base/HProgressBar.qml index 4606bfc6..998f5c76 100644 --- a/src/qml/Base/HProgressBar.qml +++ b/src/qml/Base/HProgressBar.qml @@ -4,10 +4,13 @@ import QtQuick.Controls 2.12 ProgressBar { id: bar + property color backgroundColor: theme.controls.progressBar.background + property color foregroundColor: theme.controls.progressBar.foreground + background: Rectangle { implicitWidth: 200 implicitHeight: theme.controls.progressBar.height - color: theme.controls.progressBar.background + color: backgroundColor } contentItem: Item { @@ -17,7 +20,7 @@ ProgressBar { Rectangle { width: bar.visualPosition * parent.width height: parent.height - color: theme.controls.progressBar.foreground + color: foregroundColor } } } diff --git a/src/qml/Base/HSlider.qml b/src/qml/Base/HSlider.qml new file mode 100644 index 00000000..ae0cb087 --- /dev/null +++ b/src/qml/Base/HSlider.qml @@ -0,0 +1,72 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +Slider { + id: slider + leftPadding: 0 + rightPadding: leftPadding + topPadding: 0 + bottomPadding: topPadding + + property bool enableRadius: true + property bool fullHeight: false + property color backgroundColor: theme.controls.slider.background + property color foregroundColor: theme.controls.slider.foreground + + property alias toolTip: toolTip + property alias mouseArea: mouseArea + + background: Rectangle { + color: backgroundColor + x: slider.leftPadding + y: slider.topPadding + slider.availableHeight / 2 - height / 2 + + implicitWidth: 200 + implicitHeight: theme.controls.slider.height + width: slider.availableWidth + height: fullHeight ? slider.height : implicitHeight + radius: enableRadius ? theme.controls.slider.radius : 0 + + Rectangle { + width: slider.visualPosition * parent.width + height: parent.height + color: foregroundColor + radius: parent.radius + } + } + + handle: Rectangle { + x: slider.leftPadding + slider.visualPosition * + (slider.availableWidth - width) + y: slider.topPadding + slider.availableHeight / 2 - height / 2 + + implicitWidth: theme.controls.slider.handle.size + implicitHeight: implicitWidth + radius: implicitWidth / 2 + + color: slider.pressed ? + theme.controls.slider.handle.pressedInside : + theme.controls.slider.handle.inside + + border.color: slider.pressed ? + theme.controls.slider.handle.pressedBorder : + theme.controls.slider.handle.border + + Behavior on color { HColorAnimation {} } + Behavior on border.color { HColorAnimation {} } + } + + HToolTip { + id: toolTip + parent: slider.handle + visible: slider.pressed && text + delay: 0 + } + + MouseArea { + id: mouseArea + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: slider.hovered ? Qt.PointingHandCursor : Qt.ArrowCursor + } +} diff --git a/src/qml/Base/HToolTip.qml b/src/qml/Base/HToolTip.qml index 1d4b7959..d374f559 100644 --- a/src/qml/Base/HToolTip.qml +++ b/src/qml/Base/HToolTip.qml @@ -32,7 +32,7 @@ ToolTip { bottomPadding: topPadding Layout.maximumWidth: Math.min( - mainUI.width / 1.25, theme.fontSize.normal * 0.5 * 75, + window.width / 1.25, theme.fontSize.normal * 0.5 * 75, ) } } @@ -41,7 +41,7 @@ ToolTip { HNumberAnimation { property: "opacity"; from: 0.0; to: 1.0 } } exit: Transition { - HNumberAnimation { property: "opacity"; from: 1.0; to: 0.0 } + HNumberAnimation { property: "opacity"; to: 0.0 } } TapHandler { diff --git a/src/qml/Base/MediaPlayer/OSD.qml b/src/qml/Base/MediaPlayer/OSD.qml new file mode 100644 index 00000000..7282db4d --- /dev/null +++ b/src/qml/Base/MediaPlayer/OSD.qml @@ -0,0 +1,268 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtAV 1.7 +import "../../Base" +import "../../utils.js" as Utils + +HColumnLayout { + id: osd + visible: osdScaleTransform.yScale > 0 + + transform: Scale { + id: osdScaleTransform + yScale: osdHover.hovered || + media.playbackState !== MediaPlayer.PlayingState || + osd.showup ? + 1 : 0 + origin.y: osd.height + + Behavior on yScale { HNumberAnimation {} } + } + + + property Item media: parent // QtAV.Video or QtAV.Audio + property bool showup: false + property bool enableFullScreen: false + property bool fullScreen: false + + property int savedDuration: 0 + readonly property int duration: media.duration + readonly property int boundPosition: + Math.min(media.position, savedDuration) + + + onShowupChanged: if (showup) osdHideTimer.restart() + onDurationChanged: if (duration) savedDuration = duration + + + function togglePlay() { + media.playbackState === MediaPlayer.PlayingState ? + media.pause() : media.play() + } + + function seekToPosition(pos) { // pos: 0.0 to 1.0 + if (media.playbackState === MediaPlayer.StoppedState) media.play() + if (media.seekable) media.seek(pos * savedDuration) + } + + + HoverHandler { id: osdHover } + + Timer { + id: osdHideTimer + interval: window.settings.media.autoHideOSDAfterMsec + onTriggered: osd.showup = false + } + + HSlider { + id: timeSlider + topPadding: 5 + z: 1 + to: savedDuration || boundPosition + backgroundColor: theme.mediaPlayer.progress.background + enableRadius: false + fullHeight: true + mouseArea.hoverEnabled: true + + onMoved: seekToPosition(timeSlider.position) + + Layout.fillWidth: true + Layout.preferredHeight: theme.mediaPlayer.progress.height + + HToolTip { + id: previewToolTip + x: timeSlider.mouseArea.mouseX - width / 2 + visible: preview.implicitWidth >= + previewLabel.implicitWidth + previewLabel.padding && + + preview.implicitHeight >= + previewLabel.implicitHeight + previewLabel.padding && + + ! timeSlider.pressed && timeSlider.mouseArea.containsMouse + + readonly property int wantTimestamp: + visible ? + savedDuration * + (timeSlider.mouseArea.mouseX / timeSlider.mouseArea.width) : + -1 + + Timer { + interval: 300 + running: previewToolTip.visible + repeat: true + triggeredOnStart: true + onTriggered: preview.timestamp = previewToolTip.wantTimestamp + } + + contentItem: VideoPreview { + id: preview + implicitHeight: Math.min( + window.settings.media.hoverPreviewHeight, + media.height - osd.height - theme.spacing + ) + implicitWidth: Math.min( + implicitHeight * media.savedAspectRatio, + media.width - theme.spacing, + ) + file: media.source + + HLabel { + id: previewLabel + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.margins: padding / 4 + text: Utils.formatDuration(previewToolTip.wantTimestamp) + padding: theme.spacing / 2 + opacity: previewToolTip.wantTimestamp === -1 ? 0 : 1 + + background: Rectangle { + color: theme.mediaPlayer.controls.background + radius: theme.radius + } + } + } + } + + Binding { + target: timeSlider + property: "value" + value: boundPosition + when: ! timeSlider.pressed + } + } + + Rectangle { + color: theme.mediaPlayer.controls.background + + Layout.fillWidth: true + Layout.preferredHeight: childrenRect.height + + HRowLayout { + width: parent.width + + OSDButton { + readonly property string mode: + media.playbackState === MediaPlayer.StoppedState && + savedDuration && + boundPosition >= savedDuration - 500 ? + "restart" : + + media.playbackState == MediaPlayer.PlayingState ? "pause" : + + "play" + + icon.name: "player-" + mode + toolTip.text: qsTr( + mode === "play" ? "Play" : + mode === "pause" ? "Pause" : + "Restart" + ) + onClicked: togglePlay() + } + + // OSDButton { + // icon.name: "player-loop" + // visible: false + // } + + OSDButton { + id: volumeButton + icon.name: "player-volume-" + ( + media.muted ? "mute" : media.volume > 0.5 ? "high" : "low" + ) + text: media.muted ? "" : Math.round(media.volume * 100) + toolTip.text: media.muted ? qsTr("Unmute") : qsTr("Mute") + onClicked: media.muted = ! media.muted + } + + HSlider { + value: media.volume + onMoved: media.volume = value + + visible: Layout.preferredWidth > 0 + Layout.preferredWidth: + ! media.muted && + (hovered || pressed || volumeButton.hovered) ? + theme.mediaPlayer.controls.volumeSliderWidth : 0 + Layout.fillHeight: true + + Behavior on Layout.preferredWidth { HNumberAnimation {} } + } + + OSDButton { + id: speedButton + icon.name: "player-speed" + text: qsTr("%1x").arg(Utils.round(media.playbackRate)) + toolTip.text: qsTr("Reset speed") + onClicked: media.playbackRate = 1 + } + + HSlider { + id: speedSlider + from: 0.2 + to: 4 + value: media.playbackRate + stepSize: 0.2 + snapMode: HSlider.SnapAlways + + onMoved: media.playbackRate = value + + visible: Layout.preferredWidth > 0 + Layout.preferredWidth: + (hovered || pressed || speedButton.hovered) ? + theme.mediaPlayer.controls.speedSliderWidth : 0 + Layout.fillHeight: true + + Behavior on Layout.preferredWidth { HNumberAnimation {} } + } + + OSDLabel { + text: boundPosition && savedDuration ? + + qsTr("%1 / %2") + .arg(Utils.formatDuration(boundPosition)) + .arg(Utils.formatDuration(savedDuration)) : + + boundPosition || savedDuration ? + Utils.formatDuration(boundPosition || savedDuration) : + + "" + } + + HSpacer {} + + OSDLabel { + text: boundPosition && savedDuration ? + qsTr("-%1").arg( + Utils.formatDuration(savedDuration - boundPosition) + ) : "" + } + + // OSDButton { + // icon.name: "player-track-video" + // } + + // OSDButton { + // icon.name: "player-track-audio" + // } + + // OSDButton { + // icon.name: "player-track-subtitle" + // } + + OSDButton { + icon.name: "download" + toolTip.text: qsTr("Download") + onClicked: Qt.openUrlExternally(media.source) + } + + OSDButton { + id: fullScreenButton + icon.name: "player-fullscreen" + (fullScreen ? "-exit" : "") + toolTip.text: fullScreen ? + qsTr("Exit fullscreen") : qsTr("Fullscreen") + onClicked: fullScreen = ! fullScreen + } + } + } +} diff --git a/src/qml/Base/MediaPlayer/OSDButton.qml b/src/qml/Base/MediaPlayer/OSDButton.qml new file mode 100644 index 00000000..47100f3d --- /dev/null +++ b/src/qml/Base/MediaPlayer/OSDButton.qml @@ -0,0 +1,8 @@ +import QtQuick 2.12 +import "../../Base" + + +HButton { + backgroundColor: "transparent" + iconItem.dimension: theme.mediaPlayer.controls.iconHeight +} diff --git a/src/qml/Base/MediaPlayer/OSDLabel.qml b/src/qml/Base/MediaPlayer/OSDLabel.qml new file mode 100644 index 00000000..b8ad868d --- /dev/null +++ b/src/qml/Base/MediaPlayer/OSDLabel.qml @@ -0,0 +1,9 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import "../../Base" + + +HLabel { + Layout.leftMargin: theme.spacing / 2 + Layout.rightMargin: Layout.leftMargin +} diff --git a/src/qml/Base/MediaPlayer/VideoPlayer.qml b/src/qml/Base/MediaPlayer/VideoPlayer.qml new file mode 100644 index 00000000..ef7d8497 --- /dev/null +++ b/src/qml/Base/MediaPlayer/VideoPlayer.qml @@ -0,0 +1,74 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtAV 1.7 + +Video { + id: video + autoLoad: window.settings.media.autoLoad + autoPlay: window.settings.media.autoPlay + volume: window.settings.media.defaultVolume / 100 + muted: window.settings.media.startMuted + implicitWidth: fullScreen ? window.width : 640 + implicitHeight: fullScreen ? window.height : (width / savedAspectRatio) + + + property bool hovered: false + property alias fullScreen: osd.fullScreen + + property int oldVisibility: Window.Windowed + property QtObject oldParent: video.parent + + property real savedAspectRatio: 16 / 9 + + + onSourceAspectRatioChanged: + if (sourceAspectRatio) savedAspectRatio = sourceAspectRatio + + onFullScreenChanged: { + if (fullScreen) { + oldVisibility = window.visibility + window.visibility = Window.FullScreen + + oldParent = video.parent + video.parent = mainUI.fullScreenPopup.contentItem + + mainUI.fullScreenPopup.open() + + } else { + window.visibility = oldVisibility + mainUI.fullScreenPopup.close() + + video.parent = oldParent + } + } + + + Connections { + target: mainUI.fullScreenPopup + onClosed: fullScreen = false + } + + TapHandler { + onTapped: osd.togglePlay() + onDoubleTapped: video.fullScreen = ! video.fullScreen + } + + MouseArea { + width: parent.width + height: parent.height - (osd.visible ? osd.height : 0) + acceptedButtons: Qt.NoButton + hoverEnabled: true + propagateComposedEvents: true + + onContainsMouseChanged: video.hovered = containsMouse + onMouseXChanged: osd.showup = true + onMouseYChanged: osd.showup = true + } + + OSD { + id: osd + width: parent.width + anchors.bottom: parent.bottom + enableFullScreen: true + } +} diff --git a/src/qml/Chat/Timeline/EventDelegate.qml b/src/qml/Chat/Timeline/EventDelegate.qml index 2219fb09..1d41ed0d 100644 --- a/src/qml/Chat/Timeline/EventDelegate.qml +++ b/src/qml/Chat/Timeline/EventDelegate.qml @@ -130,12 +130,6 @@ Column { contextMenu.media[0] === EventDelegate.Media.Image ? qsTr("Copy image address") : - contextMenu.media[0] === EventDelegate.Media.Video ? - qsTr("Copy video address") : - - contextMenu.media[0] === EventDelegate.Media.Audio ? - qsTr("Copy audio address") : - qsTr("Copy media address") visible: Boolean(text) diff --git a/src/qml/Chat/Timeline/EventMediaLoader.qml b/src/qml/Chat/Timeline/EventMediaLoader.qml index 3870e6b3..365ad37d 100644 --- a/src/qml/Chat/Timeline/EventMediaLoader.qml +++ b/src/qml/Chat/Timeline/EventMediaLoader.qml @@ -65,6 +65,10 @@ HLoader { fileSize: info.media_size, } + } else if (type === EventDelegate.Media.Video) { + var file = "EventVideo.qml" + var props = { source: mediaUrl } + } else { return } loader.setSource(file, props) diff --git a/src/qml/Chat/Timeline/EventVideo.qml b/src/qml/Chat/Timeline/EventVideo.qml new file mode 100644 index 00000000..12af45da --- /dev/null +++ b/src/qml/Chat/Timeline/EventVideo.qml @@ -0,0 +1,19 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtAV 1.7 +import "../../Base" +import "../../Base/MediaPlayer" +import "../../utils.js" as Utils + +VideoPlayer { + id: video + width: fullScreen ? implicitWidth : Math.min( + mainColumn.width - eventContent.spacing * 2, + theme.chat.message.videoWidth, + ) + + onHoveredChanged: { + eventDelegate.hoveredMediaTypeUrl = + hovered ? [EventDelegate.Media.Video, video.source] : [] + } +} diff --git a/src/qml/UI.qml b/src/qml/UI.qml index 00cde1e0..6e425869 100644 --- a/src/qml/UI.qml +++ b/src/qml/UI.qml @@ -12,11 +12,12 @@ Item { Component.onCompleted: window.mainUI = mainUI Keys.forwardTo: [shortcuts] - property alias shortcuts: shortcuts - property alias sidePane: sidePane - property alias pageLoader: pageLoader - property alias pressAnimation: pressAnimation - property alias debugConsole: debugConsoleLoader.item + readonly property alias shortcuts: shortcuts + readonly property alias sidePane: sidePane + readonly property alias pageLoader: pageLoader + readonly property alias pressAnimation: pressAnimation + readonly property alias debugConsole: debugConsoleLoader.item + readonly property alias fullScreenPopup: fullScreenPopup SequentialAnimation { id: pressAnimation @@ -145,4 +146,11 @@ Item { id: debugConsoleLoader source: debugMode ? "DebugConsole.qml" : "" } + + HPopup { + id: fullScreenPopup + dim: false + width: window.width + height: window.height + } } diff --git a/src/qml/utils.js b/src/qml/utils.js index 80d44d54..e2c16084 100644 --- a/src/qml/utils.js +++ b/src/qml/utils.js @@ -204,6 +204,26 @@ function formatTime(time, seconds=true) { } +function formatDuration(milliseconds) { + let totalSeconds = milliseconds / 1000 + + let hours = Math.floor(totalSeconds / 3600) + let minutes = Math.floor((totalSeconds % 3600) / 60) + let seconds = Math.floor(totalSeconds % 60) + + if (seconds < 10) seconds = "0" + seconds + if (hours < 1) return minutes + ":" + seconds + + if (minutes < 10) minutes = "0" + minutes + return hours + ":" + minutes + ":" + seconds +} + + +function round(float) { + return parseFloat(float.toFixed(2)) +} + + function getItem(array, mainKey, value) { for (let i = 0; i < array.length; i++) { if (array[i][mainKey] === value) { return array[i] } diff --git a/src/themes/Default.qpl b/src/themes/Default.qpl index b5c44016..71527a38 100644 --- a/src/themes/Default.qpl +++ b/src/themes/Default.qpl @@ -24,7 +24,7 @@ real disabledElementsOpacity: 0.3 fontSize: - int smaller: 13 * fontScale + int smaller: 13 * fontScale int small: 13 * fontScale int normal: 16 * fontScale int big: 22 * fontScale @@ -176,6 +176,19 @@ controls: color background: colors.inputBackground color foreground: colors.accentBackground + slider: + int radius: 2 + int height: controls.progressBar.height + color background: controls.progressBar.background + color foreground: controls.progressBar.foreground + + handle: + int size: 20 + color inside: hsluv(0, 0, 90) + color pressedInside: "white" + color border: "black" + color pressedBorder: colors.strongAccentBackground + image: int maxPauseIndicatorSize: 64 @@ -320,6 +333,7 @@ chat: '\n' int thumbnailWidth: 256 + int videoWidth: 512 daybreak: color background: colors.strongBackground @@ -342,3 +356,17 @@ chat: composer: color background: colors.strongBackground + + +mediaPlayer: + progress: + int height: 8 + color background: hsluv(0, 0, 0, 0.5) + + controls: + int iconHeight: 20 + int volumeSliderWidth: 100 + int speedSliderWidth: 100 + color background: hsluv( + colors.hue, colors.saturation * 1.25, colors.intensity * 2, 0.85, + )