Warning, /network/neochat/src/qml/VideoDelegate.qml is written in an unsupported language. File is not indexed.

0001 // SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
0002 // SPDX-License-Identifier: GPL-3.0-only
0003 
0004 import QtQuick
0005 import QtQuick.Controls as QQC2
0006 import QtQuick.Layouts
0007 import QtMultimedia
0008 import Qt.labs.platform as Platform
0009 
0010 import org.kde.coreaddons
0011 import org.kde.kirigami as Kirigami
0012 
0013 import org.kde.neochat
0014 
0015 /**
0016  * @brief A timeline delegate for a video message.
0017  *
0018  * @inherit MessageDelegate
0019  */
0020 MessageDelegate {
0021     id: root
0022 
0023     /**
0024      * @brief The media info for the event.
0025      *
0026      * This should consist of the following:
0027      *  - source - The mxc URL for the media.
0028      *  - mimeType - The MIME type of the media (should be video/xxx for this delegate).
0029      *  - mimeIcon - The MIME icon name (should be video-xxx).
0030      *  - size - The file size in bytes.
0031      *  - duration - The length in seconds of the audio media.
0032      *  - width - The width in pixels of the audio media.
0033      *  - height - The height in pixels of the audio media.
0034      *  - tempInfo - mediaInfo (with the same properties as this except no tempInfo) for a temporary image while the file downloads.
0035      */
0036     required property var mediaInfo
0037 
0038     /**
0039      * @brief Whether the media has been downloaded.
0040      */
0041     readonly property bool downloaded: root.progressInfo && root.progressInfo.completed
0042 
0043     /**
0044      * @brief Whether the video should be played when downloaded.
0045      */
0046     property bool playOnFinished: false
0047 
0048     /**
0049      * @brief The maximum width of the image.
0050      */
0051     readonly property var maxWidth: Kirigami.Units.gridUnit * 30
0052 
0053     /**
0054      * @brief The maximum height of the image.
0055      */
0056     readonly property var maxHeight: Kirigami.Units.gridUnit * 30
0057 
0058     onOpenContextMenu: RoomManager.viewEventMenu(eventId, author, delegateType, plainText, "", "", mediaInfo.mimeType, progressInfo)
0059 
0060     onDownloadedChanged: {
0061         if (downloaded) {
0062             vid.source = root.progressInfo.localPath
0063         }
0064 
0065         if (downloaded && playOnFinished) {
0066             playSavedFile()
0067             playOnFinished = false
0068         }
0069     }
0070 
0071     bubbleContent: Video {
0072         id: vid
0073         implicitWidth: mediaSizeHelper.currentSize.width
0074         implicitHeight: mediaSizeHelper.currentSize.height
0075 
0076         fillMode: VideoOutput.PreserveAspectFit
0077 
0078         states: [
0079             State {
0080                 name: "notDownloaded"
0081                 when: !root.progressInfo.completed && !root.progressInfo.active
0082                 PropertyChanges {
0083                     target: noDownloadLabel
0084                     visible: true
0085                 }
0086                 PropertyChanges {
0087                     target: mediaThumbnail
0088                     visible: true
0089                 }
0090             },
0091             State {
0092                 name: "downloading"
0093                 when: root.progressInfo.active && !root.progressInfo.completed
0094                 PropertyChanges {
0095                     target: downloadBar
0096                     visible: true
0097                 }
0098             },
0099             State {
0100                 name: "paused"
0101                 when: root.progressInfo.completed && (vid.playbackState === MediaPlayer.StoppedState || vid.playbackState === MediaPlayer.PausedState)
0102                 PropertyChanges {
0103                     target: videoControls
0104                     stateVisible: true
0105                 }
0106                 PropertyChanges {
0107                     target: playButton
0108                     icon.name: "media-playback-start"
0109                     onClicked: vid.play()
0110                 }
0111             },
0112             State {
0113                 name: "playing"
0114                 when: root.progressInfo.completed && vid.playbackState === MediaPlayer.PlayingState
0115                 PropertyChanges {
0116                     target: videoControls
0117                     stateVisible: true
0118                 }
0119                 PropertyChanges {
0120                     target: playButton
0121                     icon.name: "media-playback-pause"
0122                     onClicked: vid.pause()
0123                 }
0124             }
0125         ]
0126 
0127         Image {
0128             id: mediaThumbnail
0129             anchors.fill: parent
0130             visible: false
0131 
0132             source: root.mediaInfo.tempInfo.source
0133             fillMode: Image.PreserveAspectFit
0134         }
0135 
0136         QQC2.Label {
0137             id: noDownloadLabel
0138             anchors.centerIn: parent
0139 
0140             visible: false
0141             color: "white"
0142             text: i18n("Video")
0143             font.pixelSize: 16
0144 
0145             padding: 8
0146 
0147             background: Rectangle {
0148                 radius: Kirigami.Units.smallSpacing
0149                 color: "black"
0150                 opacity: 0.3
0151             }
0152         }
0153 
0154         Rectangle {
0155             id: downloadBar
0156             anchors.fill: parent
0157             visible: false
0158 
0159             color: Kirigami.Theme.backgroundColor
0160             radius: Kirigami.Units.smallSpacing
0161 
0162             QQC2.ProgressBar {
0163                 anchors.centerIn: parent
0164 
0165                 width: parent.width * 0.8
0166 
0167                 from: 0
0168                 to: root.progressInfo.total
0169                 value: root.progressInfo.progress
0170             }
0171         }
0172 
0173         QQC2.Control {
0174             id: videoControls
0175             property bool stateVisible: false
0176 
0177             anchors.bottom: vid.bottom
0178             anchors.left: vid.left
0179             anchors.right: vid.right
0180             visible: stateVisible && (videoHoverHandler.hovered || volumePopupHoverHandler.hovered || volumeSlider.hovered || videoControlTimer.running)
0181 
0182             contentItem: RowLayout {
0183                 id: controlRow
0184                 QQC2.ToolButton {
0185                     id: playButton
0186                 }
0187                 QQC2.Slider {
0188                     Layout.fillWidth: true
0189                     from: 0
0190                     to: vid.duration
0191                     value: vid.position
0192                     onMoved: vid.seek(value)
0193                 }
0194                 QQC2.Label {
0195                     text: Format.formatDuration(vid.position) + "/" + Format.formatDuration(vid.duration)
0196                 }
0197                 QQC2.ToolButton {
0198                     id: volumeButton
0199                     property var unmuteVolume: vid.volume
0200 
0201                     icon.name: vid.volume <= 0 ? "player-volume-muted" : "player-volume"
0202 
0203                     QQC2.ToolTip.visible: hovered
0204                     QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
0205                     QQC2.ToolTip.timeout: Kirigami.Units.toolTipDelay
0206                     QQC2.ToolTip.text: i18nc("@action:button", "Volume")
0207 
0208                     onClicked: {
0209                         if (vid.volume > 0) {
0210                             vid.volume = 0
0211                         } else {
0212                             if (unmuteVolume === 0) {
0213                                 vid.volume = 1
0214                             } else {
0215                                 vid.volume = unmuteVolume
0216                             }
0217                         }
0218                     }
0219                     onHoveredChanged: {
0220                         if (!hovered && (vid.state === "paused" || vid.state === "playing")) {
0221                             videoControlTimer.restart()
0222                             volumePopupTimer.restart()
0223                         }
0224                     }
0225 
0226                     QQC2.Popup {
0227                         id: volumePopup
0228                         y: -height
0229                         width: volumeButton.width
0230                         visible: videoControls.stateVisible && (volumeButton.hovered || volumePopupHoverHandler.hovered || volumeSlider.hovered || volumePopupTimer.running)
0231 
0232                         focus: true
0233                         padding: Kirigami.Units.smallSpacing
0234                         closePolicy: QQC2.Popup.NoAutoClose
0235 
0236                         QQC2.Slider {
0237                             id: volumeSlider
0238                             anchors.centerIn: parent
0239                             implicitHeight: Kirigami.Units.gridUnit * 7
0240                             orientation: Qt.Vertical
0241                             padding: 0
0242                             from: 0
0243                             to: 1
0244                             value: vid.volume
0245                             onMoved: {
0246                                 vid.volume = value
0247                                 volumeButton.unmuteVolume = value
0248                             }
0249                             onHoveredChanged: {
0250                                 if (!hovered && (vid.state === "paused" || vid.state === "playing")) {
0251                                     videoControlTimer.restart()
0252                                     volumePopupTimer.restart()
0253                                 }
0254                             }
0255                         }
0256                         Timer {
0257                             id: volumePopupTimer
0258                             interval: 500
0259                         }
0260                         HoverHandler {
0261                             id: volumePopupHoverHandler
0262                             onHoveredChanged: {
0263                                 if (!hovered && (vid.state === "paused" || vid.state === "playing")) {
0264                                     videoControlTimer.restart()
0265                                     volumePopupTimer.restart()
0266                                 }
0267                             }
0268                         }
0269                         background: Kirigami.ShadowedRectangle {
0270                             radius: 4
0271                             color: Kirigami.Theme.backgroundColor
0272                             opacity: 0.8
0273 
0274                             property color borderColor: Kirigami.Theme.textColor
0275                             border.color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0.3)
0276                             border.width: 1
0277 
0278                             shadow.xOffset: 0
0279                             shadow.yOffset: 4
0280                             shadow.color: Qt.rgba(0, 0, 0, 0.3)
0281                             shadow.size: 8
0282                         }
0283                     }
0284                 }
0285                 QQC2.ToolButton {
0286                     id: maximizeButton
0287                     display: QQC2.AbstractButton.IconOnly
0288 
0289                     action: Kirigami.Action {
0290                         text: i18n("Maximize")
0291                         icon.name: "view-fullscreen"
0292                         onTriggered: {
0293                             root.ListView.view.interactive = false
0294                             vid.pause()
0295                             // We need to make sure the index is that of the MediaMessageFilterModel.
0296                             if (root.ListView.view.model instanceof MessageFilterModel) {
0297                                 RoomManager.maximizeMedia(RoomManager.mediaMessageFilterModel.getRowForSourceItem(root.index))
0298                             } else {
0299                                 RoomManager.maximizeMedia(root.index)
0300                             }
0301                         }
0302                     }
0303                 }
0304             }
0305             background: Kirigami.ShadowedRectangle {
0306                 radius: 4
0307                 color: Kirigami.Theme.backgroundColor
0308                 opacity: 0.8
0309 
0310                 property color borderColor: Kirigami.Theme.textColor
0311                 border.color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0.3)
0312                 border.width: 1
0313 
0314                 shadow.xOffset: 0
0315                 shadow.yOffset: 4
0316                 shadow.color: Qt.rgba(0, 0, 0, 0.3)
0317                 shadow.size: 8
0318             }
0319         }
0320 
0321         Timer {
0322             id: videoControlTimer
0323             interval: 1000
0324         }
0325         HoverHandler {
0326             id: videoHoverHandler
0327             onHoveredChanged: {
0328                 if (!hovered && (vid.state === "paused" || vid.state === "playing")) {
0329                     videoControlTimer.restart()
0330                 }
0331             }
0332         }
0333 
0334         TapHandler {
0335             acceptedButtons: Qt.LeftButton
0336             gesturePolicy: TapHandler.ReleaseWithinBounds | TapHandler.WithinBounds
0337             onTapped: if (root.progressInfo.completed) {
0338                 if (vid.playbackState == MediaPlayer.PlayingState) {
0339                     vid.pause()
0340                 } else {
0341                     vid.play()
0342                 }
0343             } else {
0344                 root.downloadAndPlay()
0345             }
0346         }
0347 
0348         MediaSizeHelper {
0349             id: mediaSizeHelper
0350             contentMaxWidth: root.contentMaxWidth
0351             mediaWidth: root.mediaInfo.width
0352             mediaHeight: root.mediaInfo.height
0353         }
0354     }
0355 
0356     function downloadAndPlay() {
0357         if (vid.downloaded) {
0358             playSavedFile()
0359         } else {
0360             playOnFinished = true
0361             ListView.view.currentRoom.downloadFile(root.eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + root.eventId.replace(":", "_").replace("/", "_").replace("+", "_") + ListView.view.currentRoom.fileNameToDownload(root.eventId))
0362         }
0363     }
0364 
0365     function playSavedFile() {
0366         vid.stop()
0367         vid.play()
0368     }
0369 }