@smoove/core 0.1.0
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/LICENSE +21 -0
- package/README.md +80 -0
- package/dist/animation/color.d.ts +9 -0
- package/dist/animation/color.d.ts.map +1 -0
- package/dist/animation/color.js +224 -0
- package/dist/animation/color.js.map +1 -0
- package/dist/animation/easing.d.ts +22 -0
- package/dist/animation/easing.d.ts.map +1 -0
- package/dist/animation/easing.js +59 -0
- package/dist/animation/easing.js.map +1 -0
- package/dist/animation/interpolate-colors.d.ts +7 -0
- package/dist/animation/interpolate-colors.d.ts.map +1 -0
- package/dist/animation/interpolate-colors.js +19 -0
- package/dist/animation/interpolate-colors.js.map +1 -0
- package/dist/animation/interpolate.d.ts +12 -0
- package/dist/animation/interpolate.d.ts.map +1 -0
- package/dist/animation/interpolate.js +65 -0
- package/dist/animation/interpolate.js.map +1 -0
- package/dist/audio/asset.d.ts +21 -0
- package/dist/audio/asset.d.ts.map +1 -0
- package/dist/audio/asset.js +2 -0
- package/dist/audio/asset.js.map +1 -0
- package/dist/audio/audio-driver.d.ts +34 -0
- package/dist/audio/audio-driver.d.ts.map +1 -0
- package/dist/audio/audio-driver.js +2 -0
- package/dist/audio/audio-driver.js.map +1 -0
- package/dist/audio/audio-for-preview.d.ts +12 -0
- package/dist/audio/audio-for-preview.d.ts.map +1 -0
- package/dist/audio/audio-for-preview.js +70 -0
- package/dist/audio/audio-for-preview.js.map +1 -0
- package/dist/audio/audio-for-rendering.d.ts +16 -0
- package/dist/audio/audio-for-rendering.d.ts.map +1 -0
- package/dist/audio/audio-for-rendering.js +32 -0
- package/dist/audio/audio-for-rendering.js.map +1 -0
- package/dist/audio/audio-source-browser.d.ts +30 -0
- package/dist/audio/audio-source-browser.d.ts.map +1 -0
- package/dist/audio/audio-source-browser.js +139 -0
- package/dist/audio/audio-source-browser.js.map +1 -0
- package/dist/audio/audio-source.d.ts +38 -0
- package/dist/audio/audio-source.d.ts.map +1 -0
- package/dist/audio/audio-source.js +2 -0
- package/dist/audio/audio-source.js.map +1 -0
- package/dist/audio/index.d.ts +45 -0
- package/dist/audio/index.d.ts.map +1 -0
- package/dist/audio/index.js +143 -0
- package/dist/audio/index.js.map +1 -0
- package/dist/audio/mixer.d.ts +43 -0
- package/dist/audio/mixer.d.ts.map +1 -0
- package/dist/audio/mixer.js +51 -0
- package/dist/audio/mixer.js.map +1 -0
- package/dist/audio/types.d.ts +31 -0
- package/dist/audio/types.d.ts.map +1 -0
- package/dist/audio/types.js +2 -0
- package/dist/audio/types.js.map +1 -0
- package/dist/block.d.ts +16 -0
- package/dist/block.d.ts.map +1 -0
- package/dist/block.js +173 -0
- package/dist/block.js.map +1 -0
- package/dist/composition.d.ts.map +1 -0
- package/dist/composition.js.map +1 -0
- package/dist/easing.d.ts +22 -0
- package/dist/easing.d.ts.map +1 -0
- package/dist/easing.js +59 -0
- package/dist/easing.js.map +1 -0
- package/dist/emitter.d.ts +7 -0
- package/dist/emitter.d.ts.map +1 -0
- package/dist/emitter.js +29 -0
- package/dist/emitter.js.map +1 -0
- package/dist/engine/composition.d.ts +182 -0
- package/dist/engine/composition.d.ts.map +1 -0
- package/dist/engine/composition.js +527 -0
- package/dist/engine/composition.js.map +1 -0
- package/dist/engine/emitter.d.ts +7 -0
- package/dist/engine/emitter.d.ts.map +1 -0
- package/dist/engine/emitter.js +29 -0
- package/dist/engine/emitter.js.map +1 -0
- package/dist/engine/environment.d.ts +30 -0
- package/dist/engine/environment.d.ts.map +1 -0
- package/dist/engine/environment.js +27 -0
- package/dist/engine/environment.js.map +1 -0
- package/dist/engine/offsets.d.ts +39 -0
- package/dist/engine/offsets.d.ts.map +1 -0
- package/dist/engine/offsets.js +54 -0
- package/dist/engine/offsets.js.map +1 -0
- package/dist/engine/runtime-defaults.d.ts +50 -0
- package/dist/engine/runtime-defaults.d.ts.map +1 -0
- package/dist/engine/runtime-defaults.js +77 -0
- package/dist/engine/runtime-defaults.js.map +1 -0
- package/dist/engine/sequence.d.ts +44 -0
- package/dist/engine/sequence.d.ts.map +1 -0
- package/dist/engine/sequence.js +101 -0
- package/dist/engine/sequence.js.map +1 -0
- package/dist/engine/series.d.ts +40 -0
- package/dist/engine/series.d.ts.map +1 -0
- package/dist/engine/series.js +58 -0
- package/dist/engine/series.js.map +1 -0
- package/dist/engine/signal.d.ts +10 -0
- package/dist/engine/signal.d.ts.map +1 -0
- package/dist/engine/signal.js +39 -0
- package/dist/engine/signal.js.map +1 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js.map +1 -0
- package/dist/flex-engine.d.ts +31 -0
- package/dist/flex-engine.d.ts.map +1 -0
- package/dist/flex-engine.js +186 -0
- package/dist/flex-engine.js.map +1 -0
- package/dist/flex-types.d.ts +165 -0
- package/dist/flex-types.d.ts.map +1 -0
- package/dist/flex-types.js +2 -0
- package/dist/flex-types.js.map +1 -0
- package/dist/flex.d.ts +26 -0
- package/dist/flex.d.ts.map +1 -0
- package/dist/flex.js +179 -0
- package/dist/flex.js.map +1 -0
- package/dist/image.d.ts +10 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +156 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolate-colors.d.ts +7 -0
- package/dist/interpolate-colors.d.ts.map +1 -0
- package/dist/interpolate-colors.js +240 -0
- package/dist/interpolate-colors.js.map +1 -0
- package/dist/interpolate.d.ts +12 -0
- package/dist/interpolate.d.ts.map +1 -0
- package/dist/interpolate.js +65 -0
- package/dist/interpolate.js.map +1 -0
- package/dist/layout/block.d.ts +54 -0
- package/dist/layout/block.d.ts.map +1 -0
- package/dist/layout/block.js +186 -0
- package/dist/layout/block.js.map +1 -0
- package/dist/layout/contract.d.ts +41 -0
- package/dist/layout/contract.d.ts.map +1 -0
- package/dist/layout/contract.js +8 -0
- package/dist/layout/contract.js.map +1 -0
- package/dist/layout/flex/engine.d.ts +31 -0
- package/dist/layout/flex/engine.d.ts.map +1 -0
- package/dist/layout/flex/engine.js +186 -0
- package/dist/layout/flex/engine.js.map +1 -0
- package/dist/layout/flex/flex.d.ts +31 -0
- package/dist/layout/flex/flex.d.ts.map +1 -0
- package/dist/layout/flex/flex.js +175 -0
- package/dist/layout/flex/flex.js.map +1 -0
- package/dist/layout/flex/mixin.d.ts +52 -0
- package/dist/layout/flex/mixin.d.ts.map +1 -0
- package/dist/layout/flex/mixin.js +114 -0
- package/dist/layout/flex/mixin.js.map +1 -0
- package/dist/layout/flex/types.d.ts +31 -0
- package/dist/layout/flex/types.d.ts.map +1 -0
- package/dist/layout/flex/types.js +2 -0
- package/dist/layout/flex/types.js.map +1 -0
- package/dist/layout/image.d.ts +46 -0
- package/dist/layout/image.d.ts.map +1 -0
- package/dist/layout/image.js +198 -0
- package/dist/layout/image.js.map +1 -0
- package/dist/layout/shapes.d.ts +72 -0
- package/dist/layout/shapes.d.ts.map +1 -0
- package/dist/layout/shapes.js +29 -0
- package/dist/layout/shapes.js.map +1 -0
- package/dist/layout/text/font.d.ts +92 -0
- package/dist/layout/text/font.d.ts.map +1 -0
- package/dist/layout/text/font.js +166 -0
- package/dist/layout/text/font.js.map +1 -0
- package/dist/layout/text/geometry.d.ts +31 -0
- package/dist/layout/text/geometry.d.ts.map +1 -0
- package/dist/layout/text/geometry.js +81 -0
- package/dist/layout/text/geometry.js.map +1 -0
- package/dist/layout/text/text.d.ts +98 -0
- package/dist/layout/text/text.d.ts.map +1 -0
- package/dist/layout/text/text.js +676 -0
- package/dist/layout/text/text.js.map +1 -0
- package/dist/layout/text/types.d.ts +103 -0
- package/dist/layout/text/types.d.ts.map +1 -0
- package/dist/layout/text/types.js +2 -0
- package/dist/layout/text/types.js.map +1 -0
- package/dist/media/audio/asset.d.ts +21 -0
- package/dist/media/audio/asset.d.ts.map +1 -0
- package/dist/media/audio/asset.js +2 -0
- package/dist/media/audio/asset.js.map +1 -0
- package/dist/media/audio/audio-driver.d.ts +34 -0
- package/dist/media/audio/audio-driver.d.ts.map +1 -0
- package/dist/media/audio/audio-driver.js +2 -0
- package/dist/media/audio/audio-driver.js.map +1 -0
- package/dist/media/audio/audio-for-preview.d.ts +47 -0
- package/dist/media/audio/audio-for-preview.d.ts.map +1 -0
- package/dist/media/audio/audio-for-preview.js +232 -0
- package/dist/media/audio/audio-for-preview.js.map +1 -0
- package/dist/media/audio/audio-for-rendering.d.ts +16 -0
- package/dist/media/audio/audio-for-rendering.d.ts.map +1 -0
- package/dist/media/audio/audio-for-rendering.js +32 -0
- package/dist/media/audio/audio-for-rendering.js.map +1 -0
- package/dist/media/audio/audio-source-browser.d.ts +30 -0
- package/dist/media/audio/audio-source-browser.d.ts.map +1 -0
- package/dist/media/audio/audio-source-browser.js +139 -0
- package/dist/media/audio/audio-source-browser.js.map +1 -0
- package/dist/media/audio/audio-source-mediabunny.d.ts +45 -0
- package/dist/media/audio/audio-source-mediabunny.d.ts.map +1 -0
- package/dist/media/audio/audio-source-mediabunny.js +79 -0
- package/dist/media/audio/audio-source-mediabunny.js.map +1 -0
- package/dist/media/audio/audio-source.d.ts +38 -0
- package/dist/media/audio/audio-source.d.ts.map +1 -0
- package/dist/media/audio/audio-source.js +2 -0
- package/dist/media/audio/audio-source.js.map +1 -0
- package/dist/media/audio/index.d.ts +57 -0
- package/dist/media/audio/index.d.ts.map +1 -0
- package/dist/media/audio/index.js +170 -0
- package/dist/media/audio/index.js.map +1 -0
- package/dist/media/audio/mixer.d.ts +43 -0
- package/dist/media/audio/mixer.d.ts.map +1 -0
- package/dist/media/audio/mixer.js +51 -0
- package/dist/media/audio/mixer.js.map +1 -0
- package/dist/media/audio/shared-audio-context.d.ts +30 -0
- package/dist/media/audio/shared-audio-context.d.ts.map +1 -0
- package/dist/media/audio/shared-audio-context.js +68 -0
- package/dist/media/audio/shared-audio-context.js.map +1 -0
- package/dist/media/audio/types.d.ts +31 -0
- package/dist/media/audio/types.d.ts.map +1 -0
- package/dist/media/audio/types.js +2 -0
- package/dist/media/audio/types.js.map +1 -0
- package/dist/media/media-marker.d.ts +26 -0
- package/dist/media/media-marker.d.ts.map +1 -0
- package/dist/media/media-marker.js +26 -0
- package/dist/media/media-marker.js.map +1 -0
- package/dist/media/media-time.d.ts +17 -0
- package/dist/media/media-time.d.ts.map +1 -0
- package/dist/media/media-time.js +17 -0
- package/dist/media/media-time.js.map +1 -0
- package/dist/media/video/driver.d.ts +31 -0
- package/dist/media/video/driver.d.ts.map +1 -0
- package/dist/media/video/driver.js +3 -0
- package/dist/media/video/driver.js.map +1 -0
- package/dist/media/video/index.d.ts +66 -0
- package/dist/media/video/index.d.ts.map +1 -0
- package/dist/media/video/index.js +372 -0
- package/dist/media/video/index.js.map +1 -0
- package/dist/media/video/types.d.ts +36 -0
- package/dist/media/video/types.d.ts.map +1 -0
- package/dist/media/video/types.js +2 -0
- package/dist/media/video/types.js.map +1 -0
- package/dist/media/video/video-for-preview.d.ts +17 -0
- package/dist/media/video/video-for-preview.d.ts.map +1 -0
- package/dist/media/video/video-for-preview.js +30 -0
- package/dist/media/video/video-for-preview.js.map +1 -0
- package/dist/media/video/video-for-rendering.d.ts +17 -0
- package/dist/media/video/video-for-rendering.d.ts.map +1 -0
- package/dist/media/video/video-for-rendering.js +59 -0
- package/dist/media/video/video-for-rendering.js.map +1 -0
- package/dist/media/video/video-source-browser.d.ts +37 -0
- package/dist/media/video/video-source-browser.d.ts.map +1 -0
- package/dist/media/video/video-source-browser.js +171 -0
- package/dist/media/video/video-source-browser.js.map +1 -0
- package/dist/media/video/video-source-mediabunny.d.ts +69 -0
- package/dist/media/video/video-source-mediabunny.d.ts.map +1 -0
- package/dist/media/video/video-source-mediabunny.js +236 -0
- package/dist/media/video/video-source-mediabunny.js.map +1 -0
- package/dist/media/video/video-source.d.ts +48 -0
- package/dist/media/video/video-source.d.ts.map +1 -0
- package/dist/media/video/video-source.js +2 -0
- package/dist/media/video/video-source.js.map +1 -0
- package/dist/media-marker.d.ts +19 -0
- package/dist/media-marker.d.ts.map +1 -0
- package/dist/media-marker.js +19 -0
- package/dist/media-marker.js.map +1 -0
- package/dist/media-time.d.ts +17 -0
- package/dist/media-time.d.ts.map +1 -0
- package/dist/media-time.js +17 -0
- package/dist/media-time.js.map +1 -0
- package/dist/signal.d.ts +10 -0
- package/dist/signal.d.ts.map +1 -0
- package/dist/signal.js +39 -0
- package/dist/signal.js.map +1 -0
- package/dist/text.d.ts +91 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +704 -0
- package/dist/text.js.map +1 -0
- package/dist/video/driver.d.ts +31 -0
- package/dist/video/driver.d.ts.map +1 -0
- package/dist/video/driver.js +3 -0
- package/dist/video/driver.js.map +1 -0
- package/dist/video/index.d.ts +45 -0
- package/dist/video/index.d.ts.map +1 -0
- package/dist/video/index.js +276 -0
- package/dist/video/index.js.map +1 -0
- package/dist/video/types.d.ts +35 -0
- package/dist/video/types.d.ts.map +1 -0
- package/dist/video/types.js +2 -0
- package/dist/video/types.js.map +1 -0
- package/dist/video/video-for-preview.d.ts +12 -0
- package/dist/video/video-for-preview.d.ts.map +1 -0
- package/dist/video/video-for-preview.js +73 -0
- package/dist/video/video-for-preview.js.map +1 -0
- package/dist/video/video-for-rendering.d.ts +16 -0
- package/dist/video/video-for-rendering.d.ts.map +1 -0
- package/dist/video/video-for-rendering.js +45 -0
- package/dist/video/video-for-rendering.js.map +1 -0
- package/dist/video/video-source-browser.d.ts +37 -0
- package/dist/video/video-source-browser.d.ts.map +1 -0
- package/dist/video/video-source-browser.js +171 -0
- package/dist/video/video-source-browser.js.map +1 -0
- package/dist/video/video-source.d.ts +48 -0
- package/dist/video/video-source.d.ts.map +1 -0
- package/dist/video/video-source.js +2 -0
- package/dist/video/video-source.js.map +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview-mode {@link VideoSource}: wraps an `HTMLVideoElement` and drives it in
|
|
3
|
+
* realtime. The server renderer injects a different source via
|
|
4
|
+
* `VideoConfig.sourceFactory`.
|
|
5
|
+
*/
|
|
6
|
+
export class BrowserVideoSource {
|
|
7
|
+
_el;
|
|
8
|
+
_ready = false;
|
|
9
|
+
_readyCbs = new Set();
|
|
10
|
+
_frameCbs = new Set();
|
|
11
|
+
_rvfcId = null;
|
|
12
|
+
_rvfcRunning = false;
|
|
13
|
+
// Seek coalescing: keep at most one seek in flight. Rapid scrubbing only ever
|
|
14
|
+
// lands the latest target — intermediate ones are dropped — so the backlog
|
|
15
|
+
// never grows (which is what makes scrubbing a large clip degrade over time).
|
|
16
|
+
_seeking = false;
|
|
17
|
+
_pending = null;
|
|
18
|
+
constructor() {
|
|
19
|
+
const el = document.createElement("video");
|
|
20
|
+
el.crossOrigin = "anonymous";
|
|
21
|
+
el.playsInline = true;
|
|
22
|
+
el.preload = "auto";
|
|
23
|
+
// Muted so the browser allows programmatic play()/seek before any user gesture.
|
|
24
|
+
el.muted = true;
|
|
25
|
+
this._el = el;
|
|
26
|
+
}
|
|
27
|
+
load(src) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const onReady = () => {
|
|
30
|
+
cleanup();
|
|
31
|
+
this._ready = true;
|
|
32
|
+
this._startFrameLoop();
|
|
33
|
+
for (const cb of this._readyCbs)
|
|
34
|
+
cb();
|
|
35
|
+
resolve();
|
|
36
|
+
};
|
|
37
|
+
const onErr = () => {
|
|
38
|
+
cleanup();
|
|
39
|
+
reject(new Error(`Failed to load video: ${src}`));
|
|
40
|
+
};
|
|
41
|
+
const cleanup = () => {
|
|
42
|
+
this._el.removeEventListener("loadeddata", onReady);
|
|
43
|
+
this._el.removeEventListener("error", onErr);
|
|
44
|
+
};
|
|
45
|
+
this._el.addEventListener("loadeddata", onReady);
|
|
46
|
+
this._el.addEventListener("error", onErr);
|
|
47
|
+
this._el.src = src;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
_startFrameLoop() {
|
|
51
|
+
if (this._rvfcRunning)
|
|
52
|
+
return;
|
|
53
|
+
if (typeof this._el.requestVideoFrameCallback !== "function")
|
|
54
|
+
return;
|
|
55
|
+
this._rvfcRunning = true;
|
|
56
|
+
const tick = () => {
|
|
57
|
+
for (const cb of this._frameCbs)
|
|
58
|
+
cb();
|
|
59
|
+
this._rvfcId = this._el.requestVideoFrameCallback?.(tick) ?? null;
|
|
60
|
+
};
|
|
61
|
+
this._rvfcId = this._el.requestVideoFrameCallback(tick);
|
|
62
|
+
}
|
|
63
|
+
get element() {
|
|
64
|
+
return this._ready ? this._el : null;
|
|
65
|
+
}
|
|
66
|
+
get naturalWidth() {
|
|
67
|
+
return this._el.videoWidth;
|
|
68
|
+
}
|
|
69
|
+
get naturalHeight() {
|
|
70
|
+
return this._el.videoHeight;
|
|
71
|
+
}
|
|
72
|
+
get duration() {
|
|
73
|
+
return Number.isFinite(this._el.duration) ? this._el.duration : 0;
|
|
74
|
+
}
|
|
75
|
+
get currentTime() {
|
|
76
|
+
return this._el.currentTime;
|
|
77
|
+
}
|
|
78
|
+
get isReady() {
|
|
79
|
+
return this._ready;
|
|
80
|
+
}
|
|
81
|
+
seek(timeSeconds, mode = "precise") {
|
|
82
|
+
const clamped = Math.max(0, this.duration > 0 ? Math.min(this.duration, timeSeconds) : timeSeconds);
|
|
83
|
+
// No-op seek with nothing in flight: already there, resolve immediately.
|
|
84
|
+
if (!this._seeking && Math.abs(this._el.currentTime - clamped) <= 1e-4) {
|
|
85
|
+
return Promise.resolve();
|
|
86
|
+
}
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
if (this._pending) {
|
|
89
|
+
// Supersede the queued target — only the latest scrub position matters.
|
|
90
|
+
this._pending.time = clamped;
|
|
91
|
+
this._pending.mode = mode;
|
|
92
|
+
this._pending.resolvers.push(resolve);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
this._pending = { time: clamped, mode, resolvers: [resolve] };
|
|
96
|
+
}
|
|
97
|
+
this._drainSeek();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/** Issue the pending seek if none is in flight; chains the latest target after each completes. */
|
|
101
|
+
_drainSeek() {
|
|
102
|
+
if (this._seeking || !this._pending)
|
|
103
|
+
return;
|
|
104
|
+
const { time, mode, resolvers } = this._pending;
|
|
105
|
+
this._pending = null;
|
|
106
|
+
if (Math.abs(this._el.currentTime - time) <= 1e-4) {
|
|
107
|
+
for (const r of resolvers)
|
|
108
|
+
r();
|
|
109
|
+
this._drainSeek();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
this._seeking = true;
|
|
113
|
+
const onSeeked = () => {
|
|
114
|
+
this._el.removeEventListener("seeked", onSeeked);
|
|
115
|
+
this._seeking = false;
|
|
116
|
+
for (const r of resolvers)
|
|
117
|
+
r();
|
|
118
|
+
// A newer target may have arrived mid-seek — land it now.
|
|
119
|
+
this._drainSeek();
|
|
120
|
+
};
|
|
121
|
+
this._el.addEventListener("seeked", onSeeked);
|
|
122
|
+
// fastSeek jumps to the nearest keyframe — much snappier for scrubbing a
|
|
123
|
+
// large clip. Fall back to a precise currentTime seek when unavailable.
|
|
124
|
+
if (mode === "fast" && typeof this._el.fastSeek === "function") {
|
|
125
|
+
this._el.fastSeek(time);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
this._el.currentTime = time;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
play() {
|
|
132
|
+
return this._el.play();
|
|
133
|
+
}
|
|
134
|
+
pause() {
|
|
135
|
+
this._el.pause();
|
|
136
|
+
}
|
|
137
|
+
setMuted(muted) {
|
|
138
|
+
this._el.muted = muted;
|
|
139
|
+
}
|
|
140
|
+
setVolume(volume) {
|
|
141
|
+
this._el.volume = Math.max(0, Math.min(1, volume));
|
|
142
|
+
}
|
|
143
|
+
setPlaybackRate(rate) {
|
|
144
|
+
this._el.playbackRate = rate;
|
|
145
|
+
}
|
|
146
|
+
onReady(cb) {
|
|
147
|
+
if (this._ready) {
|
|
148
|
+
cb();
|
|
149
|
+
return () => { };
|
|
150
|
+
}
|
|
151
|
+
this._readyCbs.add(cb);
|
|
152
|
+
return () => this._readyCbs.delete(cb);
|
|
153
|
+
}
|
|
154
|
+
onFrame(cb) {
|
|
155
|
+
this._frameCbs.add(cb);
|
|
156
|
+
return () => this._frameCbs.delete(cb);
|
|
157
|
+
}
|
|
158
|
+
destroy() {
|
|
159
|
+
if (this._rvfcId !== null && typeof this._el.cancelVideoFrameCallback === "function") {
|
|
160
|
+
this._el.cancelVideoFrameCallback(this._rvfcId);
|
|
161
|
+
}
|
|
162
|
+
this._el.pause();
|
|
163
|
+
this._el.removeAttribute("src");
|
|
164
|
+
this._el.load();
|
|
165
|
+
this._readyCbs.clear();
|
|
166
|
+
this._frameCbs.clear();
|
|
167
|
+
this._pending = null;
|
|
168
|
+
this._seeking = false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=video-source-browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-source-browser.js","sourceRoot":"","sources":["../../../src/media/video/video-source-browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { AudioBufferSink } from "mediabunny";
|
|
2
|
+
import type { SchedulableAudioSource } from "../audio/audio-source-mediabunny.js";
|
|
3
|
+
import type { SeekMode, VideoSource } from "./video-source.js";
|
|
4
|
+
/**
|
|
5
|
+
* Preview-mode {@link VideoSource} backed by Mediabunny + WebCodecs. Replaces the
|
|
6
|
+
* `HTMLVideoElement`-based {@link BrowserVideoSource}: instead of a self-playing
|
|
7
|
+
* element corrected for drift, frames are **pulled** for the exact timestamp the
|
|
8
|
+
* frame clock asks for, so scrubbing and playback are frame-accurate.
|
|
9
|
+
*
|
|
10
|
+
* Decoding uses a forward-only {@link CanvasSink} iterator (the cheap path for
|
|
11
|
+
* playback); the iterator is torn down and reseeded only on a backward seek or a
|
|
12
|
+
* large forward jump. Each decoded frame is blitted onto a single owned canvas so
|
|
13
|
+
* {@link element} keeps a stable reference for `Konva.Image`.
|
|
14
|
+
*
|
|
15
|
+
* The same {@link Input} also exposes the file's audio track as an
|
|
16
|
+
* {@link AudioBufferSink} (so a `Video` with sound is decoded once, not twice),
|
|
17
|
+
* which is why this also satisfies {@link SchedulableAudioSource} — the Video's
|
|
18
|
+
* {@link PreviewAudioDriver} schedules it on the shared Web Audio context.
|
|
19
|
+
*/
|
|
20
|
+
export declare class MediabunnyVideoSource implements VideoSource, SchedulableAudioSource {
|
|
21
|
+
private readonly _canvas;
|
|
22
|
+
private readonly _ctx;
|
|
23
|
+
private _input;
|
|
24
|
+
private _sink;
|
|
25
|
+
private _audioSink;
|
|
26
|
+
private _audioFirstTs;
|
|
27
|
+
private _ready;
|
|
28
|
+
private _disposed;
|
|
29
|
+
private readonly _readyCbs;
|
|
30
|
+
private readonly _frameCbs;
|
|
31
|
+
private _w;
|
|
32
|
+
private _h;
|
|
33
|
+
private _duration;
|
|
34
|
+
private _firstTs;
|
|
35
|
+
private _iter;
|
|
36
|
+
private _next;
|
|
37
|
+
private _curTs;
|
|
38
|
+
private _seeking;
|
|
39
|
+
private _pending;
|
|
40
|
+
constructor();
|
|
41
|
+
load(src: string): Promise<void>;
|
|
42
|
+
get element(): CanvasImageSource | null;
|
|
43
|
+
get naturalWidth(): number;
|
|
44
|
+
get naturalHeight(): number;
|
|
45
|
+
get duration(): number;
|
|
46
|
+
get currentTime(): number;
|
|
47
|
+
get isReady(): boolean;
|
|
48
|
+
/** @see SchedulableAudioSource — decoded-buffer sink for the file's audio track. */
|
|
49
|
+
get sink(): AudioBufferSink | null;
|
|
50
|
+
/** @see SchedulableAudioSource — audio track start time in seconds. */
|
|
51
|
+
get firstTimestamp(): number;
|
|
52
|
+
seek(timeSeconds: number, _mode?: SeekMode): Promise<void>;
|
|
53
|
+
private _drainSeek;
|
|
54
|
+
/** Land the owned canvas on the last frame whose start timestamp is <= `t`. */
|
|
55
|
+
private _satisfy;
|
|
56
|
+
private _restartIterator;
|
|
57
|
+
private _pull;
|
|
58
|
+
/** Blit a decoded frame onto the owned canvas and notify listeners. */
|
|
59
|
+
private _draw;
|
|
60
|
+
play(): Promise<void>;
|
|
61
|
+
pause(): void;
|
|
62
|
+
setMuted(_muted: boolean): void;
|
|
63
|
+
setVolume(_volume: number): void;
|
|
64
|
+
setPlaybackRate(_rate: number): void;
|
|
65
|
+
onReady(cb: () => void): () => void;
|
|
66
|
+
onFrame(cb: () => void): () => void;
|
|
67
|
+
destroy(): void;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=video-source-mediabunny.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-source-mediabunny.d.ts","sourceRoot":"","sources":["../../../src/media/video/video-source-mediabunny.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EAKhB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAS/D;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,qBAAsB,YAAW,WAAW,EAAE,sBAAsB;IAC/E,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA2B;IAChD,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,KAAK,CAA2B;IAGxC,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,aAAa,CAAK;IAE1B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IAEnD,OAAO,CAAC,EAAE,CAAK;IACf,OAAO,CAAC,EAAE,CAAK;IACf,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,QAAQ,CAAK;IAIrB,OAAO,CAAC,KAAK,CAA6D;IAC1E,OAAO,CAAC,KAAK,CAA8B;IAC3C,OAAO,CAAC,MAAM,CAA4B;IAI1C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAA+D;;IAWzE,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkCtC,IAAI,OAAO,IAAI,iBAAiB,GAAG,IAAI,CAEtC;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,oFAAoF;IACpF,IAAI,IAAI,IAAI,eAAe,GAAG,IAAI,CAEjC;IAED,uEAAuE;IACvE,IAAI,cAAc,IAAI,MAAM,CAE3B;IAID,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,GAAE,QAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;YAmBvD,UAAU;IAgBxB,+EAA+E;YACjE,QAAQ;YAuBR,gBAAgB;YAShB,KAAK;IAMnB,uEAAuE;IACvE,OAAO,CAAC,KAAK;IAQb,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAGrB,KAAK,IAAI,IAAI;IACb,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAC/B,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAChC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAEpC,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IASnC,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAKnC,OAAO,IAAI,IAAI;CAahB"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { ALL_FORMATS, AudioBufferSink, CanvasSink, Input, UrlSource, } from "mediabunny";
|
|
2
|
+
/** Reuse decoded frames within this window before issuing a seek (1/4 of a 60fps frame). */
|
|
3
|
+
const SAME_FRAME_EPS = 1 / 240;
|
|
4
|
+
/** Forward jumps larger than this restart the decode iterator instead of decoding through. */
|
|
5
|
+
const BIGGEST_FORWARD_JUMP = 3;
|
|
6
|
+
/** Canvas pool size — holds the current lookahead frame plus one in flight. */
|
|
7
|
+
const POOL_SIZE = 2;
|
|
8
|
+
/**
|
|
9
|
+
* Preview-mode {@link VideoSource} backed by Mediabunny + WebCodecs. Replaces the
|
|
10
|
+
* `HTMLVideoElement`-based {@link BrowserVideoSource}: instead of a self-playing
|
|
11
|
+
* element corrected for drift, frames are **pulled** for the exact timestamp the
|
|
12
|
+
* frame clock asks for, so scrubbing and playback are frame-accurate.
|
|
13
|
+
*
|
|
14
|
+
* Decoding uses a forward-only {@link CanvasSink} iterator (the cheap path for
|
|
15
|
+
* playback); the iterator is torn down and reseeded only on a backward seek or a
|
|
16
|
+
* large forward jump. Each decoded frame is blitted onto a single owned canvas so
|
|
17
|
+
* {@link element} keeps a stable reference for `Konva.Image`.
|
|
18
|
+
*
|
|
19
|
+
* The same {@link Input} also exposes the file's audio track as an
|
|
20
|
+
* {@link AudioBufferSink} (so a `Video` with sound is decoded once, not twice),
|
|
21
|
+
* which is why this also satisfies {@link SchedulableAudioSource} — the Video's
|
|
22
|
+
* {@link PreviewAudioDriver} schedules it on the shared Web Audio context.
|
|
23
|
+
*/
|
|
24
|
+
export class MediabunnyVideoSource {
|
|
25
|
+
_canvas;
|
|
26
|
+
_ctx;
|
|
27
|
+
_input = null;
|
|
28
|
+
_sink = null;
|
|
29
|
+
// Audio sink off the same Input — null when the file has no decodable audio.
|
|
30
|
+
_audioSink = null;
|
|
31
|
+
_audioFirstTs = 0;
|
|
32
|
+
_ready = false;
|
|
33
|
+
_disposed = false;
|
|
34
|
+
_readyCbs = new Set();
|
|
35
|
+
_frameCbs = new Set();
|
|
36
|
+
_w = 0;
|
|
37
|
+
_h = 0;
|
|
38
|
+
_duration = 0;
|
|
39
|
+
_firstTs = 0;
|
|
40
|
+
// Forward iterator + a one-frame lookahead so we can land on the last frame
|
|
41
|
+
// whose start timestamp is <= the target without trusting reported durations.
|
|
42
|
+
_iter = null;
|
|
43
|
+
_next = null;
|
|
44
|
+
_curTs = Number.NEGATIVE_INFINITY;
|
|
45
|
+
// Seek coalescing: keep at most one decode in flight; rapid scrubbing only ever
|
|
46
|
+
// lands the latest target, so the backlog never grows.
|
|
47
|
+
_seeking = false;
|
|
48
|
+
_pending = null;
|
|
49
|
+
constructor() {
|
|
50
|
+
this._canvas = document.createElement("canvas");
|
|
51
|
+
this._canvas.width = 1;
|
|
52
|
+
this._canvas.height = 1;
|
|
53
|
+
const ctx = this._canvas.getContext("2d");
|
|
54
|
+
if (!ctx)
|
|
55
|
+
throw new Error("[smoove] 2D canvas context unavailable");
|
|
56
|
+
this._ctx = ctx;
|
|
57
|
+
}
|
|
58
|
+
async load(src) {
|
|
59
|
+
const input = new Input({ formats: ALL_FORMATS, source: new UrlSource(src) });
|
|
60
|
+
this._input = input;
|
|
61
|
+
const track = await input.getPrimaryVideoTrack();
|
|
62
|
+
if (!track)
|
|
63
|
+
throw new Error(`[smoove] no video track in: ${src}`);
|
|
64
|
+
if (!(await track.canDecode())) {
|
|
65
|
+
throw new Error(`[smoove] cannot decode video track in: ${src}`);
|
|
66
|
+
}
|
|
67
|
+
if (this._disposed)
|
|
68
|
+
return;
|
|
69
|
+
this._w = await track.getDisplayWidth();
|
|
70
|
+
this._h = await track.getDisplayHeight();
|
|
71
|
+
this._duration = await track.computeDuration();
|
|
72
|
+
this._firstTs = await track.getFirstTimestamp();
|
|
73
|
+
this._canvas.width = this._w;
|
|
74
|
+
this._canvas.height = this._h;
|
|
75
|
+
this._sink = new CanvasSink(track, { poolSize: POOL_SIZE, fit: "fill" });
|
|
76
|
+
// Wire the file's audio track (if any) for the Video's audio driver. A clip
|
|
77
|
+
// with no decodable audio just stays silent — `sink` reports null.
|
|
78
|
+
const audioTrack = await input.getPrimaryAudioTrack();
|
|
79
|
+
if (audioTrack && (await audioTrack.canDecode())) {
|
|
80
|
+
this._audioFirstTs = await audioTrack.getFirstTimestamp();
|
|
81
|
+
this._audioSink = new AudioBufferSink(audioTrack);
|
|
82
|
+
}
|
|
83
|
+
// Decode the first frame so `element` has content the moment we're ready.
|
|
84
|
+
await this._satisfy(this._firstTs);
|
|
85
|
+
if (this._disposed)
|
|
86
|
+
return;
|
|
87
|
+
this._ready = true;
|
|
88
|
+
for (const cb of this._readyCbs)
|
|
89
|
+
cb();
|
|
90
|
+
}
|
|
91
|
+
get element() {
|
|
92
|
+
return this._ready ? this._canvas : null;
|
|
93
|
+
}
|
|
94
|
+
get naturalWidth() {
|
|
95
|
+
return this._w;
|
|
96
|
+
}
|
|
97
|
+
get naturalHeight() {
|
|
98
|
+
return this._h;
|
|
99
|
+
}
|
|
100
|
+
get duration() {
|
|
101
|
+
return this._duration;
|
|
102
|
+
}
|
|
103
|
+
get currentTime() {
|
|
104
|
+
return this._curTs === Number.NEGATIVE_INFINITY ? 0 : this._curTs;
|
|
105
|
+
}
|
|
106
|
+
get isReady() {
|
|
107
|
+
return this._ready;
|
|
108
|
+
}
|
|
109
|
+
/** @see SchedulableAudioSource — decoded-buffer sink for the file's audio track. */
|
|
110
|
+
get sink() {
|
|
111
|
+
return this._audioSink;
|
|
112
|
+
}
|
|
113
|
+
/** @see SchedulableAudioSource — audio track start time in seconds. */
|
|
114
|
+
get firstTimestamp() {
|
|
115
|
+
return this._audioFirstTs;
|
|
116
|
+
}
|
|
117
|
+
// mode is ignored: Mediabunny seeks are always frame-accurate and fast enough
|
|
118
|
+
// that scrubbing doesn't need an approximate keyframe-only path.
|
|
119
|
+
seek(timeSeconds, _mode = "precise") {
|
|
120
|
+
const clamped = Math.max(this._firstTs, this._duration > 0 ? Math.min(this._duration, timeSeconds) : timeSeconds);
|
|
121
|
+
if (!this._seeking && Math.abs(this._curTs - clamped) <= SAME_FRAME_EPS) {
|
|
122
|
+
return Promise.resolve();
|
|
123
|
+
}
|
|
124
|
+
return new Promise((resolve) => {
|
|
125
|
+
if (this._pending) {
|
|
126
|
+
this._pending.time = clamped; // supersede — only the latest target matters
|
|
127
|
+
this._pending.resolvers.push(resolve);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
this._pending = { time: clamped, resolvers: [resolve] };
|
|
131
|
+
}
|
|
132
|
+
void this._drainSeek();
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async _drainSeek() {
|
|
136
|
+
if (this._seeking || !this._pending)
|
|
137
|
+
return;
|
|
138
|
+
this._seeking = true;
|
|
139
|
+
while (this._pending) {
|
|
140
|
+
const { time, resolvers } = this._pending;
|
|
141
|
+
this._pending = null;
|
|
142
|
+
try {
|
|
143
|
+
await this._satisfy(time);
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
if (!this._disposed)
|
|
147
|
+
console.error("[smoove] video seek failed:", err);
|
|
148
|
+
}
|
|
149
|
+
for (const r of resolvers)
|
|
150
|
+
r();
|
|
151
|
+
}
|
|
152
|
+
this._seeking = false;
|
|
153
|
+
}
|
|
154
|
+
/** Land the owned canvas on the last frame whose start timestamp is <= `t`. */
|
|
155
|
+
async _satisfy(t) {
|
|
156
|
+
if (!this._sink)
|
|
157
|
+
return;
|
|
158
|
+
// Backward seek or a large forward jump: the forward iterator can't help —
|
|
159
|
+
// start a fresh one seeded at the target.
|
|
160
|
+
if (t < this._curTs - SAME_FRAME_EPS || t > this._curTs + BIGGEST_FORWARD_JUMP) {
|
|
161
|
+
await this._restartIterator(t);
|
|
162
|
+
}
|
|
163
|
+
else if (!this._iter) {
|
|
164
|
+
await this._restartIterator(t);
|
|
165
|
+
}
|
|
166
|
+
// Advance while the lookahead frame still starts at or before the target.
|
|
167
|
+
while (true) {
|
|
168
|
+
if (!this._next)
|
|
169
|
+
this._next = await this._pull();
|
|
170
|
+
if (this._next && this._next.timestamp <= t + SAME_FRAME_EPS) {
|
|
171
|
+
this._draw(this._next);
|
|
172
|
+
this._next = null;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async _restartIterator(t) {
|
|
180
|
+
await this._iter?.return();
|
|
181
|
+
this._iter = this._sink?.canvases(Math.max(0, t)) ?? null;
|
|
182
|
+
this._next = null;
|
|
183
|
+
this._curTs = Number.NEGATIVE_INFINITY;
|
|
184
|
+
const first = await this._pull();
|
|
185
|
+
if (first)
|
|
186
|
+
this._draw(first);
|
|
187
|
+
}
|
|
188
|
+
async _pull() {
|
|
189
|
+
if (!this._iter)
|
|
190
|
+
return null;
|
|
191
|
+
const r = await this._iter.next();
|
|
192
|
+
return r.done ? null : r.value;
|
|
193
|
+
}
|
|
194
|
+
/** Blit a decoded frame onto the owned canvas and notify listeners. */
|
|
195
|
+
_draw(frame) {
|
|
196
|
+
this._curTs = frame.timestamp;
|
|
197
|
+
this._ctx.clearRect(0, 0, this._w, this._h);
|
|
198
|
+
this._ctx.drawImage(frame.canvas, 0, 0, this._w, this._h);
|
|
199
|
+
for (const cb of this._frameCbs)
|
|
200
|
+
cb();
|
|
201
|
+
}
|
|
202
|
+
// Pull-based: nothing self-plays. The frame clock drives `seek()` each tick.
|
|
203
|
+
play() {
|
|
204
|
+
return Promise.resolve();
|
|
205
|
+
}
|
|
206
|
+
pause() { }
|
|
207
|
+
setMuted(_muted) { }
|
|
208
|
+
setVolume(_volume) { }
|
|
209
|
+
setPlaybackRate(_rate) { }
|
|
210
|
+
onReady(cb) {
|
|
211
|
+
if (this._ready) {
|
|
212
|
+
cb();
|
|
213
|
+
return () => { };
|
|
214
|
+
}
|
|
215
|
+
this._readyCbs.add(cb);
|
|
216
|
+
return () => this._readyCbs.delete(cb);
|
|
217
|
+
}
|
|
218
|
+
onFrame(cb) {
|
|
219
|
+
this._frameCbs.add(cb);
|
|
220
|
+
return () => this._frameCbs.delete(cb);
|
|
221
|
+
}
|
|
222
|
+
destroy() {
|
|
223
|
+
this._disposed = true;
|
|
224
|
+
void this._iter?.return();
|
|
225
|
+
this._iter = null;
|
|
226
|
+
this._next = null;
|
|
227
|
+
this._input?.dispose();
|
|
228
|
+
this._input = null;
|
|
229
|
+
this._sink = null;
|
|
230
|
+
this._audioSink = null;
|
|
231
|
+
this._readyCbs.clear();
|
|
232
|
+
this._frameCbs.clear();
|
|
233
|
+
this._pending = null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=video-source-mediabunny.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-source-mediabunny.js","sourceRoot":"","sources":["../../../src/media/video/video-source-mediabunny.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Environment } from "../../engine/environment.js";
|
|
2
|
+
/** Seek precision: exact frame ("precise", default) vs. snappy nearest-keyframe ("fast", for scrubbing). */
|
|
3
|
+
export type SeekMode = "precise" | "fast";
|
|
4
|
+
/**
|
|
5
|
+
* Runtime-agnostic video source — the dependency-injection seam between the
|
|
6
|
+
* engine and the actual media decoder. The browser implementation
|
|
7
|
+
* ({@link BrowserVideoSource}) wraps an `HTMLVideoElement`; a server renderer
|
|
8
|
+
* (e.g. Bun + Skia Canvas) injects its own decoder-backed source that produces
|
|
9
|
+
* drawable frames on demand.
|
|
10
|
+
*/
|
|
11
|
+
export interface VideoSource {
|
|
12
|
+
load(src: string): Promise<void>;
|
|
13
|
+
/** Drawable surface for `Konva.Image`, or null until ready. */
|
|
14
|
+
readonly element: CanvasImageSource | null;
|
|
15
|
+
readonly naturalWidth: number;
|
|
16
|
+
readonly naturalHeight: number;
|
|
17
|
+
/** Duration in seconds, or 0 if unknown. */
|
|
18
|
+
readonly duration: number;
|
|
19
|
+
/** Current media playback position in seconds. */
|
|
20
|
+
readonly currentTime: number;
|
|
21
|
+
readonly isReady: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Seek to a time in seconds. Resolves once the frame at that time has been
|
|
24
|
+
* decoded and is ready to draw — rendering `await`s this to gate frame
|
|
25
|
+
* capture; preview fires it and forgets.
|
|
26
|
+
*
|
|
27
|
+
* `mode: "fast"` requests an approximate, snappy seek (nearest keyframe) for
|
|
28
|
+
* scrubbing; the default `"precise"` lands on the exact frame.
|
|
29
|
+
*/
|
|
30
|
+
seek(timeSeconds: number, mode?: SeekMode): Promise<void>;
|
|
31
|
+
play(): Promise<void>;
|
|
32
|
+
pause(): void;
|
|
33
|
+
setMuted(muted: boolean): void;
|
|
34
|
+
setVolume(volume: number): void;
|
|
35
|
+
setPlaybackRate(rate: number): void;
|
|
36
|
+
/** Fires once when metadata + first frame are available. */
|
|
37
|
+
onReady(cb: () => void): () => void;
|
|
38
|
+
/** Fires whenever a new frame has been decoded and is ready to draw. */
|
|
39
|
+
onFrame(cb: () => void): () => void;
|
|
40
|
+
destroy(): void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Factory for a {@link VideoSource}. Receives the resolved {@link Environment}
|
|
44
|
+
* so a single factory can pick an implementation (e.g. browser vs. decoder)
|
|
45
|
+
* based on whether we are rendering or previewing.
|
|
46
|
+
*/
|
|
47
|
+
export type VideoSourceFactory = (env: Environment) => VideoSource;
|
|
48
|
+
//# sourceMappingURL=video-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-source.d.ts","sourceRoot":"","sources":["../../../src/media/video/video-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE/D,4GAA4G;AAC5G,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,kDAAkD;IAClD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;;;;OAOG;IACH,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,4DAA4D;IAC5D,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IACpC,wEAAwE;IACxE,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IACpC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-source.js","sourceRoot":"","sources":["../../../src/media/video/video-source.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marker attrs stamped on media nodes ({@link Video}, {@link Audio}) and
|
|
3
|
+
* inspected by `Sequence`/`Composition`. Kept in a dependency-free leaf so
|
|
4
|
+
* `composition.ts` can discover media by string without importing `video/` or
|
|
5
|
+
* `audio/` (which would create an import cycle).
|
|
6
|
+
*/
|
|
7
|
+
/** Set by every tickable media node — drives Sequence discovery + mixer registration. */
|
|
8
|
+
export declare const MEDIA_MARK = "__kmIsMedia";
|
|
9
|
+
/** Additionally set by {@link Video} — backs `isVideoNode`. */
|
|
10
|
+
export declare const VIDEO_MARK = "__kmIsVideo";
|
|
11
|
+
/** Additionally set by {@link Audio} — backs `isAudioNode`. */
|
|
12
|
+
export declare const AUDIO_MARK = "__kmIsAudio";
|
|
13
|
+
/**
|
|
14
|
+
* Set by non-media nodes that still want a per-frame `_kmTick` callback (e.g.
|
|
15
|
+
* {@link Text}'s typewriter). Discovered by `Sequence` alongside media, but
|
|
16
|
+
* NOT registered with the audio mixer.
|
|
17
|
+
*/
|
|
18
|
+
export declare const TICK_MARK = "__kmIsTick";
|
|
19
|
+
//# sourceMappingURL=media-marker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-marker.d.ts","sourceRoot":"","sources":["../src/media-marker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,yFAAyF;AACzF,eAAO,MAAM,UAAU,gBAAgB,CAAC;AACxC,+DAA+D;AAC/D,eAAO,MAAM,UAAU,gBAAgB,CAAC;AACxC,+DAA+D;AAC/D,eAAO,MAAM,UAAU,gBAAgB,CAAC;AACxC;;;;GAIG;AACH,eAAO,MAAM,SAAS,eAAe,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marker attrs stamped on media nodes ({@link Video}, {@link Audio}) and
|
|
3
|
+
* inspected by `Sequence`/`Composition`. Kept in a dependency-free leaf so
|
|
4
|
+
* `composition.ts` can discover media by string without importing `video/` or
|
|
5
|
+
* `audio/` (which would create an import cycle).
|
|
6
|
+
*/
|
|
7
|
+
/** Set by every tickable media node — drives Sequence discovery + mixer registration. */
|
|
8
|
+
export const MEDIA_MARK = "__kmIsMedia";
|
|
9
|
+
/** Additionally set by {@link Video} — backs `isVideoNode`. */
|
|
10
|
+
export const VIDEO_MARK = "__kmIsVideo";
|
|
11
|
+
/** Additionally set by {@link Audio} — backs `isAudioNode`. */
|
|
12
|
+
export const AUDIO_MARK = "__kmIsAudio";
|
|
13
|
+
/**
|
|
14
|
+
* Set by non-media nodes that still want a per-frame `_kmTick` callback (e.g.
|
|
15
|
+
* {@link Text}'s typewriter). Discovered by `Sequence` alongside media, but
|
|
16
|
+
* NOT registered with the audio mixer.
|
|
17
|
+
*/
|
|
18
|
+
export const TICK_MARK = "__kmIsTick";
|
|
19
|
+
//# sourceMappingURL=media-marker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-marker.js","sourceRoot":"","sources":["../src/media-marker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,yFAAyF;AACzF,MAAM,CAAC,MAAM,UAAU,GAAG,aAAa,CAAC;AACxC,+DAA+D;AAC/D,MAAM,CAAC,MAAM,UAAU,GAAG,aAAa,CAAC;AACxC,+DAA+D;AAC/D,MAAM,CAAC,MAAM,UAAU,GAAG,aAAa,CAAC;AACxC;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,YAAY,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Resolved per-clip timing, derived from a media config + composition fps. */
|
|
2
|
+
export type MediaTiming = {
|
|
3
|
+
fps: number;
|
|
4
|
+
/** Frames trimmed from the start of the media. */
|
|
5
|
+
trimBefore: number;
|
|
6
|
+
/** Exclusive frame bound, or undefined to play to the media's natural end. */
|
|
7
|
+
trimAfter?: number;
|
|
8
|
+
loop: boolean;
|
|
9
|
+
playbackRate: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Convert a local frame to a media time in seconds — Remotion's `getMediaTime`,
|
|
13
|
+
* extended with `loop` wrap-around. Without `loop`, the media freezes on its
|
|
14
|
+
* last in-range frame (Remotion's default behavior). Shared by video and audio.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getMediaTime(localFrame: number, timing: MediaTiming): number;
|
|
17
|
+
//# sourceMappingURL=media-time.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-time.d.ts","sourceRoot":"","sources":["../src/media-time.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,MAAM,CAU5E"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a local frame to a media time in seconds — Remotion's `getMediaTime`,
|
|
3
|
+
* extended with `loop` wrap-around. Without `loop`, the media freezes on its
|
|
4
|
+
* last in-range frame (Remotion's default behavior). Shared by video and audio.
|
|
5
|
+
*/
|
|
6
|
+
export function getMediaTime(localFrame, timing) {
|
|
7
|
+
const { fps, trimBefore, trimAfter, loop, playbackRate } = timing;
|
|
8
|
+
const advanced = localFrame * playbackRate;
|
|
9
|
+
if (trimAfter === undefined) {
|
|
10
|
+
// No explicit end bound: play forward; loop has no length to wrap to.
|
|
11
|
+
return (trimBefore + advanced) / fps;
|
|
12
|
+
}
|
|
13
|
+
const loopLen = Math.max(1, trimAfter - trimBefore);
|
|
14
|
+
const eff = loop ? ((advanced % loopLen) + loopLen) % loopLen : Math.min(advanced, loopLen - 1);
|
|
15
|
+
return (trimBefore + eff) / fps;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=media-time.js.map
|