Files
QtPotato/src/qml/PlayerPage.qml
T

253 lines
8.3 KiB
QML

import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Dialogs
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.ki18n
Kirigami.Page {
id: page
property url initialUrl
readonly property alias mediaTitle: mpv.mediaTitle
readonly property alias translator: translator
signal aboutRequested()
signal settingsRequested()
signal fullScreenToggled()
title: KI18n.i18nc("@title", "Player")
padding: 0
actions: [
Kirigami.Action {
icon.name: "document-open"
text: KI18n.i18nc("@action", "Open File…")
shortcut: StandardKey.Open
onTriggered: fileDialog.open()
},
Kirigami.Action {
icon.name: "view-fullscreen"
text: KI18n.i18nc("@action", "Full Screen")
displayHint: Kirigami.DisplayHint.IconOnly
shortcut: "F"
onTriggered: page.fullScreenToggled()
},
Kirigami.Action {
icon.name: "configure"
text: KI18n.i18nc("@action", "Settings")
displayHint: Kirigami.DisplayHint.AlwaysHide
shortcut: StandardKey.Preferences
onTriggered: page.settingsRequested()
},
Kirigami.Action {
icon.name: "help-about"
text: KI18n.i18nc("@action", "About QtPotato")
displayHint: Kirigami.DisplayHint.AlwaysHide
onTriggered: page.aboutRequested()
}
]
Component.onCompleted: {
if (page.initialUrl.toString().length > 0) {
mpv.loadFile(page.initialUrl)
}
}
function formatTime(totalSeconds: real): string {
const s = Math.max(0, Math.floor(totalSeconds))
const h = Math.floor(s / 3600)
const m = Math.floor((s % 3600) / 60)
const sec = String(s % 60).padStart(2, "0")
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${sec}` : `${m}:${sec}`
}
Shortcut {
sequence: "Space"
onActivated: mpv.togglePause()
}
Shortcut {
sequence: "Left"
onActivated: mpv.seek(mpv.position - 5)
}
Shortcut {
sequence: "Right"
onActivated: mpv.seek(mpv.position + 5)
}
// Same keys as mpv: z shows subtitles earlier, x later.
Shortcut {
sequence: "Z"
enabled: mpv.fileLoaded
onActivated: page.nudgeSubtitleDelay(-0.1)
}
Shortcut {
sequence: "X"
enabled: mpv.fileLoaded
onActivated: page.nudgeSubtitleDelay(0.1)
}
function nudgeSubtitleDelay(seconds: real) {
const delay = mpv.subtitleDelay + seconds
mpv.subtitleDelay = delay
mpv.showText(KI18n.i18nc("@info:osd", "Subtitle delay: %1 ms", Math.round(delay * 1000)))
}
TranslationService {
id: translator
onTranslated: (subtitleUrl, fromCache) => {
mpv.showSecondarySubtitleFile(subtitleUrl,
KI18n.i18nc("@item:inlistbox subtitle track, %1 is a language", "%1 (translated)", QtPotatoSettings.targetLanguage))
mpv.showText(fromCache
? KI18n.i18nc("@info:osd", "Loaded the saved translation")
: KI18n.i18nc("@info:osd", "Translation ready"))
}
}
Connections {
target: mpv
// A translation belongs to the file it was started for.
function onCurrentUrlChanged() {
translator.cancel()
}
}
function isSubtitleFile(url: url): bool {
return /\.(srt|ass|ssa|vtt|sub|sup)$/i.test(url.toString())
}
FileDialog {
id: fileDialog
title: KI18n.i18nc("@title:window", "Open Video")
nameFilters: [
KI18n.i18nc("@item:inlistbox file filter", "Video files (*.mkv *.mp4 *.webm *.avi *.mov *.m4v *.ts *.flv *.wmv)"),
KI18n.i18nc("@item:inlistbox file filter", "All files (*)")
]
onAccepted: mpv.loadFile(selectedFile)
}
ColumnLayout {
anchors.fill: parent
spacing: 0
MpvItem {
id: mpv
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle {
anchors.fill: parent
color: "black"
visible: !mpv.fileLoaded
z: -1
}
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - Kirigami.Units.gridUnit * 4
visible: !mpv.fileLoaded
icon.name: "video-symbolic"
text: KI18n.i18nc("@info:placeholder", "No video open")
explanation: KI18n.i18nc("@info:placeholder", "Open a file or drop one here")
helpfulAction: page.actions[0]
}
MouseArea {
anchors.fill: parent
onClicked: mpv.togglePause()
onDoubleClicked: page.fullScreenToggled()
}
DropArea {
anchors.fill: parent
onDropped: drop => {
if (!drop.hasUrls) {
return
}
const url = drop.urls[0]
if (mpv.fileLoaded && page.isSubtitleFile(url)) {
mpv.addSubtitleFile(url)
} else {
mpv.loadFile(url)
}
}
}
}
QQC2.ToolBar {
Layout.fillWidth: true
position: QQC2.ToolBar.Footer
RowLayout {
anchors.fill: parent
spacing: Kirigami.Units.smallSpacing
QQC2.ToolButton {
enabled: mpv.fileLoaded
focusPolicy: Qt.NoFocus
icon.name: mpv.paused || !mpv.fileLoaded ? "media-playback-start" : "media-playback-pause"
text: mpv.paused || !mpv.fileLoaded ? KI18n.i18nc("@action:button", "Play") : KI18n.i18nc("@action:button", "Pause")
display: QQC2.AbstractButton.IconOnly
onClicked: mpv.togglePause()
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.Label {
text: page.formatTime(seekBar.pressed ? seekBar.value : mpv.position)
font.features: { "tnum": 1 }
}
QQC2.Slider {
id: seekBar
Layout.fillWidth: true
enabled: mpv.fileLoaded && mpv.duration > 0
focusPolicy: Qt.NoFocus
from: 0
to: Math.max(mpv.duration, 1)
onMoved: mpv.seek(value)
// Follow playback, but not while the user is dragging the handle.
Binding on value {
value: mpv.position
when: !seekBar.pressed
restoreMode: Binding.RestoreNone
}
}
QQC2.Label {
text: page.formatTime(mpv.duration)
font.features: { "tnum": 1 }
}
QQC2.ToolButton {
id: subtitleButton
enabled: mpv.fileLoaded
focusPolicy: Qt.NoFocus
checkable: true
checked: subtitlePopup.visible
icon.name: "media-view-subtitles-symbolic"
text: KI18n.i18nc("@action:button", "Subtitles")
display: QQC2.AbstractButton.IconOnly
onClicked: subtitlePopup.visible ? subtitlePopup.close() : subtitlePopup.open()
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered && !subtitlePopup.visible
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
SubtitlePopup {
id: subtitlePopup
objectName: "subtitlePopup"
mpv: mpv
translator: translator
onSettingsRequested: page.settingsRequested()
x: subtitleButton.width - width
y: -height - Kirigami.Units.smallSpacing
}
}
}
}
}
}