@mediafox/core 1.2.12 → 1.2.13
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/README.md +47 -0
- package/dist/compositor/audio-manager.d.ts.map +1 -1
- package/dist/compositor/compositor.d.ts +17 -2
- package/dist/compositor/compositor.d.ts.map +1 -1
- package/dist/compositor/index.d.ts +1 -1
- package/dist/compositor/index.d.ts.map +1 -1
- package/dist/compositor/types.d.ts +17 -0
- package/dist/compositor/types.d.ts.map +1 -1
- package/dist/compositor/worker-client.d.ts +2 -2
- package/dist/compositor/worker-client.d.ts.map +1 -1
- package/dist/compositor/worker-types.d.ts +3 -1
- package/dist/compositor/worker-types.d.ts.map +1 -1
- package/dist/compositor-worker.js +30 -30
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/package.json +1 -1
- package/src/compositor/audio-manager.ts +11 -3
- package/src/compositor/compositor-worker.ts +6 -5
- package/src/compositor/compositor.ts +150 -17
- package/src/compositor/index.ts +2 -0
- package/src/compositor/types.ts +18 -0
- package/src/compositor/worker-client.ts +5 -6
- package/src/compositor/worker-types.ts +3 -1
- package/src/index.ts +2 -0
package/package.json
CHANGED
|
@@ -113,7 +113,7 @@ export class CompositorAudioManager {
|
|
|
113
113
|
* Updates which sources are playing and their parameters.
|
|
114
114
|
*/
|
|
115
115
|
processAudioLayers(layers: AudioLayer[], mediaTime: number): void {
|
|
116
|
-
if (this.disposed
|
|
116
|
+
if (this.disposed) return;
|
|
117
117
|
|
|
118
118
|
// Track which sources are active in this frame
|
|
119
119
|
const activeSourceIds = this.activeSourceIdsScratch;
|
|
@@ -179,6 +179,14 @@ export class CompositorAudioManager {
|
|
|
179
179
|
this.startContextTime = this.audioContext.currentTime;
|
|
180
180
|
this.startMediaTime = fromTime;
|
|
181
181
|
this.pauseTime = fromTime;
|
|
182
|
+
|
|
183
|
+
// Clear any stale iterators so sources can be restarted fresh
|
|
184
|
+
for (const source of this.activeSources.values()) {
|
|
185
|
+
if (source.iterator) {
|
|
186
|
+
void source.iterator.return();
|
|
187
|
+
source.iterator = null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
182
190
|
}
|
|
183
191
|
|
|
184
192
|
/**
|
|
@@ -251,7 +259,7 @@ export class CompositorAudioManager {
|
|
|
251
259
|
|
|
252
260
|
// Throttle if we're too far ahead (more than 1 second of audio buffered)
|
|
253
261
|
const elapsedSinceStart = this.audioContext.currentTime - source.iteratorStartTime;
|
|
254
|
-
const bufferedAhead =
|
|
262
|
+
const bufferedAhead = timestamp - source.startSourceTime - elapsedSinceStart;
|
|
255
263
|
if (bufferedAhead > 1) {
|
|
256
264
|
await this.waitForCatchup(source, timestamp);
|
|
257
265
|
}
|
|
@@ -272,7 +280,7 @@ export class CompositorAudioManager {
|
|
|
272
280
|
|
|
273
281
|
// Calculate how far ahead we've buffered
|
|
274
282
|
const elapsedSinceStart = this.audioContext.currentTime - source.iteratorStartTime;
|
|
275
|
-
const bufferedAhead =
|
|
283
|
+
const bufferedAhead = targetSourceTime - source.startSourceTime - elapsedSinceStart;
|
|
276
284
|
if (bufferedAhead < 1) {
|
|
277
285
|
clearInterval(checkInterval);
|
|
278
286
|
resolve();
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { Compositor } from './compositor';
|
|
2
|
-
import type {
|
|
2
|
+
import type { CompositionFrame, CompositorLayer, CompositorSource } from './types';
|
|
3
3
|
import type {
|
|
4
4
|
CompositorWorkerExportPayload,
|
|
5
5
|
CompositorWorkerFrame,
|
|
6
6
|
CompositorWorkerInitPayload,
|
|
7
7
|
CompositorWorkerLoadPayload,
|
|
8
8
|
CompositorWorkerRenderPayload,
|
|
9
|
+
CompositorWorkerRequest,
|
|
9
10
|
CompositorWorkerResizePayload,
|
|
11
|
+
CompositorWorkerResponse,
|
|
10
12
|
CompositorWorkerSourceInfo,
|
|
11
13
|
CompositorWorkerUnloadPayload,
|
|
12
|
-
CompositorWorkerRequest,
|
|
13
|
-
CompositorWorkerResponse,
|
|
14
14
|
} from './worker-types';
|
|
15
15
|
|
|
16
16
|
type WorkerScope = {
|
|
@@ -48,6 +48,7 @@ const mapFrame = (frame: CompositorWorkerFrame): CompositionFrame => {
|
|
|
48
48
|
source,
|
|
49
49
|
sourceTime: layer.sourceTime,
|
|
50
50
|
transform: layer.transform,
|
|
51
|
+
fitMode: layer.fitMode,
|
|
51
52
|
visible: layer.visible,
|
|
52
53
|
zIndex: layer.zIndex,
|
|
53
54
|
};
|
|
@@ -122,8 +123,8 @@ workerScope.onmessage = async (event: MessageEvent<CompositorWorkerRequest>) =>
|
|
|
122
123
|
}
|
|
123
124
|
case 'resize': {
|
|
124
125
|
if (!compositor) throw new Error('Compositor not initialized');
|
|
125
|
-
const { width, height } = payload as CompositorWorkerResizePayload;
|
|
126
|
-
compositor.resize(width, height);
|
|
126
|
+
const { width, height, fitMode } = payload as CompositorWorkerResizePayload;
|
|
127
|
+
compositor.resize(width, height, fitMode);
|
|
127
128
|
postResponse({ id, ok: true, result: true });
|
|
128
129
|
return;
|
|
129
130
|
}
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
CompositorOptions,
|
|
12
12
|
CompositorSource,
|
|
13
13
|
CompositorSourceOptions,
|
|
14
|
+
FitMode,
|
|
14
15
|
FrameExportOptions,
|
|
15
16
|
PreviewOptions,
|
|
16
17
|
} from './types';
|
|
@@ -60,6 +61,7 @@ export class Compositor {
|
|
|
60
61
|
private width: number;
|
|
61
62
|
private height: number;
|
|
62
63
|
private backgroundColor: string;
|
|
64
|
+
private fitMode: FitMode;
|
|
63
65
|
private sourcePool: SourcePool;
|
|
64
66
|
private audioManager: CompositorAudioManager | null = null;
|
|
65
67
|
private workerClient: CompositorWorkerClient | null = null;
|
|
@@ -88,6 +90,9 @@ export class Compositor {
|
|
|
88
90
|
};
|
|
89
91
|
private registeredAudioSources = new Set<string>();
|
|
90
92
|
|
|
93
|
+
// Seek/Play synchronization
|
|
94
|
+
private pendingPlayAfterSeek = false;
|
|
95
|
+
|
|
91
96
|
/**
|
|
92
97
|
* Creates a new Compositor instance.
|
|
93
98
|
* @param options - Configuration options for the compositor
|
|
@@ -97,6 +102,7 @@ export class Compositor {
|
|
|
97
102
|
this.width = options.width ?? (this.canvas.width || 1920);
|
|
98
103
|
this.height = options.height ?? (this.canvas.height || 1080);
|
|
99
104
|
this.backgroundColor = options.backgroundColor ?? '#000000';
|
|
105
|
+
this.fitMode = options.fitMode ?? 'fill';
|
|
100
106
|
this.emitter = new EventEmitter({ maxListeners: 50 });
|
|
101
107
|
this.state = {
|
|
102
108
|
playing: false,
|
|
@@ -110,11 +116,7 @@ export class Compositor {
|
|
|
110
116
|
this.canvas.height = this.height;
|
|
111
117
|
|
|
112
118
|
const workerEnabled =
|
|
113
|
-
typeof options.worker === 'boolean'
|
|
114
|
-
? options.worker
|
|
115
|
-
: options.worker
|
|
116
|
-
? (options.worker.enabled ?? true)
|
|
117
|
-
: false;
|
|
119
|
+
typeof options.worker === 'boolean' ? options.worker : options.worker ? (options.worker.enabled ?? true) : false;
|
|
118
120
|
const canUseWorker =
|
|
119
121
|
workerEnabled &&
|
|
120
122
|
typeof Worker !== 'undefined' &&
|
|
@@ -434,29 +436,99 @@ export class Compositor {
|
|
|
434
436
|
const transform = layer.transform;
|
|
435
437
|
const sourceWidth = layer.source.width ?? this.width;
|
|
436
438
|
const sourceHeight = layer.source.height ?? this.height;
|
|
439
|
+
const effectiveFitMode =
|
|
440
|
+
layer.fitMode === undefined || layer.fitMode === 'auto' ? this.fitMode : layer.fitMode;
|
|
441
|
+
|
|
442
|
+
let fittedWidth = sourceWidth;
|
|
443
|
+
let fittedHeight = sourceHeight;
|
|
444
|
+
let fittedX = 0;
|
|
445
|
+
let fittedY = 0;
|
|
446
|
+
|
|
447
|
+
if (sourceWidth === 0 || sourceHeight === 0) {
|
|
448
|
+
fittedWidth = this.width;
|
|
449
|
+
fittedHeight = this.height;
|
|
450
|
+
} else if (effectiveFitMode === 'none') {
|
|
451
|
+
fittedWidth = sourceWidth;
|
|
452
|
+
fittedHeight = sourceHeight;
|
|
453
|
+
fittedX = (this.width - fittedWidth) / 2;
|
|
454
|
+
fittedY = (this.height - fittedHeight) / 2;
|
|
455
|
+
} else {
|
|
456
|
+
const sourceAspect = sourceWidth / sourceHeight;
|
|
457
|
+
const canvasAspect = this.width / this.height;
|
|
458
|
+
|
|
459
|
+
switch (effectiveFitMode) {
|
|
460
|
+
case 'fill':
|
|
461
|
+
// Stretch to fill canvas - ignore aspect ratio
|
|
462
|
+
fittedWidth = this.width;
|
|
463
|
+
fittedHeight = this.height;
|
|
464
|
+
break;
|
|
465
|
+
|
|
466
|
+
case 'cover':
|
|
467
|
+
// Scale to cover entire canvas - may crop
|
|
468
|
+
if (sourceAspect > canvasAspect) {
|
|
469
|
+
fittedHeight = this.height;
|
|
470
|
+
fittedWidth = this.height * sourceAspect;
|
|
471
|
+
fittedX = (this.width - fittedWidth) / 2;
|
|
472
|
+
} else {
|
|
473
|
+
fittedWidth = this.width;
|
|
474
|
+
fittedHeight = this.width / sourceAspect;
|
|
475
|
+
fittedY = (this.height - fittedHeight) / 2;
|
|
476
|
+
}
|
|
477
|
+
break;
|
|
478
|
+
|
|
479
|
+
default:
|
|
480
|
+
// Scale to fit entirely within canvas - may letterbox
|
|
481
|
+
if (sourceAspect > canvasAspect) {
|
|
482
|
+
fittedWidth = this.width;
|
|
483
|
+
fittedHeight = this.width / sourceAspect;
|
|
484
|
+
fittedY = (this.height - fittedHeight) / 2;
|
|
485
|
+
} else {
|
|
486
|
+
fittedHeight = this.height;
|
|
487
|
+
fittedWidth = this.height * sourceAspect;
|
|
488
|
+
fittedX = (this.width - fittedWidth) / 2;
|
|
489
|
+
}
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
437
493
|
|
|
438
|
-
// Fast path: no transform object means draw at origin with source dimensions
|
|
439
494
|
if (!transform) {
|
|
440
|
-
ctx.drawImage(image,
|
|
495
|
+
ctx.drawImage(image, fittedX, fittedY, fittedWidth, fittedHeight);
|
|
441
496
|
return;
|
|
442
497
|
}
|
|
443
498
|
|
|
444
|
-
const
|
|
445
|
-
const
|
|
446
|
-
const
|
|
447
|
-
const
|
|
499
|
+
const x = (transform.x ?? 0) + fittedX;
|
|
500
|
+
const y = (transform.y ?? 0) + fittedY;
|
|
501
|
+
const destWidth = transform.width ?? fittedWidth;
|
|
502
|
+
const destHeight = transform.height ?? fittedHeight;
|
|
448
503
|
const rotation = transform.rotation ?? 0;
|
|
449
504
|
const scaleX = transform.scaleX ?? 1;
|
|
450
505
|
const scaleY = transform.scaleY ?? 1;
|
|
451
506
|
const opacity = transform.opacity ?? 1;
|
|
507
|
+
const filter = transform.filter;
|
|
452
508
|
|
|
453
509
|
// Check if we need context state changes
|
|
454
510
|
const needsOpacity = opacity !== 1;
|
|
455
511
|
const needsTransform = rotation !== 0 || scaleX !== 1 || scaleY !== 1;
|
|
512
|
+
const needsFilter = !!filter && filter !== 'none';
|
|
513
|
+
const needsContextState = needsOpacity || needsTransform || needsFilter;
|
|
456
514
|
|
|
457
515
|
// Fast path: simple position/size only, no rotation/scale/opacity
|
|
458
|
-
if (!
|
|
516
|
+
if (!needsContextState) {
|
|
517
|
+
ctx.drawImage(image, x, y, destWidth, destHeight);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// If we only need opacity/filter but no transforms, avoid translate/rotate work
|
|
522
|
+
if (!needsTransform) {
|
|
523
|
+
ctx.save();
|
|
524
|
+
if (needsOpacity) {
|
|
525
|
+
ctx.globalAlpha = opacity;
|
|
526
|
+
}
|
|
527
|
+
if (needsFilter) {
|
|
528
|
+
ctx.filter = filter;
|
|
529
|
+
}
|
|
459
530
|
ctx.drawImage(image, x, y, destWidth, destHeight);
|
|
531
|
+
ctx.restore();
|
|
460
532
|
return;
|
|
461
533
|
}
|
|
462
534
|
|
|
@@ -470,6 +542,10 @@ export class Compositor {
|
|
|
470
542
|
ctx.globalAlpha = opacity;
|
|
471
543
|
}
|
|
472
544
|
|
|
545
|
+
if (needsFilter) {
|
|
546
|
+
ctx.filter = filter;
|
|
547
|
+
}
|
|
548
|
+
|
|
473
549
|
// Move to layer position
|
|
474
550
|
ctx.translate(x + destWidth * anchorX, y + destHeight * anchorY);
|
|
475
551
|
|
|
@@ -556,6 +632,7 @@ export class Compositor {
|
|
|
556
632
|
sourceId,
|
|
557
633
|
sourceTime: layer.sourceTime,
|
|
558
634
|
transform: layer.transform,
|
|
635
|
+
fitMode: layer.fitMode,
|
|
559
636
|
visible: layer.visible,
|
|
560
637
|
zIndex: layer.zIndex,
|
|
561
638
|
};
|
|
@@ -606,6 +683,12 @@ export class Compositor {
|
|
|
606
683
|
throw new Error('No preview configured. Call preview() first.');
|
|
607
684
|
}
|
|
608
685
|
|
|
686
|
+
// If currently seeking, queue play to execute after seek completes
|
|
687
|
+
if (this.state.seeking) {
|
|
688
|
+
this.pendingPlayAfterSeek = true;
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
|
|
609
692
|
this.state.playing = true;
|
|
610
693
|
this.lastFrameTime = performance.now();
|
|
611
694
|
this.lastRenderTime = this.lastFrameTime;
|
|
@@ -616,6 +699,11 @@ export class Compositor {
|
|
|
616
699
|
// Reset active audio tracking so sources restart after pause/seek.
|
|
617
700
|
this.activeAudioSourceIds.clear();
|
|
618
701
|
await this.audioManager.play(this.state.currentTime);
|
|
702
|
+
|
|
703
|
+
// Process audio layers immediately to start sources right away
|
|
704
|
+
// instead of waiting for render loop which might skip first frame
|
|
705
|
+
const frame = this.previewOptions.getComposition(this.state.currentTime);
|
|
706
|
+
this.processAudioLayers(frame.audio ?? [], this.state.currentTime);
|
|
619
707
|
}
|
|
620
708
|
|
|
621
709
|
// Start render loop
|
|
@@ -630,6 +718,7 @@ export class Compositor {
|
|
|
630
718
|
if (!this.state.playing) return;
|
|
631
719
|
|
|
632
720
|
this.state.playing = false;
|
|
721
|
+
this.pendingPlayAfterSeek = false;
|
|
633
722
|
this.stopRenderLoop();
|
|
634
723
|
if (this.audioManager) {
|
|
635
724
|
this.audioManager.pause();
|
|
@@ -654,15 +743,26 @@ export class Compositor {
|
|
|
654
743
|
// Seek audio
|
|
655
744
|
if (this.audioManager) {
|
|
656
745
|
await this.audioManager.seek(clampedTime);
|
|
746
|
+
// Clear tracking so sources are treated as new and restarted
|
|
747
|
+
this.activeAudioSourceIds.clear();
|
|
657
748
|
}
|
|
658
749
|
|
|
659
|
-
// Render frame at new time
|
|
750
|
+
// Render frame at new time and process audio layers during seek
|
|
660
751
|
const frame = this.previewOptions.getComposition(clampedTime);
|
|
752
|
+
if (this.audioManager) {
|
|
753
|
+
this.processAudioLayers(frame.audio ?? [], clampedTime);
|
|
754
|
+
}
|
|
661
755
|
await this.render(frame);
|
|
662
756
|
|
|
663
757
|
this.state.seeking = false;
|
|
664
758
|
this.emitter.emit('seeked', { time: clampedTime });
|
|
665
759
|
this.emitter.emit('timeupdate', { currentTime: clampedTime });
|
|
760
|
+
|
|
761
|
+
// Execute pending play if one was queued during seek
|
|
762
|
+
if (this.pendingPlayAfterSeek) {
|
|
763
|
+
this.pendingPlayAfterSeek = false;
|
|
764
|
+
await this.play();
|
|
765
|
+
}
|
|
666
766
|
}
|
|
667
767
|
|
|
668
768
|
private startRenderLoop(): void {
|
|
@@ -820,22 +920,55 @@ export class Compositor {
|
|
|
820
920
|
|
|
821
921
|
/**
|
|
822
922
|
* Resizes the compositor canvas without disposing loaded sources.
|
|
923
|
+
* When worker mode is active, delegates resizing to the worker thread
|
|
924
|
+
* since the OffscreenCanvas cannot be resized from the main thread.
|
|
823
925
|
* @param width - New width in pixels
|
|
824
926
|
* @param height - New height in pixels
|
|
927
|
+
* @param fitMode - Optional fit mode for scaling sources to the canvas
|
|
825
928
|
*/
|
|
826
|
-
resize(width: number, height: number): void {
|
|
929
|
+
resize(width: number, height: number, fitMode?: FitMode): void {
|
|
827
930
|
this.checkDisposed();
|
|
828
931
|
this.width = width;
|
|
829
932
|
this.height = height;
|
|
830
|
-
|
|
831
|
-
|
|
933
|
+
if (fitMode !== undefined) {
|
|
934
|
+
this.fitMode = fitMode;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// When worker mode is active, the canvas control has been transferred
|
|
938
|
+
// to OffscreenCanvas via transferControlToOffscreen(). Attempting to
|
|
939
|
+
// modify the HTMLCanvasElement's width/height would throw InvalidStateError.
|
|
940
|
+
// Delegate resizing entirely to the worker in this case.
|
|
832
941
|
if (this.workerClient) {
|
|
833
|
-
void this.workerClient.resize(width, height);
|
|
942
|
+
void this.workerClient.resize(width, height, this.fitMode);
|
|
834
943
|
return;
|
|
835
944
|
}
|
|
945
|
+
|
|
946
|
+
// Main thread rendering: update DOM canvas dimensions directly
|
|
947
|
+
this.canvas.width = width;
|
|
948
|
+
this.canvas.height = height;
|
|
836
949
|
this.clear();
|
|
837
950
|
}
|
|
838
951
|
|
|
952
|
+
/**
|
|
953
|
+
* Sets the fit mode for scaling sources to the canvas.
|
|
954
|
+
* @param fitMode - The fit mode to use
|
|
955
|
+
*/
|
|
956
|
+
setFitMode(fitMode: FitMode): void {
|
|
957
|
+
this.checkDisposed();
|
|
958
|
+
this.fitMode = fitMode;
|
|
959
|
+
if (this.workerClient) {
|
|
960
|
+
void this.workerClient.resize(this.width, this.height, fitMode);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Gets the current fit mode.
|
|
966
|
+
* @returns The current fit mode
|
|
967
|
+
*/
|
|
968
|
+
getFitMode(): FitMode {
|
|
969
|
+
return this.fitMode;
|
|
970
|
+
}
|
|
971
|
+
|
|
839
972
|
// Events
|
|
840
973
|
|
|
841
974
|
/**
|
package/src/compositor/index.ts
CHANGED
package/src/compositor/types.ts
CHANGED
|
@@ -2,6 +2,15 @@ import type { RendererType, Rotation } from '../types';
|
|
|
2
2
|
|
|
3
3
|
export type CompositorRendererType = RendererType;
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Fit mode for scaling video/image content within the compositor canvas.
|
|
7
|
+
* - `'fill'` (default): Stretch to exactly fill the canvas, ignoring aspect ratio. May distort the image.
|
|
8
|
+
* - `'contain'`: Scale to fit entirely within the canvas, preserving aspect ratio. May result in letterboxing/pillarboxing.
|
|
9
|
+
* - `'cover'`: Scale to completely cover the canvas, preserving aspect ratio. Parts may be cropped.
|
|
10
|
+
*/
|
|
11
|
+
export type FitMode = 'contain' | 'cover' | 'fill';
|
|
12
|
+
export type LayerFitMode = FitMode | 'none' | 'auto';
|
|
13
|
+
|
|
5
14
|
export interface CompositorOptions {
|
|
6
15
|
canvas: HTMLCanvasElement | OffscreenCanvas;
|
|
7
16
|
width?: number;
|
|
@@ -10,6 +19,8 @@ export interface CompositorOptions {
|
|
|
10
19
|
backgroundColor?: string;
|
|
11
20
|
enableAudio?: boolean;
|
|
12
21
|
worker?: boolean | CompositorWorkerOptions;
|
|
22
|
+
/** Initial fit mode for scaling sources to the canvas. Defaults to 'fill'. */
|
|
23
|
+
fitMode?: FitMode;
|
|
13
24
|
}
|
|
14
25
|
|
|
15
26
|
export interface LayerTransform {
|
|
@@ -23,12 +34,19 @@ export interface LayerTransform {
|
|
|
23
34
|
opacity?: number;
|
|
24
35
|
anchorX?: number;
|
|
25
36
|
anchorY?: number;
|
|
37
|
+
/** CSS filter string applied to this layer (e.g. "brightness(1.1) contrast(1.05)"). */
|
|
38
|
+
filter?: string;
|
|
26
39
|
}
|
|
27
40
|
|
|
28
41
|
export interface CompositorLayer {
|
|
29
42
|
source: CompositorSource;
|
|
30
43
|
sourceTime?: number;
|
|
31
44
|
transform?: LayerTransform;
|
|
45
|
+
/**
|
|
46
|
+
* Fit mode override for this layer. Use 'auto' or leave undefined to use the
|
|
47
|
+
* compositor's global fitMode, or 'none' to render at the source's original size.
|
|
48
|
+
*/
|
|
49
|
+
fitMode?: LayerFitMode;
|
|
32
50
|
visible?: boolean;
|
|
33
51
|
zIndex?: number;
|
|
34
52
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MediaSource } from '../types';
|
|
2
|
-
import type {
|
|
2
|
+
import type { CompositorSourceOptions, CompositorWorkerOptions, FrameExportOptions } from './types';
|
|
3
3
|
import type {
|
|
4
4
|
CompositorWorkerExportPayload,
|
|
5
5
|
CompositorWorkerFrame,
|
|
@@ -32,10 +32,9 @@ export class CompositorWorkerClient {
|
|
|
32
32
|
private ready: Promise<void>;
|
|
33
33
|
|
|
34
34
|
constructor(options: CompositorWorkerClientOptions) {
|
|
35
|
-
const workerOptions = typeof options.worker === 'boolean' ? {} : options.worker ?? {};
|
|
35
|
+
const workerOptions = typeof options.worker === 'boolean' ? {} : (options.worker ?? {});
|
|
36
36
|
const workerType = workerOptions.type ?? 'module';
|
|
37
|
-
const workerUrl =
|
|
38
|
-
workerOptions.url ?? new URL('./compositor-worker.js', import.meta.url);
|
|
37
|
+
const workerUrl = workerOptions.url ?? new URL('./compositor-worker.js', import.meta.url);
|
|
39
38
|
|
|
40
39
|
this.worker = new Worker(workerUrl, { type: workerType });
|
|
41
40
|
this.worker.onmessage = (event: MessageEvent<CompositorWorkerResponse>) => {
|
|
@@ -115,9 +114,9 @@ export class CompositorWorkerClient {
|
|
|
115
114
|
return this.call<boolean>('clear');
|
|
116
115
|
}
|
|
117
116
|
|
|
118
|
-
async resize(width: number, height: number): Promise<boolean> {
|
|
117
|
+
async resize(width: number, height: number, fitMode?: 'contain' | 'cover' | 'fill'): Promise<boolean> {
|
|
119
118
|
await this.ready;
|
|
120
|
-
const payload: CompositorWorkerResizePayload = { width, height };
|
|
119
|
+
const payload: CompositorWorkerResizePayload = { width, height, fitMode };
|
|
121
120
|
return this.call<boolean>('resize', payload);
|
|
122
121
|
}
|
|
123
122
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { MediaSource } from '../types';
|
|
2
|
-
import type { FrameExportOptions,
|
|
2
|
+
import type { CompositorSourceOptions, FrameExportOptions, LayerFitMode, LayerTransform, SourceType } from './types';
|
|
3
3
|
|
|
4
4
|
export interface CompositorWorkerLayer {
|
|
5
5
|
sourceId: string;
|
|
6
6
|
sourceTime?: number;
|
|
7
7
|
transform?: LayerTransform;
|
|
8
|
+
fitMode?: LayerFitMode;
|
|
8
9
|
visible?: boolean;
|
|
9
10
|
zIndex?: number;
|
|
10
11
|
}
|
|
@@ -59,6 +60,7 @@ export interface CompositorWorkerRenderPayload {
|
|
|
59
60
|
export interface CompositorWorkerResizePayload {
|
|
60
61
|
width: number;
|
|
61
62
|
height: number;
|
|
63
|
+
fitMode?: 'contain' | 'cover' | 'fill';
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
export interface CompositorWorkerExportPayload {
|