1// SPDX-FileCopyrightText: 2021 Nheko Contributors 2// 3// SPDX-License-Identifier: GPL-3.0-or-later 4 5import "../" 6import "../../" 7import QtMultimedia 5.15 8import QtQuick 2.15 9import QtQuick.Controls 2.15 10import QtQuick.Layouts 1.15 11import im.nheko 1.0 12 13Rectangle { 14 id: control 15 16 property alias desiredVolume: volumeSlider.desiredVolume 17 property bool muted: false 18 property bool playingVideo: false 19 property var mediaState 20 property bool mediaLoaded: false 21 property var duration 22 property var positionValue: 0 23 property var position 24 property bool shouldShowControls: !playingVideo || playerMouseArea.shouldShowControls || volumeSlider.state == "shown" 25 26 signal playPauseActivated() 27 signal loadActivated() 28 29 function showControls() { 30 controlHideTimer.restart(); 31 } 32 33 function durationToString(duration) { 34 function maybeZeroPrepend(time) { 35 return (time < 10) ? "0" + time.toString() : time.toString(); 36 } 37 38 var totalSeconds = Math.floor(duration / 1000); 39 var seconds = totalSeconds % 60; 40 var minutes = (Math.floor(totalSeconds / 60)) % 60; 41 var hours = (Math.floor(totalSeconds / (60 * 24))) % 24; 42 // Always show minutes and don't prepend zero into the leftmost element 43 var ss = maybeZeroPrepend(seconds); 44 var mm = (hours > 0) ? maybeZeroPrepend(minutes) : minutes.toString(); 45 var hh = hours.toString(); 46 if (hours < 1) 47 return mm + ":" + ss; 48 49 return hh + ":" + mm + ":" + ss; 50 } 51 52 color: { 53 var wc = Nheko.colors.alternateBase; 54 return Qt.rgba(wc.r, wc.g, wc.b, 0.5); 55 } 56 opacity: control.shouldShowControls ? 1 : 0 57 height: controlLayout.implicitHeight 58 59 HoverHandler { 60 id: playerMouseArea 61 62 property bool shouldShowControls: hovered || controlHideTimer.running || control.mediaState != MediaPlayer.PlayingState 63 64 onHoveredChanged: showControls() 65 } 66 67 ColumnLayout { 68 id: controlLayout 69 70 enabled: control.shouldShowControls 71 spacing: 0 72 anchors.bottom: control.bottom 73 anchors.left: control.left 74 anchors.right: control.right 75 76 NhekoSlider { 77 Layout.fillWidth: true 78 Layout.leftMargin: Nheko.paddingSmall 79 Layout.rightMargin: Nheko.paddingSmall 80 enabled: control.mediaLoaded 81 value: control.positionValue 82 onMoved: control.position = value 83 from: 0 84 to: control.duration 85 alwaysShowSlider: false 86 } 87 88 RowLayout { 89 Layout.margins: Nheko.paddingSmall 90 spacing: Nheko.paddingSmall 91 Layout.fillWidth: true 92 93 // Cache/Play/pause button 94 ImageButton { 95 id: playbackStateImage 96 97 Layout.alignment: Qt.AlignLeft 98 buttonTextColor: Nheko.colors.text 99 Layout.preferredHeight: 24 100 Layout.preferredWidth: 24 101 image: { 102 if (control.mediaLoaded) { 103 if (control.mediaState == MediaPlayer.PlayingState) 104 return ":/icons/icons/ui/pause-symbol.svg"; 105 else 106 return ":/icons/icons/ui/play-sign.svg"; 107 } else { 108 return ":/icons/icons/ui/download.svg"; 109 } 110 } 111 onClicked: control.mediaLoaded ? control.playPauseActivated() : control.loadActivated() 112 } 113 114 ImageButton { 115 id: volumeButton 116 117 Layout.alignment: Qt.AlignLeft 118 buttonTextColor: Nheko.colors.text 119 Layout.preferredHeight: 24 120 Layout.preferredWidth: 24 121 image: { 122 if (control.muted || control.desiredVolume <= 0) 123 return ":/icons/icons/ui/volume-off-indicator.svg"; 124 else 125 return ":/icons/icons/ui/volume-up.svg"; 126 } 127 onClicked: control.muted = !control.muted 128 } 129 130 NhekoSlider { 131 id: volumeSlider 132 133 property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value, QtMultimedia.LogarithmicVolumeScale, QtMultimedia.LinearVolumeScale) 134 135 state: "" 136 Layout.alignment: Qt.AlignLeft 137 Layout.preferredWidth: 0 138 opacity: 0 139 orientation: Qt.Horizontal 140 value: 1 141 onDesiredVolumeChanged: { 142 control.muted = !(desiredVolume > 0); 143 } 144 transitions: [ 145 Transition { 146 from: "" 147 to: "shown" 148 149 SequentialAnimation { 150 PauseAnimation { 151 duration: 50 152 } 153 154 NumberAnimation { 155 duration: 100 156 properties: "opacity" 157 easing.type: Easing.InQuad 158 } 159 160 } 161 162 NumberAnimation { 163 properties: "Layout.preferredWidth" 164 duration: 150 165 } 166 167 }, 168 Transition { 169 from: "shown" 170 to: "" 171 172 SequentialAnimation { 173 PauseAnimation { 174 duration: 100 175 } 176 177 ParallelAnimation { 178 NumberAnimation { 179 duration: 100 180 properties: "opacity" 181 easing.type: Easing.InQuad 182 } 183 184 NumberAnimation { 185 properties: "Layout.preferredWidth" 186 duration: 150 187 } 188 189 } 190 191 } 192 193 } 194 ] 195 196 states: State { 197 name: "shown" 198 when: Settings.mobileMode || volumeButton.hovered || volumeSlider.hovered || volumeSlider.pressed 199 200 PropertyChanges { 201 target: volumeSlider 202 Layout.preferredWidth: 100 203 } 204 205 PropertyChanges { 206 target: volumeSlider 207 opacity: 1 208 } 209 210 } 211 212 } 213 214 Label { 215 Layout.alignment: Qt.AlignRight 216 text: (!control.mediaLoaded) ? "-- / --" : (durationToString(control.positionValue) + " / " + durationToString(control.duration)) 217 color: Nheko.colors.text 218 } 219 220 Item { 221 Layout.fillWidth: true 222 } 223 224 } 225 226 } 227 228 // For hiding controls on stationary cursor 229 Timer { 230 id: controlHideTimer 231 232 interval: 1500 //ms 233 repeat: false 234 } 235 236 // Fade controls in/out 237 Behavior on opacity { 238 OpacityAnimator { 239 duration: 100 240 } 241 242 } 243 244} 245