|
|
|
@ -9,9 +9,7 @@ import QtQuick.Controls 2.1 |
|
|
|
|
import QtQuick.Layouts 1.2 |
|
|
|
|
import im.nheko 1.0 |
|
|
|
|
|
|
|
|
|
Rectangle { |
|
|
|
|
id: bg |
|
|
|
|
|
|
|
|
|
ColumnLayout { |
|
|
|
|
required property double proportionalHeight |
|
|
|
|
required property int type |
|
|
|
|
required property int originalWidth |
|
|
|
@ -21,205 +19,367 @@ Rectangle { |
|
|
|
|
required property string body |
|
|
|
|
required property string filesize |
|
|
|
|
|
|
|
|
|
radius: 10 |
|
|
|
|
color: Nheko.colors.alternateBase |
|
|
|
|
height: Math.round(content.height + 24) |
|
|
|
|
width: parent ? parent.width : undefined |
|
|
|
|
ListView.onPooled: height = 4 |
|
|
|
|
ListView.onReused: height = Math.round(content.height + 24) |
|
|
|
|
|
|
|
|
|
Column { |
|
|
|
|
id: content |
|
|
|
|
|
|
|
|
|
width: parent.width - 24 |
|
|
|
|
anchors.centerIn: parent |
|
|
|
|
|
|
|
|
|
Rectangle { |
|
|
|
|
id: videoContainer |
|
|
|
|
|
|
|
|
|
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 > timelineView.height / divisor |
|
|
|
|
|
|
|
|
|
visible: type == MtxEvent.VideoMessage |
|
|
|
|
height: tooHigh ? timelineView.height / divisor : tempHeight |
|
|
|
|
width: tooHigh ? (timelineView.height / divisor) / proportionalHeight : tempWidth |
|
|
|
|
|
|
|
|
|
Image { |
|
|
|
|
anchors.fill: parent |
|
|
|
|
source: thumbnailUrl.replace("mxc://", "image://MxcImage/") |
|
|
|
|
asynchronous: true |
|
|
|
|
fillMode: Image.PreserveAspectFit |
|
|
|
|
|
|
|
|
|
VideoOutput { |
|
|
|
|
anchors.fill: parent |
|
|
|
|
fillMode: VideoOutput.PreserveAspectFit |
|
|
|
|
source: media |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
RowLayout { |
|
|
|
|
width: parent.width |
|
|
|
|
|
|
|
|
|
Text { |
|
|
|
|
id: positionText |
|
|
|
|
|
|
|
|
|
text: "--:--:--" |
|
|
|
|
color: Nheko.colors.text |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Slider { |
|
|
|
|
id: progress |
|
|
|
|
|
|
|
|
|
//indeterminate: true |
|
|
|
|
function updatePositionTexts() { |
|
|
|
|
function formatTime(date) { |
|
|
|
|
var hh = date.getUTCHours(); |
|
|
|
|
var mm = date.getUTCMinutes(); |
|
|
|
|
var ss = date.getSeconds(); |
|
|
|
|
if (hh < 10) |
|
|
|
|
hh = "0" + hh; |
|
|
|
|
|
|
|
|
|
if (mm < 10) |
|
|
|
|
mm = "0" + mm; |
|
|
|
|
|
|
|
|
|
if (ss < 10) |
|
|
|
|
ss = "0" + ss; |
|
|
|
|
|
|
|
|
|
return hh + ":" + mm + ":" + ss; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
positionText.text = formatTime(new Date(media.position)); |
|
|
|
|
durationText.text = formatTime(new Date(media.duration)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Layout.fillWidth: true |
|
|
|
|
value: media.position |
|
|
|
|
from: 0 |
|
|
|
|
to: media.duration |
|
|
|
|
onMoved: media.seek(value) |
|
|
|
|
onValueChanged: updatePositionTexts() |
|
|
|
|
palette: Nheko.colors |
|
|
|
|
} |
|
|
|
|
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.maximumWidth: parent? parent.width: undefined |
|
|
|
|
MediaPlayer { |
|
|
|
|
id: media |
|
|
|
|
// TODO: Show error in overlay or so? |
|
|
|
|
onError: console.log(errorString) |
|
|
|
|
volume: volumeSlider.desiredVolume |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Text { |
|
|
|
|
id: durationText |
|
|
|
|
Connections { |
|
|
|
|
property bool mediaCached: false |
|
|
|
|
|
|
|
|
|
text: "--:--:--" |
|
|
|
|
color: Nheko.colors.text |
|
|
|
|
id: mediaCachedObserver |
|
|
|
|
target: room |
|
|
|
|
onMediaCached: { |
|
|
|
|
if (mxcUrl == url) { |
|
|
|
|
mediaCached = true |
|
|
|
|
media.source = "file://" + cacheUrl |
|
|
|
|
console.log("media loaded: " + mxcUrl + " at " + cacheUrl) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
console.log("media cached: " + mxcUrl + " at " + cacheUrl) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
RowLayout { |
|
|
|
|
width: parent.width |
|
|
|
|
spacing: 15 |
|
|
|
|
|
|
|
|
|
ImageButton { |
|
|
|
|
id: button |
|
|
|
|
|
|
|
|
|
Layout.alignment: Qt.AlignVCenter |
|
|
|
|
//color: Nheko.colors.window |
|
|
|
|
//radius: 22 |
|
|
|
|
height: 32 |
|
|
|
|
width: 32 |
|
|
|
|
z: 3 |
|
|
|
|
image: ":/icons/icons/ui/arrow-pointing-down.png" |
|
|
|
|
onClicked: { |
|
|
|
|
switch (button.state) { |
|
|
|
|
case "": |
|
|
|
|
room.cacheMedia(eventId); |
|
|
|
|
break; |
|
|
|
|
case "stopped": |
|
|
|
|
media.play(); |
|
|
|
|
console.log("play"); |
|
|
|
|
button.state = "playing"; |
|
|
|
|
break; |
|
|
|
|
case "playing": |
|
|
|
|
media.pause(); |
|
|
|
|
console.log("pause"); |
|
|
|
|
button.state = "stopped"; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
Layout.maximumWidth: Layout.preferredWidth |
|
|
|
|
Layout.preferredHeight: tooHigh ? timelineRoot.height / divisor : tempHeight |
|
|
|
|
Layout.preferredWidth: tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth |
|
|
|
|
Image { |
|
|
|
|
anchors.fill: parent |
|
|
|
|
source: thumbnailUrl.replace("mxc://", "image://MxcImage/") |
|
|
|
|
asynchronous: true |
|
|
|
|
fillMode: Image.PreserveAspectFit |
|
|
|
|
// Button and window colored overlay to cache media |
|
|
|
|
Rectangle { |
|
|
|
|
// Display over video controls |
|
|
|
|
z: videoOutput.z + 1 |
|
|
|
|
visible: !mediaCachedObserver.mediaCached |
|
|
|
|
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 |
|
|
|
|
} |
|
|
|
|
states: [ |
|
|
|
|
State { |
|
|
|
|
name: "stopped" |
|
|
|
|
|
|
|
|
|
PropertyChanges { |
|
|
|
|
target: button |
|
|
|
|
image: ":/icons/icons/ui/play-sign.png" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
}, |
|
|
|
|
State { |
|
|
|
|
name: "playing" |
|
|
|
|
|
|
|
|
|
PropertyChanges { |
|
|
|
|
target: button |
|
|
|
|
image: ":/icons/icons/ui/pause-symbol.png" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
CursorShape { |
|
|
|
|
MouseArea { |
|
|
|
|
id: cacheVideoArea |
|
|
|
|
anchors.fill: parent |
|
|
|
|
cursorShape: Qt.PointingHandCursor |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
MediaPlayer { |
|
|
|
|
id: media |
|
|
|
|
|
|
|
|
|
onError: console.log(errorString) |
|
|
|
|
onStatusChanged: { |
|
|
|
|
if (status == MediaPlayer.Loaded) |
|
|
|
|
progress.updatePositionTexts(); |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
onStopped: button.state = "stopped" |
|
|
|
|
hoverEnabled: true |
|
|
|
|
enabled: !mediaCachedObserver.mediaCached |
|
|
|
|
onClicked: room.cacheMedia(eventId) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Connections { |
|
|
|
|
target: room |
|
|
|
|
onMediaCached: { |
|
|
|
|
if (mxcUrl == url) { |
|
|
|
|
media.source = cacheUrl; |
|
|
|
|
button.state = "stopped"; |
|
|
|
|
console.log("media loaded: " + mxcUrl + " at " + cacheUrl); |
|
|
|
|
} |
|
|
|
|
VideoOutput { |
|
|
|
|
id: videoOutput |
|
|
|
|
clip: true |
|
|
|
|
anchors.fill: parent |
|
|
|
|
fillMode: VideoOutput.PreserveAspectFit |
|
|
|
|
source: media |
|
|
|
|
// 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))) { |
|
|
|
|
(media.playbackState == MediaPlayer.PlayingState) ? |
|
|
|
|
media.pause() : |
|
|
|
|
media.play() |
|
|
|
|
} |
|
|
|
|
console.log("media cached: " + mxcUrl + " at " + cacheUrl); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ColumnLayout { |
|
|
|
|
id: col |
|
|
|
|
Rectangle { |
|
|
|
|
id: controlRect |
|
|
|
|
property int controlHeight: 25 |
|
|
|
|
property bool shouldShowControls: playerMouseArea.shouldShowControls || |
|
|
|
|
volumeSliderRect.visible |
|
|
|
|
|
|
|
|
|
anchors.bottom: playerMouseArea.bottom |
|
|
|
|
// Window color with 128/255 alpha |
|
|
|
|
color: { |
|
|
|
|
var wc = Nheko.colors.window |
|
|
|
|
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: (media.playbackState == 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: { |
|
|
|
|
(media.playbackState == MediaPlayer.PlayingState) ? |
|
|
|
|
media.pause() : |
|
|
|
|
media.play() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Label { |
|
|
|
|
text: (!mediaCachedObserver.mediaCached) ? "-/-" : |
|
|
|
|
durationToString(media.position) + "/" + durationToString(media.duration) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Slider { |
|
|
|
|
Layout.fillWidth: true |
|
|
|
|
Layout.minimumWidth: 50 |
|
|
|
|
height: controlRect.controlHeight |
|
|
|
|
value: media.position |
|
|
|
|
onMoved: media.seek(value) |
|
|
|
|
from: 0 |
|
|
|
|
to: media.duration |
|
|
|
|
} |
|
|
|
|
// Volume slider activator |
|
|
|
|
Image { |
|
|
|
|
property color controlColor: (volumeImageArea.containsMouse) ? |
|
|
|
|
Nheko.colors.highlight : Nheko.colors.text |
|
|
|
|
|
|
|
|
|
// TODO: add icons for different volume levels |
|
|
|
|
id: volumeImage |
|
|
|
|
source: (media.volume > 0 && !media.muted) ? |
|
|
|
|
"image://colorimage/:/icons/icons/ui/volume-up.png?"+ controlColor : |
|
|
|
|
"image://colorimage/:/icons/icons/ui/volume-off-indicator.png?"+ controlColor |
|
|
|
|
Layout.rightMargin: 5 |
|
|
|
|
Layout.preferredHeight: controlRect.controlHeight |
|
|
|
|
fillMode: Image.PreserveAspectFit |
|
|
|
|
MouseArea { |
|
|
|
|
id: volumeImageArea |
|
|
|
|
anchors.fill: parent |
|
|
|
|
hoverEnabled: true |
|
|
|
|
onClicked: media.muted = !media.muted |
|
|
|
|
onExited: volumeSliderHideTimer.start() |
|
|
|
|
onPositionChanged: volumeSliderHideTimer.start() |
|
|
|
|
// For hiding volume slider after a while |
|
|
|
|
Timer { |
|
|
|
|
id: volumeSliderHideTimer |
|
|
|
|
interval: 1500 |
|
|
|
|
repeat: false |
|
|
|
|
running: false |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Rectangle { |
|
|
|
|
id: volumeSliderRect |
|
|
|
|
opacity: (visible) ? 1 : 0 |
|
|
|
|
Behavior on opacity { |
|
|
|
|
OpacityAnimator { |
|
|
|
|
duration: 100 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// TODO: figure out a better way to put the slider popup above controlRect |
|
|
|
|
anchors.bottom: volumeImage.top |
|
|
|
|
anchors.bottomMargin: 10 |
|
|
|
|
anchors.horizontalCenter: volumeImage.horizontalCenter |
|
|
|
|
color: { |
|
|
|
|
var wc = Nheko.colors.window |
|
|
|
|
return Qt.rgba(wc.r, wc.g, wc.b, 0.5) |
|
|
|
|
} |
|
|
|
|
/* TODO: base width on the slider width (some issue with it not having a geometry |
|
|
|
|
when using the width here?) */ |
|
|
|
|
width: volumeImage.width * 0.7 |
|
|
|
|
radius: volumeSlider.width / 2 |
|
|
|
|
height: controlRect.height * 2 //100 |
|
|
|
|
visible: volumeImageArea.containsMouse || |
|
|
|
|
volumeSliderHideTimer.running || |
|
|
|
|
volumeSliderRectMouseArea.containsMouse |
|
|
|
|
Slider { |
|
|
|
|
// Desired value to avoid loop onMoved -> media.volume -> value -> onMoved... |
|
|
|
|
property real desiredVolume: 1 |
|
|
|
|
|
|
|
|
|
// TODO: the slider is slightly off-center on the left for some reason... |
|
|
|
|
id: volumeSlider |
|
|
|
|
from: 0 |
|
|
|
|
to: 1 |
|
|
|
|
value: (media.muted) ? 0 : |
|
|
|
|
QtMultimedia.convertVolume(desiredVolume, |
|
|
|
|
QtMultimedia.LinearVolumeScale, |
|
|
|
|
QtMultimedia.LogarithmicVolumeScale) |
|
|
|
|
anchors.fill: parent |
|
|
|
|
anchors.bottomMargin: parent.height * 0.1 |
|
|
|
|
anchors.topMargin: parent.height * 0.1 |
|
|
|
|
anchors.horizontalCenter: parent.horizontalCenter |
|
|
|
|
orientation: Qt.Vertical |
|
|
|
|
onMoved: desiredVolume = QtMultimedia.convertVolume(value, |
|
|
|
|
QtMultimedia.LogarithmicVolumeScale, |
|
|
|
|
QtMultimedia.LinearVolumeScale) |
|
|
|
|
/* This would be better handled in 'media', but it has some issue with listening |
|
|
|
|
to this signal */ |
|
|
|
|
onDesiredVolumeChanged: media.muted = !(desiredVolume > 0) |
|
|
|
|
} |
|
|
|
|
// Used for resetting the timer on mouse moves on volumeSliderRect |
|
|
|
|
MouseArea { |
|
|
|
|
id: volumeSliderRectMouseArea |
|
|
|
|
anchors.fill: parent |
|
|
|
|
hoverEnabled: true |
|
|
|
|
propagateComposedEvents: true |
|
|
|
|
onExited: volumeSliderHideTimer.start() |
|
|
|
|
|
|
|
|
|
onClicked: mouse.accepted = false |
|
|
|
|
onPressed: mouse.accepted = false |
|
|
|
|
onReleased: mouse.accepted = false |
|
|
|
|
onPressAndHold: mouse.accepted = false |
|
|
|
|
onPositionChanged: { |
|
|
|
|
mouse.accepted = false |
|
|
|
|
volumeSliderHideTimer.start() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// This breaks separation of concerns but this same thing doesn't work when called from controlRect... |
|
|
|
|
property bool shouldShowControls: (containsMouse && controlHideTimer.running) || |
|
|
|
|
(media.playbackState != MediaPlayer.PlayingState) || |
|
|
|
|
controlRect.contains(mapToItem(controlRect, mouseX, mouseY)) |
|
|
|
|
|
|
|
|
|
// For hiding controls on stationary cursor |
|
|
|
|
Timer { |
|
|
|
|
id: controlHideTimer |
|
|
|
|
interval: 1500 //ms |
|
|
|
|
repeat: false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Text { |
|
|
|
|
Layout.fillWidth: true |
|
|
|
|
text: body |
|
|
|
|
elide: Text.ElideRight |
|
|
|
|
color: Nheko.colors.text |
|
|
|
|
} |
|
|
|
|
hoverEnabled: true |
|
|
|
|
onPositionChanged: controlHideTimer.start() |
|
|
|
|
|
|
|
|
|
Text { |
|
|
|
|
Layout.fillWidth: true |
|
|
|
|
text: filesize |
|
|
|
|
textFormat: Text.PlainText |
|
|
|
|
elide: Text.ElideRight |
|
|
|
|
color: Nheko.colors.text |
|
|
|
|
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 (!mediaCachedObserver.mediaCached) |
|
|
|
|
return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?"+controlColor |
|
|
|
|
return (media.playbackState == 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 (!mediaCachedObserver.mediaCached) { |
|
|
|
|
room.cacheMedia(eventId) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
(media.playbackState == MediaPlayer.PlayingState) ? |
|
|
|
|
media.pause() : |
|
|
|
|
media.play() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Label { |
|
|
|
|
text: (!mediaCachedObserver.mediaCached) ? "-/-" : |
|
|
|
|
durationToString(media.position) + "/" + durationToString(media.duration) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Slider { |
|
|
|
|
Layout.fillWidth: true |
|
|
|
|
Layout.minimumWidth: 50 |
|
|
|
|
height: controlRect.controlHeight |
|
|
|
|
value: media.position |
|
|
|
|
onMoved: media.seek(value) |
|
|
|
|
from: 0 |
|
|
|
|
to: media.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 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|