From 8125c68d2450290f46ee3b8967f0ed87861660bb Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sun, 17 Mar 2024 17:18:23 +0300 Subject: [PATCH] Migrate back from JASSUB to JavascriptSubtitlesOctopus JASSUB has some problems with non-Latin languages. This reverts: 3560715f410354a3c65237b85062812a53903181 0b3d2d841fc9d03af9faf4b9aef2eec044e3daab 93c4dbbe4cce4c1c3f3c678abc77d3b0ffa0e874 478f81fecf4e38b0456c99978df11c8bd9b9c224 f0e8eb52bbea3f6941020765aa1e2b539996508c 52c8cffc827a2532b161029f3df614e3db1a0fad --- package-lock.json | 48 +++------- package.json | 7 +- src/plugins/htmlVideoPlayer/plugin.js | 122 +++++++++++-------------- src/plugins/htmlVideoPlayer/style.scss | 2 +- webpack.common.js | 35 ++----- 5 files changed, 76 insertions(+), 138 deletions(-) diff --git a/package-lock.json b/package-lock.json index 32a9f62e16..f821c21a42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@fontsource/noto-sans-kr": "5.0.17", "@fontsource/noto-sans-sc": "5.0.17", "@fontsource/noto-sans-tc": "5.0.17", + "@jellyfin/libass-wasm": "4.1.1", "@jellyfin/sdk": "0.0.0-unstable.202403240502", "@loadable/component": "5.16.3", "@mui/icons-material": "5.15.11", @@ -35,14 +36,12 @@ "dompurify": "3.0.1", "epubjs": "0.3.93", "escape-html": "1.0.3", - "event-target-polyfill": "github:ThaUnknown/event-target-polyfill", "fast-text-encoding": "1.0.6", "flv.js": "1.6.2", "headroom.js": "0.12.0", "history": "5.3.0", "hls.js": "1.5.7", "intersection-observer": "0.12.2", - "jassub": "1.7.15", "jellyfin-apiclient": "1.11.0", "jquery": "3.7.1", "jstree": "3.3.16", @@ -3640,6 +3639,11 @@ "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, + "node_modules/@jellyfin/libass-wasm": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@jellyfin/libass-wasm/-/libass-wasm-4.1.1.tgz", + "integrity": "sha512-xQVJw+lZUg4U1TmLS80reBECfPtpCgRF8hhUSvUUQM9g68OvINyUU3K2yqRH+8tomGpghiRaIcr/bUJ83e0veA==" + }, "node_modules/@jellyfin/sdk": { "version": "0.0.0-unstable.202403240502", "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202403240502.tgz", @@ -9306,11 +9310,6 @@ "es5-ext": "~0.10.14" } }, - "node_modules/event-target-polyfill": { - "version": "0.0.3", - "resolved": "git+ssh://git@github.com/ThaUnknown/event-target-polyfill.git#5cb9a0ed6774af1b905b525964316911375726a7", - "license": "MIT" - }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -12228,14 +12227,6 @@ "set-function-name": "^2.0.1" } }, - "node_modules/jassub": { - "version": "1.7.15", - "resolved": "https://registry.npmjs.org/jassub/-/jassub-1.7.15.tgz", - "integrity": "sha512-8yKAJc++Y1gNfATOPRo3APk0JUhshKl5l7bRkT6WkJ8XP4RvYfVPb6ieH6WDxsMq523exwGzNvjjPEEWT+Z1nQ==", - "dependencies": { - "rvfc-polyfill": "^1.0.7" - } - }, "node_modules/jellyfin-apiclient": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/jellyfin-apiclient/-/jellyfin-apiclient-1.11.0.tgz", @@ -16900,11 +16891,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rvfc-polyfill": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/rvfc-polyfill/-/rvfc-polyfill-1.0.7.tgz", - "integrity": "sha512-seBl7J1J3/k0LuzW2T9fG6JIOpni5AbU+/87LA+zTYKgTVhsfShmS8K/yOo1eeEjGJHnAdkVAUUM+PEjN9Mpkw==" - }, "node_modules/safe-array-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", @@ -25267,6 +25253,11 @@ "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, + "@jellyfin/libass-wasm": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@jellyfin/libass-wasm/-/libass-wasm-4.1.1.tgz", + "integrity": "sha512-xQVJw+lZUg4U1TmLS80reBECfPtpCgRF8hhUSvUUQM9g68OvINyUU3K2yqRH+8tomGpghiRaIcr/bUJ83e0veA==" + }, "@jellyfin/sdk": { "version": "0.0.0-unstable.202403240502", "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202403240502.tgz", @@ -29419,10 +29410,6 @@ "es5-ext": "~0.10.14" } }, - "event-target-polyfill": { - "version": "git+ssh://git@github.com/ThaUnknown/event-target-polyfill.git#5cb9a0ed6774af1b905b525964316911375726a7", - "from": "event-target-polyfill@github:ThaUnknown/event-target-polyfill" - }, "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -31548,14 +31535,6 @@ "set-function-name": "^2.0.1" } }, - "jassub": { - "version": "1.7.15", - "resolved": "https://registry.npmjs.org/jassub/-/jassub-1.7.15.tgz", - "integrity": "sha512-8yKAJc++Y1gNfATOPRo3APk0JUhshKl5l7bRkT6WkJ8XP4RvYfVPb6ieH6WDxsMq523exwGzNvjjPEEWT+Z1nQ==", - "requires": { - "rvfc-polyfill": "^1.0.7" - } - }, "jellyfin-apiclient": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/jellyfin-apiclient/-/jellyfin-apiclient-1.11.0.tgz", @@ -34848,11 +34827,6 @@ "queue-microtask": "^1.2.2" } }, - "rvfc-polyfill": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/rvfc-polyfill/-/rvfc-polyfill-1.0.7.tgz", - "integrity": "sha512-seBl7J1J3/k0LuzW2T9fG6JIOpni5AbU+/87LA+zTYKgTVhsfShmS8K/yOo1eeEjGJHnAdkVAUUM+PEjN9Mpkw==" - }, "safe-array-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", diff --git a/package.json b/package.json index beacdd27bb..d0eeed422f 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "@fontsource/noto-sans-kr": "5.0.17", "@fontsource/noto-sans-sc": "5.0.17", "@fontsource/noto-sans-tc": "5.0.17", + "@jellyfin/libass-wasm": "4.1.1", "@jellyfin/sdk": "0.0.0-unstable.202403240502", "@loadable/component": "5.16.3", "@mui/icons-material": "5.15.11", @@ -96,14 +97,12 @@ "dompurify": "3.0.1", "epubjs": "0.3.93", "escape-html": "1.0.3", - "event-target-polyfill": "github:ThaUnknown/event-target-polyfill", "fast-text-encoding": "1.0.6", "flv.js": "1.6.2", "headroom.js": "0.12.0", "history": "5.3.0", "hls.js": "1.5.7", "intersection-observer": "0.12.2", - "jassub": "1.7.15", "jellyfin-apiclient": "1.11.0", "jquery": "3.7.1", "jstree": "3.3.16", @@ -146,8 +145,8 @@ "start": "npm run serve", "serve": "webpack serve --config webpack.dev.js", "build:analyze": "cross-env NODE_ENV=\"production\" webpack --config webpack.analyze.js", - "build:development": "cross-env NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.dev.js", - "build:production": "cross-env NODE_ENV=\"production\" NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.prod.js", + "build:development": "webpack --config webpack.dev.js", + "build:production": "cross-env NODE_ENV=\"production\" webpack --config webpack.prod.js", "build:check": "tsc --noEmit", "escheck": "es-check", "lint": "eslint \"./\"", diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index eb659abb67..c00116d515 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -217,7 +217,7 @@ export class HtmlVideoPlayer { */ #currentAssRenderer; /** - * @type {null | undefined} + * @type {number | undefined} */ #customTrackIndex; /** @@ -983,6 +983,8 @@ export class HtmlVideoPlayer { seekOnPlaybackStart(this, e.target, this._currentPlayOptions.playerStartPositionTicks, () => { if (this.#currentAssRenderer) { this.#currentAssRenderer.timeOffset = (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + this.#currentTrackOffset; + this.#currentAssRenderer.resize(); + this.#currentAssRenderer.resetRenderAheadCache(false); } }); @@ -1169,9 +1171,9 @@ export class HtmlVideoPlayer { this.#currentClock = null; this._currentAspectRatio = null; - const jassub = this.#currentAssRenderer; - if (jassub) { - jassub.destroy(); + const octopus = this.#currentAssRenderer; + if (octopus) { + octopus.dispose(); } this.#currentAssRenderer = null; } @@ -1260,76 +1262,60 @@ export class HtmlVideoPlayer { const fallbackFontList = apiClient.getUrl('/FallbackFont/Fonts', { api_key: apiClient.accessToken() }); - // TODO: replace with `event-target-polyfill` once https://github.com/benlesh/event-target-polyfill/pull/12 or 11 is merged - import('event-target-polyfill').then(() => { - import('jassub').then(({ default: JASSUB }) => { - // test SIMD support - JASSUB._test(); + const htmlVideoPlayer = this; + import('@jellyfin/libass-wasm').then(({ default: SubtitlesOctopus }) => { + const options = { + video: videoElement, + subUrl: getTextTrackUrl(track, item), + fonts: avaliableFonts, + workerUrl: `${appRouter.baseUrl()}/libraries/subtitles-octopus-worker.js`, + legacyWorkerUrl: `${appRouter.baseUrl()}/libraries/subtitles-octopus-worker-legacy.js`, + onError() { + // HACK: Clear JavascriptSubtitlesOctopus: it gets disposed when an error occurs + htmlVideoPlayer.#currentAssRenderer = null; - const options = { - video: videoElement, - subUrl: getTextTrackUrl(track, item), - fonts: avaliableFonts, - fallbackFont: 'liberation sans', - availableFonts: { 'liberation sans': `${appRouter.baseUrl()}/default.woff2` }, - // Disabled eslint compat, but is safe as corejs3 polyfills URL - // eslint-disable-next-line compat/compat - workerUrl: new URL('jassub/dist/jassub-worker.js', import.meta.url).href, - // eslint-disable-next-line compat/compat - wasmUrl: new URL('jassub/dist/jassub-worker.wasm', import.meta.url).href, - // eslint-disable-next-line compat/compat - legacyWasmUrl: new URL('jassub/dist/jassub-worker.wasm.js', import.meta.url).href, - // eslint-disable-next-line compat/compat - modernWasmUrl : new URL('jassub/dist/jassub-worker-modern.wasm', import.meta.url).href, - timeOffset: (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000, - // new jassub options; override all, even defaults - blendMode: 'js', - asyncRender: true, - offscreenRender: true, - // RVFC is polyfilled everywhere, but webOS 2 reports polyfill API's as functional even tho they aren't - onDemandRender: browser.web0sVersion !== 2, - useLocalFonts: true, - dropAllAnimations: false, - dropAllBlur: !JASSUB._supportsSIMD, - libassMemoryLimit: 40, - libassGlyphLimit: 40, - targetFps: 24, - prescaleFactor: 0.8, - prescaleHeightLimit: 1080, - maxRenderHeight: 2160 - }; + // HACK: Give JavascriptSubtitlesOctopus time to dispose itself + setTimeout(() => { + onErrorInternal(htmlVideoPlayer, 'mediadecodeerror'); + }, 0); + }, + timeOffset: (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000, - 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; + // new octopus options; override all, even defaults + renderMode: 'wasm-blend', + dropAllAnimations: false, + libassMemoryLimit: 40, + libassGlyphLimit: 40, + targetFps: 24, + prescaleFactor: 0.8, + prescaleHeightLimit: 1080, + maxRenderHeight: 2160, + resizeVariation: 0.2, + renderAhead: 90 + }; - const cleanup = () => { - this.#currentAssRenderer.destroy(); - this.#currentAssRenderer = null; - onErrorInternal(this, 'mediadecodeerror'); - }; + 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/${font.Name}`, { - api_key: apiClient.accessToken() - }); - avaliableFonts.push(fontUrl); + if (config.EnableFallbackFont) { + apiClient.getJSON(fallbackFontList).then((fontFiles = []) => { + fontFiles.forEach(font => { + const fontUrl = apiClient.getUrl(`/FallbackFont/Fonts/${font.Name}`, { + api_key: apiClient.accessToken() }); - this.#currentAssRenderer = new JASSUB(options); - this.#currentAssRenderer.addEventListener('error', cleanup, { once: true }); + avaliableFonts.push(fontUrl); }); - } else { - this.#currentAssRenderer = new JASSUB(options); - this.#currentAssRenderer.addEventListener('error', cleanup, { once: true }); - } - }); + this.#currentAssRenderer = new SubtitlesOctopus(options); + }); + } else { + this.#currentAssRenderer = new SubtitlesOctopus(options); + } }); }); } diff --git a/src/plugins/htmlVideoPlayer/style.scss b/src/plugins/htmlVideoPlayer/style.scss index bfcc90d60e..0026146081 100644 --- a/src/plugins/htmlVideoPlayer/style.scss +++ b/src/plugins/htmlVideoPlayer/style.scss @@ -17,7 +17,7 @@ z-index: 1000; } -.videoPlayerContainer .JASSUB { +.videoPlayerContainer .libassjs-canvas-parent { order: -1; } diff --git a/webpack.common.js b/webpack.common.js index ca432d895f..1c6ec2b992 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -10,13 +10,15 @@ const packageJson = require('./package.json'); const Assets = [ 'native-promise-only/npo.js', 'libarchive.js/dist/worker-bundle.js', + '@jellyfin/libass-wasm/dist/js/subtitles-octopus-worker.js', + '@jellyfin/libass-wasm/dist/js/subtitles-octopus-worker.data', + '@jellyfin/libass-wasm/dist/js/subtitles-octopus-worker.wasm', + '@jellyfin/libass-wasm/dist/js/subtitles-octopus-worker-legacy.js', + '@jellyfin/libass-wasm/dist/js/subtitles-octopus-worker-legacy.data', + '@jellyfin/libass-wasm/dist/js/subtitles-octopus-worker-legacy.js.mem', 'pdfjs-dist/build/pdf.worker.js' ]; -const JassubWasm = [ - 'jassub/dist/default.woff2' -]; - const LibarchiveWasm = [ 'libarchive.js/dist/wasm-gen/libarchive.js', 'libarchive.js/dist/wasm-gen/libarchive.wasm' @@ -102,14 +104,6 @@ const config = { }; }) }), - new CopyPlugin({ - patterns: JassubWasm.map(asset => { - return { - from: path.resolve(__dirname, `./node_modules/${asset}`), - to: path.resolve(__dirname, './dist') - }; - }) - }), new ForkTsCheckerWebpackPlugin({ typescript: { configFile: path.resolve(__dirname, 'tsconfig.json') @@ -182,8 +176,7 @@ const config = { { test: /\.(js|jsx|mjs)$/, include: [ - path.resolve(__dirname, 'node_modules/event-target-polyfill'), - path.resolve(__dirname, 'node_modules/rvfc-polyfill'), + path.resolve(__dirname, 'node_modules/@jellyfin/libass-wasm'), path.resolve(__dirname, 'node_modules/@jellyfin/sdk'), path.resolve(__dirname, 'node_modules/@react-hook/latest'), path.resolve(__dirname, 'node_modules/@react-hook/passive-layout-effect'), @@ -224,20 +217,6 @@ const config = { } }] }, - { - test: /\.js$/, - include: [ - path.resolve(__dirname, 'node_modules/jassub') - ], - use: [{ - loader: 'babel-loader', - options: { - cacheCompression: false, - cacheDirectory: true, - presets: ['@babel/preset-env'] - } - }] - }, { test: /\.worker\.ts$/, exclude: /node_modules/,