Support video events, new media player
@@ -14,6 +14,8 @@ Qt 5.12+, including:
 | 
			
		||||
- qt5-qmake
 | 
			
		||||
- qt5-devel
 | 
			
		||||
 | 
			
		||||
- qtav
 | 
			
		||||
 | 
			
		||||
- python3
 | 
			
		||||
- python3-devel
 | 
			
		||||
- olm-python3 >= 3.1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						@@ -1,5 +1,7 @@
 | 
			
		||||
- Media
 | 
			
		||||
  - Caching
 | 
			
		||||
    - What effect will it have on GIFs? Can we set `cache:false` on them or get
 | 
			
		||||
      the frame count once they're cached?
 | 
			
		||||
  - Downloading
 | 
			
		||||
    - Bottom/top bar
 | 
			
		||||
  - Uploading (+local echo)
 | 
			
		||||
@@ -11,6 +13,11 @@
 | 
			
		||||
  - GIF thumbnails: load the real animated image
 | 
			
		||||
  - Copy thumbnail URL in context menu?
 | 
			
		||||
 | 
			
		||||
  - GIFs can use the video player
 | 
			
		||||
  - Video bug: when media is done playing, clicking on progress slider always
 | 
			
		||||
    bring back to the beginning no matter where
 | 
			
		||||
  - Video: missing buttons and small size problems
 | 
			
		||||
 | 
			
		||||
  - Refactor EventContent
 | 
			
		||||
    - No background/padding around medias
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
# widgets: Make native file dialogs available to QML (must use QApplication)
 | 
			
		||||
QT        = quick widgets
 | 
			
		||||
QT        = quick widgets av
 | 
			
		||||
DEFINES  += QT_DEPRECATED_WARNINGS
 | 
			
		||||
CONFIG   += warn_off c++11 release
 | 
			
		||||
TEMPLATE  = app
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 334 B After Width: | Height: | Size: 334 B  | 
							
								
								
									
										3
									
								
								src/icons/thin/player-fullscreen-exit.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
			
		||||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
    <path d="m15 2h2v5h7v2h-9zm9 13v2h-7v5h-2v-7zm-15 7h-2v-5h-7v-2h9zm-9-13v-2h7v-5h2v7z"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 184 B  | 
							
								
								
									
										3
									
								
								src/icons/thin/player-fullscreen.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
			
		||||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
    <path d="m24 9h-2v-4h-4v-2h6zm-6 12v-2h4v-4h2v6zm-18-6h2v4h4v2h-6zm6-12v2h-4v4h-2v-6z"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 184 B  | 
							
								
								
									
										3
									
								
								src/icons/thin/player-loop.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
			
		||||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
    <path d="m18 13v5h-5l1.607-1.608c-3.404-2.824-5.642-8.392-9.179-8.392-2.113 0-3.479 1.578-3.479 4s1.365 4 3.479 4c1.664 0 2.86-1.068 4.015-2.392l1.244 1.561c-1.499 1.531-3.05 2.831-5.259 2.831-3.197 0-5.428-2.455-5.428-6s2.231-6 5.428-6c4.839 0 7.34 6.449 10.591 8.981zm.57-7c-2.211 0-3.762 1.301-5.261 2.833l1.244 1.561c1.156-1.325 2.352-2.394 4.017-2.394 2.114 0 3.48 1.578 3.48 4 0 1.819-.771 3.162-2.051 3.718v2.099c2.412-.623 4-2.829 4-5.816.001-3.546-2.231-6.001-5.429-6.001z"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 580 B  | 
							
								
								
									
										6
									
								
								src/icons/thin/player-pause.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,6 @@
 | 
			
		||||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
    <g stroke-width=".948329">
 | 
			
		||||
        <path d="m8.928091 20.993272h-4v-17.9865437h4z"/>
 | 
			
		||||
        <path d="m19.035955 3.0067283h-4v17.9865437h4z"/>
 | 
			
		||||
    </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 247 B  | 
							
								
								
									
										3
									
								
								src/icons/thin/player-play.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
			
		||||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
    <path d="m3 22v-20l18 10z"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 124 B  | 
							
								
								
									
										3
									
								
								src/icons/thin/player-restart.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
			
		||||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
    <path d="m13.5 2c-5.629 0-10.212 4.436-10.475 10h-3.025l4.537 5.917 4.463-5.917h-2.975c.26-3.902 3.508-7 7.475-7 4.136 0 7.5 3.364 7.5 7.5s-3.364 7.5-7.5 7.5c-2.381 0-4.502-1.119-5.876-2.854l-1.847 2.449c1.919 2.088 4.664 3.405 7.723 3.405 5.798 0 10.5-4.702 10.5-10.5s-4.702-10.5-10.5-10.5z"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 390 B  | 
							
								
								
									
										3
									
								
								src/icons/thin/player-speed.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
			
		||||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
    <path d="m15.91 13.34 2.636-4.026-.454-.406-3.673 3.099c-.675-.138-1.402.068-1.894.618-.736.823-.665 2.088.159 2.824s2.088.665 2.824-.159c.492-.55.615-1.295.402-1.95zm-3.91-10.646v-2.694h4v2.694c-1.439-.243-2.592-.238-4 0zm8.851 2.064 1.407-1.407 1.414 1.414-1.321 1.321c-.462-.484-.964-.927-1.5-1.328zm-18.851 4.242h8v2h-8zm-2 4h8v2h-8zm3 4h7v2h-7zm21-3c0 5.523-4.477 10-10 10-2.79 0-5.3-1.155-7.111-3h3.28c1.138.631 2.439 1 3.831 1 4.411 0 8-3.589 8-8s-3.589-8-8-8c-1.392 0-2.693.369-3.831 1h-3.28c1.811-1.845 4.321-3 7.111-3 5.523 0 10 4.477 10 10z"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 650 B  | 
							
								
								
									
										3
									
								
								src/icons/thin/player-track-audio.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
			
		||||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
    <path d="m13 0h-2v15.676c-3.379-.667-7 1.915-7 4.731 0 2.367 1.881 3.593 3.919 3.593 2.423 0 5.077-1.728 5.081-5.24v-12.76c3.009 2.223 5.623 3.243 5.059 7 1.431-1.727 1.941-2.817 1.941-4.051 0-4.446-7-5.915-7-8.949z"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 314 B  | 
							
								
								
									
										3
									
								
								src/icons/thin/player-track-subtitle.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
			
		||||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
    <path d="m0 1v16.981h4v5.019l7-5.019h13v-16.981zm13 12h-8v-1h8zm6-3h-14v-1h14zm0-3h-14v-1h14z"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 192 B  | 
							
								
								
									
										3
									
								
								src/icons/thin/player-track-video.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
			
		||||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
    <path d="m22 1v2h-2v-2h-2v4h-12v-4h-2v2h-2v-2h-2v22h2v-2h2v2h2v-4h12v4h2v-2h2v2h2v-22zm-18 18h-2v-2h2zm0-4h-2v-2h2zm0-4h-2v-2h2zm0-4h-2v-2h2zm14 9h-12v-8h12zm4 3h-2v-2h2zm0-4h-2v-2h2zm0-4h-2v-2h2zm0-4h-2v-2h2z"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 308 B  | 
							
								
								
									
										9
									
								
								src/icons/thin/player-volume-high.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,9 @@
 | 
			
		||||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
    <path d="m14.509208 8.343398c1.202865.9346638 1.945334 2.226588 1.94209 3.651856-.0032 1.425268-.748954 2.714677-1.953439 3.648082l.978322 1.538485c1.713516-1.324631 2.775344-3.158736 2.780206-5.184051.0032-2.0265727-1.05048-3.8594193-2.759132-5.1890826z" stroke-width="1.428038"/>
 | 
			
		||||
    <path d="m18.318701 4.9920248c2.307024 1.7957761 3.72921 4.2712232 3.723838 7.0065862-.0054 2.735361-1.440099 5.212198-3.754288 7.003804l.980846 1.74296c2.887361-2.234991 4.68032-5.328952 4.685693-8.743986.009-3.4150319-1.766089-6.5062134-4.648077-8.7467639z" stroke-width="1.57784"/>
 | 
			
		||||
    <g transform="matrix(.6875 0 0 1.01875 0 -.225)">
 | 
			
		||||
        <path d="m16 2-13.2653508 5v10l13.2653508 5z" stroke-width="1.236723"/>
 | 
			
		||||
        <path d="m2.7346492 7h-2.7346492v10h2.7346492z" stroke-width=".739547"/>
 | 
			
		||||
        <path d="m7 7c-4.7095572 2.7190641-5.0350591 2.9069927 0 0z" fill="none"/>
 | 
			
		||||
    </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 973 B  | 
							
								
								
									
										8
									
								
								src/icons/thin/player-volume-low.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,8 @@
 | 
			
		||||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
    <path d="m14.509208 8.343398c1.202865.9346638 1.945334 2.226588 1.94209 3.651856-.0032 1.425268-.748954 2.714677-1.953439 3.648082l.978322 1.538485c1.713516-1.324631 2.775344-3.158736 2.780206-5.184051.0032-2.0265727-1.05048-3.8594193-2.759132-5.1890826z" stroke-width="1.428038"/>
 | 
			
		||||
    <g transform="matrix(.6875 0 0 1.01875 0 -.225)">
 | 
			
		||||
        <path d="m16 2-13.2653508 5v10l13.2653508 5z" stroke-width="1.236723"/>
 | 
			
		||||
        <path d="m2.7346492 7h-2.7346492v10h2.7346492z" stroke-width=".739547"/>
 | 
			
		||||
        <path d="m7 7c-4.7095572 2.7190641-5.0350591 2.9069927 0 0z" fill="none"/>
 | 
			
		||||
    </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 684 B  | 
							
								
								
									
										11
									
								
								src/icons/thin/player-volume-mute.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,11 @@
 | 
			
		||||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
    <g transform="matrix(.6875 0 0 1.01875 0 -.225)">
 | 
			
		||||
        <path d="m16 2-13.7653508 5v10l13.7653508 5z" stroke-width="1.236723"/>
 | 
			
		||||
        <path d="m2.7346492 7h-2.7346492v10h2.7346492z" stroke-width=".739547"/>
 | 
			
		||||
        <path d="m7 7c-4.7095572 2.7190641-5.0350591 2.9069927 0 0z" fill="none"/>
 | 
			
		||||
    </g>
 | 
			
		||||
    <g fill="none" stroke="#000" stroke-width="1.767194">
 | 
			
		||||
        <path d="m14.900494 16.233449c7.7076-7.7076018 8.474709-8.4747094 8.474709-8.4747094"/>
 | 
			
		||||
        <path d="m23.375202 16.233449c-7.707601-7.7076013-8.474709-8.4747098-8.474709-8.4747098"/>
 | 
			
		||||
    </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 660 B  | 
@@ -7,8 +7,6 @@ from typing import Any, Dict
 | 
			
		||||
 | 
			
		||||
import aiofiles
 | 
			
		||||
 | 
			
		||||
import pyotherside
 | 
			
		||||
 | 
			
		||||
from .backend import Backend
 | 
			
		||||
from .theme_parser import convert_to_qml
 | 
			
		||||
from .utils import dict_update_recursive
 | 
			
		||||
@@ -106,11 +104,19 @@ class UISettings(JSONConfigFile):
 | 
			
		||||
    async def default_data(self) -> JsonData:
 | 
			
		||||
        return {
 | 
			
		||||
            "alertOnMessageForMsec": 4000,
 | 
			
		||||
            "autoPlayGIF": True,
 | 
			
		||||
            "clearRoomFilterOnEnter": True,
 | 
			
		||||
            "clearRoomFilterOnEscape": True,
 | 
			
		||||
            "theme": "Default.qpl",
 | 
			
		||||
            "writeAliases": {},
 | 
			
		||||
            "media": {
 | 
			
		||||
                "autoLoad": True,
 | 
			
		||||
                "autoPlay": False,
 | 
			
		||||
                "autoPlayGIF": True,
 | 
			
		||||
                "autoHideOSDAfterMsec": 3000,
 | 
			
		||||
                "defaultVolume": 100,
 | 
			
		||||
                "startMuted": False,
 | 
			
		||||
                "hoverPreviewHeight": 192,
 | 
			
		||||
            },
 | 
			
		||||
            "keys": {
 | 
			
		||||
                "startPythonDebugger": "Alt+Shift+D",
 | 
			
		||||
                "toggleDebugConsole":  "Alt+Shift+C",
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ Image {
 | 
			
		||||
            cache: true  // Needed to allow GIFs to loop
 | 
			
		||||
            paused: ! visible || window.hidden || userPaused
 | 
			
		||||
 | 
			
		||||
            property bool userPaused: ! window.settings.autoPlayGIF
 | 
			
		||||
            property bool userPaused: ! window.settings.media.autoPlayGIF
 | 
			
		||||
 | 
			
		||||
            TapHandler {
 | 
			
		||||
                onTapped: parent.userPaused = ! parent.userPaused
 | 
			
		||||
@@ -42,7 +42,7 @@ Image {
 | 
			
		||||
 | 
			
		||||
            HIcon {
 | 
			
		||||
                anchors.centerIn: parent
 | 
			
		||||
                svgName: "play"
 | 
			
		||||
                svgName: "play-overlay"
 | 
			
		||||
                colorize: "transparent"
 | 
			
		||||
                dimension: Math.min(
 | 
			
		||||
                    parent.width - theme.spacing * 2,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,13 @@ import QtQuick.Controls 2.12
 | 
			
		||||
ProgressBar {
 | 
			
		||||
    id: bar
 | 
			
		||||
 | 
			
		||||
    property color backgroundColor: theme.controls.progressBar.background
 | 
			
		||||
    property color foregroundColor: theme.controls.progressBar.foreground
 | 
			
		||||
 | 
			
		||||
    background: Rectangle {
 | 
			
		||||
        implicitWidth: 200
 | 
			
		||||
        implicitHeight: theme.controls.progressBar.height
 | 
			
		||||
        color: theme.controls.progressBar.background
 | 
			
		||||
        color: backgroundColor
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    contentItem: Item {
 | 
			
		||||
@@ -17,7 +20,7 @@ ProgressBar {
 | 
			
		||||
        Rectangle {
 | 
			
		||||
            width: bar.visualPosition * parent.width
 | 
			
		||||
            height: parent.height
 | 
			
		||||
            color: theme.controls.progressBar.foreground
 | 
			
		||||
            color: foregroundColor
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										72
									
								
								src/qml/Base/HSlider.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,72 @@
 | 
			
		||||
import QtQuick 2.12
 | 
			
		||||
import QtQuick.Controls 2.12
 | 
			
		||||
 | 
			
		||||
Slider {
 | 
			
		||||
    id: slider
 | 
			
		||||
    leftPadding: 0
 | 
			
		||||
    rightPadding: leftPadding
 | 
			
		||||
    topPadding: 0
 | 
			
		||||
    bottomPadding: topPadding
 | 
			
		||||
 | 
			
		||||
    property bool enableRadius: true
 | 
			
		||||
    property bool fullHeight: false
 | 
			
		||||
    property color backgroundColor: theme.controls.slider.background
 | 
			
		||||
    property color foregroundColor: theme.controls.slider.foreground
 | 
			
		||||
 | 
			
		||||
    property alias toolTip: toolTip
 | 
			
		||||
    property alias mouseArea: mouseArea
 | 
			
		||||
 | 
			
		||||
    background: Rectangle {
 | 
			
		||||
        color: backgroundColor
 | 
			
		||||
        x: slider.leftPadding
 | 
			
		||||
        y: slider.topPadding + slider.availableHeight / 2 - height / 2
 | 
			
		||||
 | 
			
		||||
        implicitWidth: 200
 | 
			
		||||
        implicitHeight: theme.controls.slider.height
 | 
			
		||||
        width: slider.availableWidth
 | 
			
		||||
        height: fullHeight ? slider.height : implicitHeight
 | 
			
		||||
        radius: enableRadius ? theme.controls.slider.radius : 0
 | 
			
		||||
 | 
			
		||||
        Rectangle {
 | 
			
		||||
            width: slider.visualPosition * parent.width
 | 
			
		||||
            height: parent.height
 | 
			
		||||
            color: foregroundColor
 | 
			
		||||
            radius: parent.radius
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    handle: Rectangle {
 | 
			
		||||
        x: slider.leftPadding + slider.visualPosition *
 | 
			
		||||
           (slider.availableWidth - width)
 | 
			
		||||
        y: slider.topPadding + slider.availableHeight / 2 - height / 2
 | 
			
		||||
 | 
			
		||||
        implicitWidth: theme.controls.slider.handle.size
 | 
			
		||||
        implicitHeight: implicitWidth
 | 
			
		||||
        radius: implicitWidth / 2
 | 
			
		||||
 | 
			
		||||
        color: slider.pressed ?
 | 
			
		||||
               theme.controls.slider.handle.pressedInside :
 | 
			
		||||
               theme.controls.slider.handle.inside
 | 
			
		||||
 | 
			
		||||
        border.color: slider.pressed ?
 | 
			
		||||
                      theme.controls.slider.handle.pressedBorder :
 | 
			
		||||
                      theme.controls.slider.handle.border
 | 
			
		||||
 | 
			
		||||
        Behavior on color { HColorAnimation {} }
 | 
			
		||||
        Behavior on border.color { HColorAnimation {} }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    HToolTip {
 | 
			
		||||
        id: toolTip
 | 
			
		||||
        parent: slider.handle
 | 
			
		||||
        visible: slider.pressed && text
 | 
			
		||||
        delay: 0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MouseArea {
 | 
			
		||||
        id: mouseArea
 | 
			
		||||
        anchors.fill: parent
 | 
			
		||||
        acceptedButtons: Qt.NoButton
 | 
			
		||||
        cursorShape: slider.hovered ? Qt.PointingHandCursor : Qt.ArrowCursor
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -32,7 +32,7 @@ ToolTip {
 | 
			
		||||
            bottomPadding: topPadding
 | 
			
		||||
 | 
			
		||||
            Layout.maximumWidth: Math.min(
 | 
			
		||||
                mainUI.width / 1.25, theme.fontSize.normal * 0.5 * 75,
 | 
			
		||||
                window.width / 1.25, theme.fontSize.normal * 0.5 * 75,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -41,7 +41,7 @@ ToolTip {
 | 
			
		||||
        HNumberAnimation { property: "opacity"; from: 0.0; to: 1.0 }
 | 
			
		||||
    }
 | 
			
		||||
    exit: Transition {
 | 
			
		||||
        HNumberAnimation { property: "opacity"; from: 1.0; to: 0.0 }
 | 
			
		||||
        HNumberAnimation { property: "opacity"; to: 0.0 }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    TapHandler {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										268
									
								
								src/qml/Base/MediaPlayer/OSD.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,268 @@
 | 
			
		||||
import QtQuick 2.12
 | 
			
		||||
import QtQuick.Layouts 1.12
 | 
			
		||||
import QtAV 1.7
 | 
			
		||||
import "../../Base"
 | 
			
		||||
import "../../utils.js" as Utils
 | 
			
		||||
 | 
			
		||||
HColumnLayout {
 | 
			
		||||
    id: osd
 | 
			
		||||
    visible: osdScaleTransform.yScale > 0
 | 
			
		||||
 | 
			
		||||
    transform: Scale {
 | 
			
		||||
        id: osdScaleTransform
 | 
			
		||||
        yScale: osdHover.hovered ||
 | 
			
		||||
                media.playbackState !== MediaPlayer.PlayingState ||
 | 
			
		||||
                osd.showup ?
 | 
			
		||||
                1 : 0
 | 
			
		||||
        origin.y: osd.height
 | 
			
		||||
 | 
			
		||||
        Behavior on yScale { HNumberAnimation {} }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    property Item media: parent  // QtAV.Video or QtAV.Audio
 | 
			
		||||
    property bool showup: false
 | 
			
		||||
    property bool enableFullScreen: false
 | 
			
		||||
    property bool fullScreen: false
 | 
			
		||||
 | 
			
		||||
    property int savedDuration: 0
 | 
			
		||||
    readonly property int duration: media.duration
 | 
			
		||||
    readonly property int boundPosition:
 | 
			
		||||
        Math.min(media.position, savedDuration)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    onShowupChanged: if (showup) osdHideTimer.restart()
 | 
			
		||||
    onDurationChanged: if (duration) savedDuration = duration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function togglePlay() {
 | 
			
		||||
        media.playbackState === MediaPlayer.PlayingState ?
 | 
			
		||||
        media.pause() : media.play()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function seekToPosition(pos) {  // pos: 0.0 to 1.0
 | 
			
		||||
        if (media.playbackState === MediaPlayer.StoppedState) media.play()
 | 
			
		||||
        if (media.seekable) media.seek(pos * savedDuration)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    HoverHandler { id: osdHover }
 | 
			
		||||
 | 
			
		||||
    Timer {
 | 
			
		||||
        id: osdHideTimer
 | 
			
		||||
        interval: window.settings.media.autoHideOSDAfterMsec
 | 
			
		||||
        onTriggered: osd.showup = false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    HSlider {
 | 
			
		||||
        id: timeSlider
 | 
			
		||||
        topPadding: 5
 | 
			
		||||
        z: 1
 | 
			
		||||
        to: savedDuration || boundPosition
 | 
			
		||||
        backgroundColor: theme.mediaPlayer.progress.background
 | 
			
		||||
        enableRadius: false
 | 
			
		||||
        fullHeight: true
 | 
			
		||||
        mouseArea.hoverEnabled: true
 | 
			
		||||
 | 
			
		||||
        onMoved: seekToPosition(timeSlider.position)
 | 
			
		||||
 | 
			
		||||
        Layout.fillWidth: true
 | 
			
		||||
        Layout.preferredHeight: theme.mediaPlayer.progress.height
 | 
			
		||||
 | 
			
		||||
        HToolTip {
 | 
			
		||||
            id: previewToolTip
 | 
			
		||||
            x: timeSlider.mouseArea.mouseX - width / 2
 | 
			
		||||
            visible: preview.implicitWidth >=
 | 
			
		||||
                     previewLabel.implicitWidth + previewLabel.padding &&
 | 
			
		||||
 | 
			
		||||
                     preview.implicitHeight >=
 | 
			
		||||
                     previewLabel.implicitHeight + previewLabel.padding &&
 | 
			
		||||
 | 
			
		||||
                     ! timeSlider.pressed && timeSlider.mouseArea.containsMouse
 | 
			
		||||
 | 
			
		||||
            readonly property int wantTimestamp:
 | 
			
		||||
                visible ?
 | 
			
		||||
                savedDuration *
 | 
			
		||||
                (timeSlider.mouseArea.mouseX / timeSlider.mouseArea.width) :
 | 
			
		||||
                -1
 | 
			
		||||
 | 
			
		||||
            Timer {
 | 
			
		||||
                interval: 300
 | 
			
		||||
                running: previewToolTip.visible
 | 
			
		||||
                repeat: true
 | 
			
		||||
                triggeredOnStart: true
 | 
			
		||||
                onTriggered: preview.timestamp = previewToolTip.wantTimestamp
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            contentItem: VideoPreview {
 | 
			
		||||
                id: preview
 | 
			
		||||
                implicitHeight: Math.min(
 | 
			
		||||
                    window.settings.media.hoverPreviewHeight,
 | 
			
		||||
                    media.height - osd.height - theme.spacing
 | 
			
		||||
                )
 | 
			
		||||
                implicitWidth: Math.min(
 | 
			
		||||
                    implicitHeight * media.savedAspectRatio,
 | 
			
		||||
                    media.width - theme.spacing,
 | 
			
		||||
                )
 | 
			
		||||
                file: media.source
 | 
			
		||||
 | 
			
		||||
                HLabel {
 | 
			
		||||
                    id: previewLabel
 | 
			
		||||
                    anchors.horizontalCenter: parent.horizontalCenter
 | 
			
		||||
                    anchors.bottom: parent.bottom
 | 
			
		||||
                    anchors.margins: padding / 4
 | 
			
		||||
                    text: Utils.formatDuration(previewToolTip.wantTimestamp)
 | 
			
		||||
                    padding: theme.spacing / 2
 | 
			
		||||
                    opacity: previewToolTip.wantTimestamp === -1 ? 0 : 1
 | 
			
		||||
 | 
			
		||||
                    background: Rectangle {
 | 
			
		||||
                        color: theme.mediaPlayer.controls.background
 | 
			
		||||
                        radius: theme.radius
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Binding {
 | 
			
		||||
            target: timeSlider
 | 
			
		||||
            property: "value"
 | 
			
		||||
            value: boundPosition
 | 
			
		||||
            when: ! timeSlider.pressed
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Rectangle {
 | 
			
		||||
        color: theme.mediaPlayer.controls.background
 | 
			
		||||
 | 
			
		||||
        Layout.fillWidth: true
 | 
			
		||||
        Layout.preferredHeight: childrenRect.height
 | 
			
		||||
 | 
			
		||||
        HRowLayout {
 | 
			
		||||
            width: parent.width
 | 
			
		||||
 | 
			
		||||
            OSDButton {
 | 
			
		||||
                readonly property string mode:
 | 
			
		||||
                    media.playbackState === MediaPlayer.StoppedState &&
 | 
			
		||||
                    savedDuration &&
 | 
			
		||||
                    boundPosition >= savedDuration - 500 ?
 | 
			
		||||
                    "restart" :
 | 
			
		||||
 | 
			
		||||
                    media.playbackState == MediaPlayer.PlayingState ? "pause" :
 | 
			
		||||
 | 
			
		||||
                    "play"
 | 
			
		||||
 | 
			
		||||
                icon.name: "player-" + mode
 | 
			
		||||
                toolTip.text: qsTr(
 | 
			
		||||
                    mode === "play"  ? "Play" :
 | 
			
		||||
                    mode === "pause" ? "Pause" :
 | 
			
		||||
                    "Restart"
 | 
			
		||||
                )
 | 
			
		||||
                onClicked: togglePlay()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // OSDButton {
 | 
			
		||||
                // icon.name: "player-loop"
 | 
			
		||||
                // visible: false
 | 
			
		||||
            // }
 | 
			
		||||
 | 
			
		||||
            OSDButton {
 | 
			
		||||
                id: volumeButton
 | 
			
		||||
                icon.name: "player-volume-" + (
 | 
			
		||||
                    media.muted ? "mute" : media.volume > 0.5 ? "high" : "low"
 | 
			
		||||
                )
 | 
			
		||||
                text: media.muted ? "" : Math.round(media.volume * 100)
 | 
			
		||||
                toolTip.text: media.muted ? qsTr("Unmute") : qsTr("Mute")
 | 
			
		||||
                onClicked: media.muted = ! media.muted
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            HSlider {
 | 
			
		||||
                value: media.volume
 | 
			
		||||
                onMoved: media.volume = value
 | 
			
		||||
 | 
			
		||||
                visible: Layout.preferredWidth > 0
 | 
			
		||||
                Layout.preferredWidth:
 | 
			
		||||
                    ! media.muted &&
 | 
			
		||||
                    (hovered || pressed || volumeButton.hovered) ?
 | 
			
		||||
                    theme.mediaPlayer.controls.volumeSliderWidth : 0
 | 
			
		||||
                Layout.fillHeight: true
 | 
			
		||||
 | 
			
		||||
                Behavior on Layout.preferredWidth { HNumberAnimation {} }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            OSDButton {
 | 
			
		||||
                id: speedButton
 | 
			
		||||
                icon.name: "player-speed"
 | 
			
		||||
                text: qsTr("%1x").arg(Utils.round(media.playbackRate))
 | 
			
		||||
                toolTip.text: qsTr("Reset speed")
 | 
			
		||||
                onClicked: media.playbackRate = 1
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            HSlider {
 | 
			
		||||
                id: speedSlider
 | 
			
		||||
                from: 0.2
 | 
			
		||||
                to: 4
 | 
			
		||||
                value: media.playbackRate
 | 
			
		||||
                stepSize: 0.2
 | 
			
		||||
                snapMode: HSlider.SnapAlways
 | 
			
		||||
 | 
			
		||||
                onMoved: media.playbackRate = value
 | 
			
		||||
 | 
			
		||||
                visible: Layout.preferredWidth > 0
 | 
			
		||||
                Layout.preferredWidth:
 | 
			
		||||
                    (hovered || pressed || speedButton.hovered) ?
 | 
			
		||||
                    theme.mediaPlayer.controls.speedSliderWidth : 0
 | 
			
		||||
                Layout.fillHeight: true
 | 
			
		||||
 | 
			
		||||
                Behavior on Layout.preferredWidth { HNumberAnimation {} }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            OSDLabel {
 | 
			
		||||
                text: boundPosition && savedDuration ?
 | 
			
		||||
 | 
			
		||||
                      qsTr("%1 / %2")
 | 
			
		||||
                      .arg(Utils.formatDuration(boundPosition))
 | 
			
		||||
                      .arg(Utils.formatDuration(savedDuration)) :
 | 
			
		||||
 | 
			
		||||
                      boundPosition || savedDuration ?
 | 
			
		||||
                      Utils.formatDuration(boundPosition || savedDuration) :
 | 
			
		||||
 | 
			
		||||
                      ""
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            HSpacer {}
 | 
			
		||||
 | 
			
		||||
            OSDLabel {
 | 
			
		||||
                text: boundPosition && savedDuration ?
 | 
			
		||||
                      qsTr("-%1").arg(
 | 
			
		||||
                          Utils.formatDuration(savedDuration - boundPosition)
 | 
			
		||||
                      ) : ""
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // OSDButton {
 | 
			
		||||
            //     icon.name: "player-track-video"
 | 
			
		||||
            // }
 | 
			
		||||
 | 
			
		||||
            // OSDButton {
 | 
			
		||||
            //     icon.name: "player-track-audio"
 | 
			
		||||
            // }
 | 
			
		||||
 | 
			
		||||
            // OSDButton {
 | 
			
		||||
            //     icon.name: "player-track-subtitle"
 | 
			
		||||
            // }
 | 
			
		||||
 | 
			
		||||
            OSDButton {
 | 
			
		||||
                icon.name: "download"
 | 
			
		||||
                toolTip.text: qsTr("Download")
 | 
			
		||||
                onClicked: Qt.openUrlExternally(media.source)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            OSDButton {
 | 
			
		||||
                id: fullScreenButton
 | 
			
		||||
                icon.name: "player-fullscreen" + (fullScreen ? "-exit" : "")
 | 
			
		||||
                toolTip.text: fullScreen ?
 | 
			
		||||
                              qsTr("Exit fullscreen") : qsTr("Fullscreen")
 | 
			
		||||
                onClicked: fullScreen = ! fullScreen
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								src/qml/Base/MediaPlayer/OSDButton.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,8 @@
 | 
			
		||||
import QtQuick 2.12
 | 
			
		||||
import "../../Base"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
HButton {
 | 
			
		||||
    backgroundColor: "transparent"
 | 
			
		||||
    iconItem.dimension: theme.mediaPlayer.controls.iconHeight
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								src/qml/Base/MediaPlayer/OSDLabel.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,9 @@
 | 
			
		||||
import QtQuick 2.12
 | 
			
		||||
import QtQuick.Layouts 1.12
 | 
			
		||||
import "../../Base"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
HLabel {
 | 
			
		||||
    Layout.leftMargin: theme.spacing / 2
 | 
			
		||||
    Layout.rightMargin: Layout.leftMargin
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										74
									
								
								src/qml/Base/MediaPlayer/VideoPlayer.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,74 @@
 | 
			
		||||
import QtQuick 2.12
 | 
			
		||||
import QtQuick.Window 2.12
 | 
			
		||||
import QtAV 1.7
 | 
			
		||||
 | 
			
		||||
Video {
 | 
			
		||||
    id: video
 | 
			
		||||
    autoLoad: window.settings.media.autoLoad
 | 
			
		||||
    autoPlay: window.settings.media.autoPlay
 | 
			
		||||
    volume: window.settings.media.defaultVolume / 100
 | 
			
		||||
    muted: window.settings.media.startMuted
 | 
			
		||||
    implicitWidth: fullScreen ? window.width : 640
 | 
			
		||||
    implicitHeight: fullScreen ? window.height : (width / savedAspectRatio)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    property bool hovered: false
 | 
			
		||||
    property alias fullScreen: osd.fullScreen
 | 
			
		||||
 | 
			
		||||
    property int oldVisibility: Window.Windowed
 | 
			
		||||
    property QtObject oldParent: video.parent
 | 
			
		||||
 | 
			
		||||
    property real savedAspectRatio: 16 / 9
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    onSourceAspectRatioChanged:
 | 
			
		||||
        if (sourceAspectRatio) savedAspectRatio = sourceAspectRatio
 | 
			
		||||
 | 
			
		||||
    onFullScreenChanged: {
 | 
			
		||||
        if (fullScreen) {
 | 
			
		||||
            oldVisibility     = window.visibility
 | 
			
		||||
            window.visibility = Window.FullScreen
 | 
			
		||||
 | 
			
		||||
            oldParent    = video.parent
 | 
			
		||||
            video.parent = mainUI.fullScreenPopup.contentItem
 | 
			
		||||
 | 
			
		||||
            mainUI.fullScreenPopup.open()
 | 
			
		||||
 | 
			
		||||
        } else {
 | 
			
		||||
            window.visibility = oldVisibility
 | 
			
		||||
            mainUI.fullScreenPopup.close()
 | 
			
		||||
 | 
			
		||||
            video.parent = oldParent
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    Connections {
 | 
			
		||||
        target: mainUI.fullScreenPopup
 | 
			
		||||
        onClosed: fullScreen = false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    TapHandler {
 | 
			
		||||
        onTapped: osd.togglePlay()
 | 
			
		||||
        onDoubleTapped: video.fullScreen = ! video.fullScreen
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MouseArea {
 | 
			
		||||
        width: parent.width
 | 
			
		||||
        height: parent.height - (osd.visible ? osd.height : 0)
 | 
			
		||||
        acceptedButtons: Qt.NoButton
 | 
			
		||||
        hoverEnabled: true
 | 
			
		||||
        propagateComposedEvents: true
 | 
			
		||||
 | 
			
		||||
        onContainsMouseChanged: video.hovered = containsMouse
 | 
			
		||||
        onMouseXChanged: osd.showup = true
 | 
			
		||||
        onMouseYChanged: osd.showup = true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    OSD {
 | 
			
		||||
        id: osd
 | 
			
		||||
        width: parent.width
 | 
			
		||||
        anchors.bottom: parent.bottom
 | 
			
		||||
        enableFullScreen: true
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -130,12 +130,6 @@ Column {
 | 
			
		||||
                contextMenu.media[0] === EventDelegate.Media.Image ?
 | 
			
		||||
                qsTr("Copy image address") :
 | 
			
		||||
 | 
			
		||||
                contextMenu.media[0] === EventDelegate.Media.Video ?
 | 
			
		||||
                qsTr("Copy video address") :
 | 
			
		||||
 | 
			
		||||
                contextMenu.media[0] === EventDelegate.Media.Audio ?
 | 
			
		||||
                qsTr("Copy audio address") :
 | 
			
		||||
 | 
			
		||||
                qsTr("Copy media address")
 | 
			
		||||
 | 
			
		||||
            visible: Boolean(text)
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,10 @@ HLoader {
 | 
			
		||||
                fileSize:     info.media_size,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } else if (type === EventDelegate.Media.Video) {
 | 
			
		||||
            var file  = "EventVideo.qml"
 | 
			
		||||
            var props = { source: mediaUrl }
 | 
			
		||||
 | 
			
		||||
        } else { return }
 | 
			
		||||
 | 
			
		||||
        loader.setSource(file, props)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								src/qml/Chat/Timeline/EventVideo.qml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,19 @@
 | 
			
		||||
import QtQuick 2.12
 | 
			
		||||
import QtQuick.Layouts 1.12
 | 
			
		||||
import QtAV 1.7
 | 
			
		||||
import "../../Base"
 | 
			
		||||
import "../../Base/MediaPlayer"
 | 
			
		||||
import "../../utils.js" as Utils
 | 
			
		||||
 | 
			
		||||
VideoPlayer {
 | 
			
		||||
    id: video
 | 
			
		||||
    width: fullScreen ? implicitWidth : Math.min(
 | 
			
		||||
        mainColumn.width - eventContent.spacing * 2,
 | 
			
		||||
        theme.chat.message.videoWidth,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    onHoveredChanged: {
 | 
			
		||||
        eventDelegate.hoveredMediaTypeUrl =
 | 
			
		||||
            hovered ? [EventDelegate.Media.Video, video.source] : []
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -12,11 +12,12 @@ Item {
 | 
			
		||||
    Component.onCompleted: window.mainUI = mainUI
 | 
			
		||||
    Keys.forwardTo: [shortcuts]
 | 
			
		||||
 | 
			
		||||
    property alias shortcuts: shortcuts
 | 
			
		||||
    property alias sidePane: sidePane
 | 
			
		||||
    property alias pageLoader: pageLoader
 | 
			
		||||
    property alias pressAnimation: pressAnimation
 | 
			
		||||
    property alias debugConsole: debugConsoleLoader.item
 | 
			
		||||
    readonly property alias shortcuts: shortcuts
 | 
			
		||||
    readonly property alias sidePane: sidePane
 | 
			
		||||
    readonly property alias pageLoader: pageLoader
 | 
			
		||||
    readonly property alias pressAnimation: pressAnimation
 | 
			
		||||
    readonly property alias debugConsole: debugConsoleLoader.item
 | 
			
		||||
    readonly property alias fullScreenPopup: fullScreenPopup
 | 
			
		||||
 | 
			
		||||
    SequentialAnimation {
 | 
			
		||||
        id: pressAnimation
 | 
			
		||||
@@ -145,4 +146,11 @@ Item {
 | 
			
		||||
        id: debugConsoleLoader
 | 
			
		||||
        source: debugMode ? "DebugConsole.qml" : ""
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    HPopup {
 | 
			
		||||
        id: fullScreenPopup
 | 
			
		||||
        dim: false
 | 
			
		||||
        width: window.width
 | 
			
		||||
        height: window.height
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -204,6 +204,26 @@ function formatTime(time, seconds=true) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function formatDuration(milliseconds) {
 | 
			
		||||
    let totalSeconds = milliseconds / 1000
 | 
			
		||||
 | 
			
		||||
    let hours   = Math.floor(totalSeconds / 3600)
 | 
			
		||||
    let minutes = Math.floor((totalSeconds % 3600) / 60)
 | 
			
		||||
    let seconds = Math.floor(totalSeconds % 60)
 | 
			
		||||
 | 
			
		||||
    if (seconds < 10) seconds = "0" + seconds
 | 
			
		||||
    if (hours < 1)    return minutes + ":" + seconds
 | 
			
		||||
 | 
			
		||||
    if (minutes < 10) minutes = "0" + minutes
 | 
			
		||||
	return hours + ":" + minutes + ":" + seconds
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function round(float) {
 | 
			
		||||
    return parseFloat(float.toFixed(2))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function getItem(array, mainKey, value) {
 | 
			
		||||
    for (let i = 0; i < array.length; i++) {
 | 
			
		||||
        if (array[i][mainKey] === value) { return array[i] }
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ real disabledElementsOpacity: 0.3
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
fontSize:
 | 
			
		||||
    int smaller: 13  * fontScale
 | 
			
		||||
    int smaller: 13 * fontScale
 | 
			
		||||
    int small:   13 * fontScale
 | 
			
		||||
    int normal:  16 * fontScale
 | 
			
		||||
    int big:     22 * fontScale
 | 
			
		||||
@@ -176,6 +176,19 @@ controls:
 | 
			
		||||
        color background: colors.inputBackground
 | 
			
		||||
        color foreground: colors.accentBackground
 | 
			
		||||
 | 
			
		||||
    slider:
 | 
			
		||||
        int radius:       2
 | 
			
		||||
        int height:       controls.progressBar.height
 | 
			
		||||
        color background: controls.progressBar.background
 | 
			
		||||
        color foreground: controls.progressBar.foreground
 | 
			
		||||
 | 
			
		||||
        handle:
 | 
			
		||||
            int size: 20
 | 
			
		||||
            color inside: hsluv(0, 0, 90)
 | 
			
		||||
            color pressedInside: "white"
 | 
			
		||||
            color border: "black"
 | 
			
		||||
            color pressedBorder: colors.strongAccentBackground
 | 
			
		||||
 | 
			
		||||
    image:
 | 
			
		||||
        int maxPauseIndicatorSize: 64
 | 
			
		||||
 | 
			
		||||
@@ -320,6 +333,7 @@ chat:
 | 
			
		||||
            '<style type"text/css">\n' + styleSheet + '\n</style>\n'
 | 
			
		||||
 | 
			
		||||
        int thumbnailWidth: 256
 | 
			
		||||
        int videoWidth: 512
 | 
			
		||||
 | 
			
		||||
    daybreak:
 | 
			
		||||
        color background: colors.strongBackground
 | 
			
		||||
@@ -342,3 +356,17 @@ chat:
 | 
			
		||||
 | 
			
		||||
    composer:
 | 
			
		||||
        color background: colors.strongBackground
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
mediaPlayer:
 | 
			
		||||
    progress:
 | 
			
		||||
        int height:        8
 | 
			
		||||
        color background:  hsluv(0, 0, 0, 0.5)
 | 
			
		||||
 | 
			
		||||
    controls:
 | 
			
		||||
        int iconHeight:        20
 | 
			
		||||
        int volumeSliderWidth: 100
 | 
			
		||||
        int speedSliderWidth:  100
 | 
			
		||||
        color background:      hsluv(
 | 
			
		||||
            colors.hue, colors.saturation * 1.25, colors.intensity * 2, 0.85,
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||