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..bf524ae16f --- /dev/null +++ b/src/apps/stable/features/playback/constants/subtitleStylingOption.ts @@ -0,0 +1,13 @@ +/** + * Options specifying if the player's native subtitle (cue) element should be used, a custom element (div), or allow + * Jellyfin to choose automatically based on known browser support. Some browsers do not properly apply CSS styling to + * the native subtitle element. + */ +export const SubtitleStylingOption = { + Auto: 'Auto', + Custom: 'Custom', + Native: 'Native' +} as const; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export type SubtitleStylingOption = typeof SubtitleStylingOption[keyof typeof SubtitleStylingOption]; 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..d247683470 --- /dev/null +++ b/src/apps/stable/features/playback/utils/subtitleStyles.ts @@ -0,0 +1,43 @@ +import { SubtitleStylingOption } from 'apps/stable/features/playback/constants/subtitleStylingOption'; +import browser from 'scripts/browser'; +import type { UserSettings } from 'scripts/settings/userSettings'; + +// TODO: This type override should be removed when userSettings are properly typed +interface SubtitleAppearanceSettings { + subtitleStyling: SubtitleStylingOption +} + +export function useCustomSubtitles(userSettings: UserSettings) { + 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 && 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; + } + + // iOS/macOS global caption settings are causing huge font-size and margins + if (browser.safari) return true; + + return false; + } +} diff --git a/src/elements/emby-scroller/Scroller.tsx b/src/elements/emby-scroller/Scroller.tsx index baf054010a..3aa70e6ada 100644 --- a/src/elements/emby-scroller/Scroller.tsx +++ b/src/elements/emby-scroller/Scroller.tsx @@ -178,7 +178,6 @@ const Scroller: FC> = ({ allowNativeScroll: !enableScrollButtons, forceHideScrollbars: enableScrollButtons, // In edge, with the native scroll, the content jumps around when hovering over the buttons - // @ts-expect-error browser doesn't explicitly declare browser.edge, so fails type checking requireAnimation: enableScrollButtons && browser.edge }; diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 34e3386270..0cb7bb419b 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -2,8 +2,11 @@ import DOMPurify from 'dompurify'; 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'; @@ -330,8 +333,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ incrementFetchQueue() { if (this.#fetchQueue <= 0) { this.isFetching = true; @@ -342,8 +345,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ decrementFetchQueue() { this.#fetchQueue--; @@ -354,8 +357,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ updateVideoUrl(streamInfo) { const mediaSource = streamInfo.mediaSource; const item = streamInfo.item; @@ -406,8 +409,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ setSrcWithFlvJs(elem, options, url) { return import('flv.js').then(({ default: flvjs }) => { const flvPlayer = flvjs.createPlayer({ @@ -432,8 +435,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ setSrcWithHlsJs(elem, options, url) { return new Promise((resolve, reject) => { requireHlsPlayer(async () => { @@ -473,8 +476,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ async setCurrentSrc(elem, options) { elem.removeEventListener('error', this.onError); @@ -577,8 +580,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ getTextTracks() { const videoElement = this.#mediaElement; if (videoElement) { @@ -595,8 +598,8 @@ export class HtmlVideoPlayer { setSubtitleOffset = debounce(this._setSubtitleOffset, 100); /** - * @private - */ + * @private + */ _setSubtitleOffset(offset) { const offsetValue = parseFloat(offset); @@ -624,8 +627,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ updateCurrentTrackOffset(offsetValue, currentTrackIndex = PRIMARY_TEXT_TRACK_INDEX) { let offsetToCompare = this.#currentTrackOffset; if (this.isSecondaryTrack(currentTrackIndex)) { @@ -650,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'; @@ -670,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'; @@ -685,8 +688,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ setTextTrackSubtitleOffset(currentTrack, offsetValue, currentTrackIndex) { if (currentTrack.cues) { offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex); @@ -712,8 +715,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ setTrackEventsSubtitleOffset(trackEvents, offsetValue, currentTrackIndex) { if (Array.isArray(trackEvents)) { offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex) * 1e7; // ticks @@ -740,8 +743,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ isAudioStreamSupported(stream, deviceProfile, container) { const codec = (stream.Codec || '').toLowerCase(); @@ -764,8 +767,8 @@ export class HtmlVideoPlayer { } /** - * @private - */ + * @private + */ getSupportedAudioStreams() { const profile = this.#lastProfile; @@ -807,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`); @@ -890,26 +893,26 @@ export class HtmlVideoPlayer { } /** - * @private - * @param e {Event} The event received from the `