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

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