@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.
Files changed (48) hide show
  1. package/dist/compositor/compositor.d.ts.map +1 -1
  2. package/dist/compositor-worker.js +1 -1
  3. package/dist/index.js +1 -1
  4. package/package.json +4 -3
  5. package/src/compositor/audio-manager.ts +411 -0
  6. package/src/compositor/compositor-worker.ts +158 -0
  7. package/src/compositor/compositor.ts +931 -0
  8. package/src/compositor/index.ts +19 -0
  9. package/src/compositor/source-pool.ts +450 -0
  10. package/src/compositor/types.ts +103 -0
  11. package/src/compositor/worker-client.ts +139 -0
  12. package/src/compositor/worker-types.ts +67 -0
  13. package/src/core/player-core.ts +273 -0
  14. package/src/core/state-facade.ts +98 -0
  15. package/src/core/track-switcher.ts +127 -0
  16. package/src/events/emitter.ts +137 -0
  17. package/src/events/types.ts +24 -0
  18. package/src/index.ts +124 -0
  19. package/src/mediafox.ts +642 -0
  20. package/src/playback/audio.ts +361 -0
  21. package/src/playback/controller.ts +446 -0
  22. package/src/playback/renderer.ts +1176 -0
  23. package/src/playback/renderers/canvas2d.ts +128 -0
  24. package/src/playback/renderers/factory.ts +172 -0
  25. package/src/playback/renderers/index.ts +5 -0
  26. package/src/playback/renderers/types.ts +57 -0
  27. package/src/playback/renderers/webgl.ts +373 -0
  28. package/src/playback/renderers/webgpu.ts +395 -0
  29. package/src/playlist/manager.ts +268 -0
  30. package/src/plugins/context.ts +93 -0
  31. package/src/plugins/index.ts +15 -0
  32. package/src/plugins/manager.ts +482 -0
  33. package/src/plugins/types.ts +243 -0
  34. package/src/sources/manager.ts +285 -0
  35. package/src/sources/source.ts +84 -0
  36. package/src/sources/types.ts +17 -0
  37. package/src/state/store.ts +389 -0
  38. package/src/state/types.ts +18 -0
  39. package/src/tracks/manager.ts +421 -0
  40. package/src/tracks/types.ts +30 -0
  41. package/src/types/jassub.d.ts +1 -0
  42. package/src/types.ts +235 -0
  43. package/src/utils/async-lock.ts +26 -0
  44. package/src/utils/dispose.ts +28 -0
  45. package/src/utils/equal.ts +33 -0
  46. package/src/utils/errors.ts +74 -0
  47. package/src/utils/logger.ts +50 -0
  48. 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
+ }