Support video events, new media player
This commit is contained in:
@@ -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,
|
||||
|
@@ -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
72
src/qml/Base/HSlider.qml
Normal 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
|
||||
}
|
||||
}
|
@@ -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 {
|
||||
|
268
src/qml/Base/MediaPlayer/OSD.qml
Normal file
268
src/qml/Base/MediaPlayer/OSD.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
src/qml/Base/MediaPlayer/OSDButton.qml
Normal file
8
src/qml/Base/MediaPlayer/OSDButton.qml
Normal file
@@ -0,0 +1,8 @@
|
||||
import QtQuick 2.12
|
||||
import "../../Base"
|
||||
|
||||
|
||||
HButton {
|
||||
backgroundColor: "transparent"
|
||||
iconItem.dimension: theme.mediaPlayer.controls.iconHeight
|
||||
}
|
9
src/qml/Base/MediaPlayer/OSDLabel.qml
Normal file
9
src/qml/Base/MediaPlayer/OSDLabel.qml
Normal 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
|
||||
}
|
74
src/qml/Base/MediaPlayer/VideoPlayer.qml
Normal file
74
src/qml/Base/MediaPlayer/VideoPlayer.qml
Normal 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
|
||||
}
|
||||
}
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
19
src/qml/Chat/Timeline/EventVideo.qml
Normal file
19
src/qml/Chat/Timeline/EventVideo.qml
Normal 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] : []
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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] }
|
||||
|
Reference in New Issue
Block a user