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         if (downloaded && playOnFinished) {
0065             playSavedFile();
0066             playOnFinished = false;
0067         }
0068     }
0069 
0070     bubbleContent: Video {
0071         id: vid
0072         implicitWidth: mediaSizeHelper.currentSize.width
0073         implicitHeight: mediaSizeHelper.currentSize.height
0074 
0075         fillMode: VideoOutput.PreserveAspectFit
0076 
0077         states: [
0078             State {
0079                 name: "notDownloaded"
0080                 when: !root.progressInfo.completed && !root.progressInfo.active
0081                 PropertyChanges {
0082                     target: noDownloadLabel
0083                     visible: true
0084                 }
0085                 PropertyChanges {
0086                     target: mediaThumbnail
0087                     visible: true
0088                 }
0089             },
0090             State {
0091                 name: "downloading"
0092                 when: root.progressInfo.active && !root.progressInfo.completed
0093                 PropertyChanges {
0094                     target: downloadBar
0095                     visible: true
0096                 }
0097             },
0098             State {
0099                 name: "paused"
0100                 when: root.progressInfo.completed && (vid.playbackState === MediaPlayer.StoppedState || vid.playbackState === MediaPlayer.PausedState)
0101                 PropertyChanges {
0102                     target: videoControls
0103                     stateVisible: true
0104                 }
0105                 PropertyChanges {
0106                     target: playButton
0107                     icon.name: "media-playback-start"
0108                     onClicked: vid.play()
0109                 }
0110             },
0111             State {
0112                 name: "playing"
0113                 when: root.progressInfo.completed && vid.playbackState === MediaPlayer.PlayingState
0114                 PropertyChanges {
0115                     target: videoControls
0116                     stateVisible: true
0117                 }
0118                 PropertyChanges {
0119                     target: playButton
0120                     icon.name: "media-playback-pause"
0121                     onClicked: vid.pause()
0122                 }
0123             }
0124         ]
0125 
0126         Image {
0127             id: mediaThumbnail
0128             anchors.fill: parent
0129             visible: false
0130 
0131             source: root.mediaInfo.tempInfo.source
0132             fillMode: Image.PreserveAspectFit
0133         }
0134 
0135         QQC2.Label {
0136             id: noDownloadLabel
0137             anchors.centerIn: parent
0138 
0139             visible: false
0140             color: "white"
0141             text: i18n("Video")
0142             font.pixelSize: 16
0143 
0144             padding: 8
0145 
0146             background: Rectangle {
0147                 radius: Kirigami.Units.smallSpacing
0148                 color: "black"
0149                 opacity: 0.3
0150             }
0151         }
0152 
0153         Rectangle {
0154             id: downloadBar
0155             anchors.fill: parent
0156             visible: false
0157 
0158             color: Kirigami.Theme.backgroundColor
0159             radius: Kirigami.Units.smallSpacing
0160 
0161             QQC2.ProgressBar {
0162                 anchors.centerIn: parent
0163 
0164                 width: parent.width * 0.8
0165 
0166                 from: 0
0167                 to: root.progressInfo.total
0168                 value: root.progressInfo.progress
0169             }
0170         }
0171 
0172         QQC2.Control {
0173             id: videoControls
0174             property bool stateVisible: false
0175 
0176             anchors.bottom: vid.bottom
0177             anchors.left: vid.left
0178             anchors.right: vid.right
0179             visible: stateVisible && (videoHoverHandler.hovered || volumePopupHoverHandler.hovered || volumeSlider.hovered || videoControlTimer.running)
0180 
0181             contentItem: RowLayout {
0182                 id: controlRow
0183                 QQC2.ToolButton {
0184                     id: playButton
0185                 }
0186                 QQC2.Slider {
0187                     Layout.fillWidth: true
0188                     from: 0
0189                     to: vid.duration
0190                     value: vid.position
0191                     onMoved: vid.seek(value)
0192                 }
0193                 QQC2.Label {
0194                     text: Format.formatDuration(vid.position) + "/" + Format.formatDuration(vid.duration)
0195                 }
0196                 QQC2.ToolButton {
0197                     id: volumeButton
0198                     property var unmuteVolume: vid.volume
0199 
0200                     icon.name: vid.volume <= 0 ? "player-volume-muted" : "player-volume"
0201 
0202                     QQC2.ToolTip.visible: hovered
0203                     QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
0204                     QQC2.ToolTip.timeout: Kirigami.Units.toolTipDelay
0205                     QQC2.ToolTip.text: i18nc("@action:button", "Volume")
0206 
0207                     onClicked: {
0208                         if (vid.volume > 0) {
0209                             vid.volume = 0;
0210                         } else {
0211                             if (unmuteVolume === 0) {
0212                                 vid.volume = 1;
0213                             } else {
0214                                 vid.volume = unmuteVolume;
0215                             }
0216                         }
0217                     }
0218                     onHoveredChanged: {
0219                         if (!hovered && (vid.state === "paused" || vid.state === "playing")) {
0220                             videoControlTimer.restart();
0221                             volumePopupTimer.restart();
0222                         }
0223                     }
0224 
0225                     QQC2.Popup {
0226                         id: volumePopup
0227                         y: -height
0228                         width: volumeButton.width
0229                         visible: videoControls.stateVisible && (volumeButton.hovered || volumePopupHoverHandler.hovered || volumeSlider.hovered || volumePopupTimer.running)
0230 
0231                         focus: true
0232                         padding: Kirigami.Units.smallSpacing
0233                         closePolicy: QQC2.Popup.NoAutoClose
0234 
0235                         QQC2.Slider {
0236                             id: volumeSlider
0237                             anchors.centerIn: parent
0238                             implicitHeight: Kirigami.Units.gridUnit * 7
0239                             orientation: Qt.Vertical
0240                             padding: 0
0241                             from: 0
0242                             to: 1
0243                             value: vid.volume
0244                             onMoved: {
0245                                 vid.volume = value;
0246                                 volumeButton.unmuteVolume = value;
0247                             }
0248                             onHoveredChanged: {
0249                                 if (!hovered && (vid.state === "paused" || vid.state === "playing")) {
0250                                     videoControlTimer.restart();
0251                                     volumePopupTimer.restart();
0252                                 }
0253                             }
0254                         }
0255                         Timer {
0256                             id: volumePopupTimer
0257                             interval: 500
0258                         }
0259                         HoverHandler {
0260                             id: volumePopupHoverHandler
0261                             onHoveredChanged: {
0262                                 if (!hovered && (vid.state === "paused" || vid.state === "playing")) {
0263                                     videoControlTimer.restart();
0264                                     volumePopupTimer.restart();
0265                                 }
0266                             }
0267                         }
0268                         background: Kirigami.ShadowedRectangle {
0269                             radius: 4
0270                             color: Kirigami.Theme.backgroundColor
0271                             opacity: 0.8
0272 
0273                             property color borderColor: Kirigami.Theme.textColor
0274                             border.color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0.3)
0275                             border.width: 1
0276 
0277                             shadow.xOffset: 0
0278                             shadow.yOffset: 4
0279                             shadow.color: Qt.rgba(0, 0, 0, 0.3)
0280                             shadow.size: 8
0281                         }
0282                     }
0283                 }
0284                 QQC2.ToolButton {
0285                     id: maximizeButton
0286                     display: QQC2.AbstractButton.IconOnly
0287 
0288                     action: Kirigami.Action {
0289                         text: i18n("Maximize")
0290                         icon.name: "view-fullscreen"
0291                         onTriggered: {
0292                             root.ListView.view.interactive = false;
0293                             vid.pause();
0294                             // We need to make sure the index is that of the MediaMessageFilterModel.
0295                             if (root.ListView.view.model instanceof MessageFilterModel) {
0296                                 RoomManager.maximizeMedia(RoomManager.mediaMessageFilterModel.getRowForSourceItem(root.index));
0297                             } else {
0298                                 RoomManager.maximizeMedia(root.index);
0299                             }
0300                         }
0301                     }
0302                 }
0303             }
0304             background: Kirigami.ShadowedRectangle {
0305                 radius: 4
0306                 color: Kirigami.Theme.backgroundColor
0307                 opacity: 0.8
0308 
0309                 property color borderColor: Kirigami.Theme.textColor
0310                 border.color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0.3)
0311                 border.width: 1
0312 
0313                 shadow.xOffset: 0
0314                 shadow.yOffset: 4
0315                 shadow.color: Qt.rgba(0, 0, 0, 0.3)
0316                 shadow.size: 8
0317             }
0318         }
0319 
0320         Timer {
0321             id: videoControlTimer
0322             interval: 1000
0323         }
0324         HoverHandler {
0325             id: videoHoverHandler
0326             onHoveredChanged: {
0327                 if (!hovered && (vid.state === "paused" || vid.state === "playing")) {
0328                     videoControlTimer.restart();
0329                 }
0330             }
0331         }
0332 
0333         TapHandler {
0334             acceptedButtons: Qt.LeftButton
0335             gesturePolicy: TapHandler.ReleaseWithinBounds | TapHandler.WithinBounds
0336             onTapped: if (root.progressInfo.completed) {
0337                 if (vid.playbackState == MediaPlayer.PlayingState) {
0338                     vid.pause();
0339                 } else {
0340                     vid.play();
0341                 }
0342             } else {
0343                 root.downloadAndPlay();
0344             }
0345         }
0346 
0347         MediaSizeHelper {
0348             id: mediaSizeHelper
0349             contentMaxWidth: root.contentMaxWidth
0350             mediaWidth: root.mediaInfo.width
0351             mediaHeight: root.mediaInfo.height
0352         }
0353     }
0354 
0355     function downloadAndPlay() {
0356         if (vid.downloaded) {
0357             playSavedFile();
0358         } else {
0359             playOnFinished = true;
0360             root.room.downloadFile(root.eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + root.eventId.replace(":", "_").replace("/", "_").replace("+", "_") + root.room.fileNameToDownload(root.eventId));
0361         }
0362     }
0363 
0364     function playSavedFile() {
0365         vid.stop();
0366         vid.play();
0367     }
0368 }