@mediafox/core 1.2.8 → 1.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compositor/compositor.d.ts.map +1 -1
- package/dist/compositor-worker.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +4 -3
- package/src/compositor/audio-manager.ts +411 -0
- package/src/compositor/compositor-worker.ts +158 -0
- package/src/compositor/compositor.ts +931 -0
- package/src/compositor/index.ts +19 -0
- package/src/compositor/source-pool.ts +450 -0
- package/src/compositor/types.ts +103 -0
- package/src/compositor/worker-client.ts +139 -0
- package/src/compositor/worker-types.ts +67 -0
- package/src/core/player-core.ts +273 -0
- package/src/core/state-facade.ts +98 -0
- package/src/core/track-switcher.ts +127 -0
- package/src/events/emitter.ts +137 -0
- package/src/events/types.ts +24 -0
- package/src/index.ts +124 -0
- package/src/mediafox.ts +642 -0
- package/src/playback/audio.ts +361 -0
- package/src/playback/controller.ts +446 -0
- package/src/playback/renderer.ts +1176 -0
- package/src/playback/renderers/canvas2d.ts +128 -0
- package/src/playback/renderers/factory.ts +172 -0
- package/src/playback/renderers/index.ts +5 -0
- package/src/playback/renderers/types.ts +57 -0
- package/src/playback/renderers/webgl.ts +373 -0
- package/src/playback/renderers/webgpu.ts +395 -0
- package/src/playlist/manager.ts +268 -0
- package/src/plugins/context.ts +93 -0
- package/src/plugins/index.ts +15 -0
- package/src/plugins/manager.ts +482 -0
- package/src/plugins/types.ts +243 -0
- package/src/sources/manager.ts +285 -0
- package/src/sources/source.ts +84 -0
- package/src/sources/types.ts +17 -0
- package/src/state/store.ts +389 -0
- package/src/state/types.ts +18 -0
- package/src/tracks/manager.ts +421 -0
- package/src/tracks/types.ts +30 -0
- package/src/types/jassub.d.ts +1 -0
- package/src/types.ts +235 -0
- package/src/utils/async-lock.ts +26 -0
- package/src/utils/dispose.ts +28 -0
- package/src/utils/equal.ts +33 -0
- package/src/utils/errors.ts +74 -0
- package/src/utils/logger.ts +50 -0
- package/src/utils/time.ts +157 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import type { IRenderer, Rotation } from './types';
|
|
2
|
+
|
|
3
|
+
export interface WebGLRendererOptions {
|
|
4
|
+
canvas: HTMLCanvasElement | OffscreenCanvas;
|
|
5
|
+
alpha?: boolean;
|
|
6
|
+
antialias?: boolean;
|
|
7
|
+
preserveDrawingBuffer?: boolean;
|
|
8
|
+
powerPreference?: 'high-performance' | 'low-power' | 'default';
|
|
9
|
+
rotation?: Rotation;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface WebGLResources {
|
|
13
|
+
gl: WebGLRenderingContext | null;
|
|
14
|
+
program: WebGLProgram | null;
|
|
15
|
+
texture: WebGLTexture | null;
|
|
16
|
+
vertexBuffer: WebGLBuffer | null;
|
|
17
|
+
texCoordBuffer: WebGLBuffer | null;
|
|
18
|
+
positionLocation: number;
|
|
19
|
+
texCoordLocation: number;
|
|
20
|
+
textureLocation: WebGLUniformLocation | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class WebGLRenderer implements IRenderer {
|
|
24
|
+
private resources: WebGLResources;
|
|
25
|
+
private isInitialized = false;
|
|
26
|
+
private canvas: HTMLCanvasElement | OffscreenCanvas;
|
|
27
|
+
private textureWidth = 0;
|
|
28
|
+
private textureHeight = 0;
|
|
29
|
+
private options: WebGLRendererOptions;
|
|
30
|
+
private boundHandleContextLost: ((event: Event) => void) | null = null;
|
|
31
|
+
private boundHandleContextRestored: (() => void) | null = null;
|
|
32
|
+
private rotation: Rotation = 0;
|
|
33
|
+
// Pre-allocated typed array for vertex positions to avoid GC at 60fps
|
|
34
|
+
private positionsArray = new Float32Array(8);
|
|
35
|
+
|
|
36
|
+
private readonly vertexShaderSource = `
|
|
37
|
+
attribute vec2 a_position;
|
|
38
|
+
attribute vec2 a_texCoord;
|
|
39
|
+
varying vec2 v_texCoord;
|
|
40
|
+
|
|
41
|
+
void main() {
|
|
42
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
43
|
+
v_texCoord = a_texCoord;
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
private readonly fragmentShaderSource = `
|
|
48
|
+
precision mediump float;
|
|
49
|
+
uniform sampler2D u_texture;
|
|
50
|
+
varying vec2 v_texCoord;
|
|
51
|
+
|
|
52
|
+
void main() {
|
|
53
|
+
vec4 color = texture2D(u_texture, v_texCoord);
|
|
54
|
+
gl_FragColor = color;
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
constructor(options: WebGLRendererOptions) {
|
|
59
|
+
this.canvas = options.canvas;
|
|
60
|
+
this.options = options;
|
|
61
|
+
this.rotation = options.rotation ?? 0;
|
|
62
|
+
this.resources = {
|
|
63
|
+
gl: null,
|
|
64
|
+
program: null,
|
|
65
|
+
texture: null,
|
|
66
|
+
vertexBuffer: null,
|
|
67
|
+
texCoordBuffer: null,
|
|
68
|
+
positionLocation: -1,
|
|
69
|
+
texCoordLocation: -1,
|
|
70
|
+
textureLocation: null,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
this.initialize();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private initialize(): boolean {
|
|
77
|
+
try {
|
|
78
|
+
const contextOptions: WebGLContextAttributes = {
|
|
79
|
+
alpha: this.options.alpha ?? false,
|
|
80
|
+
antialias: this.options.antialias ?? false,
|
|
81
|
+
depth: false,
|
|
82
|
+
stencil: false,
|
|
83
|
+
preserveDrawingBuffer: this.options.preserveDrawingBuffer ?? false,
|
|
84
|
+
powerPreference: this.options.powerPreference ?? 'high-performance',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
let gl = this.canvas.getContext('webgl', contextOptions) as WebGLRenderingContext | null;
|
|
88
|
+
|
|
89
|
+
// Try experimental-webgl for HTMLCanvasElement only
|
|
90
|
+
if (!gl && 'getContext' in this.canvas) {
|
|
91
|
+
const canvas = this.canvas as HTMLCanvasElement;
|
|
92
|
+
gl = canvas.getContext('experimental-webgl', contextOptions) as WebGLRenderingContext | null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!gl) return false;
|
|
96
|
+
this.resources.gl = gl;
|
|
97
|
+
|
|
98
|
+
const vertexShader = this.createShader(gl, gl.VERTEX_SHADER, this.vertexShaderSource);
|
|
99
|
+
const fragmentShader = this.createShader(gl, gl.FRAGMENT_SHADER, this.fragmentShaderSource);
|
|
100
|
+
|
|
101
|
+
if (!vertexShader || !fragmentShader) {
|
|
102
|
+
throw new Error('Failed to create shaders');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const program = gl.createProgram();
|
|
106
|
+
if (!program) throw new Error('Failed to create program');
|
|
107
|
+
|
|
108
|
+
gl.attachShader(program, vertexShader);
|
|
109
|
+
gl.attachShader(program, fragmentShader);
|
|
110
|
+
gl.linkProgram(program);
|
|
111
|
+
|
|
112
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
113
|
+
const error = gl.getProgramInfoLog(program);
|
|
114
|
+
throw new Error(`Failed to link program: ${error}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.resources.program = program;
|
|
118
|
+
|
|
119
|
+
this.resources.positionLocation = gl.getAttribLocation(program, 'a_position');
|
|
120
|
+
this.resources.texCoordLocation = gl.getAttribLocation(program, 'a_texCoord');
|
|
121
|
+
this.resources.textureLocation = gl.getUniformLocation(program, 'u_texture');
|
|
122
|
+
|
|
123
|
+
this.setupQuadBuffers(gl);
|
|
124
|
+
|
|
125
|
+
const texture = gl.createTexture();
|
|
126
|
+
if (!texture) throw new Error('Failed to create texture');
|
|
127
|
+
|
|
128
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
129
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
130
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
131
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
132
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
133
|
+
|
|
134
|
+
this.resources.texture = texture;
|
|
135
|
+
|
|
136
|
+
gl.disable(gl.DEPTH_TEST);
|
|
137
|
+
gl.disable(gl.CULL_FACE);
|
|
138
|
+
gl.disable(gl.BLEND);
|
|
139
|
+
|
|
140
|
+
// Store bound event handlers for proper cleanup
|
|
141
|
+
if ('addEventListener' in this.canvas) {
|
|
142
|
+
this.boundHandleContextLost = this.handleContextLost.bind(this);
|
|
143
|
+
this.boundHandleContextRestored = this.handleContextRestored.bind(this);
|
|
144
|
+
this.canvas.addEventListener('webglcontextlost', this.boundHandleContextLost, false);
|
|
145
|
+
this.canvas.addEventListener('webglcontextrestored', this.boundHandleContextRestored, false);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.isInitialized = true;
|
|
149
|
+
return true;
|
|
150
|
+
} catch {
|
|
151
|
+
this.cleanup();
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | null {
|
|
157
|
+
const shader = gl.createShader(type);
|
|
158
|
+
if (!shader) return null;
|
|
159
|
+
|
|
160
|
+
gl.shaderSource(shader, source);
|
|
161
|
+
gl.compileShader(shader);
|
|
162
|
+
|
|
163
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
164
|
+
gl.deleteShader(shader);
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return shader;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private setupQuadBuffers(gl: WebGLRenderingContext): void {
|
|
172
|
+
const positions = new Float32Array([-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0]);
|
|
173
|
+
|
|
174
|
+
const texCoords = new Float32Array([0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0]);
|
|
175
|
+
|
|
176
|
+
const positionBuffer = gl.createBuffer();
|
|
177
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
178
|
+
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
|
|
179
|
+
this.resources.vertexBuffer = positionBuffer;
|
|
180
|
+
|
|
181
|
+
const texCoordBuffer = gl.createBuffer();
|
|
182
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
|
|
183
|
+
gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);
|
|
184
|
+
this.resources.texCoordBuffer = texCoordBuffer;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public isReady(): boolean {
|
|
188
|
+
return this.isInitialized && this.resources.gl !== null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public render(source: HTMLCanvasElement | OffscreenCanvas): boolean {
|
|
192
|
+
if (!this.isInitialized || !this.resources.gl) return false;
|
|
193
|
+
|
|
194
|
+
const gl = this.resources.gl;
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const sourceWidth = source.width;
|
|
198
|
+
const sourceHeight = source.height;
|
|
199
|
+
|
|
200
|
+
if (sourceWidth === 0 || sourceHeight === 0) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const canvasWidth = this.canvas.width;
|
|
205
|
+
const canvasHeight = this.canvas.height;
|
|
206
|
+
|
|
207
|
+
if (canvasWidth === 0 || canvasHeight === 0) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
gl.viewport(0, 0, canvasWidth, canvasHeight);
|
|
212
|
+
|
|
213
|
+
// Upload texture
|
|
214
|
+
gl.bindTexture(gl.TEXTURE_2D, this.resources.texture);
|
|
215
|
+
|
|
216
|
+
if (sourceWidth !== this.textureWidth || sourceHeight !== this.textureHeight) {
|
|
217
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
|
|
218
|
+
this.textureWidth = sourceWidth;
|
|
219
|
+
this.textureHeight = sourceHeight;
|
|
220
|
+
} else {
|
|
221
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, source);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Clear with black background for letterboxing
|
|
225
|
+
gl.clearColor(0.0, 0.0, 0.0, 1.0);
|
|
226
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
227
|
+
|
|
228
|
+
// For 90/270 rotation, swap source dimensions for aspect ratio calculation
|
|
229
|
+
const isRotated90or270 = this.rotation === 90 || this.rotation === 270;
|
|
230
|
+
const effectiveWidth = isRotated90or270 ? this.textureHeight : this.textureWidth;
|
|
231
|
+
const effectiveHeight = isRotated90or270 ? this.textureWidth : this.textureHeight;
|
|
232
|
+
|
|
233
|
+
// Calculate letterbox dimensions to preserve aspect ratio
|
|
234
|
+
const scale = Math.min(canvasWidth / effectiveWidth, canvasHeight / effectiveHeight);
|
|
235
|
+
const drawW = Math.round(effectiveWidth * scale);
|
|
236
|
+
const drawH = Math.round(effectiveHeight * scale);
|
|
237
|
+
const x = Math.round((canvasWidth - drawW) / 2);
|
|
238
|
+
const y = Math.round((canvasHeight - drawH) / 2);
|
|
239
|
+
|
|
240
|
+
// Calculate clip-space coordinates
|
|
241
|
+
const left = (x / canvasWidth) * 2 - 1;
|
|
242
|
+
const right = ((x + drawW) / canvasWidth) * 2 - 1;
|
|
243
|
+
const top = 1 - (y / canvasHeight) * 2;
|
|
244
|
+
const bottom = 1 - ((y + drawH) / canvasHeight) * 2;
|
|
245
|
+
|
|
246
|
+
// Calculate center of quad
|
|
247
|
+
const cx = (left + right) / 2;
|
|
248
|
+
const cy = (top + bottom) / 2;
|
|
249
|
+
const hw = (right - left) / 2;
|
|
250
|
+
const hh = (top - bottom) / 2;
|
|
251
|
+
|
|
252
|
+
// Apply rotation by rotating vertex positions
|
|
253
|
+
const rad = (this.rotation * Math.PI) / 180;
|
|
254
|
+
const cos = Math.cos(rad);
|
|
255
|
+
const sin = Math.sin(rad);
|
|
256
|
+
|
|
257
|
+
// For rotated quads, we need to swap half-width/half-height
|
|
258
|
+
const rhw = isRotated90or270 ? hh : hw;
|
|
259
|
+
const rhh = isRotated90or270 ? hw : hh;
|
|
260
|
+
|
|
261
|
+
// Calculate rotated corner positions using pre-allocated array
|
|
262
|
+
// Corner order: bottom-left, bottom-right, top-left, top-right
|
|
263
|
+
const positions = this.positionsArray;
|
|
264
|
+
// bottom-left (-rhw, -rhh)
|
|
265
|
+
positions[0] = -rhw * cos - -rhh * sin + cx;
|
|
266
|
+
positions[1] = -rhw * sin + -rhh * cos + cy;
|
|
267
|
+
// bottom-right (rhw, -rhh)
|
|
268
|
+
positions[2] = rhw * cos - -rhh * sin + cx;
|
|
269
|
+
positions[3] = rhw * sin + -rhh * cos + cy;
|
|
270
|
+
// top-left (-rhw, rhh)
|
|
271
|
+
positions[4] = -rhw * cos - rhh * sin + cx;
|
|
272
|
+
positions[5] = -rhw * sin + rhh * cos + cy;
|
|
273
|
+
// top-right (rhw, rhh)
|
|
274
|
+
positions[6] = rhw * cos - rhh * sin + cx;
|
|
275
|
+
positions[7] = rhw * sin + rhh * cos + cy;
|
|
276
|
+
|
|
277
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.resources.vertexBuffer);
|
|
278
|
+
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.DYNAMIC_DRAW);
|
|
279
|
+
|
|
280
|
+
// Render
|
|
281
|
+
gl.useProgram(this.resources.program);
|
|
282
|
+
|
|
283
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.resources.vertexBuffer);
|
|
284
|
+
gl.enableVertexAttribArray(this.resources.positionLocation);
|
|
285
|
+
gl.vertexAttribPointer(this.resources.positionLocation, 2, gl.FLOAT, false, 0, 0);
|
|
286
|
+
|
|
287
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.resources.texCoordBuffer);
|
|
288
|
+
gl.enableVertexAttribArray(this.resources.texCoordLocation);
|
|
289
|
+
gl.vertexAttribPointer(this.resources.texCoordLocation, 2, gl.FLOAT, false, 0, 0);
|
|
290
|
+
|
|
291
|
+
gl.uniform1i(this.resources.textureLocation, 0);
|
|
292
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
293
|
+
|
|
294
|
+
return true;
|
|
295
|
+
} catch {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
public clear(): void {
|
|
301
|
+
if (!this.resources.gl) return;
|
|
302
|
+
|
|
303
|
+
const gl = this.resources.gl;
|
|
304
|
+
gl.clearColor(0.0, 0.0, 0.0, 1.0);
|
|
305
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private handleContextLost(event: Event): void {
|
|
309
|
+
event.preventDefault();
|
|
310
|
+
this.isInitialized = false;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private handleContextRestored(): void {
|
|
314
|
+
this.initialize();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private cleanup(): void {
|
|
318
|
+
const gl = this.resources.gl;
|
|
319
|
+
if (!gl) return;
|
|
320
|
+
|
|
321
|
+
if (this.resources.texture) gl.deleteTexture(this.resources.texture);
|
|
322
|
+
if (this.resources.vertexBuffer) gl.deleteBuffer(this.resources.vertexBuffer);
|
|
323
|
+
if (this.resources.texCoordBuffer) gl.deleteBuffer(this.resources.texCoordBuffer);
|
|
324
|
+
if (this.resources.program) gl.deleteProgram(this.resources.program);
|
|
325
|
+
|
|
326
|
+
this.resources = {
|
|
327
|
+
gl: null,
|
|
328
|
+
program: null,
|
|
329
|
+
texture: null,
|
|
330
|
+
vertexBuffer: null,
|
|
331
|
+
texCoordBuffer: null,
|
|
332
|
+
positionLocation: -1,
|
|
333
|
+
texCoordLocation: -1,
|
|
334
|
+
textureLocation: null,
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
this.isInitialized = false;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
public setRotation(rotation: Rotation): void {
|
|
341
|
+
this.rotation = rotation;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public getRotation(): Rotation {
|
|
345
|
+
return this.rotation;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
public dispose(): void {
|
|
349
|
+
if (this.resources.gl) {
|
|
350
|
+
const loseExt = this.resources.gl.getExtension('WEBGL_lose_context') as { loseContext: () => void } | null;
|
|
351
|
+
|
|
352
|
+
if (loseExt) {
|
|
353
|
+
try {
|
|
354
|
+
loseExt.loseContext();
|
|
355
|
+
} catch {}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
this.cleanup();
|
|
360
|
+
|
|
361
|
+
// Remove event listeners using stored references
|
|
362
|
+
if ('removeEventListener' in this.canvas) {
|
|
363
|
+
if (this.boundHandleContextLost) {
|
|
364
|
+
this.canvas.removeEventListener('webglcontextlost', this.boundHandleContextLost);
|
|
365
|
+
this.boundHandleContextLost = null;
|
|
366
|
+
}
|
|
367
|
+
if (this.boundHandleContextRestored) {
|
|
368
|
+
this.canvas.removeEventListener('webglcontextrestored', this.boundHandleContextRestored);
|
|
369
|
+
this.boundHandleContextRestored = null;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|