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 } } } } } }