@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.
- package/dist/compositor-worker.js +1 -227
- package/package.json +5 -4
- 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,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,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
|
+
}
|