@elah/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/dist/actions/index.d.ts +3 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/splitClipAtPlayhead.d.ts +8 -0
- package/dist/actions/splitClipAtPlayhead.js +34 -0
- package/dist/actions/types.d.ts +8 -0
- package/dist/actions/types.js +1 -0
- package/dist/assets/importFiles.d.ts +29 -0
- package/dist/assets/importFiles.js +351 -0
- package/dist/assets/index.d.ts +10 -0
- package/dist/assets/index.js +13 -0
- package/dist/assets/store.d.ts +13 -0
- package/dist/assets/store.js +20 -0
- package/dist/assets/types.d.ts +23 -0
- package/dist/assets/types.js +1 -0
- package/dist/debug/trace.d.ts +20 -0
- package/dist/debug/trace.js +98 -0
- package/dist/editor/TimelineEngine.d.ts +65 -0
- package/dist/editor/TimelineEngine.js +413 -0
- package/dist/editor-context.d.ts +10 -0
- package/dist/editor-context.js +10 -0
- package/dist/elements/audio.d.ts +13 -0
- package/dist/elements/audio.js +4 -0
- package/dist/elements/base.d.ts +40 -0
- package/dist/elements/base.js +39 -0
- package/dist/elements/image.d.ts +13 -0
- package/dist/elements/image.js +4 -0
- package/dist/elements/text.d.ts +12 -0
- package/dist/elements/text.js +8 -0
- package/dist/elements/video.d.ts +13 -0
- package/dist/elements/video.js +4 -0
- package/dist/export/ExportWorker.d.ts +1 -0
- package/dist/export/ExportWorker.js +371 -0
- package/dist/export/exportVideo.d.ts +3 -0
- package/dist/export/exportVideo.js +118 -0
- package/dist/export/index.d.ts +2 -0
- package/dist/export/index.js +1 -0
- package/dist/export/lazyExport.d.ts +4 -0
- package/dist/export/lazyExport.js +4 -0
- package/dist/export/types.d.ts +37 -0
- package/dist/export/types.js +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +28 -0
- package/dist/media/audio/AudioPlaybackController.d.ts +26 -0
- package/dist/media/audio/AudioPlaybackController.js +125 -0
- package/dist/media/video/DecoderBackedVideoFrameProvider.d.ts +51 -0
- package/dist/media/video/DecoderBackedVideoFrameProvider.js +125 -0
- package/dist/media/video/FrameCache.d.ts +28 -0
- package/dist/media/video/FrameCache.js +88 -0
- package/dist/media/video/StreamingFrameProducer.d.ts +57 -0
- package/dist/media/video/StreamingFrameProducer.js +356 -0
- package/dist/media/video/VideoDecoderManager.d.ts +59 -0
- package/dist/media/video/VideoDecoderManager.js +342 -0
- package/dist/media/video/VideoFrameProvider.d.ts +101 -0
- package/dist/media/video/VideoFrameProvider.js +257 -0
- package/dist/media/video/demuxer/MediabunnyDemuxer.d.ts +23 -0
- package/dist/media/video/demuxer/MediabunnyDemuxer.js +88 -0
- package/dist/media/video/demuxer/createMediabunnyBackend.d.ts +32 -0
- package/dist/media/video/demuxer/createMediabunnyBackend.js +156 -0
- package/dist/media/video/index.d.ts +8 -0
- package/dist/media/video/index.js +5 -0
- package/dist/playback/PlaybackEngine.d.ts +50 -0
- package/dist/playback/PlaybackEngine.js +188 -0
- package/dist/renderer/gpu/GpuRenderer.d.ts +38 -0
- package/dist/renderer/gpu/GpuRenderer.js +208 -0
- package/dist/renderer/gpu/RenderGraph.d.ts +10 -0
- package/dist/renderer/gpu/RenderGraph.js +80 -0
- package/dist/renderer/gpu/ShaderProgram.d.ts +14 -0
- package/dist/renderer/gpu/ShaderProgram.js +76 -0
- package/dist/renderer/gpu/TexturePool.d.ts +25 -0
- package/dist/renderer/gpu/TexturePool.js +93 -0
- package/dist/renderer/gpu/VideoTexture.d.ts +13 -0
- package/dist/renderer/gpu/VideoTexture.js +54 -0
- package/dist/renderer/gpu/WebGLContext.d.ts +28 -0
- package/dist/renderer/gpu/WebGLContext.js +102 -0
- package/dist/renderer/gpu/debug/DebugGpuRenderer.d.ts +27 -0
- package/dist/renderer/gpu/debug/DebugGpuRenderer.js +108 -0
- package/dist/renderer/gpu/debug/DebugOverlay.d.ts +17 -0
- package/dist/renderer/gpu/debug/DebugOverlay.js +83 -0
- package/dist/renderer/gpu/debug/GpuDebugCounters.d.ts +38 -0
- package/dist/renderer/gpu/debug/GpuDebugCounters.js +72 -0
- package/dist/renderer/gpu/debug/GpuDebugGlobal.d.ts +16 -0
- package/dist/renderer/gpu/debug/GpuDebugGlobal.js +14 -0
- package/dist/renderer/gpu/debug/GpuRendererDebugPanel.d.ts +31 -0
- package/dist/renderer/gpu/debug/GpuRendererDebugPanel.js +128 -0
- package/dist/renderer/gpu/debug/RecordingGl.d.ts +88 -0
- package/dist/renderer/gpu/debug/RecordingGl.js +214 -0
- package/dist/renderer/gpu/debug/playground.d.ts +20 -0
- package/dist/renderer/gpu/debug/playground.js +64 -0
- package/dist/renderer/gpu/debug/scenarios.d.ts +7 -0
- package/dist/renderer/gpu/debug/scenarios.js +145 -0
- package/dist/renderer/gpu/debug/types.d.ts +16 -0
- package/dist/renderer/gpu/debug/types.js +1 -0
- package/dist/renderer/gpu/layers/FrameProbeLayer.d.ts +16 -0
- package/dist/renderer/gpu/layers/FrameProbeLayer.js +127 -0
- package/dist/renderer/gpu/layers/ImageLayer.d.ts +23 -0
- package/dist/renderer/gpu/layers/ImageLayer.js +124 -0
- package/dist/renderer/gpu/layers/TestLayer.d.ts +16 -0
- package/dist/renderer/gpu/layers/TestLayer.js +109 -0
- package/dist/renderer/gpu/layers/TextLayer.d.ts +19 -0
- package/dist/renderer/gpu/layers/TextLayer.js +166 -0
- package/dist/renderer/gpu/layers/VideoLayer.d.ts +38 -0
- package/dist/renderer/gpu/layers/VideoLayer.js +194 -0
- package/dist/renderer/gpu/layers/drawRect.d.ts +13 -0
- package/dist/renderer/gpu/layers/drawRect.js +55 -0
- package/dist/renderer/gpu/layers/objectFit.d.ts +7 -0
- package/dist/renderer/gpu/layers/objectFit.js +26 -0
- package/dist/renderer/gpu/layers/textLayout.d.ts +47 -0
- package/dist/renderer/gpu/layers/textLayout.js +82 -0
- package/dist/renderer/gpu/layers/types.d.ts +18 -0
- package/dist/renderer/gpu/layers/types.js +1 -0
- package/dist/renderer/gpu/shaders/quad.frag.d.ts +1 -0
- package/dist/renderer/gpu/shaders/quad.frag.js +14 -0
- package/dist/renderer/gpu/shaders/quad.vert.d.ts +1 -0
- package/dist/renderer/gpu/shaders/quad.vert.js +28 -0
- package/dist/renderer/gpu/types.d.ts +21 -0
- package/dist/renderer/gpu/types.js +1 -0
- package/dist/renderer/gpu/viewport.d.ts +7 -0
- package/dist/renderer/gpu/viewport.js +33 -0
- package/dist/renderer/types.d.ts +7 -0
- package/dist/renderer/types.js +1 -0
- package/dist/resolver/resolveTimeline.d.ts +3 -0
- package/dist/resolver/resolveTimeline.js +249 -0
- package/dist/resolver/scene.d.ts +54 -0
- package/dist/resolver/scene.js +1 -0
- package/dist/stores/playback.store.d.ts +50 -0
- package/dist/stores/playback.store.js +42 -0
- package/dist/stores/selection.store.d.ts +12 -0
- package/dist/stores/selection.store.js +19 -0
- package/dist/stores/tracks.store.d.ts +19 -0
- package/dist/stores/tracks.store.js +27 -0
- package/dist/stores/transitions.store.d.ts +9 -0
- package/dist/stores/transitions.store.js +7 -0
- package/dist/track/track.d.ts +8 -0
- package/dist/track/track.js +19 -0
- package/dist/types/index.d.ts +117 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/frames.d.ts +20 -0
- package/dist/utils/frames.js +40 -0
- package/dist/utils/id.d.ts +1 -0
- package/dist/utils/id.js +3 -0
- package/dist/utils/snap.d.ts +5 -0
- package/dist/utils/snap.js +79 -0
- package/dist/visitor/add.d.ts +3 -0
- package/dist/visitor/add.js +16 -0
- package/dist/visitor/clone.d.ts +3 -0
- package/dist/visitor/clone.js +25 -0
- package/dist/visitor/remove.d.ts +5 -0
- package/dist/visitor/remove.js +29 -0
- package/dist/visitor/split.d.ts +3 -0
- package/dist/visitor/split.js +31 -0
- package/dist/visitor/update.d.ts +4 -0
- package/dist/visitor/update.js +31 -0
- package/package.json +31 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { resolveTimeline } from '../../resolver/resolveTimeline';
|
|
2
|
+
export class AudioPlaybackController {
|
|
3
|
+
constructor(playback, getProject, options = {}) {
|
|
4
|
+
this._ctx = null;
|
|
5
|
+
this._buffers = new Map();
|
|
6
|
+
this._node = null;
|
|
7
|
+
this._gain = null;
|
|
8
|
+
this._activeClipId = null;
|
|
9
|
+
this._lastEpoch = -1;
|
|
10
|
+
this._scheduleToken = 0;
|
|
11
|
+
this._unsub = null;
|
|
12
|
+
this._playback = playback;
|
|
13
|
+
this._getProject = getProject;
|
|
14
|
+
this._audioContextFactory =
|
|
15
|
+
options.audioContextFactory ?? (() => new AudioContext());
|
|
16
|
+
}
|
|
17
|
+
start() {
|
|
18
|
+
if (this._unsub)
|
|
19
|
+
return;
|
|
20
|
+
if (!this._ctx) {
|
|
21
|
+
this._ctx = this._audioContextFactory();
|
|
22
|
+
}
|
|
23
|
+
this._unsub = this._playback.subscribe((snap) => this._onSnapshot(snap));
|
|
24
|
+
this._onSnapshot({
|
|
25
|
+
currentFrame: this._playback.currentFrame,
|
|
26
|
+
isPlaying: this._playback.isPlaying,
|
|
27
|
+
playbackRate: this._playback.playbackRate,
|
|
28
|
+
loop: this._playback.loop,
|
|
29
|
+
epoch: -1,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
destroy() {
|
|
33
|
+
this._unsub?.();
|
|
34
|
+
this._unsub = null;
|
|
35
|
+
this._stop();
|
|
36
|
+
this._activeClipId = null;
|
|
37
|
+
if (this._ctx) {
|
|
38
|
+
void this._ctx.close();
|
|
39
|
+
this._ctx = null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
_onSnapshot(snap) {
|
|
43
|
+
const transportChanged = snap.epoch !== this._lastEpoch;
|
|
44
|
+
this._lastEpoch = snap.epoch;
|
|
45
|
+
const scene = resolveTimeline(snap.currentFrame, this._getProject());
|
|
46
|
+
const clip = scene.audios.length > 0 ? scene.audios[scene.audios.length - 1] : null;
|
|
47
|
+
if (!clip) {
|
|
48
|
+
this._stop();
|
|
49
|
+
this._activeClipId = null;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (this._gain) {
|
|
53
|
+
this._gain.gain.value = clip.volume;
|
|
54
|
+
}
|
|
55
|
+
if (!snap.isPlaying) {
|
|
56
|
+
this._stop();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const needRestart = transportChanged || clip.id !== this._activeClipId || this._node === null;
|
|
60
|
+
if (!needRestart)
|
|
61
|
+
return;
|
|
62
|
+
const offsetSec = clip.sourceFrame / scene.fps;
|
|
63
|
+
void this._schedule(clip.id, clip.src, offsetSec, clip.volume);
|
|
64
|
+
}
|
|
65
|
+
async _schedule(clipId, src, offsetSec, volume) {
|
|
66
|
+
const token = ++this._scheduleToken;
|
|
67
|
+
let buffer;
|
|
68
|
+
try {
|
|
69
|
+
buffer = await this._ensureBuffer(src);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (token !== this._scheduleToken || !this._ctx)
|
|
75
|
+
return;
|
|
76
|
+
this._startNode(buffer, offsetSec, volume);
|
|
77
|
+
this._activeClipId = clipId;
|
|
78
|
+
}
|
|
79
|
+
_startNode(buffer, offsetSec, volume) {
|
|
80
|
+
const ctx = this._ctx;
|
|
81
|
+
if (!ctx)
|
|
82
|
+
return;
|
|
83
|
+
this._stop();
|
|
84
|
+
if (offsetSec >= buffer.duration)
|
|
85
|
+
return;
|
|
86
|
+
const offset = Math.max(0, offsetSec);
|
|
87
|
+
if (ctx.state === 'suspended') {
|
|
88
|
+
void ctx.resume();
|
|
89
|
+
}
|
|
90
|
+
const node = ctx.createBufferSource();
|
|
91
|
+
node.buffer = buffer;
|
|
92
|
+
const gain = ctx.createGain();
|
|
93
|
+
gain.gain.value = volume;
|
|
94
|
+
node.connect(gain).connect(ctx.destination);
|
|
95
|
+
node.start(ctx.currentTime, offset);
|
|
96
|
+
this._node = node;
|
|
97
|
+
this._gain = gain;
|
|
98
|
+
}
|
|
99
|
+
_stop() {
|
|
100
|
+
if (this._node) {
|
|
101
|
+
try {
|
|
102
|
+
this._node.stop();
|
|
103
|
+
this._node.disconnect();
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
this._node = null;
|
|
109
|
+
this._gain = null;
|
|
110
|
+
this._scheduleToken++;
|
|
111
|
+
}
|
|
112
|
+
_ensureBuffer(src) {
|
|
113
|
+
const cached = this._buffers.get(src);
|
|
114
|
+
if (cached)
|
|
115
|
+
return cached;
|
|
116
|
+
const ctx = this._ctx;
|
|
117
|
+
if (!ctx)
|
|
118
|
+
return Promise.reject(new Error('AudioPlaybackController: no AudioContext'));
|
|
119
|
+
const promise = fetch(src)
|
|
120
|
+
.then((res) => res.arrayBuffer())
|
|
121
|
+
.then((buf) => ctx.decodeAudioData(buf));
|
|
122
|
+
this._buffers.set(src, promise);
|
|
123
|
+
return promise;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type FrameCacheHooks } from './FrameCache';
|
|
2
|
+
import type { VideoFrameProvider } from './VideoFrameProvider';
|
|
3
|
+
import type { DemuxerFactory } from './demuxer/MediabunnyDemuxer';
|
|
4
|
+
import type { VideoDecoderFactory } from './VideoDecoderManager';
|
|
5
|
+
export interface DecoderBackedVideoFrameProviderOptions {
|
|
6
|
+
src: string;
|
|
7
|
+
fps?: number;
|
|
8
|
+
maxOutstanding?: number;
|
|
9
|
+
maxFrames?: number;
|
|
10
|
+
demuxerFactory: DemuxerFactory;
|
|
11
|
+
decoderFactory?: VideoDecoderFactory;
|
|
12
|
+
cacheHooks?: FrameCacheHooks;
|
|
13
|
+
strictNoOutput?: boolean;
|
|
14
|
+
decodeTimeoutMs?: number;
|
|
15
|
+
}
|
|
16
|
+
type ProviderState = 'active' | 'idle' | 'disposed';
|
|
17
|
+
export declare class DecoderBackedVideoFrameProvider implements VideoFrameProvider {
|
|
18
|
+
private readonly _src;
|
|
19
|
+
private readonly _fps;
|
|
20
|
+
private readonly _maxOutstanding;
|
|
21
|
+
private readonly _cache;
|
|
22
|
+
private readonly _manager;
|
|
23
|
+
private readonly _pending;
|
|
24
|
+
private _lastRequested;
|
|
25
|
+
private _state;
|
|
26
|
+
private _idleTimer;
|
|
27
|
+
private _idleCallback;
|
|
28
|
+
private _openPromise;
|
|
29
|
+
private _openError;
|
|
30
|
+
private _reopening;
|
|
31
|
+
constructor(opts: DecoderBackedVideoFrameProviderOptions);
|
|
32
|
+
getCurrent(sourceFrame: number): VideoFrame | null;
|
|
33
|
+
requestFrame(_sourceFrame: number): void;
|
|
34
|
+
prefetch(_fromSourceFrame: number, _count: number): void;
|
|
35
|
+
setPlayhead(_sourceFrame: number, _opts?: {
|
|
36
|
+
lookaheadFrames?: number;
|
|
37
|
+
}): void;
|
|
38
|
+
markIdle(): void;
|
|
39
|
+
markActive(): void;
|
|
40
|
+
dispose(): void;
|
|
41
|
+
get state(): ProviderState;
|
|
42
|
+
get pendingCount(): number;
|
|
43
|
+
get cacheSize(): number;
|
|
44
|
+
get decoderState(): string;
|
|
45
|
+
get openError(): Error | null;
|
|
46
|
+
get openPromise(): Promise<void> | null;
|
|
47
|
+
setIdleCallback(cb: (() => void) | null): void;
|
|
48
|
+
private _clearIdleTimer;
|
|
49
|
+
private _enqueueRequestFrame;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { FrameCache } from './FrameCache';
|
|
2
|
+
import { GpuDebugCounters } from '../../renderer/gpu/debug/GpuDebugCounters';
|
|
3
|
+
import { VideoDecoderManager } from './VideoDecoderManager';
|
|
4
|
+
const DEFAULT_MAX_OUTSTANDING = 4;
|
|
5
|
+
const DEFAULT_MAX_FRAMES = 30;
|
|
6
|
+
const DEFAULT_FPS = 30;
|
|
7
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 5000;
|
|
8
|
+
export class DecoderBackedVideoFrameProvider {
|
|
9
|
+
constructor(opts) {
|
|
10
|
+
this._pending = new Set();
|
|
11
|
+
this._lastRequested = null;
|
|
12
|
+
this._state = 'active';
|
|
13
|
+
this._idleTimer = null;
|
|
14
|
+
this._idleCallback = null;
|
|
15
|
+
this._openPromise = null;
|
|
16
|
+
this._openError = null;
|
|
17
|
+
this._reopening = false;
|
|
18
|
+
this._src = opts.src;
|
|
19
|
+
this._fps = opts.fps ?? DEFAULT_FPS;
|
|
20
|
+
this._maxOutstanding = opts.maxOutstanding ?? DEFAULT_MAX_OUTSTANDING;
|
|
21
|
+
this._cache = new FrameCache({
|
|
22
|
+
maxFrames: opts.maxFrames ?? DEFAULT_MAX_FRAMES,
|
|
23
|
+
hooks: opts.cacheHooks,
|
|
24
|
+
});
|
|
25
|
+
this._manager = new VideoDecoderManager({
|
|
26
|
+
fps: this._fps,
|
|
27
|
+
demuxerFactory: opts.demuxerFactory,
|
|
28
|
+
decoderFactory: opts.decoderFactory,
|
|
29
|
+
onDroppedFrame: () => {
|
|
30
|
+
GpuDebugCounters.incDropped();
|
|
31
|
+
},
|
|
32
|
+
onError: (_err) => {
|
|
33
|
+
if (this._state === 'disposed' || this._reopening)
|
|
34
|
+
return;
|
|
35
|
+
this._reopening = true;
|
|
36
|
+
this._openPromise = this._manager
|
|
37
|
+
.reopen(this._src)
|
|
38
|
+
.catch((reopenErr) => {
|
|
39
|
+
this._openError = reopenErr;
|
|
40
|
+
})
|
|
41
|
+
.finally(() => {
|
|
42
|
+
this._reopening = false;
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
this._openPromise = this._manager.open(this._src).catch((err) => {
|
|
47
|
+
this._openError = err;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
getCurrent(sourceFrame) {
|
|
51
|
+
if (this._state === 'disposed')
|
|
52
|
+
return null;
|
|
53
|
+
this._cache.setPivot(sourceFrame);
|
|
54
|
+
const frame = this._cache.get(sourceFrame);
|
|
55
|
+
if (frame !== null) {
|
|
56
|
+
GpuDebugCounters.cacheHits++;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
GpuDebugCounters.cacheMisses++;
|
|
60
|
+
}
|
|
61
|
+
GpuDebugCounters.cacheSize = this._cache.size;
|
|
62
|
+
return frame;
|
|
63
|
+
}
|
|
64
|
+
requestFrame(_sourceFrame) {
|
|
65
|
+
}
|
|
66
|
+
prefetch(_fromSourceFrame, _count) {
|
|
67
|
+
}
|
|
68
|
+
setPlayhead(_sourceFrame, _opts) {
|
|
69
|
+
}
|
|
70
|
+
markIdle() {
|
|
71
|
+
if (this._state === 'disposed')
|
|
72
|
+
return;
|
|
73
|
+
this._state = 'idle';
|
|
74
|
+
this._clearIdleTimer();
|
|
75
|
+
this._idleTimer = setTimeout(() => {
|
|
76
|
+
this._idleCallback?.();
|
|
77
|
+
}, DEFAULT_IDLE_TIMEOUT_MS);
|
|
78
|
+
}
|
|
79
|
+
markActive() {
|
|
80
|
+
if (this._state === 'disposed')
|
|
81
|
+
return;
|
|
82
|
+
this._clearIdleTimer();
|
|
83
|
+
this._state = 'active';
|
|
84
|
+
}
|
|
85
|
+
dispose() {
|
|
86
|
+
if (this._state === 'disposed')
|
|
87
|
+
return;
|
|
88
|
+
this._state = 'disposed';
|
|
89
|
+
this._clearIdleTimer();
|
|
90
|
+
this._manager.dispose();
|
|
91
|
+
this._cache.dispose();
|
|
92
|
+
GpuDebugCounters.cacheSize = 0;
|
|
93
|
+
this._pending.clear();
|
|
94
|
+
this._lastRequested = null;
|
|
95
|
+
}
|
|
96
|
+
get state() {
|
|
97
|
+
return this._state;
|
|
98
|
+
}
|
|
99
|
+
get pendingCount() {
|
|
100
|
+
return this._pending.size;
|
|
101
|
+
}
|
|
102
|
+
get cacheSize() {
|
|
103
|
+
return this._cache.size;
|
|
104
|
+
}
|
|
105
|
+
get decoderState() {
|
|
106
|
+
return this._manager.state;
|
|
107
|
+
}
|
|
108
|
+
get openError() {
|
|
109
|
+
return this._openError;
|
|
110
|
+
}
|
|
111
|
+
get openPromise() {
|
|
112
|
+
return this._openPromise;
|
|
113
|
+
}
|
|
114
|
+
setIdleCallback(cb) {
|
|
115
|
+
this._idleCallback = cb;
|
|
116
|
+
}
|
|
117
|
+
_clearIdleTimer() {
|
|
118
|
+
if (this._idleTimer !== null) {
|
|
119
|
+
clearTimeout(this._idleTimer);
|
|
120
|
+
this._idleTimer = null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
_enqueueRequestFrame(_sourceFrame) {
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface Closeable {
|
|
2
|
+
close(): void;
|
|
3
|
+
}
|
|
4
|
+
export interface FrameCacheHooks {
|
|
5
|
+
onPut?: (sourceFrame: number) => void;
|
|
6
|
+
onEvict?: (sourceFrame: number) => void;
|
|
7
|
+
onClear?: () => void;
|
|
8
|
+
}
|
|
9
|
+
export interface FrameCacheOptions {
|
|
10
|
+
maxFrames?: number;
|
|
11
|
+
hooks?: FrameCacheHooks;
|
|
12
|
+
}
|
|
13
|
+
export declare class FrameCache<T extends Closeable = VideoFrame> {
|
|
14
|
+
private readonly _maxFrames;
|
|
15
|
+
private readonly _hooks;
|
|
16
|
+
private readonly _frames;
|
|
17
|
+
private _pivot;
|
|
18
|
+
constructor(maxFramesOrOptions?: number | FrameCacheOptions);
|
|
19
|
+
get size(): number;
|
|
20
|
+
setPivot(sourceFrame: number): void;
|
|
21
|
+
get(sourceFrame: number, maxLookback?: number): T | null;
|
|
22
|
+
put(sourceFrame: number, frame: T): void;
|
|
23
|
+
has(sourceFrame: number): boolean;
|
|
24
|
+
evictBefore(sourceFrame: number): void;
|
|
25
|
+
clear(): void;
|
|
26
|
+
dispose(): void;
|
|
27
|
+
private _evictFurthest;
|
|
28
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const DEFAULT_MAX_FRAMES = 30;
|
|
2
|
+
export class FrameCache {
|
|
3
|
+
constructor(maxFramesOrOptions) {
|
|
4
|
+
this._frames = new Map();
|
|
5
|
+
this._pivot = 0;
|
|
6
|
+
if (typeof maxFramesOrOptions === 'number' || maxFramesOrOptions === undefined) {
|
|
7
|
+
this._maxFrames = maxFramesOrOptions ?? DEFAULT_MAX_FRAMES;
|
|
8
|
+
this._hooks = {};
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
this._maxFrames = maxFramesOrOptions.maxFrames ?? DEFAULT_MAX_FRAMES;
|
|
12
|
+
this._hooks = maxFramesOrOptions.hooks ?? {};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
get size() {
|
|
16
|
+
return this._frames.size;
|
|
17
|
+
}
|
|
18
|
+
setPivot(sourceFrame) {
|
|
19
|
+
this._pivot = sourceFrame;
|
|
20
|
+
}
|
|
21
|
+
get(sourceFrame, maxLookback = 0) {
|
|
22
|
+
const exact = this._frames.get(sourceFrame);
|
|
23
|
+
if (exact !== undefined)
|
|
24
|
+
return exact;
|
|
25
|
+
if (maxLookback <= 0)
|
|
26
|
+
return null;
|
|
27
|
+
let bestKey = sourceFrame - maxLookback - 1;
|
|
28
|
+
let best = null;
|
|
29
|
+
for (const [key, frame] of this._frames) {
|
|
30
|
+
if (key <= sourceFrame && key > bestKey) {
|
|
31
|
+
bestKey = key;
|
|
32
|
+
best = frame;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return best;
|
|
36
|
+
}
|
|
37
|
+
put(sourceFrame, frame) {
|
|
38
|
+
const existing = this._frames.get(sourceFrame);
|
|
39
|
+
if (existing) {
|
|
40
|
+
existing.close();
|
|
41
|
+
this._frames.delete(sourceFrame);
|
|
42
|
+
this._hooks.onEvict?.(sourceFrame);
|
|
43
|
+
}
|
|
44
|
+
else if (this._frames.size >= this._maxFrames) {
|
|
45
|
+
this._evictFurthest();
|
|
46
|
+
}
|
|
47
|
+
this._frames.set(sourceFrame, frame);
|
|
48
|
+
this._hooks.onPut?.(sourceFrame);
|
|
49
|
+
}
|
|
50
|
+
has(sourceFrame) {
|
|
51
|
+
return this._frames.has(sourceFrame);
|
|
52
|
+
}
|
|
53
|
+
evictBefore(sourceFrame) {
|
|
54
|
+
for (const key of [...this._frames.keys()]) {
|
|
55
|
+
if (key < sourceFrame) {
|
|
56
|
+
this._frames.get(key).close();
|
|
57
|
+
this._frames.delete(key);
|
|
58
|
+
this._hooks.onEvict?.(key);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
clear() {
|
|
63
|
+
for (const frame of this._frames.values()) {
|
|
64
|
+
frame.close();
|
|
65
|
+
}
|
|
66
|
+
this._frames.clear();
|
|
67
|
+
this._hooks.onClear?.();
|
|
68
|
+
}
|
|
69
|
+
dispose() {
|
|
70
|
+
this.clear();
|
|
71
|
+
}
|
|
72
|
+
_evictFurthest() {
|
|
73
|
+
let victimKey = null;
|
|
74
|
+
let maxDist = -1;
|
|
75
|
+
for (const key of this._frames.keys()) {
|
|
76
|
+
const dist = Math.abs(key - this._pivot);
|
|
77
|
+
if (dist > maxDist || (dist === maxDist && victimKey !== null && key < victimKey)) {
|
|
78
|
+
maxDist = dist;
|
|
79
|
+
victimKey = key;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (victimKey !== null) {
|
|
83
|
+
this._frames.get(victimKey).close();
|
|
84
|
+
this._frames.delete(victimKey);
|
|
85
|
+
this._hooks.onEvict?.(victimKey);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ProvidedFrame, VideoFrameProvider } from './VideoFrameProvider';
|
|
2
|
+
import { type FrameCacheHooks } from './FrameCache';
|
|
3
|
+
import type { DemuxerFactory } from './demuxer/MediabunnyDemuxer';
|
|
4
|
+
import type { VideoDecoderFactory } from './VideoDecoderManager';
|
|
5
|
+
type ProviderState = 'active' | 'idle' | 'disposed';
|
|
6
|
+
export interface StreamingFrameProducerOptions {
|
|
7
|
+
src: string;
|
|
8
|
+
fps?: number;
|
|
9
|
+
lookaheadFrames?: number;
|
|
10
|
+
maxFrames?: number;
|
|
11
|
+
demuxerFactory: DemuxerFactory;
|
|
12
|
+
decoderFactory?: VideoDecoderFactory;
|
|
13
|
+
cacheHooks?: FrameCacheHooks;
|
|
14
|
+
frameConverter?: (frame: VideoFrame) => Promise<ImageBitmap>;
|
|
15
|
+
}
|
|
16
|
+
export declare class StreamingFrameProducer implements VideoFrameProvider {
|
|
17
|
+
private readonly _src;
|
|
18
|
+
private readonly _fps;
|
|
19
|
+
private readonly _lookaheadFrames;
|
|
20
|
+
private readonly _usPerFrame;
|
|
21
|
+
private readonly _cache;
|
|
22
|
+
private readonly _convert;
|
|
23
|
+
private readonly _manager;
|
|
24
|
+
private _state;
|
|
25
|
+
private _lastPlayhead;
|
|
26
|
+
private _latestPlayhead;
|
|
27
|
+
private _feedWatermark;
|
|
28
|
+
private _highestDecodedFrame;
|
|
29
|
+
private _ticksSinceDecodeAdvance;
|
|
30
|
+
private _resetInProgress;
|
|
31
|
+
private _lastLoggedPlayhead;
|
|
32
|
+
private _idleTimer;
|
|
33
|
+
private _idleCallback;
|
|
34
|
+
private _openPromise;
|
|
35
|
+
private _openError;
|
|
36
|
+
private _reopening;
|
|
37
|
+
constructor(opts: StreamingFrameProducerOptions);
|
|
38
|
+
getCurrent(sourceFrame: number): ProvidedFrame | null;
|
|
39
|
+
private _cacheKeysSnapshot;
|
|
40
|
+
setPlayhead(sourceFrame: number, opts?: {
|
|
41
|
+
lookaheadFrames?: number;
|
|
42
|
+
}): void;
|
|
43
|
+
markIdle(): void;
|
|
44
|
+
markActive(): void;
|
|
45
|
+
dispose(): void;
|
|
46
|
+
get state(): ProviderState;
|
|
47
|
+
get decoderState(): string;
|
|
48
|
+
get cacheSize(): number;
|
|
49
|
+
get openError(): Error | null;
|
|
50
|
+
get openPromise(): Promise<void> | null;
|
|
51
|
+
setIdleCallback(cb: (() => void) | null): void;
|
|
52
|
+
private _handleDiscontinuity;
|
|
53
|
+
private _feedWindow;
|
|
54
|
+
private _copyAndCache;
|
|
55
|
+
private _clearIdleTimer;
|
|
56
|
+
}
|
|
57
|
+
export {};
|