From 148c240be86e86655680a4b66ea1c9f7043c9c15 Mon Sep 17 00:00:00 2001 From: Aditya Mishra Date: Sun, 10 May 2026 15:34:28 +0530 Subject: [PATCH 1/2] Add subtitle style controls --- .../ft-shaka-video-player.js | 127 +++++++++++++++++- 1 file changed, 125 insertions(+), 2 deletions(-) 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 02ee2146bf928..bae42bef44cfe 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 @@ -32,6 +32,7 @@ import { MANIFEST_TYPE_SABR } from '../../helpers/player/SabrManifestParser' import { setupSabrScheme } from '../../helpers/player/SabrSchemePlugin' /** @typedef {import('../../helpers/sponsorblock').SponsorBlockCategory} SponsorBlockCategory */ +/** @typedef {{ fontScaleFactor: number, positionArea: string }} CaptionStyleConfig */ // The UTF-8 characters "h", "t", "t", and "p". const HTTP_IN_HEX = 0x68747470 @@ -210,6 +211,9 @@ export default defineComponent({ let startInFullscreen = props.startInFullscreen let startInPip = props.startInPip + /** @type {CaptionStyleConfig|null} */ + let activeCaptionStyleConfig = null + /** @type {number|null} */ let restoreCaptionIndex = null @@ -596,7 +600,10 @@ export default defineComponent({ * @returns {shaka.extern.PlayerConfiguration} */ function getPlayerConfig(format, useAutoQuality = false) { - return { + const captionStyleConfig = activeCaptionStyleConfig ?? getSavedCaptionStyleConfig() + + /** @type {shaka.extern.PlayerConfiguration} */ + const playerConfig = { // YouTube uses these values and they seem to work well in FreeTube too, // so we might as well use them streaming: { @@ -631,6 +638,108 @@ export default defineComponent({ // So use the AV1 and h264 codecs instead which it doesn't reject preferredVideoCodecs: typeof props.vrProjection === 'string' ? ['av01', 'avc1'] : [] } + + if (Object.keys(captionStyleConfig).length > 0) { + playerConfig.textDisplayer = captionStyleConfig + } + + return playerConfig + } + + /** + * @returns {{ fontScaleFactor?: number, positionArea?: string }} + */ + function getSavedCaptionStyleConfig() { + let captionSettings + + try { + captionSettings = JSON.parse(store.getters.getDefaultCaptionSettings) + } catch { + return {} + } + + if (typeof captionSettings !== 'object' || captionSettings === null || Array.isArray(captionSettings)) { + return {} + } + + /** @type {{ fontScaleFactor?: number, positionArea?: string }} */ + const captionStyleConfig = {} + const { fontScaleFactor, positionArea } = captionSettings + + if (typeof fontScaleFactor === 'number' && Number.isFinite(fontScaleFactor) && fontScaleFactor > 0) { + captionStyleConfig.fontScaleFactor = fontScaleFactor + } + + if (Object.values(shaka.config.PositionArea).includes(positionArea)) { + captionStyleConfig.positionArea = positionArea + } + + return captionStyleConfig + } + + /** + * @returns {CaptionStyleConfig|null} + */ + function getCaptionStyleConfig() { + if (!player) { + return null + } + + const textDisplayerConfig = player.getConfiguration().textDisplayer + if (!textDisplayerConfig) { + return null + } + + const { fontScaleFactor, positionArea } = textDisplayerConfig + + if (typeof fontScaleFactor !== 'number' || !Number.isFinite(fontScaleFactor) || fontScaleFactor <= 0 || + !Object.values(shaka.config.PositionArea).includes(positionArea)) { + return null + } + + return { + fontScaleFactor, + positionArea + } + } + + function saveCaptionStyleSettings() { + const captionStyleConfig = getCaptionStyleConfig() + if (!captionStyleConfig || captionStyleConfigsAreEqual(captionStyleConfig, activeCaptionStyleConfig)) { + return + } + + activeCaptionStyleConfig = captionStyleConfig + + store.dispatch('updateDefaultCaptionSettings', JSON.stringify({ + ...getSavedCaptionSettings(), + ...captionStyleConfig + })) + } + + /** + * @returns {Record} + */ + function getSavedCaptionSettings() { + try { + const captionSettings = JSON.parse(store.getters.getDefaultCaptionSettings) + + if (typeof captionSettings === 'object' && captionSettings !== null && !Array.isArray(captionSettings)) { + return captionSettings + } + } catch { } + + return {} + } + + /** + * @param {CaptionStyleConfig|null} a + * @param {CaptionStyleConfig|null} b + * @returns {boolean} + */ + function captionStyleConfigsAreEqual(a, b) { + return a?.fontScaleFactor === b?.fontScaleFactor && + a?.positionArea === b?.positionArea } /** @@ -822,6 +931,8 @@ export default defineComponent({ props.format === 'legacy' ? 'ft_legacy_quality' : 'quality', 'playback_rate', 'captions', + 'captions-position', + 'captions-size', 'ft_audio_tracks', 'loop', 'ft_screenshot', @@ -848,6 +959,8 @@ export default defineComponent({ uiConfig.overflowMenuButtons.push( 'ft_audio_tracks', 'captions', + 'captions-position', + 'captions-size', 'playback_rate', props.format === 'legacy' ? 'ft_legacy_quality' : 'quality', 'loop', @@ -900,7 +1013,11 @@ export default defineComponent({ const firstTimeConfig = { addSeekBar: seekingIsPossible.value, customContextMenu: true, - contextMenuElements: ['ft_stats'], + contextMenuElements: [ + 'captions-position', + 'captions-size', + 'ft_stats' + ], enableTooltips: true, seekBarColors: { played: 'var(--primary-color)' @@ -2712,6 +2829,8 @@ export default defineComponent({ player.addEventListener('error', event => handleError(event.detail, 'shaka error handler')) player.configure(getPlayerConfig(props.format, defaultQuality.value === 'auto')) + activeCaptionStyleConfig = getCaptionStyleConfig() + player.addEventListener('configurationchanged', saveCaptionStyleSettings) if (process.env.SUPPORTS_LOCAL_API) { player.getNetworkingEngine().registerRequestFilter(requestFilter) @@ -3153,6 +3272,8 @@ export default defineComponent({ document.removeEventListener('keydown', keyboardShortcutHandler) document.removeEventListener('fullscreenchange', fullscreenChangeHandler) + player?.removeEventListener('configurationchanged', saveCaptionStyleSettings) + if (containerResizeObserver) { containerResizeObserver.disconnect() containerResizeObserver = null @@ -3212,6 +3333,8 @@ export default defineComponent({ let uiState = { startNextVideoInFullscreen: false, startNextVideoInFullwindow: false, startNextVideoInPip: false } + player?.removeEventListener('configurationchanged', saveCaptionStyleSettings) + if (ui) { if (ui.getControls()) { // save the state of player settings to reinitialize them upon next creation From 368ea85ee6024b99c0fd6c2e50032ced17982863 Mon Sep 17 00:00:00 2001 From: Aditya Mishra Date: Sun, 10 May 2026 17:27:44 +0530 Subject: [PATCH 2/2] Fix caption position JSDoc type --- .../ft-shaka-video-player/ft-shaka-video-player.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 bae42bef44cfe..aa2c8e8a2c200 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 @@ -32,7 +32,7 @@ import { MANIFEST_TYPE_SABR } from '../../helpers/player/SabrManifestParser' import { setupSabrScheme } from '../../helpers/player/SabrSchemePlugin' /** @typedef {import('../../helpers/sponsorblock').SponsorBlockCategory} SponsorBlockCategory */ -/** @typedef {{ fontScaleFactor: number, positionArea: string }} CaptionStyleConfig */ +/** @typedef {{ fontScaleFactor: number, positionArea: shaka.config.PositionArea }} CaptionStyleConfig */ // The UTF-8 characters "h", "t", "t", and "p". const HTTP_IN_HEX = 0x68747470 @@ -647,7 +647,7 @@ export default defineComponent({ } /** - * @returns {{ fontScaleFactor?: number, positionArea?: string }} + * @returns {{ fontScaleFactor?: number, positionArea?: shaka.config.PositionArea }} */ function getSavedCaptionStyleConfig() { let captionSettings @@ -662,7 +662,7 @@ export default defineComponent({ return {} } - /** @type {{ fontScaleFactor?: number, positionArea?: string }} */ + /** @type {{ fontScaleFactor?: number, positionArea?: shaka.config.PositionArea }} */ const captionStyleConfig = {} const { fontScaleFactor, positionArea } = captionSettings