nheko/resources/qml/delegates/PlayableMediaMessage.qml
2021-11-09 19:28:53 -05:00

294 lines
12 KiB
QML

// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import "../"
import "../ui/media"
import QtMultimedia 5.15
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2
import im.nheko 1.0
ColumnLayout {
required property double proportionalHeight
required property int type
required property int originalWidth
required property string thumbnailUrl
required property string eventId
required property string url
required property string body
required property string filesize
function durationToString(duration) {
function maybeZeroPrepend(time) {
return (time < 10) ? "0" + time.toString() :
time.toString()
}
var totalSeconds = Math.floor(duration / 1000)
var seconds = totalSeconds % 60
var minutes = (Math.floor(totalSeconds / 60)) % 60
var hours = (Math.floor(totalSeconds / (60 * 24))) % 24
// Always show minutes and don't prepend zero into the leftmost element
var ss = maybeZeroPrepend(seconds)
var mm = (hours > 0) ? maybeZeroPrepend(minutes) : minutes.toString()
var hh = hours.toString()
if (hours < 1) {
return mm + ":" + ss
}
return hh + ":" + mm + ":" + ss
}
id: content
Layout.fillWidth: true
MxcMedia {
id: mxcmedia
// TODO: Show error in overlay or so?
onError: console.log(error)
roomm: room
// desiredVolume is a float from 0.0 -> 1.0, MediaPlayer volume is an int from 0 to 100
// this value automatically gets clamped for us between these two values.
volume: volumeSlider.desiredVolume * 100
muted: volumeSlider.muted
}
Rectangle {
id: videoContainer
visible: type == MtxEvent.VideoMessage
//property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width < 1 ? 400 : /////model.data.width)
// property double tempWidth: (model.data.width < 1) ? 400 : model.data.width
// property double tempHeight: tempWidth * model.data.proportionalHeight
//property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 400 : originalWidth)
property double tempWidth: Math.min(parent ? parent.width: undefined, originalWidth < 1 ? 400 : originalWidth)
property double tempHeight: tempWidth * proportionalHeight
property double divisor: isReply ? 4 : 2
property bool tooHigh: tempHeight > timelineRoot.height / divisor
color: Nheko.colors.window
Layout.preferredHeight: tooHigh ? timelineRoot.height / divisor : tempHeight
Layout.preferredWidth: tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth
Layout.maximumWidth: Layout.preferredWidth
Image {
anchors.fill: parent
source: thumbnailUrl.replace("mxc://", "image://MxcImage/")
asynchronous: true
fillMode: Image.PreserveAspectFit
// Button and window colored overlay to cache media
Item {
// Display over video controls
z: videoOutput.z + 1
visible: !mxcmedia.loaded
anchors.fill: parent
//color: Nheko.colors.window
//opacity: 0.5
Image {
property color buttonColor: (cacheVideoArea.containsMouse) ? Nheko.colors.highlight :
Nheko.colors.text
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?"+buttonColor
}
MouseArea {
id: cacheVideoArea
anchors.fill: parent
hoverEnabled: true
enabled: !mxcmedia.loaded
onClicked: mxcmedia.eventId = eventId
}
}
VideoOutput {
id: videoOutput
clip: true
anchors.fill: parent
fillMode: VideoOutput.PreserveAspectFit
source: mxcmedia
flushMode: VideoOutput.FirstFrame
// TODO: once we can use Qt 5.12, use HoverHandler
MouseArea {
id: playerMouseArea
// Toggle play state on clicks
onClicked: {
if (controlRect.shouldShowControls &&
!controlRect.contains(mapToItem(controlRect, mouseX, mouseY))) {
(mxcmedia.state == MediaPlayer.PlayingState) ?
mxcmedia.pause() :
mxcmedia.play()
}
}
Rectangle {
id: controlRect
property int controlHeight: 25
property bool shouldShowControls: playerMouseArea.shouldShowControls ||
volumeSlider.controlsVisible
anchors.bottom: playerMouseArea.bottom
// Window color with 128/255 alpha
color: {
var wc = Nheko.colors.alternateBase
return Qt.rgba(wc.r, wc.g, wc.b, 0.5)
}
height: 40
width: playerMouseArea.width
opacity: shouldShowControls ? 1 : 0
// Fade controls in/out
Behavior on opacity {
OpacityAnimator {
duration: 100
}
}
RowLayout {
anchors.fill: parent
width: parent.width
// Play/pause button
Image {
id: playbackStateImage
fillMode: Image.PreserveAspectFit
Layout.preferredHeight: controlRect.controlHeight
Layout.alignment: Qt.AlignVCenter
property color controlColor: (playbackStateArea.containsMouse) ?
Nheko.colors.highlight : Nheko.colors.text
source: (mxcmedia.state == MediaPlayer.PlayingState) ?
"image://colorimage/:/icons/icons/ui/pause-symbol.png?"+controlColor :
"image://colorimage/:/icons/icons/ui/play-sign.png?"+controlColor
MouseArea {
id: playbackStateArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
(mxcmedia.state == MediaPlayer.PlayingState) ?
mxcmedia.pause() :
mxcmedia.play()
}
}
}
Label {
text: (!mxcmedia.loaded) ? "-/-" : (durationToString(mxcmedia.position) + "/" + durationToString(mxcmedia.duration))
color: Nheko.colors.text
}
Slider {
Layout.fillWidth: true
Layout.minimumWidth: 50
height: controlRect.controlHeight
value: mxcmedia.position
onMoved: mxcmedia.position = value
from: 0
to: mxcmedia.duration
}
VolumeControl {
id: volumeSlider
orientation: Qt.Vertical
Layout.rightMargin: 5
Layout.preferredHeight: controlRect.controlHeight
}
}
}
// This breaks separation of concerns but this same thing doesn't work when called from controlRect...
property bool shouldShowControls: (containsMouse && controlHideTimer.running) ||
(mxcmedia.state != MediaPlayer.PlayingState) ||
controlRect.contains(mapToItem(controlRect, mouseX, mouseY))
// For hiding controls on stationary cursor
Timer {
id: controlHideTimer
interval: 1500 //ms
repeat: false
}
hoverEnabled: true
onPositionChanged: controlHideTimer.start()
x: videoOutput.contentRect.x
y: videoOutput.contentRect.y
width: videoOutput.contentRect.width
height: videoOutput.contentRect.height
propagateComposedEvents: true
}
}
}
}
// Audio player
// TODO: share code with the video player
Rectangle {
id: audioControlRect
visible: type != MtxEvent.VideoMessage
property int controlHeight: 25
Layout.preferredHeight: 40
RowLayout {
anchors.fill: parent
width: parent.width
// Play/pause button
Image {
id: audioPlaybackStateImage
fillMode: Image.PreserveAspectFit
Layout.preferredHeight: controlRect.controlHeight
Layout.alignment: Qt.AlignVCenter
property color controlColor: (audioPlaybackStateArea.containsMouse) ?
Nheko.colors.highlight : Nheko.colors.text
source: {
if (!mxcmedia.loaded)
return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?"+controlColor
return (mxcmedia.state == MediaPlayer.PlayingState) ?
"image://colorimage/:/icons/icons/ui/pause-symbol.png?"+controlColor :
"image://colorimage/:/icons/icons/ui/play-sign.png?"+controlColor
}
MouseArea {
id: audioPlaybackStateArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
if (!mxcmedia.loaded) {
mxcmedia.eventId = eventId
return
}
(mxcmedia.state == MediaPlayer.PlayingState) ?
mxcmedia.pause() :
mxcmedia.play()
}
}
}
Label {
text: (!mxcmedia.loaded) ? "-/-" :
durationToString(mxcmedia.position) + "/" + durationToString(mxcmedia.duration)
}
Slider {
Layout.fillWidth: true
Layout.minimumWidth: 50
height: controlRect.controlHeight
value: mxcmedia.position
onMoved: mxcmedia.seek(value)
from: 0
to: mxcmedia.duration
}
}
}
Label {
id: fileInfoLabel
background: Rectangle {
color: Nheko.colors.base
}
Layout.fillWidth: true
text: body + " [" + filesize + "]"
textFormat: Text.PlainText
elide: Text.ElideRight
color: Nheko.colors.text
}
}