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,
+ )