summary refs log tree commit diff
path: root/resources/qml
diff options
context:
space:
mode:
authorJoseph Donofry <joedonofry@gmail.com>2021-11-09 22:17:00 -0500
committerJoseph Donofry <joedonofry@gmail.com>2021-11-09 22:17:00 -0500
commitc1c9c71b08915a27538e34e422255d53b7bf1fdf (patch)
treee64f9f987461333672e012ea0c52fecb7f2ae77c /resources/qml
parentInitial Refactoring into separate controls (diff)
downloadnheko-c1c9c71b08915a27538e34e422255d53b7bf1fdf.tar.xz
Move rest of controls to separate file
Diffstat (limited to 'resources/qml')
-rw-r--r--resources/qml/delegates/PlayableMediaMessage.qml336
-rw-r--r--resources/qml/ui/media/MediaControls.qml141
-rw-r--r--resources/qml/ui/media/VolumeControl.qml67
-rw-r--r--resources/qml/ui/media/qmldir3
4 files changed, 310 insertions, 237 deletions
diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index ceeeeb1a..3af3a993 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -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
-      }
-  }
+}
diff --git a/resources/qml/ui/media/MediaControls.qml b/resources/qml/ui/media/MediaControls.qml
new file mode 100644
index 00000000..de3e98a7
--- /dev/null
+++ b/resources/qml/ui/media/MediaControls.qml
@@ -0,0 +1,141 @@
+// 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
+    }
+
+}
diff --git a/resources/qml/ui/media/VolumeControl.qml b/resources/qml/ui/media/VolumeControl.qml
index b826dfc6..cd844ed5 100644
--- a/resources/qml/ui/media/VolumeControl.qml
+++ b/resources/qml/ui/media/VolumeControl.qml
@@ -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
+
+}
diff --git a/resources/qml/ui/media/qmldir b/resources/qml/ui/media/qmldir
index 14df35df..143b603d 100644
--- a/resources/qml/ui/media/qmldir
+++ b/resources/qml/ui/media/qmldir
@@ -1,2 +1,3 @@
 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