@meframe/core 0.0.7 → 0.0.8

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.
Files changed (39) hide show
  1. package/dist/cache/CacheManager.d.ts +5 -3
  2. package/dist/cache/CacheManager.d.ts.map +1 -1
  3. package/dist/cache/CacheManager.js +3 -10
  4. package/dist/cache/CacheManager.js.map +1 -1
  5. package/dist/cache/l1/AudioL1Cache.d.ts +5 -0
  6. package/dist/cache/l1/AudioL1Cache.d.ts.map +1 -1
  7. package/dist/cache/l1/AudioL1Cache.js +31 -3
  8. package/dist/cache/l1/AudioL1Cache.js.map +1 -1
  9. package/dist/controllers/PlaybackController.d.ts +0 -4
  10. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  11. package/dist/controllers/PlaybackController.js +11 -54
  12. package/dist/controllers/PlaybackController.js.map +1 -1
  13. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  14. package/dist/orchestrator/Orchestrator.js +1 -0
  15. package/dist/orchestrator/Orchestrator.js.map +1 -1
  16. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  17. package/dist/orchestrator/VideoClipSession.js +24 -16
  18. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  19. package/dist/stages/compose/GlobalAudioSession.d.ts +20 -25
  20. package/dist/stages/compose/GlobalAudioSession.d.ts.map +1 -1
  21. package/dist/stages/compose/GlobalAudioSession.js +241 -64
  22. package/dist/stages/compose/GlobalAudioSession.js.map +1 -1
  23. package/dist/stages/compose/OfflineAudioMixer.d.ts.map +1 -1
  24. package/dist/stages/compose/OfflineAudioMixer.js +26 -8
  25. package/dist/stages/compose/OfflineAudioMixer.js.map +1 -1
  26. package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
  27. package/dist/workers/MP4Demuxer.js +4 -3
  28. package/dist/workers/MP4Demuxer.js.map +1 -1
  29. package/dist/workers/stages/demux/audio-demux.worker.js +28 -18
  30. package/dist/workers/stages/demux/audio-demux.worker.js.map +1 -1
  31. package/dist/workers/stages/demux/video-demux.worker.js +5 -2
  32. package/dist/workers/stages/demux/video-demux.worker.js.map +1 -1
  33. package/dist/workers/stages/encode/video-encode.worker.js +6 -21
  34. package/dist/workers/stages/encode/video-encode.worker.js.map +1 -1
  35. package/package.json +1 -1
  36. package/dist/cache/l1/MixedAudioL1Cache.d.ts +0 -13
  37. package/dist/cache/l1/MixedAudioL1Cache.d.ts.map +0 -1
  38. package/dist/cache/l1/MixedAudioL1Cache.js +0 -52
  39. 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":"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"}
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;AAGvD,UAAU,kBAAkB;IAC1B,EAAE,EAAE;QACF,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,EAAE,EAAE;QACF,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,UAAU,mBAAmB;IAC3B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAuBD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,oBAAoB,CAAkD;IAC9E,OAAO,CAAC,YAAY,CAAuC;IAC3D,OAAO,CAAC,gBAAgB,CAAwC;IAChE,OAAO,CAAC,QAAQ,CAAC,CAA4B;gBAEjC,MAAM,EAAE,kBAAkB,EAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC;IAOtE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,qBAAqB,CACzB,MAAM,EAAE,cAAc,CAAC,UAAU,GAAG;QAAE,KAAK,EAAE,UAAU,CAAC;QAAC,QAAQ,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,EAC1E,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,CAAC,IAAI,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAC;KAC7D,GACA,OAAO,CAAC,IAAI,CAAC;IAsEV,oBAAoB,CACxB,MAAM,EAAE,cAAc,CAAC;QAAE,KAAK,EAAE,iBAAiB,CAAC;QAAC,QAAQ,EAAE,yBAAyB,CAAA;KAAE,CAAC,EACzF,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,GAAG,OAAO,EACxB,OAAO,CAAC,EAAE;QACR,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,yBAAyB,KAAK,IAAI,CAAC;QAC3D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;KAClC,GACA,OAAO,CAAC,IAAI,CAAC;IAmGhB,gBAAgB,CACd,MAAM,EAAE,cAAc,CAAC,SAAS,CAAC,EACjC,QAAQ,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,GACzD,IAAI;IAIP,gBAAgB,CACd,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,GACrB,IAAI;IAIP,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,GAAG,IAAI;IAIjF,sBAAsB,CACpB,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;IAIlF,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAInC,eAAe,IAAI,IAAI;IAIjB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAyBvE,YAAY,CACV,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,kBAAkB,CAAC;IAsExB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMnD;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIrC;;;OAGG;IACH,gBAAgB,CACd,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GAC3D,OAAO,CAAC,OAAO,CAAC;IAqCb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B,WAAW;;;;;;;;;IAOX;;OAEG;IACG,kBAAkB,CACtB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,GAAG,OAAO,GACvB,OAAO,CAAC,cAAc,CAAC,iBAAiB,GAAG,iBAAiB,CAAC,GAAG,IAAI,CAAC;IAIxE;;;OAGG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAK7E;;OAEG;IACG,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/E;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;YAIpE,YAAY;IAS1B,OAAO,CAAC,kBAAkB;IA6C1B,OAAO,CAAC,sBAAsB;IA2B9B,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,gBAAgB;IAoBxB,OAAO,CAAC,cAAc;CAGvB"}
@@ -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;IA6CP,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,GAAG,IAAI;IAqC7E,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"}
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 combined = new Float32Array(existingPlane.length + newPlane.length);
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 durationUs = Math.min(endUs - startUs, entry.durationUs - offsetUs);
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":"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"}
@@ -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,WAAW,CAAsC;IACzD,OAAO,CAAC,aAAa,CAAyB;IAC9C,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;IAgC3B,KAAK,IAAI,IAAI;IAeb,IAAI,IAAI,IAAI;IAiBN,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BzC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAa3B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAS/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;IAwCpB,OAAO,CAAC,UAAU;YAuBJ,kBAAkB;YAwBlB,uBAAuB;IA8CrC,OAAO,CAAC,SAAS;IAKjB,OAAO,IAAI,IAAI;YAID,kBAAkB;IAWhC,OAAO,CAAC,kBAAkB;IAiC1B,OAAO,CAAC,iBAAiB;CAW1B"}
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.startAudioPlayback();
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.stopAudioPlayback();
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.stopAudioPlayback();
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
- if (this.audioSource) {
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
- if (this.audioGainNode) {
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.stopAudioPlayback();
152
- } else if (this.state === "playing") {
153
- this.startAudioPlayback();
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 {