Added a ready promise to await the subtitle file being loaded

This commit is contained in:
2026-08-26 19:19:07 +02:00
parent 9c06f0fd00
commit 6f79b9aa47
7 changed files with 67 additions and 13 deletions
+8
View File
@@ -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:
+14 -5
View File
@@ -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<void> = 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<void> {
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<void> {
this.ready = this.implementation.loadFromBuffer(buffer);
return this.ready;
}
/**
+2 -2
View File
@@ -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<void>;
/**
* 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<void>;
/**
* Disposes the renderer.
+4 -4
View File
@@ -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<void> {
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<void> {
return this.pgs.loadFromBuffer(buffer).then(() => {
this.invokeTimestampsUpdate();
});
}
+24 -2
View File
@@ -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<void> {
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<void> {
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();
}
}
}
+14
View File
@@ -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;
+1
View File
@@ -2,6 +2,7 @@
"extends": "@tsconfig/recommended/tsconfig.json",
"compilerOptions": {
"target": "es5",
"lib": ["ES2015", "DOM"],
"module": "esnext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,