Page MenuHomePhorge

D377.1793269678.diff
No OneTemporary

Authored By
Unknown
Size
16 KB
Referenced Files
None
Subscribers
None

D377.1793269678.diff

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -251,7 +251,9 @@
media/PlayButton.qml
media/MediaViewerPage.qml
media/ZoomableImage.qml
+ media/ZoomableVideo.qml
media/ZoomableControl.qml
+ media/VideoWithControl.qml
ActionSettingsPage.qml
CreateRoomPage.qml
diff --git a/src/contents/ui/actions/MediaViewerActions.qml b/src/contents/ui/actions/MediaViewerActions.qml
--- a/src/contents/ui/actions/MediaViewerActions.qml
+++ b/src/contents/ui/actions/MediaViewerActions.qml
@@ -29,6 +29,10 @@
icon.name: 'zoom-out-symbolic'
text: l10n.get('media-viewer-zoom-out')
property var defaultShortcut: StandardKey.ZoomOut
+ },
+ Kirigami.Action {
+ objectName: 'playOrPause'
+ text: l10n.get('media-viewer-play-or-pause')
}
]
}
diff --git a/src/contents/ui/event-types/Image.qml b/src/contents/ui/event-types/Image.qml
--- a/src/contents/ui/event-types/Image.qml
+++ b/src/contents/ui/event-types/Image.qml
@@ -13,6 +13,7 @@
import '.' as Types
import '..' as Kazv
+import '../matrix-helpers.js' as MatrixHelpers
Simple {
id: upper
@@ -95,12 +96,15 @@
id: image
objectName: 'mainImage'
Layout.fillWidth: true
- property double imageWidth: upper.thumbnailWidth || upper.imageWidth || image.implicitWidth || 1
- property double imageHeight: upper.thumbnailHeight || upper.imageHeight || image.implicitHeight || 1
- property double privHeight: Math.min(upper.imageMaxHeight, imageHeight)
- property double privWidth: privHeight / imageHeight * imageWidth
- width: Math.max(privWidth, upper.imageMinWidth)
- height: Math.min(Math.max(width < privWidth ? privHeight / privWidth * width : privHeight, upper.imageMinHeight), upper.imageMaxHeight)
+ property var preferredSize: MatrixHelpers.getPreferredSize({
+ iWidth: Math.max(upper.thumbnailWidth || upper.imageWidth || image.implicitWidth || 1, Kirigami.Units.gridUnit),
+ iHeight: Math.max(upper.thumbnailHeight || upper.imageHeight || image.implicitHeight || 1, Kirigami.Units.gridUnit),
+ maxHeight: upper.imageMaxHeight,
+ minWidth: upper.imageMinWidth,
+ minHeight: upper.imageMinHeight,
+ })
+ width: preferredSize.width
+ height: preferredSize.height
source: fileHandler.kazvIOJob ? "" : fileHandler.cachedFile
horizontalAlignment: Image.AlignLeft
fillMode: Image.PreserveAspectCrop
diff --git a/src/contents/ui/event-types/Video.qml b/src/contents/ui/event-types/Video.qml
--- a/src/contents/ui/event-types/Video.qml
+++ b/src/contents/ui/event-types/Video.qml
@@ -15,6 +15,7 @@
import '.' as Types
import '..' as Kazv
import '../media' as M
+import '../matrix-helpers.js' as MatrixHelpers
Simple {
id: upper
@@ -23,10 +24,12 @@
property var body: event.content.body
property var videoInfo: event.content.info || {}
- property var videoResolution: video.metaData.value(MediaMetaData.Resolution)
+ property var videoResolution: mediaPlayer.metaData.value(MediaMetaData.Resolution)
property var videoWidth: videoResolution && videoResolution.width || (videoInfo.w || 1)
property var videoHeight: videoResolution && videoResolution.height || (videoInfo.h || 1)
- property var videoMaxHeight: compactMode ? Kirigami.Units.gridUnit * 5 : Infinity
+ property var videoMaxHeight: compactMode ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.enormous
+ property var videoMinHeight: compactMode ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.huge
+ property var videoMinWidth: compactMode ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.huge
property var thumbnailInfo: videoInfo.thumbnail_info || {}
property var thumbnailMxcUri: videoInfo.thumbnail_url
property var thumbnailUri: matrixSession.mxcUriToHttp(thumbnailMxcUri)
@@ -69,94 +72,68 @@
text: l10n.get('event-message-video-sent', { gender, body })
visible: !compactMode
}
- property var video: Video {
- id: video
- objectName: 'video'
+ property var mediaPlayer: MediaPlayer {
+ id: mediaPlayer
source: fileHandler.kazvIOJob ? "" : fileHandler.cachedFile
autoPlay: !hasAudio
loops: MediaPlayer.Infinite
- Layout.fillWidth: true
- property double videoWidth: upper.thumbnailWidth || upper.videoWidth || video.implicitWidth || 1
- property double videoHeight: upper.thumbnailHeight || upper.videoHeight || video.implicitHeight || 1
- property double privHeight: Math.min(videoMaxHeight, videoHeight)
- property double privWidth: privHeight / videoHeight * videoWidth
- Layout.preferredWidth: privWidth
- Layout.preferredHeight: width < privWidth ? privHeight / privWidth * width : privHeight
- fillMode: VideoOutput.PreserveAspectFit
-
- M.PlayButton {
- id: playButton
- z: 10
- anchors.centerIn: parent
- height: Math.min(video.height, video.width, Kirigami.Units.iconSizes.large)
- width: Math.min(video.height, video.width, Kirigami.Units.iconSizes.large)
- states: [
- State {
- name: 'shown'
- when: video.playbackState !== MediaPlayer.PlayingState
- PropertyChanges {
- target: playButton
- visible: true
- opacity: 1
- }
- },
- State {
- name: 'hidden'
- when: video.playbackState === MediaPlayer.PlayingState
- PropertyChanges {
- target: playButton
- opacity: 0
- visible: false
- }
- }
- ]
- transitions: [
- Transition {
- NumberAnimation {
- properties: "opacity,visible"
- duration: Kirigami.Units.shortDuration
- }
- }
- ]
- onClicked: video.playOrPause();
- }
-
- TapHandler {
- onSingleTapped: {
- video.playOrPause();
- }
+ videoOutput: video
+ }
+ property var videoBtn: AbstractButton {
+ id: videoBtn
+ Accessible.name: l10n.get('event-video-open-viewer-action')
+ Accessible.role: Accessible.Button
+ Accessible.onPressAction: pushMediaViewerPage(event)
+ hoverEnabled: true
+ focusPolicy: Qt.StrongFocus
+ topInset: 0
+ bottomInset: 0
+ leftInset: 0
+ rightInset: 0
+ padding: Kirigami.Units.smallSpacing
+ onClicked: pushMediaViewerPage(event)
+ background: Kazv.FocusRingBackground {
+ target: videoBtn
}
- Loader {
- anchors.fill: parent
- property Component thumbnail: Image {
- source: thumbnailUri
- fillMode: Image.PreserveAspectFit
- data: [bubble.mediaFileMenu]
- }
-
- property Component cover: Rectangle {
- color: Kirigami.Theme.negativeBackgroundColor //Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
- }
+ contentItem: Item {
+ implicitWidth: video.width
+ implicitHeight: video.height
+ M.VideoWithControl {
+ id: video
+ objectName: 'video'
+ player: mediaPlayer
+ property var preferredSize: MatrixHelpers.getPreferredSize({
+ iWidth: Math.max(upper.thumbnailWidth || upper.videoWidth || video.implicitWidth || 1, Kirigami.Units.gridUnit),
+ iHeight: Math.max(upper.thumbnailHeight || upper.videoHeight || video.implicitHeight || 1, Kirigami.Units.gridUnit),
+ maxHeight: upper.videoMaxHeight,
+ minWidth: upper.videoMinWidth,
+ minHeight: upper.videoMinHeight,
+ })
+ width: preferredSize.width
+ height: preferredSize.height
+ fillMode: VideoOutput.PreserveAspectCrop
+
+ Loader {
+ anchors.fill: parent
+ property Component thumbnail: Image {
+ source: thumbnailUri
+ fillMode: Image.PreserveAspectFit
+ data: [bubble.mediaFileMenu]
+ }
- sourceComponent: (video.playbackState !== MediaPlayer.StoppedState) ? undefined : (upper.hasThumbnail ? thumbnail : cover)
- }
+ property Component cover: Rectangle {
+ color: Kirigami.Theme.negativeBackgroundColor //Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
+ }
- function playOrPause() {
- console.log('playback state ==', video.playbackState);
- if (video.playbackState == MediaPlayer.PlayingState) {
- video.pause();
- } else {
- if (video.playbackState == MediaPlayer.StoppedState) {
- video.seek(0);
+ sourceComponent: (mediaPlayer.playbackState !== MediaPlayer.StoppedState) ? undefined : (upper.hasThumbnail ? thumbnail : cover)
}
- video.play();
}
}
}
data: [
label,
- video,
+ videoBtn,
bubble.progressBar
]
}
diff --git a/src/contents/ui/media/MediaViewerPage.qml b/src/contents/ui/media/MediaViewerPage.qml
--- a/src/contents/ui/media/MediaViewerPage.qml
+++ b/src/contents/ui/media/MediaViewerPage.qml
@@ -7,6 +7,7 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
+import QtMultimedia
import moe.kazv.mxc.kazv as MK
import org.kde.kirigami as Kirigami
@@ -17,6 +18,7 @@
required property var event
property var jobManager: kazvIOManager
property var session: matrixSession
+ property bool isVideo: event.content?.msgtype === 'm.video'
title: l10n.get('media-viewer-page-title')
padding: 0
@@ -33,6 +35,14 @@
objectName: 'zoomOut'
onTriggered: loader.item?.zoomOut()
},
+ Kazv.KazvAction {
+ objectName: 'playOrPause'
+ visible: mediaViewerPage.isVideo
+ property bool isPlaying: loader.item?.mediaPlayer && loader.item?.mediaPlayer.playbackState === MediaPlayer.PlayingState
+ icon.name: isPlaying ? 'media-playback-pause' : 'media-playback-start'
+ text: isPlaying ? l10n.get('media-viewer-pause') : l10n.get('media-viewer-play')
+ onTriggered: loader.item?.itemForScaling.playOrPause()
+ }
]
Kazv.FileHandler {
@@ -49,9 +59,18 @@
imageAlt: event.content?.body
}
+ property Component videoComp: ZoomableVideo {
+ videoUrl: fileHandler.kazvIOJob ? '' : fileHandler.cachedFile
+ videoAlt: event.content?.body
+ }
+
Loader {
id: loader
anchors.fill: parent
- sourceComponent: fileHandler.kazvIOJob ? undefined : mediaViewerPage.imageComp
+ sourceComponent: fileHandler.kazvIOJob ? undefined : (
+ mediaViewerPage.isVideo
+ ? mediaViewerPage.videoComp
+ : mediaViewerPage.imageComp
+ )
}
}
diff --git a/src/contents/ui/media/VideoWithControl.qml b/src/contents/ui/media/VideoWithControl.qml
new file mode 100644
--- /dev/null
+++ b/src/contents/ui/media/VideoWithControl.qml
@@ -0,0 +1,29 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2020-2026 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtMultimedia
+import moe.kazv.mxc.kazv as MK
+import org.kde.kirigami as Kirigami
+
+VideoOutput {
+ id: video
+ required property var player
+
+ function playOrPause() {
+ console.log('playback state ==', video.player.playbackState);
+ if (video.player.playbackState == MediaPlayer.PlayingState) {
+ video.player.pause();
+ } else {
+ if (video.player.playbackState == MediaPlayer.StoppedState) {
+ video.player.position = 0;
+ }
+ video.player.play();
+ }
+ }
+}
diff --git a/src/contents/ui/media/ZoomableVideo.qml b/src/contents/ui/media/ZoomableVideo.qml
new file mode 100644
--- /dev/null
+++ b/src/contents/ui/media/ZoomableVideo.qml
@@ -0,0 +1,102 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2020-2026 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtMultimedia
+import moe.kazv.mxc.kazv as MK
+
+import org.kde.kirigami as Kirigami
+import '..' as Kazv
+
+ColumnLayout {
+ id: cont
+ required property string videoUrl
+ required property string videoAlt
+ readonly property var itemForScaling: video
+ readonly property var mediaPlayer: mediaPlayer
+
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ PlayButton {
+ id: playButton
+ z: 10
+ anchors.centerIn: parent
+ height: Kirigami.Units.iconSizes.large
+ width: Kirigami.Units.iconSizes.large
+ states: [
+ State {
+ name: 'shown'
+ when: video.player.playbackState !== MediaPlayer.PlayingState
+ PropertyChanges {
+ target: playButton
+ visible: true
+ opacity: 1
+ }
+ },
+ State {
+ name: 'hidden'
+ when: video.player.playbackState === MediaPlayer.PlayingState
+ PropertyChanges {
+ target: playButton
+ opacity: 0
+ visible: false
+ }
+ }
+ ]
+ transitions: [
+ Transition {
+ NumberAnimation {
+ properties: "opacity,visible"
+ duration: Kirigami.Units.shortDuration
+ }
+ }
+ ]
+ onClicked: video.playOrPause();
+ }
+
+ ZoomableControl {
+ id: zoomableCtrl
+ anchors.fill: parent
+ property var mediaPlayer: MediaPlayer {
+ id: mediaPlayer
+ source: cont.videoUrl
+ autoPlay: true
+ loops: MediaPlayer.Infinite
+ videoOutput: video
+ audioOutput: AudioOutput {}
+ }
+
+ VideoWithControl {
+ id: video
+ player: mediaPlayer
+ width: implicitWidth
+ height: implicitHeight
+ anchors.centerIn: parent
+ Accessible.name: cont.videoAlt
+ Accessible.role: Accessible.Canvas
+ }
+
+ property var tapHandler: TapHandler {
+ onTapped: video.playOrPause()
+ }
+ }
+ }
+
+ function toOriginalSize() {
+ zoomableCtrl.toOriginalSize();
+ }
+
+ function zoomIn() {
+ zoomableCtrl.zoomIn();
+ }
+
+ function zoomOut() {
+ zoomableCtrl.zoomOut();
+ }
+}
diff --git a/src/js/matrix-helpers.js b/src/js/matrix-helpers.js
--- a/src/js/matrix-helpers.js
+++ b/src/js/matrix-helpers.js
@@ -125,3 +125,31 @@
}
const toolTipTimeout = 5000;
+
+function getPreferredSize({
+ // original (implicit) size
+ iWidth,
+ iHeight,
+ // maximum size
+ maxWidth,
+ maxHeight,
+ // minimum size
+ minWidth,
+ minHeight,
+})
+{
+ // first, bound the height by its maximum
+ const privHeight = Math.min(maxHeight, iHeight);
+ // the width is scaled to proportion
+ const privWidth = privHeight / iHeight * iWidth;
+ // then, bound the width by the minimum
+ const width = Math.max(privWidth, minWidth);
+ const height = Math.min(
+ Math.max(
+ // if width is clamped to the minimum, scale height accordingly
+ width > privWidth ? privHeight / privWidth * width : privHeight,
+ // then clamp the height
+ minHeight
+ ), maxHeight);
+ return { width, height };
+}
diff --git a/src/l10n/cmn-Hans/100-ui.ftl b/src/l10n/cmn-Hans/100-ui.ftl
--- a/src/l10n/cmn-Hans/100-ui.ftl
+++ b/src/l10n/cmn-Hans/100-ui.ftl
@@ -240,6 +240,7 @@
event-message-file-sent = 发送了文件「{ $body }」。
event-message-video-sent = 发送了视频「{ $body }」。
event-summary-video-sent = 发送了视频「{ $body }」。
+event-video-open-viewer-action = 放大
event-message-audio-sent = 发送了音频「{ $body }」。
event-message-audio-play-audio = 播放音频
event-sending = 发送中...
@@ -500,3 +501,6 @@
media-viewer-page-title = 媒体查看器
media-viewer-zoomable = 媒体大小控制
media-viewer-zoomable-desc = 使用放大和缩小操作以改变大小。使用方向键以滚动。
+media-viewer-play-or-pause = 播放或暂停
+media-viewer-play = 播放
+media-viewer-pause = 暂停
diff --git a/src/l10n/en/100-ui.ftl b/src/l10n/en/100-ui.ftl
--- a/src/l10n/en/100-ui.ftl
+++ b/src/l10n/en/100-ui.ftl
@@ -259,6 +259,7 @@
event-message-file-sent = sent a file "{ $body }".
event-message-video-sent = sent a video "{ $body }".
event-summary-video-sent = sent a video "{ $body }".
+event-video-open-viewer-action = Enlarge
event-message-audio-sent = sent an audio "{ $body }".
event-message-audio-play-audio = Play audio
event-sending = Sending...
@@ -522,3 +523,6 @@
media-viewer-page-title = Media Viewer
media-viewer-zoomable = Media size control
media-viewer-zoomable-desc = Use zoom in and out actions to change size. Use arrow keys to scroll.
+media-viewer-play-or-pause = Play or pause
+media-viewer-play = Play
+media-viewer-pause = Pause

File Metadata

Mime Type
text/plain
Expires
Thu, Oct 29, 3:27 AM (22 h, 51 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1799333
Default Alt Text
D377.1793269678.diff (16 KB)

Event Timeline