@mediafox/core 1.2.9 → 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 (46) hide show
  1. package/dist/compositor-worker.js +1 -227
  2. package/package.json +5 -4
  3. package/src/compositor/audio-manager.ts +411 -0
  4. package/src/compositor/compositor-worker.ts +158 -0
  5. package/src/compositor/compositor.ts +931 -0
  6. package/src/compositor/index.ts +19 -0
  7. package/src/compositor/source-pool.ts +450 -0
  8. package/src/compositor/types.ts +103 -0
  9. package/src/compositor/worker-client.ts +139 -0
  10. package/src/compositor/worker-types.ts +67 -0
  11. package/src/core/player-core.ts +273 -0
  12. package/src/core/state-facade.ts +98 -0
  13. package/src/core/track-switcher.ts +127 -0
  14. package/src/events/emitter.ts +137 -0
  15. package/src/events/types.ts +24 -0
  16. package/src/index.ts +124 -0
  17. package/src/mediafox.ts +642 -0
  18. package/src/playback/audio.ts +361 -0
  19. package/src/playback/controller.ts +446 -0
  20. package/src/playback/renderer.ts +1176 -0
  21. package/src/playback/renderers/canvas2d.ts +128 -0
  22. package/src/playback/renderers/factory.ts +172 -0
  23. package/src/playback/renderers/index.ts +5 -0
  24. package/src/playback/renderers/types.ts +57 -0
  25. package/src/playback/renderers/webgl.ts +373 -0
  26. package/src/playback/renderers/webgpu.ts +395 -0
  27. package/src/playlist/manager.ts +268 -0
  28. package/src/plugins/context.ts +93 -0
  29. package/src/plugins/index.ts +15 -0
  30. package/src/plugins/manager.ts +482 -0
  31. package/src/plugins/types.ts +243 -0
  32. package/src/sources/manager.ts +285 -0
  33. package/src/sources/source.ts +84 -0
  34. package/src/sources/types.ts +17 -0
  35. package/src/state/store.ts +389 -0
  36. package/src/state/types.ts +18 -0
  37. package/src/tracks/manager.ts +421 -0
  38. package/src/tracks/types.ts +30 -0
  39. package/src/types/jassub.d.ts +1 -0
  40. package/src/types.ts +235 -0
  41. package/src/utils/async-lock.ts +26 -0
  42. package/src/utils/dispose.ts +28 -0
  43. package/src/utils/equal.ts +33 -0
  44. package/src/utils/errors.ts +74 -0
  45. package/src/utils/logger.ts +50 -0
  46. package/src/utils/time.ts +157 -0
@@ -0,0 +1,128 @@
1
+ import type { IRenderer, Rotation } from './types';
2
+
3
+ export interface Canvas2DRendererOptions {
4
+ canvas: HTMLCanvasElement | OffscreenCanvas;
5
+ rotation?: Rotation;
6
+ }
7
+
8
+ export class Canvas2DRenderer implements IRenderer {
9
+ private canvas: HTMLCanvasElement | OffscreenCanvas;
10
+ private ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null = null;
11
+ private isInitialized = false;
12
+ private rotation: Rotation = 0;
13
+
14
+ constructor(options: Canvas2DRendererOptions) {
15
+ this.canvas = options.canvas;
16
+ this.rotation = options.rotation ?? 0;
17
+ this.initialize();
18
+ }
19
+
20
+ private initialize(): boolean {
21
+ try {
22
+ this.ctx = this.canvas.getContext('2d', {
23
+ alpha: false,
24
+ desynchronized: true,
25
+ }) as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null;
26
+
27
+ if (!this.ctx) {
28
+ return false;
29
+ }
30
+
31
+ this.ctx.imageSmoothingEnabled = true;
32
+ this.ctx.imageSmoothingQuality = 'high';
33
+
34
+ this.isInitialized = true;
35
+ return true;
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ public isReady(): boolean {
42
+ return this.isInitialized && this.ctx !== null;
43
+ }
44
+
45
+ public render(source: HTMLCanvasElement | OffscreenCanvas): boolean {
46
+ if (!this.isReady() || !this.ctx) {
47
+ return false;
48
+ }
49
+
50
+ try {
51
+ const sourceWidth = source.width;
52
+ const sourceHeight = source.height;
53
+
54
+ if (sourceWidth === 0 || sourceHeight === 0) {
55
+ return false;
56
+ }
57
+
58
+ const canvasWidth = this.canvas.width;
59
+ const canvasHeight = this.canvas.height;
60
+
61
+ if (canvasWidth === 0 || canvasHeight === 0) {
62
+ return false;
63
+ }
64
+
65
+ // For 90/270 rotation, swap source dimensions for aspect ratio calculation
66
+ const isRotated90or270 = this.rotation === 90 || this.rotation === 270;
67
+ const effectiveSourceWidth = isRotated90or270 ? sourceHeight : sourceWidth;
68
+ const effectiveSourceHeight = isRotated90or270 ? sourceWidth : sourceHeight;
69
+
70
+ // Calculate letterbox dimensions to preserve aspect ratio
71
+ const scale = Math.min(canvasWidth / effectiveSourceWidth, canvasHeight / effectiveSourceHeight);
72
+ const destW = Math.round(effectiveSourceWidth * scale);
73
+ const destH = Math.round(effectiveSourceHeight * scale);
74
+ const dx = Math.round((canvasWidth - destW) / 2);
75
+ const dy = Math.round((canvasHeight - destH) / 2);
76
+
77
+ // Clear and fill with black for letterboxing
78
+ this.ctx.fillStyle = 'black';
79
+ this.ctx.fillRect(0, 0, canvasWidth, canvasHeight);
80
+
81
+ // Save context state
82
+ this.ctx.save();
83
+
84
+ // Move to center of destination area
85
+ this.ctx.translate(dx + destW / 2, dy + destH / 2);
86
+
87
+ // Apply rotation
88
+ if (this.rotation !== 0) {
89
+ this.ctx.rotate((this.rotation * Math.PI) / 180);
90
+ }
91
+
92
+ // Draw the video frame (swap dimensions for 90/270 rotation)
93
+ if (isRotated90or270) {
94
+ this.ctx.drawImage(source, 0, 0, sourceWidth, sourceHeight, -destH / 2, -destW / 2, destH, destW);
95
+ } else {
96
+ this.ctx.drawImage(source, 0, 0, sourceWidth, sourceHeight, -destW / 2, -destH / 2, destW, destH);
97
+ }
98
+
99
+ // Restore context state
100
+ this.ctx.restore();
101
+
102
+ return true;
103
+ } catch {
104
+ return false;
105
+ }
106
+ }
107
+
108
+ public clear(): void {
109
+ if (!this.isReady() || !this.ctx) {
110
+ return;
111
+ }
112
+ this.ctx.fillStyle = 'black';
113
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
114
+ }
115
+
116
+ public setRotation(rotation: Rotation): void {
117
+ this.rotation = rotation;
118
+ }
119
+
120
+ public getRotation(): Rotation {
121
+ return this.rotation;
122
+ }
123
+
124
+ public dispose(): void {
125
+ this.ctx = null;
126
+ this.isInitialized = false;
127
+ }
128
+ }
@@ -0,0 +1,172 @@
1
+ import { Canvas2DRenderer } from './canvas2d';
2
+ import type { IRenderer, RendererCreationResult, RendererOptions, RendererType } from './types';
3
+ import { WebGLRenderer } from './webgl';
4
+ import { WebGPURenderer } from './webgpu';
5
+
6
+ export class RendererFactory {
7
+ private canvas: HTMLCanvasElement | OffscreenCanvas;
8
+ private powerPreference: 'high-performance' | 'low-power';
9
+
10
+ constructor(options: RendererOptions) {
11
+ this.canvas = options.canvas;
12
+ this.powerPreference = options.powerPreference || 'high-performance';
13
+ }
14
+
15
+ public async createRenderer(type: RendererType): Promise<IRenderer | null> {
16
+ try {
17
+ switch (type) {
18
+ case 'webgpu':
19
+ return await this.createWebGPURenderer();
20
+ case 'webgl':
21
+ return this.createWebGLRenderer();
22
+ case 'canvas2d':
23
+ return this.createCanvas2DRenderer();
24
+ default:
25
+ return null;
26
+ }
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ public async createRendererWithFallback(preferredType: RendererType): Promise<RendererCreationResult> {
33
+ // Determine fallback order based on preference
34
+ // Always try the preferred type first, then fall back to others
35
+ const fallbackOrder: RendererType[] = [preferredType];
36
+ if (preferredType !== 'webgl') fallbackOrder.push('webgl');
37
+ if (preferredType !== 'canvas2d') fallbackOrder.push('canvas2d');
38
+
39
+ for (const type of fallbackOrder) {
40
+ const renderer = await this.createRenderer(type);
41
+ if (renderer?.isReady()) {
42
+ return { renderer, actualType: type };
43
+ }
44
+
45
+ // Clean up failed renderer
46
+ if (renderer) {
47
+ renderer.dispose();
48
+ }
49
+ }
50
+
51
+ // Last resort: canvas2d always works
52
+ const fallbackRenderer = this.createCanvas2DRenderer();
53
+ return { renderer: fallbackRenderer, actualType: 'canvas2d' };
54
+ }
55
+
56
+ private async createWebGPURenderer(): Promise<IRenderer | null> {
57
+ const nav = navigator as Navigator & { gpu?: GPU };
58
+ if (!nav.gpu) return null;
59
+
60
+ // WebGPU only works with HTMLCanvasElement, not OffscreenCanvas
61
+ if (!('getContext' in this.canvas)) {
62
+ console.log('WebGPU requires HTMLCanvasElement, not OffscreenCanvas');
63
+ return null;
64
+ }
65
+
66
+ const renderer = new WebGPURenderer({
67
+ canvas: this.canvas,
68
+ powerPreference: this.powerPreference,
69
+ });
70
+
71
+ // Wait for async initialization with proper timeout
72
+ const timeout = 1000; // 1 second timeout
73
+ const startTime = performance.now();
74
+
75
+ // Create a promise that resolves when renderer is ready
76
+ const waitForReady = async (): Promise<boolean> => {
77
+ while (!renderer.isReady()) {
78
+ if (performance.now() - startTime > timeout) {
79
+ console.log('WebGPU renderer initialization timed out');
80
+ return false;
81
+ }
82
+ // Use requestIdleCallback if available for better performance
83
+ if ('requestIdleCallback' in window) {
84
+ await new Promise((resolve) => requestIdleCallback(() => resolve(undefined)));
85
+ } else {
86
+ await new Promise((resolve) => requestAnimationFrame(() => resolve(undefined)));
87
+ }
88
+ }
89
+ return true;
90
+ };
91
+
92
+ const ready = await waitForReady();
93
+ if (ready) {
94
+ console.log('WebGPU renderer initialized successfully');
95
+ return renderer;
96
+ }
97
+
98
+ return renderer.isReady() ? renderer : null;
99
+ }
100
+
101
+ private createWebGLRenderer(): IRenderer | null {
102
+ try {
103
+ const renderer = new WebGLRenderer({
104
+ canvas: this.canvas,
105
+ powerPreference: this.powerPreference,
106
+ preserveDrawingBuffer: false,
107
+ antialias: false,
108
+ alpha: false,
109
+ });
110
+
111
+ return renderer.isReady() ? renderer : null;
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+
117
+ private createCanvas2DRenderer(): IRenderer {
118
+ return new Canvas2DRenderer({ canvas: this.canvas });
119
+ }
120
+
121
+ /**
122
+ * Get all available renderer types for the current environment
123
+ * Returns in priority order: WebGPU, WebGL, Canvas2D
124
+ */
125
+ public static getSupportedRenderers(): RendererType[] {
126
+ const available: RendererType[] = [];
127
+
128
+ // Check WebGPU support (highest priority)
129
+ const nav = navigator as Navigator & { gpu?: GPU };
130
+ if (nav.gpu) {
131
+ available.push('webgpu');
132
+ }
133
+
134
+ // Check WebGL support (medium priority)
135
+ try {
136
+ const canvas = document.createElement('canvas');
137
+ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
138
+ if (gl) available.push('webgl');
139
+ } catch {}
140
+
141
+ // Canvas2D is always available (fallback)
142
+ available.push('canvas2d');
143
+
144
+ return available;
145
+ }
146
+
147
+ /**
148
+ * Get display name for renderer type
149
+ */
150
+ public static getRendererDisplayName(type: RendererType): string {
151
+ switch (type) {
152
+ case 'canvas2d':
153
+ return 'Canvas 2D';
154
+ case 'webgl':
155
+ return 'WebGL';
156
+ case 'webgpu':
157
+ return 'WebGPU';
158
+ default:
159
+ return 'Unknown';
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Check if a specific renderer type is supported
165
+ */
166
+ public static isRendererSupported(type: RendererType): boolean {
167
+ if (type === 'canvas2d') return true;
168
+
169
+ const supported = RendererFactory.getSupportedRenderers();
170
+ return supported.includes(type);
171
+ }
172
+ }
@@ -0,0 +1,5 @@
1
+ export * from './canvas2d';
2
+ export * from './factory';
3
+ export * from './types';
4
+ export * from './webgl';
5
+ export * from './webgpu';
@@ -0,0 +1,57 @@
1
+ export type RendererType = 'webgpu' | 'webgl' | 'canvas2d';
2
+ export type Rotation = 0 | 90 | 180 | 270;
3
+
4
+ /**
5
+ * Base interface for video renderers.
6
+ * All renderers must implement this interface.
7
+ */
8
+ export interface IRenderer {
9
+ /**
10
+ * Check if the renderer is ready to render frames
11
+ */
12
+ isReady(): boolean;
13
+
14
+ /**
15
+ * Render a frame from source canvas
16
+ * @param source - Source canvas containing the video frame
17
+ * @returns true if rendering succeeded, false otherwise
18
+ */
19
+ render(source: HTMLCanvasElement | OffscreenCanvas): boolean;
20
+
21
+ /**
22
+ * Clear the target canvas
23
+ */
24
+ clear(): void;
25
+
26
+ /**
27
+ * Dispose and clean up renderer resources
28
+ */
29
+ dispose(): void;
30
+
31
+ /**
32
+ * Set the rotation angle for rendering
33
+ * @param rotation - Rotation angle in degrees (0, 90, 180, 270)
34
+ */
35
+ setRotation(rotation: Rotation): void;
36
+
37
+ /**
38
+ * Get the current rotation angle
39
+ */
40
+ getRotation(): Rotation;
41
+ }
42
+
43
+ /**
44
+ * Options for creating a renderer
45
+ */
46
+ export interface RendererOptions {
47
+ canvas: HTMLCanvasElement | OffscreenCanvas;
48
+ powerPreference?: 'high-performance' | 'low-power';
49
+ }
50
+
51
+ /**
52
+ * Result of renderer creation with fallback
53
+ */
54
+ export interface RendererCreationResult {
55
+ renderer: IRenderer;
56
+ actualType: RendererType;
57
+ }