diff --git a/README.md b/README.md index 3fc8ebf..57e96cc 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,14 @@ const pgsRenderer = new libpgs.PgsRenderer({ }); ``` +### Awaiting the subtitle + +`ready` resolves once the subtitle file can be rendered, and every `loadFrom*` call returns that same promise: + +```javascript +await pgsRenderer.ready; +``` + ### Time offset You can also adjust time offset between video and subtitle: diff --git a/src/pgsRenderer.ts b/src/pgsRenderer.ts index ec04103..4ad38d4 100644 --- a/src/pgsRenderer.ts +++ b/src/pgsRenderer.ts @@ -47,7 +47,9 @@ export class PgsRenderer { this.aspectRatio = options.aspectRatio; } if (options.subUrl) { - this.loadFromUrl(options.subUrl); + // A failed load is reported through `ready`. It is handled here too, + // or callers that never await it would get an unhandled rejection. + this.loadFromUrl(options.subUrl).catch(() => {}); } this.registerVideoEvents(); @@ -72,20 +74,27 @@ export class PgsRenderer { private implementation: PgsRendererImpl; + /** + * Resolves once the last loaded subtitle file - `subUrl` included - can be rendered. + */ + public ready: Promise = Promise.resolve(); + /** * Loads the subtitle file from the given url. * @param url The url to the PGS file. */ - public loadFromUrl(url: string): void { - this.implementation.loadFromUrl(url); + public loadFromUrl(url: string): Promise { + this.ready = this.implementation.loadFromUrl(url); + return this.ready; } /** * Loads the subtitle file from the given buffer. * @param buffer The PGS data. */ - public loadFromBuffer(buffer: ArrayBuffer): void { - this.implementation.loadFromBuffer(buffer); + public loadFromBuffer(buffer: ArrayBuffer): Promise { + this.ready = this.implementation.loadFromBuffer(buffer); + return this.ready; } /** diff --git a/src/pgsRendererImpl.ts b/src/pgsRendererImpl.ts index 3cddda0..483ec8f 100644 --- a/src/pgsRendererImpl.ts +++ b/src/pgsRendererImpl.ts @@ -61,13 +61,13 @@ export abstract class PgsRendererImpl { * Loads the subtitle file from the given url. * @param url The url to the PGS file. */ - public abstract loadFromUrl(url: string): void; + public abstract loadFromUrl(url: string): Promise; /** * Loads the subtitle file from the given buffer. * @param buffer The PGS data. */ - public abstract loadFromBuffer(buffer: ArrayBuffer): void; + public abstract loadFromBuffer(buffer: ArrayBuffer): Promise; /** * Disposes the renderer. diff --git a/src/pgsRendererInMainThread.ts b/src/pgsRendererInMainThread.ts index 30f72f4..e386f15 100644 --- a/src/pgsRendererInMainThread.ts +++ b/src/pgsRendererInMainThread.ts @@ -35,8 +35,8 @@ export class PgsRendererInMainThread extends PgsRendererImpl { this.pgs.cacheSubtitleAtIndex(index + 1); } - public loadFromUrl(url: string): void { - this.pgs.loadFromUrl(url, { + public loadFromUrl(url: string): Promise { + return this.pgs.loadFromUrl(url, { onProgress: () => { this.invokeTimestampsUpdate(); } @@ -45,8 +45,8 @@ export class PgsRendererInMainThread extends PgsRendererImpl { }); } - public loadFromBuffer(buffer: ArrayBuffer): void { - this.pgs.loadFromBuffer(buffer).then(() => { + public loadFromBuffer(buffer: ArrayBuffer): Promise { + return this.pgs.loadFromBuffer(buffer).then(() => { this.invokeTimestampsUpdate(); }); } diff --git a/src/pgsRendererInWorker.ts b/src/pgsRendererInWorker.ts index 5ebf7fb..7753d36 100644 --- a/src/pgsRendererInWorker.ts +++ b/src/pgsRendererInWorker.ts @@ -15,18 +15,20 @@ export abstract class PgsRendererInWorker extends PgsRendererImpl { this.worker.onmessage = this.$onWorkerMessage; } - public loadFromUrl(url: string): void { + public loadFromUrl(url: string): Promise { this.worker.postMessage({ op: 'loadFromUrl', url: url, }); + return new Promise((resolve, reject) => this.pendingLoads.push({resolve: resolve, reject: reject})); } - public loadFromBuffer(buffer: ArrayBuffer): void { + public loadFromBuffer(buffer: ArrayBuffer): Promise { this.worker.postMessage({ op: 'loadFromBuffer', buffer: buffer, }); + return new Promise((resolve, reject) => this.pendingLoads.push({resolve: resolve, reject: reject})); } /** @@ -34,6 +36,11 @@ export abstract class PgsRendererInWorker extends PgsRendererImpl { */ protected readonly worker: Worker; + /** + * The loads the worker still has to answer, in the order it answers them. + */ + private readonly pendingLoads: { resolve: () => void, reject: (error: Error) => void }[] = []; + /** * Handles messages from the worker. * @param e The event message. @@ -53,6 +60,17 @@ export abstract class PgsRendererInWorker extends PgsRendererImpl { this.setUpdateTimestamps(e.data.updateTimestamps); break; } + + // Is called once a subtitle file was fully loaded, or failed to load. + case 'loaded': { + const pending = this.pendingLoads.shift(); + if (e.data.error) { + pending?.reject(new Error(e.data.error)); + } else { + pending?.resolve(); + } + break; + } } } @@ -61,5 +79,9 @@ export abstract class PgsRendererInWorker extends PgsRendererImpl { */ public dispose(): void { this.worker.terminate(); + // No answer is coming anymore, and being disposed is not a load failure. + for (const pending of this.pendingLoads) { + pending.resolve(); + } } } diff --git a/src/worker.ts b/src/worker.ts index fa62009..35fee20 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -17,6 +17,14 @@ const submitTimestamps = () => { }) } +// Inform the main process that the subtitle file was fully loaded, or failed to load. +const submitLoaded = (error?: string) => { + postMessage({ + op: 'loaded', + error: error + }) +} + // Handles messages from the main thread. onmessage = (e: MessageEvent) => { switch (e.data.op) { @@ -41,6 +49,9 @@ onmessage = (e: MessageEvent) => { } }).then(() => { submitTimestamps(); + submitLoaded(); + }).catch((e: unknown) => { + submitLoaded(`${e}`); }); break; } @@ -50,6 +61,9 @@ onmessage = (e: MessageEvent) => { const buffer: ArrayBuffer = e.data.buffer; pgs.loadFromBuffer(buffer).then(() => { submitTimestamps(); + submitLoaded(); + }).catch((e: unknown) => { + submitLoaded(`${e}`); }); break; diff --git a/tsconfig.json b/tsconfig.json index 2da48e5..222e22b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "extends": "@tsconfig/recommended/tsconfig.json", "compilerOptions": { "target": "es5", + "lib": ["ES2015", "DOM"], "module": "esnext", "esModuleInterop": true, "forceConsistentCasingInFileNames": true,