From 15395885fb6daac10ea9ef2ee126b1f0742a2029 Mon Sep 17 00:00:00 2001 From: Lukas <38840142+lukasstorck@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:25:33 +0100 Subject: [PATCH] add skip to previous/next video buttons in media player (#8285) * add skip to previous/next video buttons in media player * remove skip buttons from playlist UI * fix skip button size * hide skip buttons in video player when watching a playlist * update video player button order to current YouTube layout (play, previous, next) * fix long tooltip text on skip buttons in video player * Add translations --------- Co-authored-by: efb4f5ff-1298-471a-8973-3d47447115dc <73130443+efb4f5ff-1298-471a-8973-3d47447115dc@users.noreply.github.com> --- src/constants.js | 4 +- .../ft-shaka-video-player.css | 4 ++ .../ft-shaka-video-player.js | 64 +++++++++++++++-- .../player-components/SkipButton.js | 69 +++++++++++++++++++ .../watch-video-playlist.vue | 22 ------ src/renderer/views/Watch/Watch.vue | 1 + static/locales/af.yaml | 4 +- static/locales/am.yaml | 4 +- static/locales/ar.yaml | 4 +- static/locales/awa.yaml | 4 +- static/locales/be.yaml | 4 +- static/locales/bg.yaml | 4 +- static/locales/br.yaml | 4 +- static/locales/ca.yaml | 4 +- static/locales/ckb.yaml | 4 +- static/locales/cs.yaml | 4 +- static/locales/cy.yaml | 4 +- static/locales/da.yaml | 4 +- static/locales/de-DE.yaml | 4 +- static/locales/el.yaml | 4 +- static/locales/en-GB.yaml | 4 +- static/locales/en-US.yaml | 4 +- static/locales/es-MX.yaml | 4 +- static/locales/es.yaml | 4 +- static/locales/et.yaml | 4 +- static/locales/eu.yaml | 4 +- static/locales/fa.yaml | 4 +- static/locales/fi.yaml | 4 +- static/locales/fr-FR.yaml | 4 +- static/locales/gl.yaml | 4 +- static/locales/he.yaml | 4 +- static/locales/hr.yaml | 4 +- static/locales/hu.yaml | 4 +- static/locales/id.yaml | 4 +- static/locales/is.yaml | 4 +- static/locales/it.yaml | 4 +- static/locales/ja.yaml | 4 +- static/locales/ko.yaml | 4 +- static/locales/lt.yaml | 4 +- static/locales/lv.yaml | 4 +- static/locales/mr.yaml | 4 +- static/locales/my.yaml | 4 +- static/locales/nb-NO.yaml | 4 +- static/locales/nl.yaml | 4 +- static/locales/nn.yaml | 4 +- static/locales/pl.yaml | 4 +- static/locales/pt-BR.yaml | 4 +- static/locales/pt-PT.yaml | 4 +- static/locales/pt.yaml | 4 +- static/locales/ro.yaml | 4 +- static/locales/ru.yaml | 4 +- static/locales/sk.yaml | 4 +- static/locales/sl.yaml | 4 +- static/locales/sm.yaml | 4 +- static/locales/sq.yaml | 4 +- static/locales/sr.yaml | 4 +- static/locales/sv.yaml | 4 +- static/locales/ta.yaml | 4 +- static/locales/tr.yaml | 4 +- static/locales/uk.yaml | 4 +- static/locales/vi.yaml | 4 +- static/locales/vls.yaml | 4 +- static/locales/zh-CN.yaml | 4 +- static/locales/zh-TW.yaml | 4 +- 64 files changed, 250 insertions(+), 146 deletions(-) create mode 100644 src/renderer/components/ft-shaka-video-player/player-components/SkipButton.js diff --git a/src/constants.js b/src/constants.js index 68dfbc504..f04e554b3 100644 --- a/src/constants.js +++ b/src/constants.js @@ -223,7 +223,9 @@ const PlayerIcons = { PLAY_CIRCLE_FILLED: 'm426-330 195-125q14-9 14-25t-14-25L426-630q-15-10-30.5-1.5T380-605v250q0 18 15.5 26.5T426-330Zm54 250q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z', RECORD_VOICE_OVER_FILLED: 'M920-600q0 69-24.5 131.5T829-355q-12 14-30 15t-32-13q-13-13-12-31t12-33q30-38 46.5-85t16.5-98q0-51-16.5-97T767-781q-12-15-12.5-33t12.5-32q13-14 31.5-13.5T829-845q42 51 66.5 113.5T920-600Zm-182 0q0 32-10 61.5T700-484q-11 15-29.5 15.5T638-482q-13-13-13.5-31.5T633-549q6-11 9.5-24t3.5-27q0-14-3.5-27t-9.5-25q-9-17-8.5-35t13.5-31q14-14 32.5-13.5T700-716q18 25 28 54.5t10 61.5ZM360-440q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47ZM40-200v-32q0-33 17-62t47-44q51-26 115-44t141-18q77 0 141 18t115 44q30 15 47 44t17 62v32q0 33-23.5 56.5T600-120H120q-33 0-56.5-23.5T40-200Z', TUNE_FILLED: 'M480-120q-17 0-28.5-11.5T440-160v-160q0-17 11.5-28.5T480-360q17 0 28.5 11.5T520-320v40h280q17 0 28.5 11.5T840-240q0 17-11.5 28.5T800-200H520v40q0 17-11.5 28.5T480-120Zm-320-80q-17 0-28.5-11.5T120-240q0-17 11.5-28.5T160-280h160q17 0 28.5 11.5T360-240q0 17-11.5 28.5T320-200H160Zm160-160q-17 0-28.5-11.5T280-400v-40H160q-17 0-28.5-11.5T120-480q0-17 11.5-28.5T160-520h120v-40q0-17 11.5-28.5T320-600q17 0 28.5 11.5T360-560v160q0 17-11.5 28.5T320-360Zm160-80q-17 0-28.5-11.5T440-480q0-17 11.5-28.5T480-520h320q17 0 28.5 11.5T840-480q0 17-11.5 28.5T800-440H480Zm160-160q-17 0-28.5-11.5T600-640v-160q0-17 11.5-28.5T640-840q17 0 28.5 11.5T680-800v40h120q17 0 28.5 11.5T840-720q0 17-11.5 28.5T800-680H680v40q0 17-11.5 28.5T640-600Zm-480-80q-17 0-28.5-11.5T120-720q0-17 11.5-28.5T160-760h320q17 0 28.5 11.5T520-720q0 17-11.5 28.5T480-680H160Z', - RECTANGLE_DEFAULT: 'M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm0-80h640v-480H160v480Zm0 0v-480 480Z' + RECTANGLE_DEFAULT: 'M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm0-80h640v-480H160v480Zm0 0v-480 480Z', + SKIP_NEXT_FILLED: 'M660-280v-400q0-17 11.5-28.5T700-720q17 0 28.5 11.5T740-680v400q0 17-11.5 28.5T700-240q-17 0-28.5-11.5T660-280Zm-440-35v-330q0-18 12-29t28-11q5 0 11 1t11 5l248 166q9 6 13.5 14.5T548-480q0 10-4.5 18.5T530-447L282-281q-5 4-11 5t-11 1q-16 0-28-11t-12-29Z', + SKIP_PREVIOUS_FILLED: 'M220-280v-400q0-17 11.5-28.5T260-720q17 0 28.5 11.5T300-680v400q0 17-11.5 28.5T260-240q-17 0-28.5-11.5T220-280Zm458-1L430-447q-9-6-13.5-14.5T412-480q0-10 4.5-18.5T430-513l248-166q5-4 11-5t11-1q16 0 28 11t12 29v330q0 18-12 29t-28 11q-5 0-11-1t-11-5Z' } // Utils diff --git a/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.css b/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.css index 59289b738..826559e0d 100644 --- a/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.css +++ b/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.css @@ -201,6 +201,10 @@ padding: min(calc(15% / 2), 55px); } +:deep(.shaka-controls-button-panel>.ft-shaka-skip-button .material-svg-icon) { + font-size: 32px; +} + .ftVideoPlayer:fullscreen :deep(.theatre-button), .ftVideoPlayer:fullscreen :deep(.full-window-button), .fullWindow :deep(.theatre-button) { diff --git a/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js b/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js index e4b52d37b..4d55fce37 100644 --- a/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js +++ b/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js @@ -11,6 +11,7 @@ import { ScreenshotButton } from './player-components/ScreenshotButton' import { StatsButton } from './player-components/StatsButton' import { TheatreModeButton } from './player-components/TheatreModeButton' import { AutoplayToggle } from './player-components/AutoplayToggle' +import { SkipButton } from './player-components/SkipButton' import { deduplicateAudioTracks, findMostSimilarAudioBandwidth, @@ -126,6 +127,10 @@ export default defineComponent({ type: Boolean, default: false }, + watchingPlaylist: { + type: Boolean, + default: false + }, vrProjection: { type: String, default: null @@ -786,15 +791,23 @@ export default defineComponent({ }) const uiConfig = computed(() => { + const controlPanelElements = [ + 'play_pause', + 'mute', + 'volume', + 'time_and_duration', + 'spacer' + ] + const controlPanelElementsWithSkipButtons = [ + ...controlPanelElements.slice(0, 1), + 'ft_skip_previous', + 'ft_skip_next', + ...controlPanelElements.slice(1) + ] + /** @type {shaka.extern.UIConfiguration} */ const uiConfig = { - controlPanelElements: [ - 'play_pause', - 'mute', - 'volume', - 'time_and_duration', - 'spacer' - ], + controlPanelElements: props.watchingPlaylist ? controlPanelElementsWithSkipButtons : controlPanelElements, overflowMenuButtons: [], // only set this to label when we actually have labels, so that the warning doesn't show up @@ -1838,6 +1851,36 @@ export default defineComponent({ shakaOverflowMenu.registerElement('ft_screenshot', new ScreenshotButtonFactory()) } + function registerSkipButtons() { + // skip to next video button + events.addEventListener('nextVideo', () => { + emit('skip-to-next') + }) + + class SkipNextButtonFactory { + create(rootElement, controls) { + return new SkipButton(events, rootElement, controls, 'next') + } + } + + shakaControls.registerElement('ft_skip_next', new SkipNextButtonFactory()) + shakaOverflowMenu.registerElement('ft_skip_next', new SkipNextButtonFactory()) + + // skip to previous video button + events.addEventListener('previousVideo', () => { + emit('skip-to-prev') + }) + + class SkipPreviousButtonFactory { + create(rootElement, controls) { + return new SkipButton(events, rootElement, controls, 'previous') + } + } + + shakaControls.registerElement('ft_skip_previous', new SkipPreviousButtonFactory()) + shakaOverflowMenu.registerElement('ft_skip_previous', new SkipPreviousButtonFactory()) + } + /** * As shaka-player doesn't let you unregister custom control factories, * overwrite them with `null` instead so the referenced objects @@ -1863,6 +1906,12 @@ export default defineComponent({ shakaControls.registerElement('ft_screenshot', null) shakaOverflowMenu.registerElement('ft_screenshot', null) + + shakaControls.registerElement('ft_next_previous', null) + shakaOverflowMenu.registerElement('ft_next_previous', null) + + shakaControls.registerElement('ft_skip_previous', null) + shakaOverflowMenu.registerElement('ft_skip_previous', null) } // #endregion custom player controls @@ -2577,6 +2626,7 @@ export default defineComponent({ registerFullWindowButton() registerLegacyQualitySelection() registerStatsButton() + registerSkipButtons() if (ui.isMobile()) { onlyUseOverFlowMenu.value = true diff --git a/src/renderer/components/ft-shaka-video-player/player-components/SkipButton.js b/src/renderer/components/ft-shaka-video-player/player-components/SkipButton.js new file mode 100644 index 000000000..e6cdcf914 --- /dev/null +++ b/src/renderer/components/ft-shaka-video-player/player-components/SkipButton.js @@ -0,0 +1,69 @@ +import shaka from 'shaka-player' + +import i18n from '../../../i18n/index' +import { KeyboardShortcuts, PlayerIcons } from '../../../../constants' +import { addKeyboardShortcutToActionTitle } from '../../../helpers/utils' + +export class SkipButton extends shaka.ui.Element { + /** + * @param {EventTarget} events + * @param {HTMLElement} parent + * @param {shaka.ui.Controls} controls + * @param {'next'|'previous'} type + */ + constructor(events, parent, controls, type = 'next') { + super(parent, controls) + + this.type_ = type + + /** @private */ + this.button_ = document.createElement('button') + this.button_.classList.add(`skip-${type}-button`, 'shaka-tooltip', 'ft-shaka-skip-button') + + /** @private */ + const icon = type === 'next' ? PlayerIcons.SKIP_NEXT_FILLED : PlayerIcons.SKIP_PREVIOUS_FILLED + this.icon_ = new shaka.ui.MaterialSVGIcon(this.button_, icon) + + const label = document.createElement('label') + label.classList.add( + 'shaka-overflow-button-label', + 'shaka-overflow-menu-only', + 'shaka-simple-overflow-button-label-inline' + ) + + /** @private */ + this.nameSpan_ = document.createElement('span') + label.appendChild(this.nameSpan_) + this.button_.appendChild(label) + this.parent.appendChild(this.button_) + + // listener + + this.eventManager.listen(this.button_, 'click', () => { + const eventName = type === 'next' ? 'nextVideo' : 'previousVideo' + events.dispatchEvent(new CustomEvent(eventName)) + }) + + this.eventManager.listen(events, 'localeChanged', () => { + this.updateLocalisedStrings_() + }) + + this.updateLocalisedStrings_() + } + + /** @private */ + updateLocalisedStrings_() { + const labelText = + this.type_ === 'next' + ? i18n.global.t('Video.Next') + : i18n.global.t('Video.Previous') + + const shortcut = + this.type_ === 'next' + ? KeyboardShortcuts.VIDEO_PLAYER.PLAYBACK.SKIP_TO_NEXT + : KeyboardShortcuts.VIDEO_PLAYER.PLAYBACK.SKIP_TO_PREV + + const label = addKeyboardShortcutToActionTitle(labelText, shortcut) + this.nameSpan_.textContent = this.button_.ariaLabel = label + } +} diff --git a/src/renderer/components/watch-video-playlist/watch-video-playlist.vue b/src/renderer/components/watch-video-playlist/watch-video-playlist.vue index e88cf9c78..d7cb9b494 100644 --- a/src/renderer/components/watch-video-playlist/watch-video-playlist.vue +++ b/src/renderer/components/watch-video-playlist/watch-video-playlist.vue @@ -120,28 +120,6 @@ :icon="['fas', 'exchange-alt']" /> - -