@meframe/core 0.0.7 → 0.0.9
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/cache/CacheManager.d.ts +5 -3
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +3 -10
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/l1/AudioL1Cache.d.ts +5 -0
- package/dist/cache/l1/AudioL1Cache.d.ts.map +1 -1
- package/dist/cache/l1/AudioL1Cache.js +31 -3
- package/dist/cache/l1/AudioL1Cache.js.map +1 -1
- package/dist/controllers/PlaybackController.d.ts +0 -4
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +11 -54
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/controllers/PreRenderService.d.ts.map +1 -1
- package/dist/controllers/PreRenderService.js +29 -14
- package/dist/controllers/PreRenderService.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +2 -1
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +24 -16
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/stages/compose/GlobalAudioSession.d.ts +20 -25
- package/dist/stages/compose/GlobalAudioSession.d.ts.map +1 -1
- package/dist/stages/compose/GlobalAudioSession.js +241 -64
- package/dist/stages/compose/GlobalAudioSession.js.map +1 -1
- package/dist/stages/compose/OfflineAudioMixer.d.ts.map +1 -1
- package/dist/stages/compose/OfflineAudioMixer.js +26 -8
- package/dist/stages/compose/OfflineAudioMixer.js.map +1 -1
- package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts +4 -0
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +11 -3
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/workers/MP4Demuxer.js +4 -3
- package/dist/workers/MP4Demuxer.js.map +1 -1
- package/dist/workers/stages/demux/audio-demux.worker.js +28 -18
- package/dist/workers/stages/demux/audio-demux.worker.js.map +1 -1
- package/dist/workers/stages/demux/video-demux.worker.js +5 -2
- package/dist/workers/stages/demux/video-demux.worker.js.map +1 -1
- package/dist/workers/stages/encode/video-encode.worker.js +6 -21
- package/dist/workers/stages/encode/video-encode.worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/cache/l1/MixedAudioL1Cache.d.ts +0 -13
- package/dist/cache/l1/MixedAudioL1Cache.d.ts.map +0 -1
- package/dist/cache/l1/MixedAudioL1Cache.js +0 -52
- package/dist/cache/l1/MixedAudioL1Cache.js.map +0 -1
|
@@ -37,7 +37,6 @@ export interface WaitForFrameResult {
|
|
|
37
37
|
export declare class CacheManager {
|
|
38
38
|
private readonly videoL1Cache;
|
|
39
39
|
private readonly audioL1Cache;
|
|
40
|
-
private readonly mixedAudioL1Cache;
|
|
41
40
|
readonly l2Cache: L2Cache;
|
|
42
41
|
private pendingFramePromises;
|
|
43
42
|
private frameWaiters;
|
|
@@ -70,9 +69,12 @@ export declare class CacheManager {
|
|
|
70
69
|
}): void;
|
|
71
70
|
putClipAudioData(clipId: string, audioData: AudioData, clipStartUs: TimeUs, clipDurationUs: TimeUs): void;
|
|
72
71
|
getClipPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null;
|
|
72
|
+
getClipPCMWithMetadata(clipId: string, startUs: TimeUs, endUs: TimeUs): {
|
|
73
|
+
planes: Float32Array[];
|
|
74
|
+
sampleRate: number;
|
|
75
|
+
numberOfChannels: number;
|
|
76
|
+
} | null;
|
|
73
77
|
hasClipPCM(clipId: string): boolean;
|
|
74
|
-
putMixedAudio(startUs: TimeUs, endUs: TimeUs, buffer: AudioBuffer): void;
|
|
75
|
-
getMixedAudio(startUs: TimeUs, endUs: TimeUs): AudioBuffer | null;
|
|
76
78
|
resetAudioCache(): void;
|
|
77
79
|
getFrame(timeUs: TimeUs, clipId: string): Promise<RcFrame | null>;
|
|
78
80
|
waitForFrame(timeUs: TimeUs, clipId: string, options?: WaitForFrameOptions): Promise<WaitForFrameResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CacheManager.d.ts","sourceRoot":"","sources":["../../src/cache/CacheManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"CacheManager.d.ts","sourceRoot":"","sources":["../../src/cache/CacheManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,12 +2,10 @@ import { VideoL1Cache } from "./l1/VideoL1Cache.js";
|
|
|
2
2
|
import { L2Cache } from "./L2Cache.js";
|
|
3
3
|
import { MeframeEvent } from "../event/events.js";
|
|
4
4
|
import { AudioL1Cache } from "./l1/AudioL1Cache.js";
|
|
5
|
-
import { MixedAudioL1Cache } from "./l1/MixedAudioL1Cache.js";
|
|
6
5
|
const DEFAULT_WAIT_TOLERANCE_US = 33333;
|
|
7
6
|
class CacheManager {
|
|
8
7
|
videoL1Cache;
|
|
9
8
|
audioL1Cache;
|
|
10
|
-
mixedAudioL1Cache;
|
|
11
9
|
l2Cache;
|
|
12
10
|
pendingFramePromises = /* @__PURE__ */ new Map();
|
|
13
11
|
frameWaiters = /* @__PURE__ */ new Map();
|
|
@@ -17,7 +15,6 @@ class CacheManager {
|
|
|
17
15
|
this.videoL1Cache = new VideoL1Cache(config.l1);
|
|
18
16
|
this.l2Cache = new L2Cache(config.l2);
|
|
19
17
|
this.audioL1Cache = new AudioL1Cache();
|
|
20
|
-
this.mixedAudioL1Cache = new MixedAudioL1Cache();
|
|
21
18
|
this.eventBus = eventBus;
|
|
22
19
|
}
|
|
23
20
|
async init() {
|
|
@@ -176,18 +173,14 @@ class CacheManager {
|
|
|
176
173
|
getClipPCM(clipId, startUs, endUs) {
|
|
177
174
|
return this.audioL1Cache.getPCM(clipId, startUs, endUs);
|
|
178
175
|
}
|
|
176
|
+
getClipPCMWithMetadata(clipId, startUs, endUs) {
|
|
177
|
+
return this.audioL1Cache.getPCMWithMetadata(clipId, startUs, endUs);
|
|
178
|
+
}
|
|
179
179
|
hasClipPCM(clipId) {
|
|
180
180
|
return this.audioL1Cache.hasClipPCM(clipId);
|
|
181
181
|
}
|
|
182
|
-
putMixedAudio(startUs, endUs, buffer) {
|
|
183
|
-
this.mixedAudioL1Cache.putMixed(startUs, endUs, buffer);
|
|
184
|
-
}
|
|
185
|
-
getMixedAudio(startUs, endUs) {
|
|
186
|
-
return this.mixedAudioL1Cache.getMixed(startUs, endUs);
|
|
187
|
-
}
|
|
188
182
|
resetAudioCache() {
|
|
189
183
|
this.audioL1Cache.reset();
|
|
190
|
-
this.mixedAudioL1Cache.clear();
|
|
191
184
|
}
|
|
192
185
|
async getFrame(timeUs, clipId) {
|
|
193
186
|
const rcFrame = this.videoL1Cache.get(timeUs, clipId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CacheManager.js","sources":["../../src/cache/CacheManager.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\nimport { RcFrame } from '../model';\nimport { VideoL1Cache } from './l1/VideoL1Cache';\nimport { L2Cache } from './L2Cache';\nimport { MeframeEvent } from '../event/events';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\nimport { AudioL1Cache } from './l1/AudioL1Cache';\nimport { MixedAudioL1Cache } from './l1/MixedAudioL1Cache';\n\ninterface CacheManagerConfig {\n l1: {\n maxMemoryMB: number;\n maxGOPs?: number;\n gopIntervalUs?: number;\n };\n l2: {\n maxSizeMB: number;\n projectId: string;\n };\n}\n\ninterface WaitForFrameOptions {\n signal?: AbortSignal;\n timeoutMs?: number;\n toleranceUs?: number;\n}\n\ninterface FrameWaiter {\n requestKey: string;\n clipId: string;\n targetTimeUs: TimeUs;\n resolve: (result: WaitForFrameResult) => void;\n reject: (reason?: unknown) => void;\n toleranceUs: number;\n timeoutId?: ReturnType<typeof setTimeout>;\n abortCleanup?: () => void;\n}\n\ninterface ClipReadyWaiter {\n clipId: string;\n minFrameCount: number;\n resolve: (ready: boolean) => void;\n reject: (reason?: unknown) => void;\n timeoutId?: ReturnType<typeof setTimeout>;\n}\n\nconst DEFAULT_WAIT_TOLERANCE_US = 33_333; // ≈1 frame @30fps\n\nexport interface WaitForFrameResult {\n frame: RcFrame | null;\n source: 'l1' | 'wait';\n timestampUs: TimeUs;\n clipId: string;\n}\n\n/**\n * Simplified CacheManager for 2-Clip strategy\n *\n * Core features:\n * - L1 (VRAM) for composed VideoFrames\n * - L2 (IndexedDB/OPFS) for encoded chunks\n * - Clip-level cache management\n */\nexport class CacheManager {\n private readonly videoL1Cache: VideoL1Cache;\n private readonly audioL1Cache: AudioL1Cache;\n private readonly mixedAudioL1Cache: MixedAudioL1Cache;\n readonly l2Cache: L2Cache;\n private pendingFramePromises = new Map<string, Promise<WaitForFrameResult>>();\n private frameWaiters = new Map<string, Set<FrameWaiter>>();\n private clipReadyWaiters = new Map<string, ClipReadyWaiter[]>();\n private eventBus?: EventBus<EventPayloadMap>;\n\n constructor(config: CacheManagerConfig, eventBus?: EventBus<EventPayloadMap>) {\n this.videoL1Cache = new VideoL1Cache(config.l1);\n this.l2Cache = new L2Cache(config.l2);\n this.audioL1Cache = new AudioL1Cache();\n this.mixedAudioL1Cache = new MixedAudioL1Cache();\n this.eventBus = eventBus;\n }\n\n async init(): Promise<void> {\n await this.l2Cache.init();\n }\n\n async receiveComposedFrames(\n stream: ReadableStream<VideoFrame | { frame: VideoFrame; metadata?: any }>,\n params: {\n clipId: string;\n trackId: string;\n fps: number;\n onFrame: (info: { clipId: string; timeUs: TimeUs }) => void;\n }\n ): Promise<void> {\n const reader = stream.getReader();\n const process = async (): Promise<void> => {\n const { done, value } = await reader.read();\n if (done) {\n reader.releaseLock();\n return;\n }\n if (value) {\n const fps = params.fps > 0 ? params.fps : 30;\n const frameDuration = Math.round(1_000_000 / fps);\n\n const frame = (value as any).frame || value;\n const metadata = (value as any).metadata;\n const gopSerial = metadata?.gopSerial;\n const isKeyframe = metadata?.isKeyframe;\n\n const timestamp = frame.timestamp ?? 0;\n\n const rcFrame = this.videoL1Cache.addFrame(\n frame,\n params.clipId,\n frameDuration,\n params.trackId,\n gopSerial,\n isKeyframe\n );\n if (!rcFrame) {\n await process();\n return;\n }\n\n this.notifyFrameWaiters(params.clipId, timestamp, frameDuration, rcFrame);\n\n if (timestamp === 0) {\n this.eventBus?.emit(MeframeEvent.CacheCover, {\n timeUs: timestamp,\n clipId: params.clipId,\n level: 'L1',\n size: rcFrame.sizeEstimate ?? 0,\n });\n }\n\n const info = { clipId: params.clipId, timeUs: timestamp };\n this.eventBus?.emit(MeframeEvent.ComposeFrameReady, {\n timeUs: timestamp,\n frameNumber: Math.floor(timestamp / frameDuration),\n renderTimeMs: 0,\n trackId: params.trackId,\n clipId: params.clipId,\n });\n\n params.onFrame(info);\n }\n\n await process();\n };\n\n try {\n await process();\n } catch (error) {\n this.eventBus?.emit(MeframeEvent.ComposeFrameDropped, {\n timeUs: 0,\n reason: 'compose_slow',\n });\n reader.releaseLock();\n throw error;\n }\n }\n\n async receiveEncodedChunks(\n stream: ReadableStream<{ chunk: EncodedVideoChunk; metadata: EncodedVideoChunkMetadata }>,\n clipId: string,\n track: 'video' | 'audio',\n options?: {\n onComplete?: (metadata: EncodedVideoChunkMetadata) => void;\n onError?: (error: Error) => void;\n }\n ): Promise<void> {\n const reader = stream.getReader();\n const chunks: Array<EncodedVideoChunk | EncodedAudioChunk> = [];\n const batchSize = track === 'video' ? 30 : 50;\n let decoderConfig: any = null;\n let metadata: EncodedVideoChunkMetadata | EncodedAudioChunkMetadata | undefined;\n const process = async (): Promise<void> => {\n const { done, value } = await reader.read();\n const { chunk, metadata: chunkMetadata } = value ?? {};\n if (chunkMetadata) {\n metadata = chunkMetadata;\n // Extract decoderConfig from first chunk's metadata\n if (!decoderConfig && chunkMetadata.decoderConfig) {\n decoderConfig = chunkMetadata.decoderConfig;\n }\n }\n if (done) {\n // Flush remaining chunks on stream end\n if (chunks.length > 0) {\n await this.l2Cache.put(clipId, chunks, track, {\n isComplete: true,\n metadata: decoderConfig,\n });\n const firstChunk = chunks[0];\n if (firstChunk) {\n this.eventBus?.emit(MeframeEvent.CacheWrite, {\n clipId,\n timeUs: firstChunk.timestamp,\n level: 'L2',\n size: chunks.reduce((sum, c) => sum + c.byteLength, 0),\n });\n }\n } else {\n // Mark as complete even if no chunks in final batch\n await this.l2Cache.markComplete(clipId, track);\n }\n reader.releaseLock();\n\n // Notify completion\n if (options?.onComplete) {\n options.onComplete(metadata!);\n }\n return;\n }\n\n if (chunk) {\n chunks.push(chunk);\n\n this.eventBus?.emit(MeframeEvent.EncodeChunkReady, {\n timeUs: chunk.timestamp,\n durationUs: chunk.duration ?? 0,\n track,\n size: chunk.byteLength,\n });\n\n // Batch write to L2 when buffer is full\n if (chunks.length >= batchSize) {\n const batchToWrite = chunks.splice(0);\n if (batchToWrite.length > 0) {\n await this.l2Cache.put(clipId, batchToWrite, track, {\n metadata: decoderConfig,\n });\n this.eventBus?.emit(MeframeEvent.CacheWrite, {\n clipId,\n timeUs: chunk.timestamp,\n level: 'L2',\n size: batchToWrite.reduce((sum, c) => sum + c.byteLength, 0),\n });\n }\n }\n }\n\n await process();\n };\n\n try {\n await process();\n } catch (error) {\n // Flush any accumulated chunks before throwing\n if (chunks.length > 0) {\n await this.l2Cache.put(clipId, chunks, track, {\n metadata: decoderConfig,\n });\n }\n this.eventBus?.emit(MeframeEvent.EncodeChunkError, {\n timeUs: 0,\n track,\n error: error as Error,\n });\n reader.releaseLock();\n\n // Notify error\n if (options?.onError) {\n options.onError(error as Error);\n }\n throw error;\n }\n }\n\n acceptMixedAudio(\n stream: ReadableStream<AudioData>,\n metadata: { sampleRate: number; numberOfChannels: number }\n ): void {\n this.audioL1Cache.attachStream(stream, metadata);\n }\n\n putClipAudioData(\n clipId: string,\n audioData: AudioData,\n clipStartUs: TimeUs,\n clipDurationUs: TimeUs\n ): void {\n this.audioL1Cache.putClipAudioData(clipId, audioData, clipStartUs, clipDurationUs);\n }\n\n getClipPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null {\n return this.audioL1Cache.getPCM(clipId, startUs, endUs);\n }\n\n hasClipPCM(clipId: string): boolean {\n return this.audioL1Cache.hasClipPCM(clipId);\n }\n\n putMixedAudio(startUs: TimeUs, endUs: TimeUs, buffer: AudioBuffer): void {\n this.mixedAudioL1Cache.putMixed(startUs, endUs, buffer);\n }\n\n getMixedAudio(startUs: TimeUs, endUs: TimeUs): AudioBuffer | null {\n return this.mixedAudioL1Cache.getMixed(startUs, endUs);\n }\n\n resetAudioCache(): void {\n this.audioL1Cache.reset();\n this.mixedAudioL1Cache.clear();\n }\n\n async getFrame(timeUs: TimeUs, clipId: string): Promise<RcFrame | null> {\n const rcFrame = this.videoL1Cache.get(timeUs, clipId);\n if (rcFrame) {\n return rcFrame;\n }\n\n const decodeKey = this.makeRequestKey(clipId, timeUs);\n const pending = this.pendingFramePromises.get(decodeKey);\n if (pending) {\n const result = await pending;\n return result.frame;\n }\n\n const decodePromise = this.decodeFromL2(timeUs, clipId);\n const tracked = decodePromise.finally(() => {\n this.pendingFramePromises.delete(decodeKey);\n });\n\n this.pendingFramePromises.set(\n decodeKey,\n tracked.then((frame) => ({ frame, source: 'wait' as const, timestampUs: timeUs, clipId }))\n );\n return tracked;\n }\n\n waitForFrame(\n timeUs: TimeUs,\n clipId: string,\n options: WaitForFrameOptions = {}\n ): Promise<WaitForFrameResult> {\n const existing = this.videoL1Cache.get(timeUs, clipId);\n if (existing) {\n return Promise.resolve({ frame: existing, source: 'l1', timestampUs: timeUs, clipId });\n }\n\n const requestKey = this.makeRequestKey(clipId, timeUs);\n const existingPromise = this.pendingFramePromises.get(requestKey);\n if (existingPromise) {\n return existingPromise;\n }\n\n const promise = new Promise<WaitForFrameResult>((resolve, reject) => {\n const toleranceUs = Math.max(\n options.toleranceUs ?? DEFAULT_WAIT_TOLERANCE_US,\n DEFAULT_WAIT_TOLERANCE_US\n );\n\n const waiter: FrameWaiter = {\n requestKey,\n clipId,\n targetTimeUs: timeUs,\n resolve,\n reject,\n toleranceUs,\n };\n\n let waiters = this.frameWaiters.get(clipId);\n if (!waiters) {\n waiters = new Set();\n this.frameWaiters.set(clipId, waiters);\n }\n waiters.add(waiter);\n\n const signal = options.signal;\n if (signal) {\n const onAbort = (): void => {\n this.removeWaiter(waiter);\n this.cleanupWaiter(waiter);\n reject(new DOMException('Render aborted', 'AbortError'));\n };\n\n if (signal.aborted) {\n onAbort();\n return;\n }\n\n signal.addEventListener('abort', onAbort, { once: true });\n waiter.abortCleanup = () => {\n signal.removeEventListener('abort', onAbort);\n };\n }\n\n if (options.timeoutMs && options.timeoutMs > 0) {\n waiter.timeoutId = setTimeout(() => {\n this.removeWaiter(waiter);\n this.cleanupWaiter(waiter);\n reject(new Error('waitForFrame timeout'));\n }, options.timeoutMs);\n }\n });\n\n const trackedPromise = promise.finally(() => {\n this.pendingFramePromises.delete(requestKey);\n });\n\n this.pendingFramePromises.set(requestKey, trackedPromise);\n return trackedPromise;\n }\n\n async invalidateClip(clipId: string): Promise<void> {\n console.log(`[CacheManager] invalidateClip(${clipId}) - clearing L1 and L2`);\n this.videoL1Cache.invalidateRange(0, Infinity, clipId);\n await this.l2Cache.invalidateClip(clipId);\n }\n\n /**\n * Evict a clip from L1 cache\n */\n evictClip(clipId: string): void {\n this.videoL1Cache.evictClip(clipId);\n }\n\n /**\n * Check if a clip is cached in L1\n */\n isClipCached(clipId: string): boolean {\n return this.videoL1Cache.isClipCached(clipId);\n }\n\n /**\n * Wait for a clip to have minimum frames cached\n * Used by PlaybackController for buffering state\n */\n waitForClipReady(\n clipId: string,\n options: { minFrameCount?: number; timeoutMs?: number } = {}\n ): Promise<boolean> {\n const minFrameCount = options.minFrameCount ?? 30;\n const currentFrameCount = this.videoL1Cache.getClipFrameCount(clipId);\n\n if (currentFrameCount >= minFrameCount) {\n return Promise.resolve(true);\n }\n\n return new Promise<boolean>((resolve, reject) => {\n const waiter: ClipReadyWaiter = {\n clipId,\n minFrameCount,\n resolve,\n reject,\n };\n\n const waiters = this.clipReadyWaiters.get(clipId) || [];\n waiters.push(waiter);\n this.clipReadyWaiters.set(clipId, waiters);\n\n if (options.timeoutMs && options.timeoutMs > 0) {\n waiter.timeoutId = setTimeout(() => {\n const waiters = this.clipReadyWaiters.get(clipId);\n if (waiters) {\n const remaining = waiters.filter((w) => w !== waiter);\n if (remaining.length === 0) {\n this.clipReadyWaiters.delete(clipId);\n } else {\n this.clipReadyWaiters.set(clipId, remaining);\n }\n }\n resolve(false);\n }, options.timeoutMs);\n }\n });\n }\n\n async clear(): Promise<void> {\n this.videoL1Cache.clear();\n await this.l2Cache.clear();\n }\n\n getMetadata() {\n return {\n l1: this.videoL1Cache.getMetadata(),\n l2: this.l2Cache.getMetadata(),\n };\n }\n\n /**\n * Create read stream from L2 cache for export\n */\n async createL2ReadStream(\n clipId: string,\n track: 'video' | 'audio'\n ): Promise<ReadableStream<EncodedVideoChunk | EncodedAudioChunk> | null> {\n return this.l2Cache.createReadStream(clipId, track);\n }\n\n /**\n * Check if clip is fully cached in L2\n * Returns true only if the clip is marked as complete\n */\n async hasClipInL2(clipId: string, track: 'video' | 'audio'): Promise<boolean> {\n const result = await this.l2Cache.hasCompleteClip(clipId, track);\n return result;\n }\n\n /**\n * Mark clip as complete in L2 cache\n */\n async markClipComplete(clipId: string, track: 'video' | 'audio'): Promise<void> {\n await this.l2Cache.markComplete(clipId, track);\n }\n\n /**\n * Get chunk metadata (decoderConfig) from L2 cache\n */\n async getL2Metadata(clipId: string, track: 'video' | 'audio'): Promise<any | null> {\n return this.l2Cache.getClipMetadata(clipId, track);\n }\n\n private async decodeFromL2(timeUs: TimeUs, clipId: string): Promise<RcFrame | null> {\n const encodedChunk = await this.l2Cache.get(timeUs, clipId);\n if (!encodedChunk) {\n return null;\n }\n\n return null;\n }\n\n private notifyFrameWaiters(\n clipId: string,\n timestampUs: TimeUs,\n frameDurationUs: TimeUs,\n frame: RcFrame\n ): void {\n const waiters = this.frameWaiters.get(clipId);\n if (!waiters || waiters.size === 0) {\n return;\n }\n\n const resolved: FrameWaiter[] = [];\n\n for (const waiter of waiters) {\n const matches = this.matchesTimestamp(\n waiter.targetTimeUs,\n timestampUs,\n frameDurationUs,\n waiter.toleranceUs\n );\n\n if (!matches) continue;\n\n resolved.push(waiter);\n this.cleanupWaiter(waiter);\n\n waiter.resolve({\n frame,\n source: 'wait',\n timestampUs,\n clipId,\n });\n }\n\n for (const waiter of resolved) {\n waiters.delete(waiter);\n }\n\n if (waiters.size === 0) {\n this.frameWaiters.delete(clipId);\n }\n\n this.notifyClipReadyWaiters(clipId);\n }\n\n private notifyClipReadyWaiters(clipId: string): void {\n const waiters = this.clipReadyWaiters.get(clipId);\n if (!waiters || waiters.length === 0) {\n return;\n }\n\n const frameCount = this.videoL1Cache.getClipFrameCount(clipId);\n const resolved: ClipReadyWaiter[] = [];\n\n for (const waiter of waiters) {\n if (frameCount >= waiter.minFrameCount) {\n resolved.push(waiter);\n if (waiter.timeoutId) {\n clearTimeout(waiter.timeoutId);\n }\n waiter.resolve(true);\n }\n }\n\n const remaining = waiters.filter((w) => !resolved.includes(w));\n if (remaining.length === 0) {\n this.clipReadyWaiters.delete(clipId);\n } else {\n this.clipReadyWaiters.set(clipId, remaining);\n }\n }\n\n private cleanupWaiter(waiter: FrameWaiter): void {\n if (waiter.timeoutId) {\n clearTimeout(waiter.timeoutId);\n waiter.timeoutId = undefined;\n }\n\n if (waiter.abortCleanup) {\n waiter.abortCleanup();\n waiter.abortCleanup = undefined;\n }\n }\n\n private removeWaiter(waiter: FrameWaiter): void {\n const waiters = this.frameWaiters.get(waiter.clipId);\n if (!waiters) return;\n\n waiters.delete(waiter);\n if (waiters.size === 0) {\n this.frameWaiters.delete(waiter.clipId);\n }\n }\n\n private matchesTimestamp(\n targetTimeUs: TimeUs,\n actualTimeUs: TimeUs,\n frameDurationUs: TimeUs,\n toleranceUs: TimeUs\n ): boolean {\n if (targetTimeUs === actualTimeUs) return true;\n\n const delta = Math.abs(targetTimeUs - actualTimeUs);\n if (delta <= toleranceUs) {\n return true;\n }\n\n if (actualTimeUs >= targetTimeUs && actualTimeUs < targetTimeUs + frameDurationUs) {\n return true;\n }\n\n return false;\n }\n\n private makeRequestKey(clipId: string, timeUs: TimeUs): string {\n return `${clipId}:${timeUs}`;\n }\n}\n"],"names":["waiters"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CacheManager.js","sources":["../../src/cache/CacheManager.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\nimport { RcFrame } from '../model';\nimport { VideoL1Cache } from './l1/VideoL1Cache';\nimport { L2Cache } from './L2Cache';\nimport { MeframeEvent } from '../event/events';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\nimport { AudioL1Cache } from './l1/AudioL1Cache';\n\ninterface CacheManagerConfig {\n l1: {\n maxMemoryMB: number;\n maxGOPs?: number;\n gopIntervalUs?: number;\n };\n l2: {\n maxSizeMB: number;\n projectId: string;\n };\n}\n\ninterface WaitForFrameOptions {\n signal?: AbortSignal;\n timeoutMs?: number;\n toleranceUs?: number;\n}\n\ninterface FrameWaiter {\n requestKey: string;\n clipId: string;\n targetTimeUs: TimeUs;\n resolve: (result: WaitForFrameResult) => void;\n reject: (reason?: unknown) => void;\n toleranceUs: number;\n timeoutId?: ReturnType<typeof setTimeout>;\n abortCleanup?: () => void;\n}\n\ninterface ClipReadyWaiter {\n clipId: string;\n minFrameCount: number;\n resolve: (ready: boolean) => void;\n reject: (reason?: unknown) => void;\n timeoutId?: ReturnType<typeof setTimeout>;\n}\n\nconst DEFAULT_WAIT_TOLERANCE_US = 33_333; // ≈1 frame @30fps\n\nexport interface WaitForFrameResult {\n frame: RcFrame | null;\n source: 'l1' | 'wait';\n timestampUs: TimeUs;\n clipId: string;\n}\n\n/**\n * Simplified CacheManager for 2-Clip strategy\n *\n * Core features:\n * - L1 (VRAM) for composed VideoFrames\n * - L2 (IndexedDB/OPFS) for encoded chunks\n * - Clip-level cache management\n */\nexport class CacheManager {\n private readonly videoL1Cache: VideoL1Cache;\n private readonly audioL1Cache: AudioL1Cache;\n readonly l2Cache: L2Cache;\n private pendingFramePromises = new Map<string, Promise<WaitForFrameResult>>();\n private frameWaiters = new Map<string, Set<FrameWaiter>>();\n private clipReadyWaiters = new Map<string, ClipReadyWaiter[]>();\n private eventBus?: EventBus<EventPayloadMap>;\n\n constructor(config: CacheManagerConfig, eventBus?: EventBus<EventPayloadMap>) {\n this.videoL1Cache = new VideoL1Cache(config.l1);\n this.l2Cache = new L2Cache(config.l2);\n this.audioL1Cache = new AudioL1Cache();\n this.eventBus = eventBus;\n }\n\n async init(): Promise<void> {\n await this.l2Cache.init();\n }\n\n async receiveComposedFrames(\n stream: ReadableStream<VideoFrame | { frame: VideoFrame; metadata?: any }>,\n params: {\n clipId: string;\n trackId: string;\n fps: number;\n onFrame: (info: { clipId: string; timeUs: TimeUs }) => void;\n }\n ): Promise<void> {\n const reader = stream.getReader();\n const process = async (): Promise<void> => {\n const { done, value } = await reader.read();\n if (done) {\n reader.releaseLock();\n return;\n }\n if (value) {\n const fps = params.fps > 0 ? params.fps : 30;\n const frameDuration = Math.round(1_000_000 / fps);\n\n const frame = (value as any).frame || value;\n const metadata = (value as any).metadata;\n const gopSerial = metadata?.gopSerial;\n const isKeyframe = metadata?.isKeyframe;\n\n const timestamp = frame.timestamp ?? 0;\n\n const rcFrame = this.videoL1Cache.addFrame(\n frame,\n params.clipId,\n frameDuration,\n params.trackId,\n gopSerial,\n isKeyframe\n );\n if (!rcFrame) {\n await process();\n return;\n }\n\n this.notifyFrameWaiters(params.clipId, timestamp, frameDuration, rcFrame);\n\n if (timestamp === 0) {\n this.eventBus?.emit(MeframeEvent.CacheCover, {\n timeUs: timestamp,\n clipId: params.clipId,\n level: 'L1',\n size: rcFrame.sizeEstimate ?? 0,\n });\n }\n\n const info = { clipId: params.clipId, timeUs: timestamp };\n this.eventBus?.emit(MeframeEvent.ComposeFrameReady, {\n timeUs: timestamp,\n frameNumber: Math.floor(timestamp / frameDuration),\n renderTimeMs: 0,\n trackId: params.trackId,\n clipId: params.clipId,\n });\n\n params.onFrame(info);\n }\n\n await process();\n };\n\n try {\n await process();\n } catch (error) {\n this.eventBus?.emit(MeframeEvent.ComposeFrameDropped, {\n timeUs: 0,\n reason: 'compose_slow',\n });\n reader.releaseLock();\n throw error;\n }\n }\n\n async receiveEncodedChunks(\n stream: ReadableStream<{ chunk: EncodedVideoChunk; metadata: EncodedVideoChunkMetadata }>,\n clipId: string,\n track: 'video' | 'audio',\n options?: {\n onComplete?: (metadata: EncodedVideoChunkMetadata) => void;\n onError?: (error: Error) => void;\n }\n ): Promise<void> {\n const reader = stream.getReader();\n const chunks: Array<EncodedVideoChunk | EncodedAudioChunk> = [];\n const batchSize = track === 'video' ? 30 : 50;\n let decoderConfig: any = null;\n let metadata: EncodedVideoChunkMetadata | EncodedAudioChunkMetadata | undefined;\n const process = async (): Promise<void> => {\n const { done, value } = await reader.read();\n const { chunk, metadata: chunkMetadata } = value ?? {};\n if (chunkMetadata) {\n metadata = chunkMetadata;\n // Extract decoderConfig from first chunk's metadata\n if (!decoderConfig && chunkMetadata.decoderConfig) {\n decoderConfig = chunkMetadata.decoderConfig;\n }\n }\n if (done) {\n // Flush remaining chunks on stream end\n if (chunks.length > 0) {\n await this.l2Cache.put(clipId, chunks, track, {\n isComplete: true,\n metadata: decoderConfig,\n });\n const firstChunk = chunks[0];\n if (firstChunk) {\n this.eventBus?.emit(MeframeEvent.CacheWrite, {\n clipId,\n timeUs: firstChunk.timestamp,\n level: 'L2',\n size: chunks.reduce((sum, c) => sum + c.byteLength, 0),\n });\n }\n } else {\n // Mark as complete even if no chunks in final batch\n await this.l2Cache.markComplete(clipId, track);\n }\n reader.releaseLock();\n\n // Notify completion\n if (options?.onComplete) {\n options.onComplete(metadata!);\n }\n return;\n }\n\n if (chunk) {\n chunks.push(chunk);\n\n this.eventBus?.emit(MeframeEvent.EncodeChunkReady, {\n timeUs: chunk.timestamp,\n durationUs: chunk.duration ?? 0,\n track,\n size: chunk.byteLength,\n });\n\n // Batch write to L2 when buffer is full\n if (chunks.length >= batchSize) {\n const batchToWrite = chunks.splice(0);\n if (batchToWrite.length > 0) {\n await this.l2Cache.put(clipId, batchToWrite, track, {\n metadata: decoderConfig,\n });\n this.eventBus?.emit(MeframeEvent.CacheWrite, {\n clipId,\n timeUs: chunk.timestamp,\n level: 'L2',\n size: batchToWrite.reduce((sum, c) => sum + c.byteLength, 0),\n });\n }\n }\n }\n\n await process();\n };\n\n try {\n await process();\n } catch (error) {\n // Flush any accumulated chunks before throwing\n if (chunks.length > 0) {\n await this.l2Cache.put(clipId, chunks, track, {\n metadata: decoderConfig,\n });\n }\n this.eventBus?.emit(MeframeEvent.EncodeChunkError, {\n timeUs: 0,\n track,\n error: error as Error,\n });\n reader.releaseLock();\n\n // Notify error\n if (options?.onError) {\n options.onError(error as Error);\n }\n throw error;\n }\n }\n\n acceptMixedAudio(\n stream: ReadableStream<AudioData>,\n metadata: { sampleRate: number; numberOfChannels: number }\n ): void {\n this.audioL1Cache.attachStream(stream, metadata);\n }\n\n putClipAudioData(\n clipId: string,\n audioData: AudioData,\n clipStartUs: TimeUs,\n clipDurationUs: TimeUs\n ): void {\n this.audioL1Cache.putClipAudioData(clipId, audioData, clipStartUs, clipDurationUs);\n }\n\n getClipPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null {\n return this.audioL1Cache.getPCM(clipId, startUs, endUs);\n }\n\n getClipPCMWithMetadata(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs\n ): { planes: Float32Array[]; sampleRate: number; numberOfChannels: number } | null {\n return this.audioL1Cache.getPCMWithMetadata(clipId, startUs, endUs);\n }\n\n hasClipPCM(clipId: string): boolean {\n return this.audioL1Cache.hasClipPCM(clipId);\n }\n\n resetAudioCache(): void {\n this.audioL1Cache.reset();\n }\n\n async getFrame(timeUs: TimeUs, clipId: string): Promise<RcFrame | null> {\n const rcFrame = this.videoL1Cache.get(timeUs, clipId);\n if (rcFrame) {\n return rcFrame;\n }\n\n const decodeKey = this.makeRequestKey(clipId, timeUs);\n const pending = this.pendingFramePromises.get(decodeKey);\n if (pending) {\n const result = await pending;\n return result.frame;\n }\n\n const decodePromise = this.decodeFromL2(timeUs, clipId);\n const tracked = decodePromise.finally(() => {\n this.pendingFramePromises.delete(decodeKey);\n });\n\n this.pendingFramePromises.set(\n decodeKey,\n tracked.then((frame) => ({ frame, source: 'wait' as const, timestampUs: timeUs, clipId }))\n );\n return tracked;\n }\n\n waitForFrame(\n timeUs: TimeUs,\n clipId: string,\n options: WaitForFrameOptions = {}\n ): Promise<WaitForFrameResult> {\n const existing = this.videoL1Cache.get(timeUs, clipId);\n if (existing) {\n return Promise.resolve({ frame: existing, source: 'l1', timestampUs: timeUs, clipId });\n }\n\n const requestKey = this.makeRequestKey(clipId, timeUs);\n const existingPromise = this.pendingFramePromises.get(requestKey);\n if (existingPromise) {\n return existingPromise;\n }\n\n const promise = new Promise<WaitForFrameResult>((resolve, reject) => {\n const toleranceUs = Math.max(\n options.toleranceUs ?? DEFAULT_WAIT_TOLERANCE_US,\n DEFAULT_WAIT_TOLERANCE_US\n );\n\n const waiter: FrameWaiter = {\n requestKey,\n clipId,\n targetTimeUs: timeUs,\n resolve,\n reject,\n toleranceUs,\n };\n\n let waiters = this.frameWaiters.get(clipId);\n if (!waiters) {\n waiters = new Set();\n this.frameWaiters.set(clipId, waiters);\n }\n waiters.add(waiter);\n\n const signal = options.signal;\n if (signal) {\n const onAbort = (): void => {\n this.removeWaiter(waiter);\n this.cleanupWaiter(waiter);\n reject(new DOMException('Render aborted', 'AbortError'));\n };\n\n if (signal.aborted) {\n onAbort();\n return;\n }\n\n signal.addEventListener('abort', onAbort, { once: true });\n waiter.abortCleanup = () => {\n signal.removeEventListener('abort', onAbort);\n };\n }\n\n if (options.timeoutMs && options.timeoutMs > 0) {\n waiter.timeoutId = setTimeout(() => {\n this.removeWaiter(waiter);\n this.cleanupWaiter(waiter);\n reject(new Error('waitForFrame timeout'));\n }, options.timeoutMs);\n }\n });\n\n const trackedPromise = promise.finally(() => {\n this.pendingFramePromises.delete(requestKey);\n });\n\n this.pendingFramePromises.set(requestKey, trackedPromise);\n return trackedPromise;\n }\n\n async invalidateClip(clipId: string): Promise<void> {\n console.log(`[CacheManager] invalidateClip(${clipId}) - clearing L1 and L2`);\n this.videoL1Cache.invalidateRange(0, Infinity, clipId);\n await this.l2Cache.invalidateClip(clipId);\n }\n\n /**\n * Evict a clip from L1 cache\n */\n evictClip(clipId: string): void {\n this.videoL1Cache.evictClip(clipId);\n }\n\n /**\n * Check if a clip is cached in L1\n */\n isClipCached(clipId: string): boolean {\n return this.videoL1Cache.isClipCached(clipId);\n }\n\n /**\n * Wait for a clip to have minimum frames cached\n * Used by PlaybackController for buffering state\n */\n waitForClipReady(\n clipId: string,\n options: { minFrameCount?: number; timeoutMs?: number } = {}\n ): Promise<boolean> {\n const minFrameCount = options.minFrameCount ?? 30;\n const currentFrameCount = this.videoL1Cache.getClipFrameCount(clipId);\n\n if (currentFrameCount >= minFrameCount) {\n return Promise.resolve(true);\n }\n\n return new Promise<boolean>((resolve, reject) => {\n const waiter: ClipReadyWaiter = {\n clipId,\n minFrameCount,\n resolve,\n reject,\n };\n\n const waiters = this.clipReadyWaiters.get(clipId) || [];\n waiters.push(waiter);\n this.clipReadyWaiters.set(clipId, waiters);\n\n if (options.timeoutMs && options.timeoutMs > 0) {\n waiter.timeoutId = setTimeout(() => {\n const waiters = this.clipReadyWaiters.get(clipId);\n if (waiters) {\n const remaining = waiters.filter((w) => w !== waiter);\n if (remaining.length === 0) {\n this.clipReadyWaiters.delete(clipId);\n } else {\n this.clipReadyWaiters.set(clipId, remaining);\n }\n }\n resolve(false);\n }, options.timeoutMs);\n }\n });\n }\n\n async clear(): Promise<void> {\n this.videoL1Cache.clear();\n await this.l2Cache.clear();\n }\n\n getMetadata() {\n return {\n l1: this.videoL1Cache.getMetadata(),\n l2: this.l2Cache.getMetadata(),\n };\n }\n\n /**\n * Create read stream from L2 cache for export\n */\n async createL2ReadStream(\n clipId: string,\n track: 'video' | 'audio'\n ): Promise<ReadableStream<EncodedVideoChunk | EncodedAudioChunk> | null> {\n return this.l2Cache.createReadStream(clipId, track);\n }\n\n /**\n * Check if clip is fully cached in L2\n * Returns true only if the clip is marked as complete\n */\n async hasClipInL2(clipId: string, track: 'video' | 'audio'): Promise<boolean> {\n const result = await this.l2Cache.hasCompleteClip(clipId, track);\n return result;\n }\n\n /**\n * Mark clip as complete in L2 cache\n */\n async markClipComplete(clipId: string, track: 'video' | 'audio'): Promise<void> {\n await this.l2Cache.markComplete(clipId, track);\n }\n\n /**\n * Get chunk metadata (decoderConfig) from L2 cache\n */\n async getL2Metadata(clipId: string, track: 'video' | 'audio'): Promise<any | null> {\n return this.l2Cache.getClipMetadata(clipId, track);\n }\n\n private async decodeFromL2(timeUs: TimeUs, clipId: string): Promise<RcFrame | null> {\n const encodedChunk = await this.l2Cache.get(timeUs, clipId);\n if (!encodedChunk) {\n return null;\n }\n\n return null;\n }\n\n private notifyFrameWaiters(\n clipId: string,\n timestampUs: TimeUs,\n frameDurationUs: TimeUs,\n frame: RcFrame\n ): void {\n const waiters = this.frameWaiters.get(clipId);\n if (!waiters || waiters.size === 0) {\n return;\n }\n\n const resolved: FrameWaiter[] = [];\n\n for (const waiter of waiters) {\n const matches = this.matchesTimestamp(\n waiter.targetTimeUs,\n timestampUs,\n frameDurationUs,\n waiter.toleranceUs\n );\n\n if (!matches) continue;\n\n resolved.push(waiter);\n this.cleanupWaiter(waiter);\n\n waiter.resolve({\n frame,\n source: 'wait',\n timestampUs,\n clipId,\n });\n }\n\n for (const waiter of resolved) {\n waiters.delete(waiter);\n }\n\n if (waiters.size === 0) {\n this.frameWaiters.delete(clipId);\n }\n\n this.notifyClipReadyWaiters(clipId);\n }\n\n private notifyClipReadyWaiters(clipId: string): void {\n const waiters = this.clipReadyWaiters.get(clipId);\n if (!waiters || waiters.length === 0) {\n return;\n }\n\n const frameCount = this.videoL1Cache.getClipFrameCount(clipId);\n const resolved: ClipReadyWaiter[] = [];\n\n for (const waiter of waiters) {\n if (frameCount >= waiter.minFrameCount) {\n resolved.push(waiter);\n if (waiter.timeoutId) {\n clearTimeout(waiter.timeoutId);\n }\n waiter.resolve(true);\n }\n }\n\n const remaining = waiters.filter((w) => !resolved.includes(w));\n if (remaining.length === 0) {\n this.clipReadyWaiters.delete(clipId);\n } else {\n this.clipReadyWaiters.set(clipId, remaining);\n }\n }\n\n private cleanupWaiter(waiter: FrameWaiter): void {\n if (waiter.timeoutId) {\n clearTimeout(waiter.timeoutId);\n waiter.timeoutId = undefined;\n }\n\n if (waiter.abortCleanup) {\n waiter.abortCleanup();\n waiter.abortCleanup = undefined;\n }\n }\n\n private removeWaiter(waiter: FrameWaiter): void {\n const waiters = this.frameWaiters.get(waiter.clipId);\n if (!waiters) return;\n\n waiters.delete(waiter);\n if (waiters.size === 0) {\n this.frameWaiters.delete(waiter.clipId);\n }\n }\n\n private matchesTimestamp(\n targetTimeUs: TimeUs,\n actualTimeUs: TimeUs,\n frameDurationUs: TimeUs,\n toleranceUs: TimeUs\n ): boolean {\n if (targetTimeUs === actualTimeUs) return true;\n\n const delta = Math.abs(targetTimeUs - actualTimeUs);\n if (delta <= toleranceUs) {\n return true;\n }\n\n if (actualTimeUs >= targetTimeUs && actualTimeUs < targetTimeUs + frameDurationUs) {\n return true;\n }\n\n return false;\n }\n\n private makeRequestKey(clipId: string, timeUs: TimeUs): string {\n return `${clipId}:${timeUs}`;\n }\n}\n"],"names":["waiters"],"mappings":"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"}
|
|
@@ -11,6 +11,11 @@ export declare class AudioL1Cache {
|
|
|
11
11
|
attachStream(stream: ReadableStream<AudioData>, metadata: AudioMetadata): void;
|
|
12
12
|
putClipAudioData(clipId: string, audioData: AudioData, clipStartUs: TimeUs, clipDurationUs: TimeUs): void;
|
|
13
13
|
getPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null;
|
|
14
|
+
getPCMWithMetadata(clipId: string, startUs: TimeUs, endUs: TimeUs): {
|
|
15
|
+
planes: Float32Array[];
|
|
16
|
+
sampleRate: number;
|
|
17
|
+
numberOfChannels: number;
|
|
18
|
+
} | null;
|
|
14
19
|
hasClipPCM(clipId: string): boolean;
|
|
15
20
|
clearClipPCM(clipId: string): void;
|
|
16
21
|
private evictLRU;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioL1Cache.d.ts","sourceRoot":"","sources":["../../../src/cache/l1/AudioL1Cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAYhD,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,QAAQ,CAA8D;IAC9E,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAM;IAEtB,YAAY,CAAC,MAAM,EAAE,cAAc,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI;IAqB9E,gBAAgB,CACd,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,GACrB,IAAI;
|
|
1
|
+
{"version":3,"file":"AudioL1Cache.d.ts","sourceRoot":"","sources":["../../../src/cache/l1/AudioL1Cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAYhD,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,QAAQ,CAA8D;IAC9E,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAM;IAEtB,YAAY,CAAC,MAAM,EAAE,cAAc,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI;IAqB9E,gBAAgB,CACd,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,GACrB,IAAI;IA6DP,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,GAAG,IAAI;IA4C7E,kBAAkB,CAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ;QAAE,MAAM,EAAE,YAAY,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAkBlF,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAInC,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIlC,OAAO,CAAC,QAAQ;IAgBhB,OAAO,CAAC,0BAA0B;IAqIlC,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IA6BhC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,SAAS,GAAG;QAAE,QAAQ,EAAE,aAAa,CAAA;KAAE,CAAC,GAAG,IAAI;IAkC5E,KAAK,IAAI,IAAI;IAIb,KAAK,IAAI,IAAI;IAQb,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIpC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAI7B,OAAO,IAAI,IAAI;IAIf,OAAO,CAAC,SAAS;CAiBlB"}
|
|
@@ -48,13 +48,22 @@ class AudioL1Cache {
|
|
|
48
48
|
this.evictLRU();
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
const maxFrames = Math.ceil(clipDurationUs / 1e6 * sampleRate);
|
|
52
|
+
const currentFrames = entry.planes[0]?.length ?? 0;
|
|
53
|
+
if (currentFrames >= maxFrames) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
51
56
|
for (let channel = 0; channel < numberOfChannels; channel++) {
|
|
52
57
|
const existingPlane = entry.planes[channel];
|
|
53
58
|
const newPlane = planes[channel];
|
|
54
59
|
if (!existingPlane || !newPlane) continue;
|
|
55
|
-
const
|
|
60
|
+
const framesToAppend = Math.min(newPlane.length, maxFrames - currentFrames);
|
|
61
|
+
if (framesToAppend <= 0) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const combined = new Float32Array(existingPlane.length + framesToAppend);
|
|
56
65
|
combined.set(existingPlane, 0);
|
|
57
|
-
combined.set(newPlane, existingPlane.length);
|
|
66
|
+
combined.set(newPlane.subarray(0, framesToAppend), existingPlane.length);
|
|
58
67
|
entry.planes[channel] = combined;
|
|
59
68
|
}
|
|
60
69
|
entry.lastAccessedAt = Date.now();
|
|
@@ -66,7 +75,11 @@ class AudioL1Cache {
|
|
|
66
75
|
}
|
|
67
76
|
entry.lastAccessedAt = Date.now();
|
|
68
77
|
const offsetUs = Math.max(0, startUs - entry.startUs);
|
|
69
|
-
const
|
|
78
|
+
const actualStoredFrames = entry.planes[0]?.length ?? 0;
|
|
79
|
+
const actualStoredUs = actualStoredFrames / entry.sampleRate * 1e6;
|
|
80
|
+
const availableDurationUs = Math.max(0, actualStoredUs - offsetUs);
|
|
81
|
+
const requestedDurationUs = endUs - startUs;
|
|
82
|
+
const durationUs = Math.min(requestedDurationUs, availableDurationUs);
|
|
70
83
|
if (durationUs <= 0) {
|
|
71
84
|
return null;
|
|
72
85
|
}
|
|
@@ -88,6 +101,21 @@ class AudioL1Cache {
|
|
|
88
101
|
}
|
|
89
102
|
return result;
|
|
90
103
|
}
|
|
104
|
+
getPCMWithMetadata(clipId, startUs, endUs) {
|
|
105
|
+
const planes = this.getPCM(clipId, startUs, endUs);
|
|
106
|
+
if (!planes) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const entry = this.clipPCM.get(clipId);
|
|
110
|
+
if (!entry) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
planes,
|
|
115
|
+
sampleRate: entry.sampleRate,
|
|
116
|
+
numberOfChannels: entry.numberOfChannels
|
|
117
|
+
};
|
|
118
|
+
}
|
|
91
119
|
hasClipPCM(clipId) {
|
|
92
120
|
return this.clipPCM.has(clipId);
|
|
93
121
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioL1Cache.js","sources":["../../../src/cache/l1/AudioL1Cache.ts"],"sourcesContent":["import type { AudioMetadata, AudioSlot } from './types';\nimport type { TimeUs } from '../../model/types';\n\ninterface PCMClipEntry {\n clipId: string;\n sampleRate: number;\n numberOfChannels: number;\n planes: Float32Array[];\n startUs: TimeUs;\n durationUs: TimeUs;\n lastAccessedAt: number;\n}\n\nexport class AudioL1Cache {\n private slots: AudioSlot[] = [];\n private clipPCM = new Map<string, PCMClipEntry>();\n private metadata: AudioMetadata = { sampleRate: 48_000, numberOfChannels: 2 };\n private volume = 1;\n private muted = false;\n private maxClips = 20;\n\n attachStream(stream: ReadableStream<AudioData>, metadata: AudioMetadata): void {\n this.metadata = metadata;\n const reader = stream.getReader();\n\n const pump = async (): Promise<void> => {\n const { done, value } = await reader.read();\n if (done) {\n reader.releaseLock();\n return;\n }\n\n this.addAudio(value);\n await pump();\n };\n\n pump().catch((error) => {\n console.error('[AudioL1Cache] stream error', error);\n reader.releaseLock();\n });\n }\n\n putClipAudioData(\n clipId: string,\n audioData: AudioData,\n clipStartUs: TimeUs,\n clipDurationUs: TimeUs\n ): void {\n const numberOfChannels = audioData.numberOfChannels ?? this.metadata.numberOfChannels;\n const numberOfFrames = audioData.numberOfFrames ?? 0;\n const sampleRate = audioData.sampleRate ?? this.metadata.sampleRate;\n\n if (!numberOfChannels || !numberOfFrames) {\n audioData.close();\n return;\n }\n\n const planes = this.extractPlanesFromAudioData(audioData, numberOfChannels, numberOfFrames);\n audioData.close();\n\n let entry = this.clipPCM.get(clipId);\n if (!entry) {\n entry = {\n clipId,\n sampleRate,\n numberOfChannels,\n planes: Array.from({ length: numberOfChannels }, () => new Float32Array(0)),\n startUs: clipStartUs,\n durationUs: clipDurationUs,\n lastAccessedAt: Date.now(),\n };\n this.clipPCM.set(clipId, entry);\n\n if (this.clipPCM.size > this.maxClips) {\n this.evictLRU();\n }\n }\n\n for (let channel = 0; channel < numberOfChannels; channel++) {\n const existingPlane = entry.planes[channel];\n const newPlane = planes[channel];\n if (!existingPlane || !newPlane) continue;\n\n const combined = new Float32Array(existingPlane.length + newPlane.length);\n combined.set(existingPlane, 0);\n combined.set(newPlane, existingPlane.length);\n entry.planes[channel] = combined;\n }\n\n entry.lastAccessedAt = Date.now();\n }\n\n getPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null {\n const entry = this.clipPCM.get(clipId);\n if (!entry) {\n return null;\n }\n\n entry.lastAccessedAt = Date.now();\n\n const offsetUs = Math.max(0, startUs - entry.startUs);\n const durationUs = Math.min(endUs - startUs, entry.durationUs - offsetUs);\n\n if (durationUs <= 0) {\n return null;\n }\n\n const offsetFrames = Math.floor((offsetUs / 1_000_000) * entry.sampleRate);\n const frameCount = Math.ceil((durationUs / 1_000_000) * entry.sampleRate);\n\n const result: Float32Array[] = [];\n for (let channel = 0; channel < entry.numberOfChannels; channel++) {\n const plane = entry.planes[channel];\n if (!plane) {\n result.push(new Float32Array(frameCount));\n continue;\n }\n\n const channelData = new Float32Array(frameCount);\n const copyLength = Math.min(frameCount, plane.length - offsetFrames);\n for (let i = 0; i < copyLength; i++) {\n channelData[i] = plane[offsetFrames + i] ?? 0;\n }\n result.push(channelData);\n }\n\n return result;\n }\n\n hasClipPCM(clipId: string): boolean {\n return this.clipPCM.has(clipId);\n }\n\n clearClipPCM(clipId: string): void {\n this.clipPCM.delete(clipId);\n }\n\n private evictLRU(): void {\n let oldestClipId: string | null = null;\n let oldestTime = Date.now();\n\n for (const [clipId, entry] of this.clipPCM) {\n if (entry.lastAccessedAt < oldestTime) {\n oldestTime = entry.lastAccessedAt;\n oldestClipId = clipId;\n }\n }\n\n if (oldestClipId) {\n this.clipPCM.delete(oldestClipId);\n }\n }\n\n private extractPlanesFromAudioData(\n audioData: AudioData,\n numberOfChannels: number,\n numberOfFrames: number\n ): Float32Array[] {\n const planes: Float32Array[] = Array.from(\n { length: numberOfChannels },\n () => new Float32Array(numberOfFrames)\n );\n\n const toFloat = (value: number): number => value / 32768;\n\n const fillInterleaved = (format: 'f32' | 's16'): boolean => {\n const samples =\n format === 'f32'\n ? new Float32Array(numberOfFrames * numberOfChannels)\n : new Int16Array(numberOfFrames * numberOfChannels);\n\n try {\n audioData.copyTo(samples, { format, planeIndex: 0 });\n } catch {\n return false;\n }\n\n for (let frame = 0; frame < numberOfFrames; frame += 1) {\n const offset = frame * numberOfChannels;\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n if (format === 'f32') {\n plane[frame] = (samples as Float32Array)[offset + channel] ?? 0;\n } else {\n plane[frame] = toFloat((samples as Int16Array)[offset + channel] ?? 0);\n }\n }\n }\n\n return true;\n };\n\n const fillPlanar = (format: 'f32-planar' | 's16-planar'): boolean => {\n try {\n if (format === 'f32-planar') {\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n audioData.copyTo(plane, { planeIndex: channel, format: 'f32-planar' });\n }\n return true;\n }\n\n const tmp = new Int16Array(numberOfFrames);\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n audioData.copyTo(tmp, { planeIndex: channel, format: 's16-planar' as any });\n for (let i = 0; i < numberOfFrames; i += 1) {\n plane[i] = toFloat(tmp[i] ?? 0);\n }\n }\n return true;\n } catch {\n return false;\n }\n };\n\n const fillFallback = (): boolean => {\n try {\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n audioData.copyTo(plane, { planeIndex: channel });\n }\n return true;\n } catch {\n return false;\n }\n };\n\n const reportedFormat = (audioData as any).format as string | undefined;\n const attempts: Array<() => boolean> = [];\n const scheduled = new Set<string>();\n\n const scheduleAttempt = (token: string, attempt: () => boolean): void => {\n if (!scheduled.has(token)) {\n scheduled.add(token);\n attempts.push(attempt);\n }\n };\n\n if (reportedFormat) {\n switch (reportedFormat) {\n case 'f32':\n scheduleAttempt('f32', () => fillInterleaved('f32'));\n break;\n case 's16':\n scheduleAttempt('s16', () => fillInterleaved('s16'));\n break;\n case 'f32-planar':\n scheduleAttempt('f32-planar', () => fillPlanar('f32-planar'));\n break;\n case 's16-planar':\n scheduleAttempt('s16-planar', () => fillPlanar('s16-planar'));\n break;\n default:\n break;\n }\n }\n\n scheduleAttempt('f32', () => fillInterleaved('f32'));\n scheduleAttempt('f32-planar', () => fillPlanar('f32-planar'));\n scheduleAttempt('s16', () => fillInterleaved('s16'));\n scheduleAttempt('s16-planar', () => fillPlanar('s16-planar'));\n\n let filled = false;\n for (const attempt of attempts) {\n if (attempt()) {\n filled = true;\n break;\n }\n }\n\n if (!filled) {\n filled = fillFallback();\n }\n\n if (!filled) {\n throw new Error('AudioL1Cache: unsupported AudioData format');\n }\n\n return planes;\n }\n\n addAudio(audio: AudioData): void {\n const numberOfChannels = audio.numberOfChannels ?? this.metadata.numberOfChannels;\n const numberOfFrames = audio.numberOfFrames ?? 0;\n\n if (!numberOfChannels || !numberOfFrames) {\n audio.close();\n return;\n }\n\n if (audio.sampleRate && audio.sampleRate > 0 && audio.sampleRate !== this.metadata.sampleRate) {\n this.metadata = { ...this.metadata, sampleRate: audio.sampleRate };\n }\n\n if (numberOfChannels !== this.metadata.numberOfChannels) {\n this.metadata = { ...this.metadata, numberOfChannels };\n }\n\n const planes = this.extractPlanesFromAudioData(audio, numberOfChannels, numberOfFrames);\n\n this.slots.push({\n timestampUs: audio.timestamp ?? 0,\n durationUs:\n audio.duration ?? Math.round((numberOfFrames / this.metadata.sampleRate) * 1_000_000),\n planes,\n });\n\n audio.close();\n }\n\n getClosest(timeUs: number): (AudioSlot & { metadata: AudioMetadata }) | null {\n if (this.slots.length === 0) {\n return null;\n }\n\n let closest: AudioSlot | null = null;\n let minDelta = Number.MAX_SAFE_INTEGER;\n\n for (const slot of this.slots) {\n const start = slot.timestampUs;\n const end = start + slot.durationUs;\n if (timeUs >= start && timeUs <= end) {\n closest = slot;\n break;\n }\n\n const delta = Math.min(Math.abs(timeUs - start), Math.abs(timeUs - end));\n if (delta < minDelta) {\n closest = slot;\n minDelta = delta;\n }\n }\n\n if (!closest) {\n return null;\n }\n\n return {\n ...closest,\n planes: this.applyGain(closest.planes),\n metadata: this.metadata,\n };\n }\n\n flush(): void {\n this.slots = [];\n }\n\n reset(): void {\n this.flush();\n this.clipPCM.clear();\n this.metadata = { sampleRate: 48_000, numberOfChannels: 2 };\n this.volume = 1;\n this.muted = false;\n }\n\n setPlaybackRate(_rate: number): void {\n // Reserved for future use\n }\n\n setVolume(volume: number): void {\n this.volume = Math.max(0, Math.min(1, volume));\n }\n\n setMute(muted: boolean): void {\n this.muted = muted;\n }\n\n dispose(): void {\n this.reset();\n }\n\n private applyGain(planes: Float32Array[]): Float32Array[] {\n if (this.muted || this.volume === 0) {\n return planes.map((plane) => new Float32Array(plane.length));\n }\n\n if (this.volume === 1) {\n return planes.map((plane) => plane.slice());\n }\n\n return planes.map((plane) => {\n const scaled = new Float32Array(plane.length);\n for (let i = 0; i < plane.length; i += 1) {\n scaled[i] = (plane[i] ?? 0) * this.volume;\n }\n return scaled;\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AudioL1Cache.js","sources":["../../../src/cache/l1/AudioL1Cache.ts"],"sourcesContent":["import type { AudioMetadata, AudioSlot } from './types';\nimport type { TimeUs } from '../../model/types';\n\ninterface PCMClipEntry {\n clipId: string;\n sampleRate: number;\n numberOfChannels: number;\n planes: Float32Array[];\n startUs: TimeUs;\n durationUs: TimeUs;\n lastAccessedAt: number;\n}\n\nexport class AudioL1Cache {\n private slots: AudioSlot[] = [];\n private clipPCM = new Map<string, PCMClipEntry>();\n private metadata: AudioMetadata = { sampleRate: 48_000, numberOfChannels: 2 };\n private volume = 1;\n private muted = false;\n private maxClips = 20;\n\n attachStream(stream: ReadableStream<AudioData>, metadata: AudioMetadata): void {\n this.metadata = metadata;\n const reader = stream.getReader();\n\n const pump = async (): Promise<void> => {\n const { done, value } = await reader.read();\n if (done) {\n reader.releaseLock();\n return;\n }\n\n this.addAudio(value);\n await pump();\n };\n\n pump().catch((error) => {\n console.error('[AudioL1Cache] stream error', error);\n reader.releaseLock();\n });\n }\n\n putClipAudioData(\n clipId: string,\n audioData: AudioData,\n clipStartUs: TimeUs,\n clipDurationUs: TimeUs\n ): void {\n const numberOfChannels = audioData.numberOfChannels ?? this.metadata.numberOfChannels;\n const numberOfFrames = audioData.numberOfFrames ?? 0;\n const sampleRate = audioData.sampleRate ?? this.metadata.sampleRate;\n\n if (!numberOfChannels || !numberOfFrames) {\n audioData.close();\n return;\n }\n\n const planes = this.extractPlanesFromAudioData(audioData, numberOfChannels, numberOfFrames);\n audioData.close();\n\n let entry = this.clipPCM.get(clipId);\n if (!entry) {\n entry = {\n clipId,\n sampleRate,\n numberOfChannels,\n planes: Array.from({ length: numberOfChannels }, () => new Float32Array(0)),\n startUs: clipStartUs,\n durationUs: clipDurationUs,\n lastAccessedAt: Date.now(),\n };\n this.clipPCM.set(clipId, entry);\n\n if (this.clipPCM.size > this.maxClips) {\n this.evictLRU();\n }\n }\n\n // Calculate how many frames we should store based on clip duration\n const maxFrames = Math.ceil((clipDurationUs / 1_000_000) * sampleRate);\n const currentFrames = entry.planes[0]?.length ?? 0;\n\n // If we've already stored enough frames for the clip duration, skip\n if (currentFrames >= maxFrames) {\n return;\n }\n\n for (let channel = 0; channel < numberOfChannels; channel++) {\n const existingPlane = entry.planes[channel];\n const newPlane = planes[channel];\n if (!existingPlane || !newPlane) continue;\n\n // Calculate how many frames from newPlane we should actually append\n const framesToAppend = Math.min(newPlane.length, maxFrames - currentFrames);\n\n if (framesToAppend <= 0) {\n continue;\n }\n\n const combined = new Float32Array(existingPlane.length + framesToAppend);\n combined.set(existingPlane, 0);\n combined.set(newPlane.subarray(0, framesToAppend), existingPlane.length);\n entry.planes[channel] = combined;\n }\n\n entry.lastAccessedAt = Date.now();\n }\n\n getPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null {\n const entry = this.clipPCM.get(clipId);\n if (!entry) {\n return null;\n }\n\n entry.lastAccessedAt = Date.now();\n\n const offsetUs = Math.max(0, startUs - entry.startUs);\n\n // Calculate actual stored duration based on actual frames, not expected durationUs\n const actualStoredFrames = entry.planes[0]?.length ?? 0;\n const actualStoredUs = (actualStoredFrames / entry.sampleRate) * 1_000_000;\n const availableDurationUs = Math.max(0, actualStoredUs - offsetUs);\n\n const requestedDurationUs = endUs - startUs;\n const durationUs = Math.min(requestedDurationUs, availableDurationUs);\n\n if (durationUs <= 0) {\n return null;\n }\n\n const offsetFrames = Math.floor((offsetUs / 1_000_000) * entry.sampleRate);\n const frameCount = Math.ceil((durationUs / 1_000_000) * entry.sampleRate);\n\n const result: Float32Array[] = [];\n for (let channel = 0; channel < entry.numberOfChannels; channel++) {\n const plane = entry.planes[channel];\n if (!plane) {\n result.push(new Float32Array(frameCount));\n continue;\n }\n\n const channelData = new Float32Array(frameCount);\n const copyLength = Math.min(frameCount, plane.length - offsetFrames);\n for (let i = 0; i < copyLength; i++) {\n channelData[i] = plane[offsetFrames + i] ?? 0;\n }\n result.push(channelData);\n }\n\n return result;\n }\n\n getPCMWithMetadata(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs\n ): { planes: Float32Array[]; sampleRate: number; numberOfChannels: number } | null {\n const planes = this.getPCM(clipId, startUs, endUs);\n if (!planes) {\n return null;\n }\n\n const entry = this.clipPCM.get(clipId);\n if (!entry) {\n return null;\n }\n\n return {\n planes,\n sampleRate: entry.sampleRate,\n numberOfChannels: entry.numberOfChannels,\n };\n }\n\n hasClipPCM(clipId: string): boolean {\n return this.clipPCM.has(clipId);\n }\n\n clearClipPCM(clipId: string): void {\n this.clipPCM.delete(clipId);\n }\n\n private evictLRU(): void {\n let oldestClipId: string | null = null;\n let oldestTime = Date.now();\n\n for (const [clipId, entry] of this.clipPCM) {\n if (entry.lastAccessedAt < oldestTime) {\n oldestTime = entry.lastAccessedAt;\n oldestClipId = clipId;\n }\n }\n\n if (oldestClipId) {\n this.clipPCM.delete(oldestClipId);\n }\n }\n\n private extractPlanesFromAudioData(\n audioData: AudioData,\n numberOfChannels: number,\n numberOfFrames: number\n ): Float32Array[] {\n const planes: Float32Array[] = Array.from(\n { length: numberOfChannels },\n () => new Float32Array(numberOfFrames)\n );\n\n const toFloat = (value: number): number => value / 32768;\n\n const fillInterleaved = (format: 'f32' | 's16'): boolean => {\n const samples =\n format === 'f32'\n ? new Float32Array(numberOfFrames * numberOfChannels)\n : new Int16Array(numberOfFrames * numberOfChannels);\n\n try {\n audioData.copyTo(samples, { format, planeIndex: 0 });\n } catch {\n return false;\n }\n\n for (let frame = 0; frame < numberOfFrames; frame += 1) {\n const offset = frame * numberOfChannels;\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n if (format === 'f32') {\n plane[frame] = (samples as Float32Array)[offset + channel] ?? 0;\n } else {\n plane[frame] = toFloat((samples as Int16Array)[offset + channel] ?? 0);\n }\n }\n }\n\n return true;\n };\n\n const fillPlanar = (format: 'f32-planar' | 's16-planar'): boolean => {\n try {\n if (format === 'f32-planar') {\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n audioData.copyTo(plane, { planeIndex: channel, format: 'f32-planar' });\n }\n return true;\n }\n\n const tmp = new Int16Array(numberOfFrames);\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n audioData.copyTo(tmp, { planeIndex: channel, format: 's16-planar' as any });\n for (let i = 0; i < numberOfFrames; i += 1) {\n plane[i] = toFloat(tmp[i] ?? 0);\n }\n }\n return true;\n } catch {\n return false;\n }\n };\n\n const fillFallback = (): boolean => {\n try {\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n audioData.copyTo(plane, { planeIndex: channel });\n }\n return true;\n } catch {\n return false;\n }\n };\n\n const reportedFormat = (audioData as any).format as string | undefined;\n const attempts: Array<() => boolean> = [];\n const scheduled = new Set<string>();\n\n const scheduleAttempt = (token: string, attempt: () => boolean): void => {\n if (!scheduled.has(token)) {\n scheduled.add(token);\n attempts.push(attempt);\n }\n };\n\n if (reportedFormat) {\n switch (reportedFormat) {\n case 'f32':\n scheduleAttempt('f32', () => fillInterleaved('f32'));\n break;\n case 's16':\n scheduleAttempt('s16', () => fillInterleaved('s16'));\n break;\n case 'f32-planar':\n scheduleAttempt('f32-planar', () => fillPlanar('f32-planar'));\n break;\n case 's16-planar':\n scheduleAttempt('s16-planar', () => fillPlanar('s16-planar'));\n break;\n default:\n break;\n }\n }\n\n scheduleAttempt('f32', () => fillInterleaved('f32'));\n scheduleAttempt('f32-planar', () => fillPlanar('f32-planar'));\n scheduleAttempt('s16', () => fillInterleaved('s16'));\n scheduleAttempt('s16-planar', () => fillPlanar('s16-planar'));\n\n let filled = false;\n for (const attempt of attempts) {\n if (attempt()) {\n filled = true;\n break;\n }\n }\n\n if (!filled) {\n filled = fillFallback();\n }\n\n if (!filled) {\n throw new Error('AudioL1Cache: unsupported AudioData format');\n }\n\n return planes;\n }\n\n addAudio(audio: AudioData): void {\n const numberOfChannels = audio.numberOfChannels ?? this.metadata.numberOfChannels;\n const numberOfFrames = audio.numberOfFrames ?? 0;\n\n if (!numberOfChannels || !numberOfFrames) {\n audio.close();\n return;\n }\n\n if (audio.sampleRate && audio.sampleRate > 0 && audio.sampleRate !== this.metadata.sampleRate) {\n this.metadata = { ...this.metadata, sampleRate: audio.sampleRate };\n }\n\n if (numberOfChannels !== this.metadata.numberOfChannels) {\n this.metadata = { ...this.metadata, numberOfChannels };\n }\n\n const planes = this.extractPlanesFromAudioData(audio, numberOfChannels, numberOfFrames);\n\n this.slots.push({\n timestampUs: audio.timestamp ?? 0,\n durationUs:\n audio.duration ?? Math.round((numberOfFrames / this.metadata.sampleRate) * 1_000_000),\n planes,\n });\n\n audio.close();\n }\n\n getClosest(timeUs: number): (AudioSlot & { metadata: AudioMetadata }) | null {\n if (this.slots.length === 0) {\n return null;\n }\n\n let closest: AudioSlot | null = null;\n let minDelta = Number.MAX_SAFE_INTEGER;\n\n for (const slot of this.slots) {\n const start = slot.timestampUs;\n const end = start + slot.durationUs;\n if (timeUs >= start && timeUs <= end) {\n closest = slot;\n break;\n }\n\n const delta = Math.min(Math.abs(timeUs - start), Math.abs(timeUs - end));\n if (delta < minDelta) {\n closest = slot;\n minDelta = delta;\n }\n }\n\n if (!closest) {\n return null;\n }\n\n return {\n ...closest,\n planes: this.applyGain(closest.planes),\n metadata: this.metadata,\n };\n }\n\n flush(): void {\n this.slots = [];\n }\n\n reset(): void {\n this.flush();\n this.clipPCM.clear();\n this.metadata = { sampleRate: 48_000, numberOfChannels: 2 };\n this.volume = 1;\n this.muted = false;\n }\n\n setPlaybackRate(_rate: number): void {\n // Reserved for future use\n }\n\n setVolume(volume: number): void {\n this.volume = Math.max(0, Math.min(1, volume));\n }\n\n setMute(muted: boolean): void {\n this.muted = muted;\n }\n\n dispose(): void {\n this.reset();\n }\n\n private applyGain(planes: Float32Array[]): Float32Array[] {\n if (this.muted || this.volume === 0) {\n return planes.map((plane) => new Float32Array(plane.length));\n }\n\n if (this.volume === 1) {\n return planes.map((plane) => plane.slice());\n }\n\n return planes.map((plane) => {\n const scaled = new Float32Array(plane.length);\n for (let i = 0; i < plane.length; i += 1) {\n scaled[i] = (plane[i] ?? 0) * this.volume;\n }\n return scaled;\n });\n }\n}\n"],"names":[],"mappings":"AAaO,MAAM,aAAa;AAAA,EAChB,QAAqB,CAAA;AAAA,EACrB,8BAAc,IAAA;AAAA,EACd,WAA0B,EAAE,YAAY,MAAQ,kBAAkB,EAAA;AAAA,EAClE,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,WAAW;AAAA,EAEnB,aAAa,QAAmC,UAA+B;AAC7E,SAAK,WAAW;AAChB,UAAM,SAAS,OAAO,UAAA;AAEtB,UAAM,OAAO,YAA2B;AACtC,YAAM,EAAE,MAAM,MAAA,IAAU,MAAM,OAAO,KAAA;AACrC,UAAI,MAAM;AACR,eAAO,YAAA;AACP;AAAA,MACF;AAEA,WAAK,SAAS,KAAK;AACnB,YAAM,KAAA;AAAA,IACR;AAEA,SAAA,EAAO,MAAM,CAAC,UAAU;AACtB,cAAQ,MAAM,+BAA+B,KAAK;AAClD,aAAO,YAAA;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,iBACE,QACA,WACA,aACA,gBACM;AACN,UAAM,mBAAmB,UAAU,oBAAoB,KAAK,SAAS;AACrE,UAAM,iBAAiB,UAAU,kBAAkB;AACnD,UAAM,aAAa,UAAU,cAAc,KAAK,SAAS;AAEzD,QAAI,CAAC,oBAAoB,CAAC,gBAAgB;AACxC,gBAAU,MAAA;AACV;AAAA,IACF;AAEA,UAAM,SAAS,KAAK,2BAA2B,WAAW,kBAAkB,cAAc;AAC1F,cAAU,MAAA;AAEV,QAAI,QAAQ,KAAK,QAAQ,IAAI,MAAM;AACnC,QAAI,CAAC,OAAO;AACV,cAAQ;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ,MAAM,KAAK,EAAE,QAAQ,iBAAA,GAAoB,MAAM,IAAI,aAAa,CAAC,CAAC;AAAA,QAC1E,SAAS;AAAA,QACT,YAAY;AAAA,QACZ,gBAAgB,KAAK,IAAA;AAAA,MAAI;AAE3B,WAAK,QAAQ,IAAI,QAAQ,KAAK;AAE9B,UAAI,KAAK,QAAQ,OAAO,KAAK,UAAU;AACrC,aAAK,SAAA;AAAA,MACP;AAAA,IACF;AAGA,UAAM,YAAY,KAAK,KAAM,iBAAiB,MAAa,UAAU;AACrE,UAAM,gBAAgB,MAAM,OAAO,CAAC,GAAG,UAAU;AAGjD,QAAI,iBAAiB,WAAW;AAC9B;AAAA,IACF;AAEA,aAAS,UAAU,GAAG,UAAU,kBAAkB,WAAW;AAC3D,YAAM,gBAAgB,MAAM,OAAO,OAAO;AAC1C,YAAM,WAAW,OAAO,OAAO;AAC/B,UAAI,CAAC,iBAAiB,CAAC,SAAU;AAGjC,YAAM,iBAAiB,KAAK,IAAI,SAAS,QAAQ,YAAY,aAAa;AAE1E,UAAI,kBAAkB,GAAG;AACvB;AAAA,MACF;AAEA,YAAM,WAAW,IAAI,aAAa,cAAc,SAAS,cAAc;AACvE,eAAS,IAAI,eAAe,CAAC;AAC7B,eAAS,IAAI,SAAS,SAAS,GAAG,cAAc,GAAG,cAAc,MAAM;AACvE,YAAM,OAAO,OAAO,IAAI;AAAA,IAC1B;AAEA,UAAM,iBAAiB,KAAK,IAAA;AAAA,EAC9B;AAAA,EAEA,OAAO,QAAgB,SAAiB,OAAsC;AAC5E,UAAM,QAAQ,KAAK,QAAQ,IAAI,MAAM;AACrC,QAAI,CAAC,OAAO;AACV,aAAO;AAAA,IACT;AAEA,UAAM,iBAAiB,KAAK,IAAA;AAE5B,UAAM,WAAW,KAAK,IAAI,GAAG,UAAU,MAAM,OAAO;AAGpD,UAAM,qBAAqB,MAAM,OAAO,CAAC,GAAG,UAAU;AACtD,UAAM,iBAAkB,qBAAqB,MAAM,aAAc;AACjE,UAAM,sBAAsB,KAAK,IAAI,GAAG,iBAAiB,QAAQ;AAEjE,UAAM,sBAAsB,QAAQ;AACpC,UAAM,aAAa,KAAK,IAAI,qBAAqB,mBAAmB;AAEpE,QAAI,cAAc,GAAG;AACnB,aAAO;AAAA,IACT;AAEA,UAAM,eAAe,KAAK,MAAO,WAAW,MAAa,MAAM,UAAU;AACzE,UAAM,aAAa,KAAK,KAAM,aAAa,MAAa,MAAM,UAAU;AAExE,UAAM,SAAyB,CAAA;AAC/B,aAAS,UAAU,GAAG,UAAU,MAAM,kBAAkB,WAAW;AACjE,YAAM,QAAQ,MAAM,OAAO,OAAO;AAClC,UAAI,CAAC,OAAO;AACV,eAAO,KAAK,IAAI,aAAa,UAAU,CAAC;AACxC;AAAA,MACF;AAEA,YAAM,cAAc,IAAI,aAAa,UAAU;AAC/C,YAAM,aAAa,KAAK,IAAI,YAAY,MAAM,SAAS,YAAY;AACnE,eAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,oBAAY,CAAC,IAAI,MAAM,eAAe,CAAC,KAAK;AAAA,MAC9C;AACA,aAAO,KAAK,WAAW;AAAA,IACzB;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,mBACE,QACA,SACA,OACiF;AACjF,UAAM,SAAS,KAAK,OAAO,QAAQ,SAAS,KAAK;AACjD,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,KAAK,QAAQ,IAAI,MAAM;AACrC,QAAI,CAAC,OAAO;AACV,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL;AAAA,MACA,YAAY,MAAM;AAAA,MAClB,kBAAkB,MAAM;AAAA,IAAA;AAAA,EAE5B;AAAA,EAEA,WAAW,QAAyB;AAClC,WAAO,KAAK,QAAQ,IAAI,MAAM;AAAA,EAChC;AAAA,EAEA,aAAa,QAAsB;AACjC,SAAK,QAAQ,OAAO,MAAM;AAAA,EAC5B;AAAA,EAEQ,WAAiB;AACvB,QAAI,eAA8B;AAClC,QAAI,aAAa,KAAK,IAAA;AAEtB,eAAW,CAAC,QAAQ,KAAK,KAAK,KAAK,SAAS;AAC1C,UAAI,MAAM,iBAAiB,YAAY;AACrC,qBAAa,MAAM;AACnB,uBAAe;AAAA,MACjB;AAAA,IACF;AAEA,QAAI,cAAc;AAChB,WAAK,QAAQ,OAAO,YAAY;AAAA,IAClC;AAAA,EACF;AAAA,EAEQ,2BACN,WACA,kBACA,gBACgB;AAChB,UAAM,SAAyB,MAAM;AAAA,MACnC,EAAE,QAAQ,iBAAA;AAAA,MACV,MAAM,IAAI,aAAa,cAAc;AAAA,IAAA;AAGvC,UAAM,UAAU,CAAC,UAA0B,QAAQ;AAEnD,UAAM,kBAAkB,CAAC,WAAmC;AAC1D,YAAM,UACJ,WAAW,QACP,IAAI,aAAa,iBAAiB,gBAAgB,IAClD,IAAI,WAAW,iBAAiB,gBAAgB;AAEtD,UAAI;AACF,kBAAU,OAAO,SAAS,EAAE,QAAQ,YAAY,GAAG;AAAA,MACrD,QAAQ;AACN,eAAO;AAAA,MACT;AAEA,eAAS,QAAQ,GAAG,QAAQ,gBAAgB,SAAS,GAAG;AACtD,cAAM,SAAS,QAAQ;AACvB,iBAAS,UAAU,GAAG,UAAU,kBAAkB,WAAW,GAAG;AAC9D,gBAAM,QAAQ,OAAO,OAAO;AAC5B,cAAI,CAAC,MAAO;AACZ,cAAI,WAAW,OAAO;AACpB,kBAAM,KAAK,IAAK,QAAyB,SAAS,OAAO,KAAK;AAAA,UAChE,OAAO;AACL,kBAAM,KAAK,IAAI,QAAS,QAAuB,SAAS,OAAO,KAAK,CAAC;AAAA,UACvE;AAAA,QACF;AAAA,MACF;AAEA,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,CAAC,WAAiD;AACnE,UAAI;AACF,YAAI,WAAW,cAAc;AAC3B,mBAAS,UAAU,GAAG,UAAU,kBAAkB,WAAW,GAAG;AAC9D,kBAAM,QAAQ,OAAO,OAAO;AAC5B,gBAAI,CAAC,MAAO;AACZ,sBAAU,OAAO,OAAO,EAAE,YAAY,SAAS,QAAQ,cAAc;AAAA,UACvE;AACA,iBAAO;AAAA,QACT;AAEA,cAAM,MAAM,IAAI,WAAW,cAAc;AACzC,iBAAS,UAAU,GAAG,UAAU,kBAAkB,WAAW,GAAG;AAC9D,gBAAM,QAAQ,OAAO,OAAO;AAC5B,cAAI,CAAC,MAAO;AACZ,oBAAU,OAAO,KAAK,EAAE,YAAY,SAAS,QAAQ,cAAqB;AAC1E,mBAAS,IAAI,GAAG,IAAI,gBAAgB,KAAK,GAAG;AAC1C,kBAAM,CAAC,IAAI,QAAQ,IAAI,CAAC,KAAK,CAAC;AAAA,UAChC;AAAA,QACF;AACA,eAAO;AAAA,MACT,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,eAAe,MAAe;AAClC,UAAI;AACF,iBAAS,UAAU,GAAG,UAAU,kBAAkB,WAAW,GAAG;AAC9D,gBAAM,QAAQ,OAAO,OAAO;AAC5B,cAAI,CAAC,MAAO;AACZ,oBAAU,OAAO,OAAO,EAAE,YAAY,SAAS;AAAA,QACjD;AACA,eAAO;AAAA,MACT,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,iBAAkB,UAAkB;AAC1C,UAAM,WAAiC,CAAA;AACvC,UAAM,gCAAgB,IAAA;AAEtB,UAAM,kBAAkB,CAAC,OAAe,YAAiC;AACvE,UAAI,CAAC,UAAU,IAAI,KAAK,GAAG;AACzB,kBAAU,IAAI,KAAK;AACnB,iBAAS,KAAK,OAAO;AAAA,MACvB;AAAA,IACF;AAEA,QAAI,gBAAgB;AAClB,cAAQ,gBAAA;AAAA,QACN,KAAK;AACH,0BAAgB,OAAO,MAAM,gBAAgB,KAAK,CAAC;AACnD;AAAA,QACF,KAAK;AACH,0BAAgB,OAAO,MAAM,gBAAgB,KAAK,CAAC;AACnD;AAAA,QACF,KAAK;AACH,0BAAgB,cAAc,MAAM,WAAW,YAAY,CAAC;AAC5D;AAAA,QACF,KAAK;AACH,0BAAgB,cAAc,MAAM,WAAW,YAAY,CAAC;AAC5D;AAAA,MAEA;AAAA,IAEN;AAEA,oBAAgB,OAAO,MAAM,gBAAgB,KAAK,CAAC;AACnD,oBAAgB,cAAc,MAAM,WAAW,YAAY,CAAC;AAC5D,oBAAgB,OAAO,MAAM,gBAAgB,KAAK,CAAC;AACnD,oBAAgB,cAAc,MAAM,WAAW,YAAY,CAAC;AAE5D,QAAI,SAAS;AACb,eAAW,WAAW,UAAU;AAC9B,UAAI,WAAW;AACb,iBAAS;AACT;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,QAAQ;AACX,eAAS,aAAA;AAAA,IACX;AAEA,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,4CAA4C;AAAA,IAC9D;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,SAAS,OAAwB;AAC/B,UAAM,mBAAmB,MAAM,oBAAoB,KAAK,SAAS;AACjE,UAAM,iBAAiB,MAAM,kBAAkB;AAE/C,QAAI,CAAC,oBAAoB,CAAC,gBAAgB;AACxC,YAAM,MAAA;AACN;AAAA,IACF;AAEA,QAAI,MAAM,cAAc,MAAM,aAAa,KAAK,MAAM,eAAe,KAAK,SAAS,YAAY;AAC7F,WAAK,WAAW,EAAE,GAAG,KAAK,UAAU,YAAY,MAAM,WAAA;AAAA,IACxD;AAEA,QAAI,qBAAqB,KAAK,SAAS,kBAAkB;AACvD,WAAK,WAAW,EAAE,GAAG,KAAK,UAAU,iBAAA;AAAA,IACtC;AAEA,UAAM,SAAS,KAAK,2BAA2B,OAAO,kBAAkB,cAAc;AAEtF,SAAK,MAAM,KAAK;AAAA,MACd,aAAa,MAAM,aAAa;AAAA,MAChC,YACE,MAAM,YAAY,KAAK,MAAO,iBAAiB,KAAK,SAAS,aAAc,GAAS;AAAA,MACtF;AAAA,IAAA,CACD;AAED,UAAM,MAAA;AAAA,EACR;AAAA,EAEA,WAAW,QAAkE;AAC3E,QAAI,KAAK,MAAM,WAAW,GAAG;AAC3B,aAAO;AAAA,IACT;AAEA,QAAI,UAA4B;AAChC,QAAI,WAAW,OAAO;AAEtB,eAAW,QAAQ,KAAK,OAAO;AAC7B,YAAM,QAAQ,KAAK;AACnB,YAAM,MAAM,QAAQ,KAAK;AACzB,UAAI,UAAU,SAAS,UAAU,KAAK;AACpC,kBAAU;AACV;AAAA,MACF;AAEA,YAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,SAAS,KAAK,GAAG,KAAK,IAAI,SAAS,GAAG,CAAC;AACvE,UAAI,QAAQ,UAAU;AACpB,kBAAU;AACV,mBAAW;AAAA,MACb;AAAA,IACF;AAEA,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL,GAAG;AAAA,MACH,QAAQ,KAAK,UAAU,QAAQ,MAAM;AAAA,MACrC,UAAU,KAAK;AAAA,IAAA;AAAA,EAEnB;AAAA,EAEA,QAAc;AACZ,SAAK,QAAQ,CAAA;AAAA,EACf;AAAA,EAEA,QAAc;AACZ,SAAK,MAAA;AACL,SAAK,QAAQ,MAAA;AACb,SAAK,WAAW,EAAE,YAAY,MAAQ,kBAAkB,EAAA;AACxD,SAAK,SAAS;AACd,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,gBAAgB,OAAqB;AAAA,EAErC;AAAA,EAEA,UAAU,QAAsB;AAC9B,SAAK,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,MAAM,CAAC;AAAA,EAC/C;AAAA,EAEA,QAAQ,OAAsB;AAC5B,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,UAAgB;AACd,SAAK,MAAA;AAAA,EACP;AAAA,EAEQ,UAAU,QAAwC;AACxD,QAAI,KAAK,SAAS,KAAK,WAAW,GAAG;AACnC,aAAO,OAAO,IAAI,CAAC,UAAU,IAAI,aAAa,MAAM,MAAM,CAAC;AAAA,IAC7D;AAEA,QAAI,KAAK,WAAW,GAAG;AACrB,aAAO,OAAO,IAAI,CAAC,UAAU,MAAM,OAAO;AAAA,IAC5C;AAEA,WAAO,OAAO,IAAI,CAAC,UAAU;AAC3B,YAAM,SAAS,IAAI,aAAa,MAAM,MAAM;AAC5C,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,eAAO,CAAC,KAAK,MAAM,CAAC,KAAK,KAAK,KAAK;AAAA,MACrC;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;"}
|
|
@@ -22,8 +22,6 @@ export declare class PlaybackController implements IPlaybackController, PreviewH
|
|
|
22
22
|
private lastFrameTime;
|
|
23
23
|
private fps;
|
|
24
24
|
private audioContext;
|
|
25
|
-
private audioSource;
|
|
26
|
-
private audioGainNode;
|
|
27
25
|
private audioSession;
|
|
28
26
|
private isBuffering;
|
|
29
27
|
constructor(orchestrator: Orchestrator, eventBus: IEventBus, options: PlaybackOptions);
|
|
@@ -50,7 +48,5 @@ export declare class PlaybackController implements IPlaybackController, PreviewH
|
|
|
50
48
|
private clampTime;
|
|
51
49
|
dispose(): void;
|
|
52
50
|
private ensureAudioContext;
|
|
53
|
-
private startAudioPlayback;
|
|
54
|
-
private stopAudioPlayback;
|
|
55
51
|
}
|
|
56
52
|
//# sourceMappingURL=PlaybackController.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlaybackController.d.ts","sourceRoot":"","sources":["../../src/controllers/PlaybackController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EAEnB,eAAe,EACf,SAAS,EACT,aAAa,EACb,MAAM,EACP,MAAM,SAAS,CAAC;AAGjB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,mBAAmB,EAAE,aAAa;IAC3E,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAY;IAC5B,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,GAAG,CAA+D;IAG1E,aAAa,EAAE,MAAM,CAAK;IAC1B,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,MAAM,CAAO;IACrB,OAAO,CAAC,IAAI,CAAS;IAGrB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,SAAS,CAAK;IAGtB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"PlaybackController.d.ts","sourceRoot":"","sources":["../../src/controllers/PlaybackController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EAEnB,eAAe,EACf,SAAS,EACT,aAAa,EACb,MAAM,EACP,MAAM,SAAS,CAAC;AAGjB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,mBAAmB,EAAE,aAAa;IAC3E,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAY;IAC5B,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,GAAG,CAA+D;IAG1E,aAAa,EAAE,MAAM,CAAK;IAC1B,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,MAAM,CAAO;IACrB,OAAO,CAAC,IAAI,CAAS;IAGrB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,SAAS,CAAK;IAGtB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,YAAY,CAAmC;IAGvD,OAAO,CAAC,WAAW,CAAS;gBAEhB,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe;IAyCrF,IAAI,IAAI,IAAI;YAME,aAAa;IAkC3B,KAAK,IAAI,IAAI;IAeb,IAAI,IAAI,IAAI;IAgBN,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BzC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAW3B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAO/B,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAQ7B,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAI5B,IAAI,QAAQ,IAAI,MAAM,CAOrB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAKlD,MAAM,IAAI,IAAI;IAId,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAIxD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAIzD,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,YAAY;IA2CpB,OAAO,CAAC,UAAU;YAuBJ,kBAAkB;YAwBlB,uBAAuB;IA8CrC,OAAO,CAAC,SAAS;IAKjB,OAAO,IAAI,IAAI;YAID,kBAAkB;CAOjC"}
|
|
@@ -19,8 +19,6 @@ class PlaybackController {
|
|
|
19
19
|
lastFrameTime = 0;
|
|
20
20
|
fps = 0;
|
|
21
21
|
audioContext = null;
|
|
22
|
-
audioSource = null;
|
|
23
|
-
audioGainNode = null;
|
|
24
22
|
audioSession = null;
|
|
25
23
|
// Buffering state
|
|
26
24
|
isBuffering = false;
|
|
@@ -74,7 +72,9 @@ class PlaybackController {
|
|
|
74
72
|
this.state = "playing";
|
|
75
73
|
this.startTime = performance.now() - this.currentTimeUs / 1e3 / this.playbackRate;
|
|
76
74
|
await this.ensureAudioContext();
|
|
77
|
-
this.
|
|
75
|
+
if (this.audioSession && this.audioContext) {
|
|
76
|
+
await this.audioSession.startPlayback(this.currentTimeUs, this.audioContext);
|
|
77
|
+
}
|
|
78
78
|
this.playbackLoop();
|
|
79
79
|
this.eventBus.emit(MeframeEvent.PlaybackPlay);
|
|
80
80
|
} catch (error) {
|
|
@@ -90,7 +90,7 @@ class PlaybackController {
|
|
|
90
90
|
cancelAnimationFrame(this.rafId);
|
|
91
91
|
this.rafId = null;
|
|
92
92
|
}
|
|
93
|
-
this.
|
|
93
|
+
this.audioSession?.stopPlayback();
|
|
94
94
|
this.eventBus.emit(MeframeEvent.PlaybackPause);
|
|
95
95
|
}
|
|
96
96
|
stop() {
|
|
@@ -102,7 +102,6 @@ class PlaybackController {
|
|
|
102
102
|
this.fps = 0;
|
|
103
103
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
104
104
|
this.audioSession?.reset();
|
|
105
|
-
this.stopAudioPlayback();
|
|
106
105
|
this.eventBus.emit(MeframeEvent.PlaybackStop);
|
|
107
106
|
}
|
|
108
107
|
async seek(timeUs) {
|
|
@@ -114,7 +113,7 @@ class PlaybackController {
|
|
|
114
113
|
const clamped = this.clampTime(timeUs);
|
|
115
114
|
this.currentTimeUs = clamped;
|
|
116
115
|
this.state = "seeking";
|
|
117
|
-
this.
|
|
116
|
+
this.audioSession?.stopPlayback();
|
|
118
117
|
try {
|
|
119
118
|
await this.renderCurrentFrame(clamped);
|
|
120
119
|
this.eventBus.emit(MeframeEvent.PlaybackSeek, { timeUs: this.currentTimeUs });
|
|
@@ -135,22 +134,18 @@ class PlaybackController {
|
|
|
135
134
|
this.playbackRate = rate;
|
|
136
135
|
this.startTime = performance.now() - elapsed / rate;
|
|
137
136
|
this.eventBus.emit(MeframeEvent.PlaybackRateChange, { rate });
|
|
138
|
-
|
|
139
|
-
this.audioSource.playbackRate.value = this.playbackRate;
|
|
140
|
-
}
|
|
137
|
+
this.audioSession?.setPlaybackRate(this.playbackRate);
|
|
141
138
|
}
|
|
142
139
|
setVolume(volume) {
|
|
143
140
|
this.volume = Math.max(0, Math.min(1, volume));
|
|
144
141
|
this.eventBus.emit(MeframeEvent.PlaybackVolumeChange, { volume: this.volume });
|
|
145
|
-
|
|
146
|
-
this.audioGainNode.gain.value = this.volume;
|
|
147
|
-
}
|
|
142
|
+
this.audioSession?.setVolume(this.volume);
|
|
148
143
|
}
|
|
149
144
|
setMute(muted) {
|
|
150
145
|
if (muted) {
|
|
151
|
-
this.
|
|
152
|
-
} else if (this.state === "playing") {
|
|
153
|
-
this.
|
|
146
|
+
this.audioSession?.stopPlayback();
|
|
147
|
+
} else if (this.state === "playing" && this.audioContext) {
|
|
148
|
+
this.audioSession?.startPlayback(this.currentTimeUs, this.audioContext);
|
|
154
149
|
}
|
|
155
150
|
}
|
|
156
151
|
setLoop(loop) {
|
|
@@ -198,6 +193,7 @@ class PlaybackController {
|
|
|
198
193
|
return;
|
|
199
194
|
}
|
|
200
195
|
this.updateTime();
|
|
196
|
+
this.audioSession?.updateTime(this.currentTimeUs);
|
|
201
197
|
await this.renderCurrentFrame();
|
|
202
198
|
if (this.state !== "playing") {
|
|
203
199
|
return;
|
|
@@ -296,45 +292,6 @@ class PlaybackController {
|
|
|
296
292
|
return;
|
|
297
293
|
}
|
|
298
294
|
this.audioContext = new AudioContext();
|
|
299
|
-
this.audioGainNode = this.audioContext.createGain();
|
|
300
|
-
this.audioGainNode.gain.value = this.volume;
|
|
301
|
-
this.audioGainNode.connect(this.audioContext.destination);
|
|
302
|
-
}
|
|
303
|
-
startAudioPlayback() {
|
|
304
|
-
if (!this.audioContext || !this.audioSession || !this.audioGainNode) {
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
this.stopAudioPlayback();
|
|
308
|
-
this.audioSession.prepareAudioForPlayback(this.currentTimeUs).then((audioInfo) => {
|
|
309
|
-
if (!audioInfo || !this.audioContext || !this.audioGainNode) {
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
const source = this.audioContext.createBufferSource();
|
|
313
|
-
source.buffer = audioInfo.buffer;
|
|
314
|
-
source.playbackRate.value = this.playbackRate;
|
|
315
|
-
source.onended = () => {
|
|
316
|
-
this.audioSource = null;
|
|
317
|
-
if (this.state === "playing") {
|
|
318
|
-
this.startAudioPlayback();
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
source.connect(this.audioGainNode);
|
|
322
|
-
source.start(0, audioInfo.offsetSeconds, audioInfo.durationSeconds);
|
|
323
|
-
this.audioSource = source;
|
|
324
|
-
}).catch((error) => {
|
|
325
|
-
console.error("[PlaybackController] Audio playback error:", error);
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
stopAudioPlayback() {
|
|
329
|
-
if (this.audioSource) {
|
|
330
|
-
try {
|
|
331
|
-
this.audioSource.stop();
|
|
332
|
-
} catch (error) {
|
|
333
|
-
console.warn("[PlaybackController] stop audio failed", error);
|
|
334
|
-
}
|
|
335
|
-
this.audioSource.disconnect();
|
|
336
|
-
this.audioSource = null;
|
|
337
|
-
}
|
|
338
295
|
}
|
|
339
296
|
}
|
|
340
297
|
export {
|