From b58ee4c1ba6b862287949d3e3b97d19b96e1f6ad Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Mon, 15 Sep 2025 00:22:52 -0400 Subject: [PATCH 1/4] Refactor native subtitle styling check --- .../constants/subtitleStylingOption.ts | 9 ++++ .../features/playback/utils/subtitleStyles.ts | 46 +++++++++++++++++++ src/plugins/htmlVideoPlayer/plugin.js | 46 +------------------ src/scripts/settings/userSettings.js | 2 +- 4 files changed, 58 insertions(+), 45 deletions(-) create mode 100644 src/apps/stable/features/playback/constants/subtitleStylingOption.ts create mode 100644 src/apps/stable/features/playback/utils/subtitleStyles.ts diff --git a/src/apps/stable/features/playback/constants/subtitleStylingOption.ts b/src/apps/stable/features/playback/constants/subtitleStylingOption.ts new file mode 100644 index 0000000000..12ac60f6eb --- /dev/null +++ b/src/apps/stable/features/playback/constants/subtitleStylingOption.ts @@ -0,0 +1,9 @@ +/** + * Options specifying if the player's native styling of subtitles should be used, Jellyfin's custom styling, or allow + * Jellyfin to choose automatically based on known browser support. + */ +export const SubtitleStylingOption = { + Auto: 'Auto', + Custom: 'Custom', + Native: 'Native' +}; diff --git a/src/apps/stable/features/playback/utils/subtitleStyles.ts b/src/apps/stable/features/playback/utils/subtitleStyles.ts new file mode 100644 index 0000000000..f583e01b28 --- /dev/null +++ b/src/apps/stable/features/playback/utils/subtitleStyles.ts @@ -0,0 +1,46 @@ +import { SubtitleStylingOption } from 'apps/stable/features/playback/constants/subtitleStylingOption'; +import _browser from 'scripts/browser'; +import type { UserSettings } from 'scripts/settings/userSettings'; + +// TODO: These type overrides should be removed when browser and userSettings are properly typed +type Browser = typeof _browser & { + edge?: boolean; + firefox?: boolean; +}; + +interface SubtitleAppearanceSettings { + subtitleStyling: keyof typeof SubtitleStylingOption +} + +export function useCustomSubtitles(userSettings: UserSettings) { + const browser = _browser as Browser; + const subtitleAppearance = userSettings.getSubtitleAppearanceSettings() as SubtitleAppearanceSettings; + switch (subtitleAppearance.subtitleStyling) { + case SubtitleStylingOption.Native: + return false; + case SubtitleStylingOption.Custom: + return true; + default: + // after a system update, ps4 isn't showing anything when creating a track element dynamically + // going to have to do it ourselves + if (browser.ps4) { + return true; + } + + // Tizen 5 doesn't support displaying secondary subtitles + if (browser.tizenVersion >= 5 || browser.web0s) { + return true; + } + + if (browser.edge) { + return true; + } + + // font-size styling does not seem to work natively in firefox. Switching to custom subtitles element for firefox. + if (browser.firefox) { + return true; + } + + return false; + } +} diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 34e3386270..1a6704857f 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -2,6 +2,7 @@ import DOMPurify from 'dompurify'; import debounce from 'lodash-es/debounce'; import Screenfull from 'screenfull'; +import { useCustomSubtitles } from 'apps/stable/features/playback/utils/subtitleStyles'; import { AppFeature } from 'constants/appFeature'; import { ServerConnections } from 'lib/jellyfin-apiclient'; import { MediaError } from 'types/mediaError'; @@ -1357,49 +1358,6 @@ export class HtmlVideoPlayer { }); } - /** - * @private - */ - requiresCustomSubtitlesElement(userSettings) { - const subtitleAppearance = userSettings.getSubtitleAppearanceSettings(); - switch (subtitleAppearance.subtitleStyling) { - case 'Native': - return false; - case 'Custom': - return true; - default: - // after a system update, ps4 isn't showing anything when creating a track element dynamically - // going to have to do it ourselves - if (browser.ps4) { - return true; - } - - // Tizen 5 doesn't support displaying secondary subtitles - if (browser.tizenVersion >= 5 || browser.web0s) { - return true; - } - - if (browser.edge) { - return true; - } - - // font-size styling does not seem to work natively in firefox. Switching to custom subtitles element for firefox. - if (browser.firefox) { - return true; - } - - if (browser.iOS) { - const userAgent = navigator.userAgent.toLowerCase(); - // works in the browser but not the native app - if ((userAgent.includes('os 9') || userAgent.includes('os 8')) && !userAgent.includes('safari')) { - return true; - } - } - - return false; - } - } - /** * @private */ @@ -1496,7 +1454,7 @@ export class HtmlVideoPlayer { return; } - if (this.requiresCustomSubtitlesElement(userSettings)) { + if (useCustomSubtitles(userSettings)) { this.renderSubtitlesWithCustomElement(videoElement, track, item, targetTextTrackIndex); return; } diff --git a/src/scripts/settings/userSettings.js b/src/scripts/settings/userSettings.js index 30904ac51a..439c1df87a 100644 --- a/src/scripts/settings/userSettings.js +++ b/src/scripts/settings/userSettings.js @@ -600,7 +600,7 @@ export class UserSettings { /** * Get subtitle appearance settings. - * @param {string|undefined} key - Settings key. + * @param {string|undefined} [key] - Settings key. * @return {Object} Subtitle appearance settings. */ getSubtitleAppearanceSettings(key) { From eff386ffd813e7b7f48145fe874523a37c579939 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Mon, 15 Sep 2025 08:41:57 -0400 Subject: [PATCH 2/4] Remove unnecessary dynamic imports --- src/plugins/htmlVideoPlayer/plugin.js | 269 +++++++++++++------------- 1 file changed, 132 insertions(+), 137 deletions(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 1a6704857f..0cb7bb419b 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -3,8 +3,10 @@ import debounce from 'lodash-es/debounce'; import Screenfull from 'screenfull'; import { useCustomSubtitles } from 'apps/stable/features/playback/utils/subtitleStyles'; +import subtitleAppearanceHelper from 'components/subtitlesettings/subtitleappearancehelper'; import { AppFeature } from 'constants/appFeature'; import { ServerConnections } from 'lib/jellyfin-apiclient'; +import { currentSettings as userSettings } from 'scripts/settings/userSettings'; import { MediaError } from 'types/mediaError'; import browser from '../../scripts/browser'; @@ -331,8 +333,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ incrementFetchQueue() { if (this.#fetchQueue <= 0) { this.isFetching = true; @@ -343,8 +345,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ decrementFetchQueue() { this.#fetchQueue--; @@ -355,8 +357,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ updateVideoUrl(streamInfo) { const mediaSource = streamInfo.mediaSource; const item = streamInfo.item; @@ -407,8 +409,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ setSrcWithFlvJs(elem, options, url) { return import('flv.js').then(({ default: flvjs }) => { const flvPlayer = flvjs.createPlayer({ @@ -433,8 +435,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ setSrcWithHlsJs(elem, options, url) { return new Promise((resolve, reject) => { requireHlsPlayer(async () => { @@ -474,8 +476,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ async setCurrentSrc(elem, options) { elem.removeEventListener('error', this.onError); @@ -578,8 +580,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ getTextTracks() { const videoElement = this.#mediaElement; if (videoElement) { @@ -596,8 +598,8 @@ export class HtmlVideoPlayer { setSubtitleOffset = debounce(this._setSubtitleOffset, 100); /** - * @private - */ + * @private + */ _setSubtitleOffset(offset) { const offsetValue = parseFloat(offset); @@ -625,8 +627,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ updateCurrentTrackOffset(offsetValue, currentTrackIndex = PRIMARY_TEXT_TRACK_INDEX) { let offsetToCompare = this.#currentTrackOffset; if (this.isSecondaryTrack(currentTrackIndex)) { @@ -651,19 +653,19 @@ export class HtmlVideoPlayer { } /** - * @private - * These browsers will not clear the existing active cue when setting an offset - * for native TextTracks. - * Any previous text tracks that are on the screen when the offset changes will remain next - * to the new tracks until they reach the end time of the new offset's instance of the track. - */ + * @private + * These browsers will not clear the existing active cue when setting an offset + * for native TextTracks. + * Any previous text tracks that are on the screen when the offset changes will remain next + * to the new tracks until they reach the end time of the new offset's instance of the track. + */ requiresHidingActiveCuesOnOffsetChange() { return !!browser.firefox; } /** - * @private - */ + * @private + */ hideTextTrackWithActiveCues(currentTrack) { if (currentTrack.activeCues) { currentTrack.mode = 'hidden'; @@ -671,11 +673,11 @@ export class HtmlVideoPlayer { } /** - * Forces the active cue to clear by disabling then re-enabling the track. - * The track mode is reverted inside of a 0ms timeout to free up the track - * and allow it to disable and clear the active cue. - * @private - */ + * Forces the active cue to clear by disabling then re-enabling the track. + * The track mode is reverted inside of a 0ms timeout to free up the track + * and allow it to disable and clear the active cue. + * @private + */ forceClearTextTrackActiveCues(currentTrack) { if (currentTrack.activeCues) { currentTrack.mode = 'disabled'; @@ -686,8 +688,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ setTextTrackSubtitleOffset(currentTrack, offsetValue, currentTrackIndex) { if (currentTrack.cues) { offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex); @@ -713,8 +715,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ setTrackEventsSubtitleOffset(trackEvents, offsetValue, currentTrackIndex) { if (Array.isArray(trackEvents)) { offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex) * 1e7; // ticks @@ -741,8 +743,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ isAudioStreamSupported(stream, deviceProfile, container) { const codec = (stream.Codec || '').toLowerCase(); @@ -765,8 +767,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ getSupportedAudioStreams() { const profile = this.#lastProfile; @@ -808,8 +810,8 @@ export class HtmlVideoPlayer { // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/audioTracks /** - * @type {ArrayLike|any[]} - */ + * @type {ArrayLike|any[]} + */ const elemAudioTracks = elem.audioTracks || []; console.debug(`found ${elemAudioTracks.length} audio tracks`); @@ -891,26 +893,26 @@ export class HtmlVideoPlayer { } /** - * @private - * @param e {Event} The event received from the `