Support video events, new media player

This commit is contained in:
miruka 2019-09-17 16:30:04 -04:00
parent 76ffdfd28a
commit 692749e72f
32 changed files with 605 additions and 22 deletions

View File

@ -14,6 +14,8 @@ Qt 5.12+, including:
- qt5-qmake
- qt5-devel
- qtav
- python3
- python3-devel
- olm-python3 >= 3.1

View File

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

View File

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

View File

Before

Width:  |  Height:  |  Size: 334 B

After

Width:  |  Height:  |  Size: 334 B

View File

@ -0,0 +1,3 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m15 2h2v5h7v2h-9zm9 13v2h-7v5h-2v-7zm-15 7h-2v-5h-7v-2h9zm-9-13v-2h7v-5h2v7z"/>
</svg>

After

Width:  |  Height:  |  Size: 184 B

View File

@ -0,0 +1,3 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m24 9h-2v-4h-4v-2h6zm-6 12v-2h4v-4h2v6zm-18-6h2v4h4v2h-6zm6-12v2h-4v4h-2v-6z"/>
</svg>

After

Width:  |  Height:  |  Size: 184 B

View File

@ -0,0 +1,3 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m18 13v5h-5l1.607-1.608c-3.404-2.824-5.642-8.392-9.179-8.392-2.113 0-3.479 1.578-3.479 4s1.365 4 3.479 4c1.664 0 2.86-1.068 4.015-2.392l1.244 1.561c-1.499 1.531-3.05 2.831-5.259 2.831-3.197 0-5.428-2.455-5.428-6s2.231-6 5.428-6c4.839 0 7.34 6.449 10.591 8.981zm.57-7c-2.211 0-3.762 1.301-5.261 2.833l1.244 1.561c1.156-1.325 2.352-2.394 4.017-2.394 2.114 0 3.48 1.578 3.48 4 0 1.819-.771 3.162-2.051 3.718v2.099c2.412-.623 4-2.829 4-5.816.001-3.546-2.231-6.001-5.429-6.001z"/>
</svg>

After

Width:  |  Height:  |  Size: 580 B

View File

@ -0,0 +1,6 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<g stroke-width=".948329">
<path d="m8.928091 20.993272h-4v-17.9865437h4z"/>
<path d="m19.035955 3.0067283h-4v17.9865437h4z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 247 B

View File

@ -0,0 +1,3 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m3 22v-20l18 10z"/>
</svg>

After

Width:  |  Height:  |  Size: 124 B

View File

@ -0,0 +1,3 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m13.5 2c-5.629 0-10.212 4.436-10.475 10h-3.025l4.537 5.917 4.463-5.917h-2.975c.26-3.902 3.508-7 7.475-7 4.136 0 7.5 3.364 7.5 7.5s-3.364 7.5-7.5 7.5c-2.381 0-4.502-1.119-5.876-2.854l-1.847 2.449c1.919 2.088 4.664 3.405 7.723 3.405 5.798 0 10.5-4.702 10.5-10.5s-4.702-10.5-10.5-10.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 390 B

View File

@ -0,0 +1,3 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m15.91 13.34 2.636-4.026-.454-.406-3.673 3.099c-.675-.138-1.402.068-1.894.618-.736.823-.665 2.088.159 2.824s2.088.665 2.824-.159c.492-.55.615-1.295.402-1.95zm-3.91-10.646v-2.694h4v2.694c-1.439-.243-2.592-.238-4 0zm8.851 2.064 1.407-1.407 1.414 1.414-1.321 1.321c-.462-.484-.964-.927-1.5-1.328zm-18.851 4.242h8v2h-8zm-2 4h8v2h-8zm3 4h7v2h-7zm21-3c0 5.523-4.477 10-10 10-2.79 0-5.3-1.155-7.111-3h3.28c1.138.631 2.439 1 3.831 1 4.411 0 8-3.589 8-8s-3.589-8-8-8c-1.392 0-2.693.369-3.831 1h-3.28c1.811-1.845 4.321-3 7.111-3 5.523 0 10 4.477 10 10z"/>
</svg>

After

Width:  |  Height:  |  Size: 650 B

View File

@ -0,0 +1,3 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m13 0h-2v15.676c-3.379-.667-7 1.915-7 4.731 0 2.367 1.881 3.593 3.919 3.593 2.423 0 5.077-1.728 5.081-5.24v-12.76c3.009 2.223 5.623 3.243 5.059 7 1.431-1.727 1.941-2.817 1.941-4.051 0-4.446-7-5.915-7-8.949z"/>
</svg>

After

Width:  |  Height:  |  Size: 314 B

View File

@ -0,0 +1,3 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m0 1v16.981h4v5.019l7-5.019h13v-16.981zm13 12h-8v-1h8zm6-3h-14v-1h14zm0-3h-14v-1h14z"/>
</svg>

After

Width:  |  Height:  |  Size: 192 B

View File

@ -0,0 +1,3 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m22 1v2h-2v-2h-2v4h-12v-4h-2v2h-2v-2h-2v22h2v-2h2v2h2v-4h12v4h2v-2h2v2h2v-22zm-18 18h-2v-2h2zm0-4h-2v-2h2zm0-4h-2v-2h2zm0-4h-2v-2h2zm14 9h-12v-8h12zm4 3h-2v-2h2zm0-4h-2v-2h2zm0-4h-2v-2h2zm0-4h-2v-2h2z"/>
</svg>

After

Width:  |  Height:  |  Size: 308 B

View File

@ -0,0 +1,9 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m14.509208 8.343398c1.202865.9346638 1.945334 2.226588 1.94209 3.651856-.0032 1.425268-.748954 2.714677-1.953439 3.648082l.978322 1.538485c1.713516-1.324631 2.775344-3.158736 2.780206-5.184051.0032-2.0265727-1.05048-3.8594193-2.759132-5.1890826z" stroke-width="1.428038"/>
<path d="m18.318701 4.9920248c2.307024 1.7957761 3.72921 4.2712232 3.723838 7.0065862-.0054 2.735361-1.440099 5.212198-3.754288 7.003804l.980846 1.74296c2.887361-2.234991 4.68032-5.328952 4.685693-8.743986.009-3.4150319-1.766089-6.5062134-4.648077-8.7467639z" stroke-width="1.57784"/>
<g transform="matrix(.6875 0 0 1.01875 0 -.225)">
<path d="m16 2-13.2653508 5v10l13.2653508 5z" stroke-width="1.236723"/>
<path d="m2.7346492 7h-2.7346492v10h2.7346492z" stroke-width=".739547"/>
<path d="m7 7c-4.7095572 2.7190641-5.0350591 2.9069927 0 0z" fill="none"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 973 B

View File

@ -0,0 +1,8 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m14.509208 8.343398c1.202865.9346638 1.945334 2.226588 1.94209 3.651856-.0032 1.425268-.748954 2.714677-1.953439 3.648082l.978322 1.538485c1.713516-1.324631 2.775344-3.158736 2.780206-5.184051.0032-2.0265727-1.05048-3.8594193-2.759132-5.1890826z" stroke-width="1.428038"/>
<g transform="matrix(.6875 0 0 1.01875 0 -.225)">
<path d="m16 2-13.2653508 5v10l13.2653508 5z" stroke-width="1.236723"/>
<path d="m2.7346492 7h-2.7346492v10h2.7346492z" stroke-width=".739547"/>
<path d="m7 7c-4.7095572 2.7190641-5.0350591 2.9069927 0 0z" fill="none"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 684 B

View File

@ -0,0 +1,11 @@
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<g transform="matrix(.6875 0 0 1.01875 0 -.225)">
<path d="m16 2-13.7653508 5v10l13.7653508 5z" stroke-width="1.236723"/>
<path d="m2.7346492 7h-2.7346492v10h2.7346492z" stroke-width=".739547"/>
<path d="m7 7c-4.7095572 2.7190641-5.0350591 2.9069927 0 0z" fill="none"/>
</g>
<g fill="none" stroke="#000" stroke-width="1.767194">
<path d="m14.900494 16.233449c7.7076-7.7076018 8.474709-8.4747094 8.474709-8.4747094"/>
<path d="m23.375202 16.233449c-7.707601-7.7076013-8.474709-8.4747098-8.474709-8.4747098"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 660 B

View File

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

View File

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

View File

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

72
src/qml/Base/HSlider.qml Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,8 @@
import QtQuick 2.12
import "../../Base"
HButton {
backgroundColor: "transparent"
iconItem.dimension: theme.mediaPlayer.controls.iconHeight
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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] : []
}
}

View File

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

View File

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

View File

@ -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:
'<style type"text/css">\n' + styleSheet + '\n</style>\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,
)