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 }