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 }