diff --git a/client/src/assets/player/peertube-player-local-storage.ts b/client/src/assets/player/peertube-player-local-storage.ts index 64040abf1..f9a2a85fe 100644 --- a/client/src/assets/player/peertube-player-local-storage.ts +++ b/client/src/assets/player/peertube-player-local-storage.ts @@ -55,6 +55,8 @@ function getAverageBandwidthInStore () { return undefined } +// --------------------------------------------------------------------------- + function saveLastSubtitle (language: string) { return setLocalStorage('last-subtitle', language) } @@ -63,6 +65,16 @@ function getStoredLastSubtitle () { return getLocalStorage('last-subtitle') } +function savePreferredSubtitle (language: string) { + return setLocalStorage('preferred-subtitle', language) +} + +function getStoredPreferredSubtitle () { + return getLocalStorage('preferred-subtitle') +} + +// --------------------------------------------------------------------------- + function saveVideoWatchHistory (videoUUID: string, duration: number) { return setLocalStorage(`video-watch-history`, JSON.stringify({ ...getStoredVideoWatchHistory(), @@ -128,7 +140,9 @@ export { getStoredLastSubtitle, saveVideoWatchHistory, getStoredVideoWatchHistory, - cleanupVideoWatch + cleanupVideoWatch, + savePreferredSubtitle, + getStoredPreferredSubtitle } // --------------------------------------------------------------------------- diff --git a/client/src/assets/player/peertube-player.ts b/client/src/assets/player/peertube-player.ts index 6e1c531cc..fe98a5eb4 100644 --- a/client/src/assets/player/peertube-player.ts +++ b/client/src/assets/player/peertube-player.ts @@ -6,6 +6,7 @@ import './shared/stats/stats-plugin' import './shared/bezels/bezels-plugin' import './shared/peertube/peertube-plugin' import './shared/resolutions/peertube-resolutions-plugin' +import './shared/control-bar/caption-toggle-button' import './shared/control-bar/storyboard-plugin' import './shared/control-bar/chapters-plugin' import './shared/control-bar/time-tooltip' diff --git a/client/src/assets/player/shared/control-bar/caption-toggle-button.ts b/client/src/assets/player/shared/control-bar/caption-toggle-button.ts new file mode 100644 index 000000000..0dbe5c610 --- /dev/null +++ b/client/src/assets/player/shared/control-bar/caption-toggle-button.ts @@ -0,0 +1,59 @@ +import videojs from 'video.js' +import { TheaterButtonOptions } from '../../types' +import { getStoredPreferredSubtitle } from '../../peertube-player-local-storage' + +const Button = videojs.getComponent('Button') +class CaptionToggleButton extends Button { + + constructor (player: videojs.Player, options: TheaterButtonOptions & videojs.ComponentOptions) { + super(player, options) + + player.on('texttrackchange', () => this.update()) + } + + buildCSSClass () { + // Inherits vjs-captions-button for the icon + return `vjs-caption-toggle-control vjs-captions-button ${super.buildCSSClass()}` + } + + handleClick (event: any) { + super.handleClick(event) + + const toEnable = this.getShowing() + ? undefined + : this.getCaptionToEnable()?.id + + for (const track of this.player_.textTracks().tracks_) { + if (toEnable && track.id === toEnable) track.mode = 'showing' + else track.mode = 'disabled' + } + } + + private update () { + if (this.getShowing()) { + this.controlText('Disable subtitles') + this.addClass('enabled') + return + } + + this.controlText(this.player_.localize('Enable {1} subtitle', [ this.getCaptionToEnable()?.label ])) + this.removeClass('enabled') + } + + private getShowing () { + return this.listCaptions().find(t => t.mode === 'showing') + } + + private getCaptionToEnable () { + const captionToEnable = getStoredPreferredSubtitle() || this.listCaptions()[0]?.id + const captions = this.listCaptions() + + return captions.find(t => t.id === captionToEnable) || captions[0] + } + + private listCaptions () { + return this.player_.textTracks().tracks_.filter(t => t.kind === 'captions') + } +} + +videojs.registerComponent('CaptionToggleButton', CaptionToggleButton) diff --git a/client/src/assets/player/shared/control-bar/index.ts b/client/src/assets/player/shared/control-bar/index.ts index 091e876e2..d6d58a538 100644 --- a/client/src/assets/player/shared/control-bar/index.ts +++ b/client/src/assets/player/shared/control-bar/index.ts @@ -1,3 +1,4 @@ +export * from './caption-toggle-button' export * from './chapters-plugin' export * from './next-previous-video-button' export * from './p2p-info-button' diff --git a/client/src/assets/player/shared/peertube/peertube-plugin.ts b/client/src/assets/player/shared/peertube/peertube-plugin.ts index 80b767024..b8e918eac 100644 --- a/client/src/assets/player/shared/peertube/peertube-plugin.ts +++ b/client/src/assets/player/shared/peertube/peertube-plugin.ts @@ -10,6 +10,7 @@ import { getStoredVolume, saveLastSubtitle, saveMuteInStore, + savePreferredSubtitle, saveVideoWatchHistory, saveVolumeInStore } from '../../peertube-player-local-storage' @@ -113,6 +114,7 @@ class PeerTubePlugin extends Plugin { this.currentSubtitle = showing.language saveLastSubtitle(showing.language) + savePreferredSubtitle(showing.language) }) this.player.on('video-change', () => { @@ -382,6 +384,8 @@ class PeerTubePlugin extends Plugin { this.player.tech(true).clearTracks('text') + this.player.removeClass('vjs-has-captions') + for (const caption of this.options.videoCaptions()) { this.player.addRemoteTextTrack({ kind: 'captions', @@ -391,6 +395,8 @@ class PeerTubePlugin extends Plugin { src: caption.src, default: this.currentSubtitle === caption.language }, true) + + this.player.addClass('vjs-has-captions') } this.player.trigger('captions-changed') diff --git a/client/src/assets/player/shared/player-options-builder/control-bar-options-builder.ts b/client/src/assets/player/shared/player-options-builder/control-bar-options-builder.ts index b6e55a369..983ac4259 100644 --- a/client/src/assets/player/shared/player-options-builder/control-bar-options-builder.ts +++ b/client/src/assets/player/shared/player-options-builder/control-bar-options-builder.ts @@ -34,9 +34,12 @@ export class ControlBarOptionsBuilder { ...this.getProgressControl(), p2PInfoButton: {}, + muteToggle: {}, volumeControl: {}, + captionToggleButton: {}, + ...this.getSettingsButton(), ...this.getPeerTubeLinkButton(), diff --git a/client/src/sass/player/control-bar.scss b/client/src/sass/player/control-bar.scss index b4f3128ca..d5678c012 100644 --- a/client/src/sass/player/control-bar.scss +++ b/client/src/sass/player/control-bar.scss @@ -40,6 +40,7 @@ .vjs-fullscreen-control, .vjs-peertube-link, .vjs-theater-control, + .vjs-caption-toggle-control, .vjs-settings { color: pvar(--embedForegroundColor) !important; @@ -297,7 +298,7 @@ .vjs-volume-control { @include margin(0, 5px, 0, 5px); - width: $control-bar-icon-size; + width: 28px; display: flex; align-items: center; } @@ -369,7 +370,7 @@ @include disable-outline; cursor: pointer; - width: $control-bar-button-width; + width: $control-bar-button-width - 5px; .vjs-icon-placeholder { display: inline-block; @@ -418,6 +419,31 @@ } } + .vjs-caption-toggle-control { + // Redefined if the player parent has captions class + display: none; + width: $control-bar-button-width - 4px; + + &, + &:hover { + opacity: 0.5; + } + + &.enabled, + &.enabled:hover { + opacity: $primary-foreground-opacity; + } + + &:focus { + text-shadow: none; + } + + > .vjs-icon-placeholder::before { + font-size: 2.3em; + line-height: 1.3; + } + } + .vjs-fullscreen-control { @include disable-outline; @@ -456,6 +482,10 @@ display: none; } + .vjs-mute-control { + @include margin(0, 5px, 0, 5px); + } + .vjs-peertube { padding: 0 !important; @@ -519,3 +549,7 @@ height: 100%; top: 0; } + +.vjs-peertube-skin.vjs-has-captions .vjs-caption-toggle-control { + display: block !important; +} diff --git a/scripts/i18n/create-custom-files.ts b/scripts/i18n/create-custom-files.ts index c1451113f..68cde7dd3 100755 --- a/scripts/i18n/create-custom-files.ts +++ b/scripts/i18n/create-custom-files.ts @@ -76,7 +76,9 @@ const playerKeys = { 'Cancel': 'Cancel', 'Up Next': 'Up Next', 'Autoplay is suspended': 'Autoplay is suspended', - '{1} (from edge: {2})': '{1} (from edge: {2})' + '{1} (from edge: {2})': '{1} (from edge: {2})', + 'Disable subtitles': 'Disable subtitles', + 'Enable {1} subtitle': 'Enable {1} subtitle' } Object.assign(playerKeys, videojs)