From 1c7fadcad7879056e4bfda4a12253097970871cb Mon Sep 17 00:00:00 2001 From: p0358 Date: Sat, 26 Oct 2024 00:49:55 +0200 Subject: [PATCH] Smart font streaming for external ASS subtitles Also refactors the function renderSSaAss to be more parallel and readable and nice... --- src/plugins/htmlVideoPlayer/plugin.js | 100 ++++++++++++++++++-------- 1 file changed, 69 insertions(+), 31 deletions(-) diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index 0697b65e569..f5cc42b0e2e 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -164,6 +164,27 @@ function getTextTrackUrl(track, item, format) { return url; } +function getSubtitlesFontsUrl(track, item) { + if (itemHelper.isLocalItem(item) && track.Path) { + return false; + } + + if (!track.IsExternal || track.IsExternalUrl) { + return false; + } + + const apiClient = ServerConnections.getApiClient(item.ServerId); + + const replacementRegex = /(.*Videos\/[0-9a-f-]+\/[0-9a-f]+\/Subtitles\/[0-9]+(?:\/[0-9]+)\/)(Stream\.[^?]*)(.*)/; + if (!replacementRegex.test(track.DeliveryUrl)) { + return false; + } + + return apiClient.getUrl(track.DeliveryUrl.replace(replacementRegex, '$1Fonts$3'), { + api_key: apiClient.accessToken() + }); +} + function getDefaultProfile() { return profileBuilder({}); } @@ -1258,28 +1279,49 @@ export class HtmlVideoPlayer { * @private */ renderSsaAss(videoElement, track, item) { - const supportedFonts = ['application/vnd.ms-opentype', 'application/x-truetype-font', 'font/otf', 'font/ttf', 'font/woff', 'font/woff2']; - const availableFonts = []; + const workerUrl = `${appRouter.baseUrl()}/libraries/subtitles-octopus-worker.js`; + const legacyWorkerUrl = `${appRouter.baseUrl()}/libraries/subtitles-octopus-worker-legacy.js`; + const supportedFontMimeTypes = ['application/vnd.ms-opentype', 'application/x-truetype-font', 'font/otf', 'font/ttf', 'font/woff', 'font/woff2']; + const fontsToPreload = []; const attachments = this._currentPlayOptions.mediaSource.MediaAttachments || []; const apiClient = ServerConnections.getApiClient(item); attachments.forEach(i => { // we only require font files and ignore embedded media attachments like covers as there are cases where ffmpeg fails to extract those - if (supportedFonts.includes(i.MimeType)) { + if (supportedFontMimeTypes.includes(i.MimeType)) { // embedded font url - availableFonts.push(apiClient.getUrl(i.DeliveryUrl)); + fontsToPreload.push(apiClient.getUrl(i.DeliveryUrl)); } }); - const fallbackFontList = apiClient.getUrl('/FallbackFont/Fonts', { + const subtitlesFontListUrl = getSubtitlesFontsUrl(track, item); // will return false if we shouldn't request this + const fallbackFontListUrl = apiClient.getUrl('/FallbackFont/Fonts', { api_key: apiClient.accessToken() }); const htmlVideoPlayer = this; - import('@jellyfin/libass-wasm').then(({ default: SubtitlesOctopus }) => { + + const fallbackFontListPromise = apiClient.getNamedConfiguration('encoding').then(encodingOptions => { + return encodingOptions.EnableFallbackFont ? apiClient.getJSON(fallbackFontListUrl) : null; + }); + + Promise.all([ + import('@jellyfin/libass-wasm'), + // Worker in Tizen 5 doesn't resolve relative path with async request + resolveUrl(workerUrl), + resolveUrl(legacyWorkerUrl), + subtitlesFontListUrl ? apiClient.getJSON(subtitlesFontListUrl) : null, + fallbackFontListPromise + ]).then(([ + { default: SubtitlesOctopus }, + workerUrlResolved, + legacyWorkerUrlResolved, + subtitlesFontFiles, + fallbackFontFiles + ]) => { const options = { video: videoElement, subUrl: getTextTrackUrl(track, item), - fonts: availableFonts, - workerUrl: `${appRouter.baseUrl()}/libraries/subtitles-octopus-worker.js`, - legacyWorkerUrl: `${appRouter.baseUrl()}/libraries/subtitles-octopus-worker-legacy.js`, + fonts: fontsToPreloadResolved, + workerUrl: workerUrlResolved, + legacyWorkerUrl: legacyWorkerUrl, onError() { // HACK: Clear JavascriptSubtitlesOctopus: it gets disposed when an error occurs htmlVideoPlayer.#currentAssRenderer = null; @@ -1304,29 +1346,25 @@ export class HtmlVideoPlayer { renderAhead: 90 }; - Promise.all([ - apiClient.getNamedConfiguration('encoding'), - // Worker in Tizen 5 doesn't resolve relative path with async request - resolveUrl(options.workerUrl), - resolveUrl(options.legacyWorkerUrl) - ]).then(([config, workerUrl, legacyWorkerUrl]) => { - options.workerUrl = workerUrl; - options.legacyWorkerUrl = legacyWorkerUrl; - - if (config.EnableFallbackFont) { - apiClient.getJSON(fallbackFontList).then((fontFiles = []) => { - fontFiles.forEach(font => { - const fontUrl = apiClient.getUrl(`/FallbackFont/Fonts/${encodeURIComponent(font.Name)}`, { - api_key: apiClient.accessToken() - }); - availableFonts.push(fontUrl); - }); - this.#currentAssRenderer = new SubtitlesOctopus(options); + if (subtitlesFontFiles) { + subtitlesFontFiles.forEach(font => { + const fontUrl = apiClient.getUrl(`/Fonts/${encodeURIComponent(font.Key)}`, { + api_key: apiClient.accessToken() }); - } else { - this.#currentAssRenderer = new SubtitlesOctopus(options); - } - }); + fontsToPreload.push(fontUrl); + }); + } + + if (fallbackFontFiles) { + fallbackFontFiles.forEach(font => { + const fontUrl = apiClient.getUrl(`/FallbackFont/Fonts/${encodeURIComponent(font.Name)}`, { + api_key: apiClient.accessToken() + }); + fontsToPreload.push(fontUrl); + }); + } + + this.#currentAssRenderer = new SubtitlesOctopus(options); }); }