mirror of
https://github.com/zoriya/libpgs-js.git
synced 2026-08-27 00:00:51 +00:00
Added an optional mode setting to force a custom worker mode.
Handled browser specific edge cases for older WebOS versions.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import {PgsRendererMode} from "./pgsRendererMode";
|
||||
|
||||
export class BrowserSupport {
|
||||
/**
|
||||
* Checks if the web worker is supported in the current environment.
|
||||
*/
|
||||
public static isWorkerSupported(): boolean {
|
||||
return !!Worker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the offscreen-canvas and `transferControlToOffscreen` are supported in the current environment.
|
||||
*/
|
||||
public static isOffscreenCanvasSupported(): boolean {
|
||||
return !!HTMLCanvasElement.prototype.transferControlToOffscreen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the optimal PGS renderer mode for the current platform.
|
||||
*/
|
||||
public static getRendererModeByPlatform(): PgsRendererMode {
|
||||
// Handle browser specific edge cases. This list is not complete!
|
||||
// If you find any more compatible issues with legacy browsers, please report them on the issue tracker.
|
||||
const userAgent = navigator.userAgent;
|
||||
|
||||
// Detect Chrome
|
||||
const chrome = /Chrome\/(\d+)/.exec(userAgent);
|
||||
const chromeVersion = chrome ? parseInt(chrome[1]) : undefined;
|
||||
|
||||
// Detect Firefox
|
||||
const firefox = /Firefox\/(\d+)/.exec(userAgent);
|
||||
const firefoxVersion = firefox ? parseInt(firefox[1]) : undefined;
|
||||
|
||||
// Detect WebOS
|
||||
const webOS = navigator.userAgent.indexOf('Web0S') >= 0;
|
||||
|
||||
|
||||
// ImageData in workers is supported since Chrome 36
|
||||
if (chromeVersion && chromeVersion < 36) {
|
||||
return PgsRendererMode.mainThread;
|
||||
}
|
||||
|
||||
// ImageData in workers is supported since Firefox 25
|
||||
if (firefoxVersion && firefoxVersion < 25) {
|
||||
return PgsRendererMode.mainThread;
|
||||
}
|
||||
|
||||
// Older version of WebOS are based on WebKit and don't support ImageData in Workers.
|
||||
if (webOS && chromeVersion === undefined) {
|
||||
return PgsRendererMode.mainThread;
|
||||
}
|
||||
// WebOS 5 / Chrome 68 should support workers, but they do not load nor respond to messages.
|
||||
if (webOS && chromeVersion && chromeVersion <= 68) {
|
||||
return PgsRendererMode.mainThread;
|
||||
}
|
||||
|
||||
|
||||
// Detect by features
|
||||
const isWorkerSupported = this.isWorkerSupported();
|
||||
const isOffscreenCanvasSupported = this.isOffscreenCanvasSupported();
|
||||
if (isWorkerSupported) {
|
||||
if (isOffscreenCanvasSupported) {
|
||||
return PgsRendererMode.worker;
|
||||
} else {
|
||||
return PgsRendererMode.workerWithoutOffscreenCanvas;
|
||||
}
|
||||
} else {
|
||||
return PgsRendererMode.mainThread;
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-31
@@ -2,6 +2,9 @@ import {PgsRendererOptions} from "./pgsRendererOptions";
|
||||
import {PgsRendererImpl} from "./pgsRendererImpl";
|
||||
import {PgsRendererInWorkerWithOffscreenCanvas} from "./pgsRendererInWorkerWithOffscreenCanvas";
|
||||
import {PgsRendererInMainThread} from "./pgsRendererInMainThread";
|
||||
import {PgsRendererInWorkerWithoutOffscreenCanvas} from "./pgsRendererInWorkerWithoutOffscreenCanvas";
|
||||
import {PgsRendererMode} from "./pgsRendererMode";
|
||||
import {BrowserSupport} from "./browserSupport";
|
||||
|
||||
/**
|
||||
* Renders PGS subtitle on-top of a video element using a canvas element. This also handles timestamp updates if a
|
||||
@@ -32,7 +35,7 @@ export class PgsRenderer {
|
||||
}
|
||||
|
||||
|
||||
this.implementation = this.createPgsRendererFormPlatform(options);
|
||||
this.implementation = this.createPgsRenderer(options);
|
||||
this.implementation.onTimestampsUpdated = () => {
|
||||
// Re-render the current subtitle if the timestamps were updates (e.g. through partial load).
|
||||
this.renderAtVideoTimestamp();
|
||||
@@ -48,38 +51,19 @@ export class PgsRenderer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the web worker is supported in the current environment.
|
||||
*/
|
||||
private isWorkerSupported(): boolean {
|
||||
return !!window.Worker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the offscreen-canvas and `transferControlToOffscreen` are supported in the current environment.
|
||||
*/
|
||||
private isOffscreenCanvasSupported(): boolean {
|
||||
return !!HTMLCanvasElement.prototype.transferControlToOffscreen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a platform check and returns the optimal implementation for subtitle rendering.
|
||||
* Creates the PGS renderer implementation for the given option.
|
||||
* @param options The PGS renderer options.
|
||||
*/
|
||||
private createPgsRendererFormPlatform(options: PgsRendererOptions): PgsRendererImpl {
|
||||
// Jellyfin still supports webOS 1.2 and Tizen 2.3 with real old WebKit and Chromium version.
|
||||
// We can run a different fallback implementation if the offscreen canvas is not supported.
|
||||
|
||||
const isWorkerSupported = this.isWorkerSupported();
|
||||
const isOffscreenCanvasSupported = this.isOffscreenCanvasSupported();
|
||||
console.log(`isWebWorkerSupported: ${isWorkerSupported}, isOffscreenCanvasSupported: ${isOffscreenCanvasSupported}`);
|
||||
|
||||
// FIXME: I would like to use `PgsRendererInWorkerWithoutOffscreenCanvas` if the offscreen canvas is not
|
||||
// available, but even the web worker thread won't start on webOS and I can't figure out why.
|
||||
if (isWorkerSupported && isOffscreenCanvasSupported) {
|
||||
return new PgsRendererInWorkerWithOffscreenCanvas(options, this.canvas);
|
||||
} else {
|
||||
|
||||
return new PgsRendererInMainThread(options, this.canvas);
|
||||
private createPgsRenderer(options: PgsRendererOptions): PgsRendererImpl {
|
||||
// Use the mode provided in the settings or detect by browser.
|
||||
const mode = options.mode ?? BrowserSupport.getRendererModeByPlatform();
|
||||
switch (mode) {
|
||||
case PgsRendererMode.worker:
|
||||
return new PgsRendererInWorkerWithOffscreenCanvas(options, this.canvas);
|
||||
case PgsRendererMode.workerWithoutOffscreenCanvas:
|
||||
return new PgsRendererInWorkerWithoutOffscreenCanvas(options, this.canvas);
|
||||
case PgsRendererMode.mainThread:
|
||||
return new PgsRendererInMainThread(options, this.canvas);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
export enum PgsRendererMode {
|
||||
/**
|
||||
* A worker thread is used to load, build and render the subtitles. This requires OffscreenCanvas support.
|
||||
*/
|
||||
worker = 'worker',
|
||||
|
||||
/**
|
||||
* A worker thread is used to load and build the subtitles. They are rendered in the main thread.
|
||||
*/
|
||||
workerWithoutOffscreenCanvas = 'workerWithoutOffscreenCanvas',
|
||||
|
||||
/**
|
||||
* The subtitles are loaded, built and rendered in the main thread.
|
||||
*/
|
||||
mainThread = 'mainThread',
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import {PgsRendererMode} from "./pgsRendererMode";
|
||||
|
||||
export interface PgsRendererOptions {
|
||||
/**
|
||||
* The video element to sync the subtitle to.
|
||||
@@ -25,4 +27,10 @@ export interface PgsRendererOptions {
|
||||
* The url to the worker javascript file.
|
||||
*/
|
||||
workerUrl?: string;
|
||||
|
||||
/**
|
||||
* The forced renderer mode.
|
||||
* If not provided, the renderer checks the current browser to detect the best mode for your platform.
|
||||
*/
|
||||
mode?: PgsRendererMode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import {BrowserSupport} from "../src/browserSupport";
|
||||
import {PgsRendererMode} from "../src/pgsRendererMode";
|
||||
|
||||
const devices = [
|
||||
{
|
||||
name: 'WebOS 1.2',
|
||||
userAgent: 'Mozilla/5.0 (Web0S; Linux i686) AppleWebKit/537.41 (KHTML, like Gecko) Large Screen WebAppManager Safari/537.41',
|
||||
worker: true,
|
||||
offscreenCanvas: false,
|
||||
expectedMode: PgsRendererMode.mainThread
|
||||
},
|
||||
{
|
||||
name: 'WebOS 5.0',
|
||||
userAgent: 'Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36 WebAppManager',
|
||||
worker: true,
|
||||
offscreenCanvas: false,
|
||||
expectedMode: PgsRendererMode.mainThread
|
||||
},
|
||||
{
|
||||
name: 'Chrome 35',
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36',
|
||||
worker: true,
|
||||
offscreenCanvas: false,
|
||||
expectedMode: PgsRendererMode.mainThread
|
||||
},
|
||||
{
|
||||
name: 'Chrome 68',
|
||||
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.0.0 Safari/537.36',
|
||||
worker: true,
|
||||
offscreenCanvas: false,
|
||||
expectedMode: PgsRendererMode.workerWithoutOffscreenCanvas
|
||||
},
|
||||
{
|
||||
name: 'Chrome 128',
|
||||
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36',
|
||||
worker: true,
|
||||
offscreenCanvas: true,
|
||||
expectedMode: PgsRendererMode.worker
|
||||
},
|
||||
{
|
||||
name: 'Firefox 24',
|
||||
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0',
|
||||
worker: true,
|
||||
offscreenCanvas: false,
|
||||
expectedMode: PgsRendererMode.mainThread
|
||||
},
|
||||
{
|
||||
name: 'Firefox 130',
|
||||
userAgent: 'Mozilla/5.0 (X11; Linux x86_64; rv:130.0) Gecko/20100101 Firefox/130.0',
|
||||
worker: true,
|
||||
offscreenCanvas: true,
|
||||
expectedMode: PgsRendererMode.worker
|
||||
}
|
||||
];
|
||||
|
||||
describe.each(devices)('on $name', (device) => {
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(global, 'navigator', {
|
||||
configurable: true,
|
||||
value: {
|
||||
userAgent: device.userAgent
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(global, 'Worker', {
|
||||
configurable: true,
|
||||
value: device.worker ? {} : undefined,
|
||||
});
|
||||
|
||||
Object.defineProperty(global, 'HTMLCanvasElement', {
|
||||
configurable: true,
|
||||
value: {
|
||||
prototype: {
|
||||
transferControlToOffscreen: device.offscreenCanvas ? () => {} : undefined
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
it(`worker support is ${device.worker}`, () => {
|
||||
expect(BrowserSupport.isWorkerSupported()).toBe(device.worker);
|
||||
});
|
||||
|
||||
it(`offscreen canvas support is ${device.offscreenCanvas}`, () => {
|
||||
expect(BrowserSupport.isOffscreenCanvasSupported()).toBe(device.offscreenCanvas);
|
||||
});
|
||||
|
||||
it(`render mode is ${device.expectedMode}`, () => {
|
||||
expect(BrowserSupport.getRendererModeByPlatform()).toBe(device.expectedMode);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user