mirror of
https://github.com/zoriya/libpgs-js.git
synced 2026-08-27 00:00:51 +00:00
Refactored renderer to return pre-compiled render data without rendering it. Will be used for caching.
This commit is contained in:
+2
-15
@@ -1,4 +1,5 @@
|
||||
import {PgsRendererOptions} from "./pgsRendererOptions";
|
||||
import {PgsRendererHelper} from "./pgsRendererHelper";
|
||||
|
||||
/**
|
||||
* Renders PGS subtitle on-top of a video element using a canvas element. This also handles timestamp updates if a
|
||||
@@ -132,21 +133,7 @@ export class PgsRenderer {
|
||||
* @param time The timestamp in seconds.
|
||||
*/
|
||||
public renderAtTimestamp(time: number): void {
|
||||
time = time * 1000 * 90; // Convert to PGS time
|
||||
|
||||
// All position before and after the available timestamps are invalid (-1).
|
||||
let index = -1;
|
||||
if (this.updateTimestamps.length > 0 && time < this.updateTimestamps[this.updateTimestamps.length - 1]) {
|
||||
|
||||
// Find the last subtitle index for the given time stamp
|
||||
for (const updateTimestamp of this.updateTimestamps) {
|
||||
|
||||
if (updateTimestamp > time) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
const index = PgsRendererHelper.getIndexFromTimestamps(time, this.updateTimestamps);
|
||||
|
||||
// Only tell the worker, if the subtitle index was changed!
|
||||
if (this.previousTimestampIndex === index) return;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
export class PgsRendererHelper {
|
||||
/**
|
||||
* Returns the array index position for the previous timestamp position from the given array.
|
||||
* Returns -1 if the given time is outside the timestamp range.
|
||||
* @param time The timestamp to check in seconds.
|
||||
* @param pgsTimestamps The list of available PGS timestamps.
|
||||
*/
|
||||
public static getIndexFromTimestamps(time: number, pgsTimestamps: number[]): number {
|
||||
const pgsTime = time * 1000 * 90; // Convert to PGS time
|
||||
|
||||
// All position before and after the available timestamps are invalid (-1).
|
||||
let index = -1;
|
||||
if (pgsTimestamps.length > 0 && pgsTime < pgsTimestamps[pgsTimestamps.length - 1]) {
|
||||
|
||||
// Find the last subtitle index for the given time stamp
|
||||
for (const pgsTimestamp of pgsTimestamps) {
|
||||
|
||||
if (pgsTimestamp > pgsTime) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
}
|
||||
+30
-29
@@ -10,6 +10,8 @@ import {Rect} from "./utils/rect";
|
||||
import {StreamBinaryReader} from "./utils/streamBinaryReader";
|
||||
import {BinaryReader} from "./utils/binaryReader";
|
||||
import {ArrayBinaryReader} from "./utils/arrayBinaryReader";
|
||||
import {CompositionRenderData, RenderData} from "./renderData";
|
||||
import {PgsRendererHelper} from "./pgsRendererHelper";
|
||||
|
||||
export interface PgsLoadOptions {
|
||||
/**
|
||||
@@ -121,22 +123,7 @@ export class PgsRendererInternal {
|
||||
* @param time The timestamp in seconds.
|
||||
*/
|
||||
public renderAtTimestamp(time: number): void {
|
||||
time = time * 1000 * 90; // Convert to PGS time
|
||||
|
||||
// All position before and after the available timestamps are invalid (-1).
|
||||
let index = -1;
|
||||
if (this.updateTimestamps.length > 0 && time < this.updateTimestamps[this.updateTimestamps.length - 1]) {
|
||||
|
||||
// Find the last subtitle index for the given time stamp
|
||||
for (const updateTimestamp of this.updateTimestamps) {
|
||||
|
||||
if (updateTimestamp > time) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
const index = PgsRendererHelper.getIndexFromTimestamps(time, this.updateTimestamps);
|
||||
this.renderAtIndex(index);
|
||||
}
|
||||
|
||||
@@ -147,12 +134,31 @@ export class PgsRendererInternal {
|
||||
public renderAtIndex(index: number): void {
|
||||
if (!this.canvas || !this.context) return;
|
||||
// Clear the canvas on invalid indices. It is possible to seek to a position before the first subtitle while
|
||||
// a later subtile is on screen. This subtitle must be clear, even there is no valid new subtitle data.
|
||||
// a later subtitle is on screen. This subtitle must be clear, even there is no valid new subtitle data.
|
||||
// Ignoring the render would keep the previous subtitle on screen.
|
||||
if (!this.dirtyArea.empty) {
|
||||
this.context.clearRect(this.dirtyArea.x, this.dirtyArea.y, this.dirtyArea.width, this.dirtyArea.height);
|
||||
this.dirtyArea.reset();
|
||||
}
|
||||
|
||||
const renderData = this.buildRenderDataAtIndex(index);
|
||||
if (!renderData) return;
|
||||
|
||||
// Resize the canvas if needed.
|
||||
if (this.canvas.width != renderData.width ||
|
||||
this.canvas.height != renderData.height) {
|
||||
this.canvas.width = renderData.width;
|
||||
this.canvas.height = renderData.height;
|
||||
}
|
||||
|
||||
renderData.draw(this.context, this.dirtyArea);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-compiles the required render data (windows and pixel data) for the frame at the given index.
|
||||
* @param index The index of the display set to render.
|
||||
*/
|
||||
public buildRenderDataAtIndex(index: number): RenderData | undefined {
|
||||
if (index < 0 || index >= this.displaySets.length) {
|
||||
return;
|
||||
}
|
||||
@@ -160,13 +166,6 @@ export class PgsRendererInternal {
|
||||
const displaySet = this.displaySets[index];
|
||||
if (!displaySet.presentationComposition) return;
|
||||
|
||||
// Resize the canvas if needed.
|
||||
if (this.canvas.width != displaySet.presentationComposition.width ||
|
||||
this.canvas.height != displaySet.presentationComposition.height) {
|
||||
this.canvas.width = displaySet.presentationComposition.width;
|
||||
this.canvas.height = displaySet.presentationComposition.height;
|
||||
}
|
||||
|
||||
// We need to collect all valid objects and palettes up to this point. PGS can update and reuse elements from
|
||||
// previous display sets. The `compositionState` defines if the previous elements should be cleared.
|
||||
// If it is `0` previous elements should be kept. Because the user can seek through the file, we can not store
|
||||
@@ -196,6 +195,7 @@ export class PgsRendererInternal {
|
||||
.find(w => w.id === displaySet.presentationComposition?.paletteId);
|
||||
if (!palette) return;
|
||||
|
||||
const compositionData: CompositionRenderData[] = [];
|
||||
for (const compositionObject of displaySet.presentationComposition.compositionObjects) {
|
||||
// Find the window to draw on.
|
||||
let window = ctxWindows.find(w => w.id === compositionObject.windowId);
|
||||
@@ -204,13 +204,14 @@ export class PgsRendererInternal {
|
||||
// Builds the subtitle.
|
||||
const pixelData = this.getPixelDataFromComposition(compositionObject, palette, ctxObjects);
|
||||
if (pixelData) {
|
||||
this.context.drawImage(pixelData, window.horizontalPosition, window.verticalPosition);
|
||||
|
||||
// Mark this area as dirty.
|
||||
this.dirtyArea.union(window.horizontalPosition, window.verticalPosition,
|
||||
pixelData.width, pixelData.height);
|
||||
compositionData.push(new CompositionRenderData(window, pixelData));
|
||||
}
|
||||
}
|
||||
|
||||
if (compositionData.length === 0) return;
|
||||
|
||||
return new RenderData(displaySet.presentationComposition.width, displaySet.presentationComposition.height,
|
||||
compositionData);
|
||||
}
|
||||
|
||||
private createCanvas(width: number, height: number): OffscreenCanvas | HTMLCanvasElement {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import {WindowDefinition} from "./pgs/windowDefinitionSegment";
|
||||
import {Rect} from "./utils/rect";
|
||||
|
||||
/**
|
||||
* This class contains the compiled subtitle data for a whole frame.
|
||||
*/
|
||||
export class RenderData {
|
||||
/**
|
||||
* The total width of the presentation composition (screen).
|
||||
*/
|
||||
public readonly width: number;
|
||||
|
||||
/**
|
||||
* The total height of the presentation composition (screen).
|
||||
*/
|
||||
public readonly height: number;
|
||||
|
||||
/**
|
||||
* The pre-compiled composition elements.
|
||||
*/
|
||||
public readonly compositionData: CompositionRenderData[];
|
||||
|
||||
public constructor(width: number, height: number, compositionData: CompositionRenderData[]) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.compositionData = compositionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the whole subtitle frame to the given context.
|
||||
* @param context The context to draw on.
|
||||
* @param dirtyArea If given, it will extend the dirty rect to include the affected subtitle area.
|
||||
*/
|
||||
public draw(context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, dirtyArea?: Rect): void {
|
||||
for (const composition of this.compositionData) {
|
||||
composition.draw(context, dirtyArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class contains the compiled subtitle data for a single composition.
|
||||
*/
|
||||
export class CompositionRenderData {
|
||||
/**
|
||||
* The pgs window to draw on (the on-screen position).
|
||||
*/
|
||||
public readonly window: WindowDefinition;
|
||||
|
||||
/**
|
||||
* The compiled pixel data of the subtitle.
|
||||
*/
|
||||
public readonly pixelData: HTMLCanvasElement | OffscreenCanvas;
|
||||
|
||||
public constructor(window: WindowDefinition, pixelData: HTMLCanvasElement | OffscreenCanvas) {
|
||||
this.window = window;
|
||||
this.pixelData = pixelData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws this subtitle composition to the given context.
|
||||
* @param context The context to draw on.
|
||||
* @param dirtyArea If given, it will extend the dirty rect to include the affected subtitle area.
|
||||
*/
|
||||
public draw(context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, dirtyArea?: Rect): void {
|
||||
context.drawImage(this.pixelData, this.window.horizontalPosition, this.window.verticalPosition);
|
||||
|
||||
// Mark this area as dirty.
|
||||
dirtyArea?.union(this.window.horizontalPosition, this.window.verticalPosition,
|
||||
this.pixelData.width, this.pixelData.height);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user