Skip to content
Snippets Groups Projects
Verified Commit c1c9c71b authored by Joe Donofry's avatar Joe Donofry
Browse files

Move rest of controls to separate file

parent f6fcae12
No related branches found
No related tags found
1 merge request!15Video player enhancements
Pipeline #2017 passed
......@@ -11,6 +11,8 @@ import QtQuick.Layouts 1.2
import im.nheko 1.0
ColumnLayout {
id: content
required property double proportionalHeight
required property int type
required property int originalWidth
......@@ -22,52 +24,51 @@ ColumnLayout {
function durationToString(duration) {
function maybeZeroPrepend(time) {
return (time < 10) ? "0" + time.toString() :
time.toString()
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
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
}
var ss = maybeZeroPrepend(seconds);
var mm = (hours > 0) ? maybeZeroPrepend(minutes) : minutes.toString();
var hh = hours.toString();
if (hours < 1)
return mm + ":" + ss;
id: content
return hh + ":" + mm + ":" + ss;
}
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
// 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: mediaControls.desiredVolume * 100
muted: mediaControls.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 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
visible: type == MtxEvent.VideoMessage
color: Nheko.colors.window
Layout.preferredHeight: tooHigh ? timelineRoot.height / divisor : tempHeight
Layout.preferredWidth: tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth
Layout.maximumWidth: Layout.preferredWidth
......@@ -77,217 +78,144 @@ ColumnLayout {
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
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
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
MediaControls {
id: mediaControls
anchors.fill: parent
x: videoOutput.contentRect.x
y: videoOutput.contentRect.y
width: videoOutput.contentRect.width
height: videoOutput.contentRect.height
positionValue: mxcmedia.position
duration: mxcmedia.duration
mediaLoaded: mxcmedia.loaded
mediaState: mxcmedia.state
volumeOrientation: Qt.Vertical
onPositionChanged: mxcmedia.position = position
onActivated: mxcmedia.state == MediaPlayer.PlayingState ? mxcmedia.pause() : mxcmedia.play()
}
}
}
}
// Audio player
// TODO: share code with the video player
Rectangle {
id: audioControlRect
property int controlHeight: 25
visible: type != MtxEvent.VideoMessage
Layout.preferredHeight: 40
RowLayout {
anchors.fill: parent
width: parent.width
// Play/pause button
Image {
id: audioPlaybackStateImage
property color controlColor: (audioPlaybackStateArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
fillMode: Image.PreserveAspectFit
Layout.preferredHeight: controlRect.controlHeight
Layout.alignment: Qt.AlignVCenter
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: playerMouseArea
// Toggle play state on clicks
id: audioPlaybackStateArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
if (controlRect.shouldShowControls &&
!controlRect.contains(mapToItem(controlRect, mouseX, mouseY))) {
(mxcmedia.state == MediaPlayer.PlayingState) ?
mxcmedia.pause() :
mxcmedia.play()
if (!mxcmedia.loaded) {
mxcmedia.eventId = eventId;
return ;
}
(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
}
}
}
}
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.position = value
from: 0
to: mxcmedia.duration
}
}
}
Label {
id: fileInfoLabel
Layout.fillWidth: true
text: body + " [" + filesize + "]"
textFormat: Text.PlainText
elide: Text.ElideRight
color: Nheko.colors.text
background: Rectangle {
color: Nheko.colors.base
}
}
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
}
}
}
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
import QtMultimedia 5.15
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2
import im.nheko 1.0
Item {
id: control
property alias desiredVolume: volumeSlider.desiredVolume
property alias muted: volumeSlider.muted
property alias volumeOrientation: volumeSlider.orientation
property var mediaState
property bool mediaLoaded: false
property var duration
property var positionValue: 0
property var position
property int controlHeight: 25
property bool shouldShowControls: playerMouseArea.shouldShowControls || volumeSlider.controlsVisible
signal activated(real mouseX, real mouseY)
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;
}
MouseArea {
id: playerMouseArea
property bool shouldShowControls: (containsMouse && controlHideTimer.running) || (control.mediaState != MediaPlayer.PlayingState) || controlRect.contains(mapToItem(controlRect, mouseX, mouseY))
onClicked: control.activated(mouseX, mouseY)
hoverEnabled: true
onPositionChanged: controlHideTimer.start()
onExited: controlHideTimer.start()
onEntered: controlHideTimer.start()
anchors.fill: control
propagateComposedEvents: true
}
Rectangle {
id: controlRect
// Window color with 128/255 alpha
color: {
var wc = Nheko.colors.alternateBase;
return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
}
anchors.bottom: control.bottom
anchors.left: control.left
anchors.right: control.right
height: 40
opacity: control.shouldShowControls ? 1 : 0
RowLayout {
anchors.fill: parent
width: parent.width
// Play/pause button
Image {
id: playbackStateImage
property color controlColor: (playbackStateArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
fillMode: Image.PreserveAspectFit
Layout.preferredHeight: control.controlHeight
Layout.alignment: Qt.AlignVCenter
source: (control.mediaState == 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: control.activated(mouseX, mouseY)
}
}
Label {
text: (!control.mediaLoaded) ? "-/-" : (durationToString(control.positionValue) + "/" + durationToString(control.duration))
color: Nheko.colors.text
}
Slider {
Layout.fillWidth: true
Layout.minimumWidth: 50
height: control.controlHeight
value: control.positionValue
onMoved: control.position = value
from: 0
to: control.duration
}
VolumeControl {
id: volumeSlider
Layout.rightMargin: 5
Layout.preferredHeight: control.controlHeight
}
}
// Fade controls in/out
Behavior on opacity {
OpacityAnimator {
duration: 100
}
}
}
// For hiding controls on stationary cursor
Timer {
id: controlHideTimer
interval: 1500 //ms
repeat: false
}
}
......@@ -5,101 +5,104 @@
import QtMultimedia 5.15
import QtQuick 2.15
import QtQuick.Controls 2.15
import im.nheko 1.0
// Volume slider activator
Image {
// TODO: add icons for different volume levels
id: volumeImage
property alias desiredVolume: volumeSlider.desiredVolume
property alias orientation: volumeSlider.orientation
property alias controlsVisible: volumeSliderRect.visible
property bool muted: false
property color controlColor: (volumeImageArea.containsMouse) ?
Nheko.colors.highlight : Nheko.colors.text
// TODO: add icons for different volume levels
id: volumeImage
source: (desiredVolume > 0 && !muted) ?
"image://colorimage/:/icons/icons/ui/volume-up.png?"+ controlColor :
"image://colorimage/:/icons/icons/ui/volume-off-indicator.png?"+ controlColor
property color controlColor: (volumeImageArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text
source: (desiredVolume > 0 && !muted) ? "image://colorimage/:/icons/icons/ui/volume-up.png?" + controlColor : "image://colorimage/:/icons/icons/ui/volume-off-indicator.png?" + controlColor
fillMode: Image.PreserveAspectFit
MouseArea {
id: volumeImageArea
id: volumeImageArea
anchors.fill: parent
hoverEnabled: true
onExited: volumeSliderHideTimer.start()
onPositionChanged: volumeSliderHideTimer.start()
onClicked: volumeImage.muted = !volumeImage.muted
// 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)
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
visible: volumeImageArea.containsMouse || volumeSliderHideTimer.running || volumeSliderRectMouseArea.containsMouse
Slider {
// TODO: the slider is slightly off-center on the left for some reason...
id: volumeSlider
value: 1.0
// Desired value to avoid loop onMoved -> media.volume -> value -> onMoved...
property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value,
QtMultimedia.LogarithmicVolumeScale,
QtMultimedia.LinearVolumeScale)
property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value, QtMultimedia.LogarithmicVolumeScale, QtMultimedia.LinearVolumeScale)
anchors.fill: parent
value: 1
anchors.fill: volumeSliderRect
anchors.bottomMargin: volumeSliderRect.height * 0.1
anchors.topMargin: volumeSliderRect.height * 0.1
anchors.horizontalCenter: volumeSliderRect.horizontalCenter
orientation: Qt.Vertical
onDesiredVolumeChanged: {
volumeImage.muted = !(desiredVolume > 0.0)
volumeImage.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()
mouse.accepted = false;
volumeSliderHideTimer.start();
}
}
Behavior on opacity {
OpacityAnimator {
duration: 100
}
}
// TODO: figure out a better way to put the slider popup above controlRect
}
}
\ No newline at end of file
}
module im.nheko.UI.Media
VolumeSlider 1.0 VolumeSlider.qml
\ No newline at end of file
VolumeSlider 1.0 VolumeSlider.qml
MediaControls 1.0 MediaControls.qml
\ No newline at end of file
......@@ -183,6 +183,7 @@
<file>qml/ui/Ripple.qml</file>
<file>qml/ui/Spinner.qml</file>
<file>qml/ui/animations/BlinkAnimation.qml</file>
<file>qml/ui/media/MediaControls.qml</file>
<file>qml/ui/media/VolumeControl.qml</file>
<file>qml/voip/ActiveCallBar.qml</file>
<file>qml/voip/CallDevices.qml</file>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment