@meframe/core 0.1.6 → 0.1.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 (70) hide show
  1. package/dist/cache/CacheManager.d.ts +12 -0
  2. package/dist/cache/CacheManager.d.ts.map +1 -1
  3. package/dist/cache/CacheManager.js +14 -2
  4. package/dist/cache/CacheManager.js.map +1 -1
  5. package/dist/cache/l1/AudioL1Cache.d.ts +15 -0
  6. package/dist/cache/l1/AudioL1Cache.d.ts.map +1 -1
  7. package/dist/cache/l1/AudioL1Cache.js +38 -8
  8. package/dist/cache/l1/AudioL1Cache.js.map +1 -1
  9. package/dist/controllers/PlaybackController.d.ts +18 -28
  10. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  11. package/dist/controllers/PlaybackController.js +343 -253
  12. package/dist/controllers/PlaybackController.js.map +1 -1
  13. package/dist/controllers/PlaybackStateMachine.d.ts +16 -0
  14. package/dist/controllers/PlaybackStateMachine.d.ts.map +1 -0
  15. package/dist/controllers/PlaybackStateMachine.js +313 -0
  16. package/dist/controllers/PlaybackStateMachine.js.map +1 -0
  17. package/dist/controllers/index.d.ts +2 -1
  18. package/dist/controllers/index.d.ts.map +1 -1
  19. package/dist/controllers/types.d.ts +172 -2
  20. package/dist/controllers/types.d.ts.map +1 -1
  21. package/dist/controllers/types.js +54 -0
  22. package/dist/controllers/types.js.map +1 -0
  23. package/dist/model/types.d.ts +0 -1
  24. package/dist/model/types.d.ts.map +1 -1
  25. package/dist/model/types.js.map +1 -1
  26. package/dist/orchestrator/ExportScheduler.d.ts.map +1 -1
  27. package/dist/orchestrator/ExportScheduler.js +22 -19
  28. package/dist/orchestrator/ExportScheduler.js.map +1 -1
  29. package/dist/orchestrator/GlobalAudioSession.d.ts +18 -2
  30. package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
  31. package/dist/orchestrator/GlobalAudioSession.js +79 -13
  32. package/dist/orchestrator/GlobalAudioSession.js.map +1 -1
  33. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  34. package/dist/orchestrator/Orchestrator.js +8 -3
  35. package/dist/orchestrator/Orchestrator.js.map +1 -1
  36. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  37. package/dist/orchestrator/VideoClipSession.js +4 -2
  38. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  39. package/dist/orchestrator/types.d.ts +7 -1
  40. package/dist/orchestrator/types.d.ts.map +1 -1
  41. package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
  42. package/dist/stages/load/ResourceLoader.js +13 -6
  43. package/dist/stages/load/ResourceLoader.js.map +1 -1
  44. package/dist/utils/timeout-utils.d.ts +9 -0
  45. package/dist/utils/timeout-utils.d.ts.map +1 -0
  46. package/dist/worker/BaseWorker.d.ts +2 -0
  47. package/dist/worker/BaseWorker.d.ts.map +1 -1
  48. package/dist/worker/BaseWorker.js.map +1 -1
  49. package/dist/worker/WorkerChannel.d.ts +2 -0
  50. package/dist/worker/WorkerChannel.d.ts.map +1 -1
  51. package/dist/worker/WorkerChannel.js +17 -1
  52. package/dist/worker/WorkerChannel.js.map +1 -1
  53. package/dist/workers/{WorkerChannel.DjBEVvEA.js → WorkerChannel.DQK8rAab.js} +18 -2
  54. package/dist/workers/{WorkerChannel.DjBEVvEA.js.map → WorkerChannel.DQK8rAab.js.map} +1 -1
  55. package/dist/workers/stages/compose/{audio-compose.worker.CiM_KP27.js → audio-compose.worker.B4Io5w9i.js} +2 -2
  56. package/dist/workers/stages/compose/{audio-compose.worker.CiM_KP27.js.map → audio-compose.worker.B4Io5w9i.js.map} +1 -1
  57. package/dist/workers/stages/compose/{video-compose.worker.CQwmNfXT.js → video-compose.worker.CA2_Kpg-.js} +2 -2
  58. package/dist/workers/stages/compose/{video-compose.worker.CQwmNfXT.js.map → video-compose.worker.CA2_Kpg-.js.map} +1 -1
  59. package/dist/workers/stages/decode/{audio-decode.worker.CpjkrZtT.js → audio-decode.worker.-DGlQrJD.js} +2 -2
  60. package/dist/workers/stages/decode/{audio-decode.worker.CpjkrZtT.js.map → audio-decode.worker.-DGlQrJD.js.map} +1 -1
  61. package/dist/workers/stages/decode/{video-decode.worker.BQtw6eWn.js → video-decode.worker.BnWVUkng.js} +2 -2
  62. package/dist/workers/stages/decode/{video-decode.worker.BQtw6eWn.js.map → video-decode.worker.BnWVUkng.js.map} +1 -1
  63. package/dist/workers/stages/demux/{audio-demux.worker.C4V11GQi.js → audio-demux.worker.D-_LoVqW.js} +2 -2
  64. package/dist/workers/stages/demux/{audio-demux.worker.C4V11GQi.js.map → audio-demux.worker.D-_LoVqW.js.map} +1 -1
  65. package/dist/workers/stages/demux/{video-demux.worker.5pJr0Ij-.js → video-demux.worker.BWDrLGni.js} +2 -2
  66. package/dist/workers/stages/demux/{video-demux.worker.5pJr0Ij-.js.map → video-demux.worker.BWDrLGni.js.map} +1 -1
  67. package/dist/workers/stages/encode/{video-encode.worker.CX2_3YhQ.js → video-encode.worker.D6aB_rF9.js} +2 -2
  68. package/dist/workers/stages/encode/{video-encode.worker.CX2_3YhQ.js.map → video-encode.worker.D6aB_rF9.js.map} +1 -1
  69. package/dist/workers/worker-manifest.json +7 -7
  70. package/package.json +1 -1
@@ -70,6 +70,18 @@ export declare class CacheManager {
70
70
  * Returns true only if at least threshold (default 80%) of requested duration is available
71
71
  */
72
72
  hasWindowPCM(clipId: string, startUs: TimeUs, endUs: TimeUs, strictMode?: boolean): boolean;
73
+ /**
74
+ * Get audio range coverage information for incremental decoding
75
+ * @param clipId - Clip identifier
76
+ * @param startUs - Range start time in microseconds
77
+ * @param endUs - Range end time in microseconds
78
+ * @param threshold - Coverage threshold (0-1)
79
+ * @returns Coverage info { covered: boolean, coverageRatio: number }
80
+ */
81
+ getAudioRangeCoverage(clipId: string, startUs: TimeUs, endUs: TimeUs, threshold?: number): {
82
+ covered: boolean;
83
+ coverageRatio: number;
84
+ };
73
85
  clearAudioCache(): void;
74
86
  clearClipAudioData(clipId: string): void;
75
87
  /**
@@ -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;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAQvD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD,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,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAYD;;;;;;;;GAQG;AACH,qBAAa,YAAY;IACvB,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,OAAO,CAAC,gBAAgB,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,CAA4B;gBAEjC,MAAM,EAAE,kBAAkB,EAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC;IAWtE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B;;;OAGG;IACH,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAKrC;;OAEG;IACG,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI9D;;OAEG;IACG,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAI7F;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAIhD,gBAAgB,CACd,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,MAAM,EACtB,YAAY,CAAC,EAAE,MAAM,GACpB,IAAI;IAIP,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;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO;IAI5D;;;OAGG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,OAAO,GAAG,OAAO;IAK3F;;;;;;;OAOG;IACH,qBAAqB,CACnB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,GACjB;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE;IAI9C,eAAe,IAAI,IAAI;IAIvB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIxC;;;;;;;;OAQG;IACH,QAAQ,CACN,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,GACnB,OAAO;IA8BV;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IAIlD,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD;;OAEG;IACH,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIxC;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIpC;;;;OAIG;IACH,gBAAgB,CACd,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAO,GACjF,OAAO,CAAC,OAAO,CAAC;IA0CnB,eAAe,IAAI,IAAI;IAIjB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B,WAAW;;;IAMX;;;OAGG;IACH,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI;CAsBxE"}
@@ -1,6 +1,6 @@
1
1
  import { VideoL1Cache } from "./l1/VideoL1Cache.js";
2
2
  import { MeframeEvent } from "../event/events.js";
3
- import { AudioL1Cache } from "./l1/AudioL1Cache.js";
3
+ import { AudioL1Cache, EXPORT_PCM_COVERAGE_THRESHOLD, PREVIEW_PCM_COVERAGE_THRESHOLD } from "./l1/AudioL1Cache.js";
4
4
  import { WaiterReplacedError } from "../utils/errors.js";
5
5
  import { OPFSManager } from "./storage/opfs/OPFSManager.js";
6
6
  import { ResourceCache } from "./resource/ResourceCache.js";
@@ -72,7 +72,19 @@ class CacheManager {
72
72
  * Returns true only if at least threshold (default 80%) of requested duration is available
73
73
  */
74
74
  hasWindowPCM(clipId, startUs, endUs, strictMode) {
75
- return this.audioL1Cache.hasWindowData(clipId, startUs, endUs, strictMode);
75
+ const threshold = strictMode ? EXPORT_PCM_COVERAGE_THRESHOLD : PREVIEW_PCM_COVERAGE_THRESHOLD;
76
+ return this.audioL1Cache.getAudioRangeCoverage(clipId, startUs, endUs, threshold).covered;
77
+ }
78
+ /**
79
+ * Get audio range coverage information for incremental decoding
80
+ * @param clipId - Clip identifier
81
+ * @param startUs - Range start time in microseconds
82
+ * @param endUs - Range end time in microseconds
83
+ * @param threshold - Coverage threshold (0-1)
84
+ * @returns Coverage info { covered: boolean, coverageRatio: number }
85
+ */
86
+ getAudioRangeCoverage(clipId, startUs, endUs, threshold) {
87
+ return this.audioL1Cache.getAudioRangeCoverage(clipId, startUs, endUs, threshold);
76
88
  }
77
89
  clearAudioCache() {
78
90
  this.audioL1Cache.clear();
@@ -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 { MeframeEvent } from '../event/events';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\nimport { AudioL1Cache } from './l1/AudioL1Cache';\nimport { WaiterReplacedError } from '../utils/errors';\nimport { OPFSManager } from './storage/opfs/OPFSManager';\nimport { ResourceCache } from './resource/ResourceCache';\nimport { MP4IndexCache } from './resource/MP4IndexCache';\nimport { AudioSampleCache } from './resource/AudioSampleCache';\nimport type { MP4Index } from '../stages/demux/types';\n\ninterface CacheManagerConfig {\n l1: {\n maxMemoryMB: number;\n maxGOPs?: number;\n gopIntervalUs?: number;\n };\n resource: {\n projectId: string;\n };\n}\n\ninterface ClipReadyWaiter {\n clipId: string;\n minFrameCount: number;\n startTimeUs: TimeUs;\n currentCount: number;\n resolve: (ready: boolean) => void;\n reject: (reason?: unknown) => void;\n timeoutId?: ReturnType<typeof setTimeout>;\n}\n\n/**\n * CacheManager for Window Cache Architecture\n *\n * Core features:\n * - L1 (VRAM) for composed VideoFrames (window cache ±3s)\n * - ResourceCache (OPFS) for original MP4 files\n * - MP4IndexCache (RAM) for GOP/Sample indexes\n * - AudioSampleCache (RAM) for extracted audio chunks\n */\nexport class CacheManager {\n readonly videoL1Cache: VideoL1Cache;\n private readonly audioL1Cache: AudioL1Cache;\n readonly resourceCache: ResourceCache;\n readonly mp4IndexCache: MP4IndexCache;\n readonly audioSampleCache: AudioSampleCache;\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.audioL1Cache = new AudioL1Cache();\n\n const opfsManager = new OPFSManager();\n this.resourceCache = new ResourceCache(opfsManager, config.resource.projectId);\n this.mp4IndexCache = new MP4IndexCache();\n this.audioSampleCache = new AudioSampleCache();\n this.eventBus = eventBus;\n }\n\n async init(): Promise<void> {\n await this.resourceCache.init();\n }\n\n /**\n * Set window center for L1 cache management (unified for video and audio)\n * L1 cache uses center ±3.5s window for video, ±5s for audio\n */\n setWindow(centerTimeUs: TimeUs): void {\n this.videoL1Cache.setWindow(centerTimeUs);\n this.audioL1Cache.setWindow(centerTimeUs);\n }\n\n /**\n * Check if resource exists in OPFS cache\n */\n async hasResourceInCache(resourceId: string): Promise<boolean> {\n return await this.resourceCache.hasResource(resourceId);\n }\n\n /**\n * Read byte range from resource (for GOP-level access)\n */\n async readResourceRange(resourceId: string, start: number, end: number): Promise<ArrayBuffer> {\n return await this.resourceCache.readRange(resourceId, start, end);\n }\n\n /**\n * Get MP4 index for a resource\n */\n getMP4Index(resourceId: string): MP4Index | null {\n return this.mp4IndexCache.get(resourceId);\n }\n\n putClipAudioData(\n clipId: string,\n audioData: AudioData,\n clipDurationUs: TimeUs,\n globalTimeUs?: TimeUs\n ): void {\n this.audioL1Cache.putClipAudioData(clipId, audioData, clipDurationUs, globalTimeUs);\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 /**\n * Check if a specific audio chunk is already cached (by timestamp)\n */\n hasAudioSlotAt(clipId: string, timestampUs: TimeUs): boolean {\n return this.audioL1Cache.hasSlotAt(clipId, timestampUs);\n }\n\n /**\n * Check if sufficient PCM data exists for the requested time window\n * Returns true only if at least threshold (default 80%) of requested duration is available\n */\n hasWindowPCM(clipId: string, startUs: TimeUs, endUs: TimeUs, strictMode?: boolean): boolean {\n return this.audioL1Cache.hasWindowData(clipId, startUs, endUs, strictMode);\n }\n\n clearAudioCache(): void {\n this.audioL1Cache.clear();\n }\n\n clearClipAudioData(clipId: string): void {\n this.audioL1Cache.clearClipPCM(clipId);\n }\n\n /**\n * Add a frame to L1 cache\n * Handles event notifications (CacheCover, ComposeFrameReady)\n * @param frame - VideoFrame to add\n * @param clipId - Clip identifier\n * @param frameDuration - Frame duration in microseconds\n * @param trackId - Track identifier\n * @param globalTimeUs - Global timestamp for event emission and window management\n */\n addFrame(\n frame: VideoFrame,\n clipId: string,\n frameDuration: TimeUs,\n trackId: string,\n globalTimeUs: TimeUs\n ): RcFrame {\n const rcFrame = this.videoL1Cache.addFrame(frame, clipId, frameDuration, trackId, globalTimeUs);\n\n const relativeTimeUs = frame.timestamp ?? 0;\n\n // Check and notify clip ready\n this.checkAndNotifyClipReady(clipId, relativeTimeUs);\n\n // Emit cover event for first frame (globalTimeUs = 0 in composition)\n if (globalTimeUs === 0) {\n this.eventBus?.emit(MeframeEvent.CacheCover, {\n timeUs: globalTimeUs,\n clipId,\n level: 'L1',\n size: rcFrame.sizeEstimate ?? 0,\n });\n }\n\n // Emit frame ready event\n this.eventBus?.emit(MeframeEvent.ComposeFrameReady, {\n timeUs: globalTimeUs,\n frameNumber: Math.floor(globalTimeUs / frameDuration),\n renderTimeMs: 0,\n trackId,\n clipId,\n });\n\n return rcFrame;\n }\n\n /**\n * Get frame from L1 cache\n * @param timeUs - Clip-relative timestamp (0-based)\n * @param clipId - Clip identifier\n */\n getFrame(timeUs: TimeUs, clipId: string): RcFrame | null {\n return this.videoL1Cache.get(timeUs, clipId);\n }\n\n async invalidateClip(clipId: string): Promise<void> {\n this.videoL1Cache.invalidateClip(clipId);\n }\n\n /**\n * Evict a clip from L1 cache\n */\n invalidateClipInL1(clipId: string): void {\n this.videoL1Cache.invalidateClip(clipId);\n }\n\n /**\n * Check if a clip is cached in L1\n */\n hasClipInL1(clipId: string): boolean {\n return this.videoL1Cache.hasClip(clipId);\n }\n\n /**\n * Wait for a clip to have minimum frames cached\n * Used by PlaybackController for buffering state\n * Only one waiter per clip - new waiter replaces old one\n */\n waitForClipReady(\n clipId: string,\n options: { minFrameCount?: number; timeoutMs?: number; startTimeUs?: TimeUs } = {}\n ): Promise<boolean> {\n const minFrameCount = options.minFrameCount ?? 30;\n const startTimeUs = options.startTimeUs ?? 0;\n\n // Check if already have enough frames\n const currentFrameCount = this.videoL1Cache.getClipFrameCount(clipId, startTimeUs);\n if (currentFrameCount >= minFrameCount) {\n return Promise.resolve(true);\n }\n\n // Cancel previous waiter if exists\n const oldWaiter = this.clipReadyWaiters.get(clipId);\n if (oldWaiter) {\n if (oldWaiter.timeoutId) {\n clearTimeout(oldWaiter.timeoutId);\n }\n oldWaiter.reject(new WaiterReplacedError(clipId));\n }\n\n return new Promise<boolean>((resolve, reject) => {\n const waiter: ClipReadyWaiter = {\n clipId,\n minFrameCount,\n startTimeUs,\n currentCount: currentFrameCount,\n resolve,\n reject,\n };\n\n this.clipReadyWaiters.set(clipId, waiter);\n\n if (options.timeoutMs && options.timeoutMs > 0) {\n waiter.timeoutId = setTimeout(() => {\n if (this.clipReadyWaiters.get(clipId) === waiter) {\n this.clipReadyWaiters.delete(clipId);\n }\n resolve(false);\n }, options.timeoutMs);\n }\n });\n }\n\n clearVideoCache(): void {\n this.videoL1Cache.clear();\n }\n\n async clear(): Promise<void> {\n this.clearVideoCache();\n this.clearAudioCache();\n }\n\n getMetadata() {\n return {\n l1: this.videoL1Cache.getMetadata(),\n };\n }\n\n /**\n * Check if incoming frame satisfies clip ready condition\n * O(1) complexity - only checks single waiter and increments counter\n */\n checkAndNotifyClipReady(clipId: string, frameTimestampUs: TimeUs): void {\n const waiter = this.clipReadyWaiters.get(clipId);\n if (!waiter) {\n return;\n }\n\n // Count all frames if startTimeUs is 0 (buffering scenario)\n // Otherwise only count frames at or after the target start time\n const shouldCount = waiter.startTimeUs === 0 || frameTimestampUs >= waiter.startTimeUs;\n\n if (shouldCount) {\n waiter.currentCount++;\n\n if (waiter.currentCount >= waiter.minFrameCount) {\n if (waiter.timeoutId) {\n clearTimeout(waiter.timeoutId);\n }\n waiter.resolve(true);\n this.clipReadyWaiters.delete(clipId);\n }\n }\n }\n}\n"],"names":[],"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 { MeframeEvent } from '../event/events';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\nimport {\n AudioL1Cache,\n EXPORT_PCM_COVERAGE_THRESHOLD,\n PREVIEW_PCM_COVERAGE_THRESHOLD,\n} from './l1/AudioL1Cache';\nimport { WaiterReplacedError } from '../utils/errors';\nimport { OPFSManager } from './storage/opfs/OPFSManager';\nimport { ResourceCache } from './resource/ResourceCache';\nimport { MP4IndexCache } from './resource/MP4IndexCache';\nimport { AudioSampleCache } from './resource/AudioSampleCache';\nimport type { MP4Index } from '../stages/demux/types';\n\ninterface CacheManagerConfig {\n l1: {\n maxMemoryMB: number;\n maxGOPs?: number;\n gopIntervalUs?: number;\n };\n resource: {\n projectId: string;\n };\n}\n\ninterface ClipReadyWaiter {\n clipId: string;\n minFrameCount: number;\n startTimeUs: TimeUs;\n currentCount: number;\n resolve: (ready: boolean) => void;\n reject: (reason?: unknown) => void;\n timeoutId?: ReturnType<typeof setTimeout>;\n}\n\n/**\n * CacheManager for Window Cache Architecture\n *\n * Core features:\n * - L1 (VRAM) for composed VideoFrames (window cache ±3s)\n * - ResourceCache (OPFS) for original MP4 files\n * - MP4IndexCache (RAM) for GOP/Sample indexes\n * - AudioSampleCache (RAM) for extracted audio chunks\n */\nexport class CacheManager {\n readonly videoL1Cache: VideoL1Cache;\n private readonly audioL1Cache: AudioL1Cache;\n readonly resourceCache: ResourceCache;\n readonly mp4IndexCache: MP4IndexCache;\n readonly audioSampleCache: AudioSampleCache;\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.audioL1Cache = new AudioL1Cache();\n\n const opfsManager = new OPFSManager();\n this.resourceCache = new ResourceCache(opfsManager, config.resource.projectId);\n this.mp4IndexCache = new MP4IndexCache();\n this.audioSampleCache = new AudioSampleCache();\n this.eventBus = eventBus;\n }\n\n async init(): Promise<void> {\n await this.resourceCache.init();\n }\n\n /**\n * Set window center for L1 cache management (unified for video and audio)\n * L1 cache uses center ±3.5s window for video, ±5s for audio\n */\n setWindow(centerTimeUs: TimeUs): void {\n this.videoL1Cache.setWindow(centerTimeUs);\n this.audioL1Cache.setWindow(centerTimeUs);\n }\n\n /**\n * Check if resource exists in OPFS cache\n */\n async hasResourceInCache(resourceId: string): Promise<boolean> {\n return await this.resourceCache.hasResource(resourceId);\n }\n\n /**\n * Read byte range from resource (for GOP-level access)\n */\n async readResourceRange(resourceId: string, start: number, end: number): Promise<ArrayBuffer> {\n return await this.resourceCache.readRange(resourceId, start, end);\n }\n\n /**\n * Get MP4 index for a resource\n */\n getMP4Index(resourceId: string): MP4Index | null {\n return this.mp4IndexCache.get(resourceId);\n }\n\n putClipAudioData(\n clipId: string,\n audioData: AudioData,\n clipDurationUs: TimeUs,\n globalTimeUs?: TimeUs\n ): void {\n this.audioL1Cache.putClipAudioData(clipId, audioData, clipDurationUs, globalTimeUs);\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 /**\n * Check if a specific audio chunk is already cached (by timestamp)\n */\n hasAudioSlotAt(clipId: string, timestampUs: TimeUs): boolean {\n return this.audioL1Cache.hasSlotAt(clipId, timestampUs);\n }\n\n /**\n * Check if sufficient PCM data exists for the requested time window\n * Returns true only if at least threshold (default 80%) of requested duration is available\n */\n hasWindowPCM(clipId: string, startUs: TimeUs, endUs: TimeUs, strictMode?: boolean): boolean {\n const threshold = strictMode ? EXPORT_PCM_COVERAGE_THRESHOLD : PREVIEW_PCM_COVERAGE_THRESHOLD;\n return this.audioL1Cache.getAudioRangeCoverage(clipId, startUs, endUs, threshold).covered;\n }\n\n /**\n * Get audio range coverage information for incremental decoding\n * @param clipId - Clip identifier\n * @param startUs - Range start time in microseconds\n * @param endUs - Range end time in microseconds\n * @param threshold - Coverage threshold (0-1)\n * @returns Coverage info { covered: boolean, coverageRatio: number }\n */\n getAudioRangeCoverage(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs,\n threshold?: number\n ): { covered: boolean; coverageRatio: number } {\n return this.audioL1Cache.getAudioRangeCoverage(clipId, startUs, endUs, threshold);\n }\n\n clearAudioCache(): void {\n this.audioL1Cache.clear();\n }\n\n clearClipAudioData(clipId: string): void {\n this.audioL1Cache.clearClipPCM(clipId);\n }\n\n /**\n * Add a frame to L1 cache\n * Handles event notifications (CacheCover, ComposeFrameReady)\n * @param frame - VideoFrame to add\n * @param clipId - Clip identifier\n * @param frameDuration - Frame duration in microseconds\n * @param trackId - Track identifier\n * @param globalTimeUs - Global timestamp for event emission and window management\n */\n addFrame(\n frame: VideoFrame,\n clipId: string,\n frameDuration: TimeUs,\n trackId: string,\n globalTimeUs: TimeUs\n ): RcFrame {\n const rcFrame = this.videoL1Cache.addFrame(frame, clipId, frameDuration, trackId, globalTimeUs);\n\n const relativeTimeUs = frame.timestamp ?? 0;\n\n // Check and notify clip ready\n this.checkAndNotifyClipReady(clipId, relativeTimeUs);\n\n // Emit cover event for first frame (globalTimeUs = 0 in composition)\n if (globalTimeUs === 0) {\n this.eventBus?.emit(MeframeEvent.CacheCover, {\n timeUs: globalTimeUs,\n clipId,\n level: 'L1',\n size: rcFrame.sizeEstimate ?? 0,\n });\n }\n\n // Emit frame ready event\n this.eventBus?.emit(MeframeEvent.ComposeFrameReady, {\n timeUs: globalTimeUs,\n frameNumber: Math.floor(globalTimeUs / frameDuration),\n renderTimeMs: 0,\n trackId,\n clipId,\n });\n\n return rcFrame;\n }\n\n /**\n * Get frame from L1 cache\n * @param timeUs - Clip-relative timestamp (0-based)\n * @param clipId - Clip identifier\n */\n getFrame(timeUs: TimeUs, clipId: string): RcFrame | null {\n return this.videoL1Cache.get(timeUs, clipId);\n }\n\n async invalidateClip(clipId: string): Promise<void> {\n this.videoL1Cache.invalidateClip(clipId);\n }\n\n /**\n * Evict a clip from L1 cache\n */\n invalidateClipInL1(clipId: string): void {\n this.videoL1Cache.invalidateClip(clipId);\n }\n\n /**\n * Check if a clip is cached in L1\n */\n hasClipInL1(clipId: string): boolean {\n return this.videoL1Cache.hasClip(clipId);\n }\n\n /**\n * Wait for a clip to have minimum frames cached\n * Used by PlaybackController for buffering state\n * Only one waiter per clip - new waiter replaces old one\n */\n waitForClipReady(\n clipId: string,\n options: { minFrameCount?: number; timeoutMs?: number; startTimeUs?: TimeUs } = {}\n ): Promise<boolean> {\n const minFrameCount = options.minFrameCount ?? 30;\n const startTimeUs = options.startTimeUs ?? 0;\n\n // Check if already have enough frames\n const currentFrameCount = this.videoL1Cache.getClipFrameCount(clipId, startTimeUs);\n if (currentFrameCount >= minFrameCount) {\n return Promise.resolve(true);\n }\n\n // Cancel previous waiter if exists\n const oldWaiter = this.clipReadyWaiters.get(clipId);\n if (oldWaiter) {\n if (oldWaiter.timeoutId) {\n clearTimeout(oldWaiter.timeoutId);\n }\n oldWaiter.reject(new WaiterReplacedError(clipId));\n }\n\n return new Promise<boolean>((resolve, reject) => {\n const waiter: ClipReadyWaiter = {\n clipId,\n minFrameCount,\n startTimeUs,\n currentCount: currentFrameCount,\n resolve,\n reject,\n };\n\n this.clipReadyWaiters.set(clipId, waiter);\n\n if (options.timeoutMs && options.timeoutMs > 0) {\n waiter.timeoutId = setTimeout(() => {\n if (this.clipReadyWaiters.get(clipId) === waiter) {\n this.clipReadyWaiters.delete(clipId);\n }\n resolve(false);\n }, options.timeoutMs);\n }\n });\n }\n\n clearVideoCache(): void {\n this.videoL1Cache.clear();\n }\n\n async clear(): Promise<void> {\n this.clearVideoCache();\n this.clearAudioCache();\n }\n\n getMetadata() {\n return {\n l1: this.videoL1Cache.getMetadata(),\n };\n }\n\n /**\n * Check if incoming frame satisfies clip ready condition\n * O(1) complexity - only checks single waiter and increments counter\n */\n checkAndNotifyClipReady(clipId: string, frameTimestampUs: TimeUs): void {\n const waiter = this.clipReadyWaiters.get(clipId);\n if (!waiter) {\n return;\n }\n\n // Count all frames if startTimeUs is 0 (buffering scenario)\n // Otherwise only count frames at or after the target start time\n const shouldCount = waiter.startTimeUs === 0 || frameTimestampUs >= waiter.startTimeUs;\n\n if (shouldCount) {\n waiter.currentCount++;\n\n if (waiter.currentCount >= waiter.minFrameCount) {\n if (waiter.timeoutId) {\n clearTimeout(waiter.timeoutId);\n }\n waiter.resolve(true);\n this.clipReadyWaiters.delete(clipId);\n }\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  import { TimeUs } from '../../model/types';
2
2
 
3
+ export declare const PREVIEW_PCM_COVERAGE_THRESHOLD = 0.95;
4
+ export declare const EXPORT_PCM_COVERAGE_THRESHOLD = 0.99;
3
5
  interface AudioDataSlot {
4
6
  timestampUs: TimeUs;
5
7
  durationUs: TimeUs;
@@ -36,6 +38,19 @@ export declare class AudioL1Cache {
36
38
  * @param strictMode - If true, require 99% coverage (export). If false, accept 95% (preview)
37
39
  */
38
40
  hasWindowData(clipId: string, startUs: TimeUs, endUs: TimeUs, strictMode?: boolean): boolean;
41
+ /**
42
+ * Get audio range coverage information for incremental decoding
43
+ * @param clipId - Clip identifier
44
+ * @param startUs - Range start time in microseconds
45
+ * @param endUs - Range end time in microseconds
46
+ * @param threshold - Coverage threshold (0-1), default 0.95
47
+ * @returns Coverage info { covered: boolean, coverageRatio: number }
48
+ */
49
+ getAudioRangeCoverage(clipId: string, startUs: TimeUs, endUs: TimeUs, threshold?: number): {
50
+ covered: boolean;
51
+ coverageRatio: number;
52
+ };
53
+ private computeRangeCoverageDuration;
39
54
  clearClipPCM(clipId: string): void;
40
55
  /**
41
56
  * Update window center (unified global window)
@@ -1 +1 @@
1
- {"version":3,"file":"AudioL1Cache.d.ts","sourceRoot":"","sources":["../../../src/cache/l1/AudioL1Cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAIhD,UAAU,aAAa;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B,qBAAa,YAAY;IAEvB,OAAO,CAAC,eAAe,CAAsC;IAI7D,OAAO,CAAC,YAAY,CAAa;IAGjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAa;IAC3C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAO;IACzC,OAAO,CAAC,aAAa,CAAK;IAE1B,gBAAgB,CACd,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,eAAe,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,MAAM,GACpB,IAAI;IAiEP,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE,GAAG,IAAI;IAmBxF,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,GAAG,IAAI;IA8F7E,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;IAoBlF,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAInC;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAE,MAAa,GAAG,OAAO;IAmBnF;;;;;;OAMG;IACH,aAAa,CACX,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,OAAe,GAC1B,OAAO;IAuCV,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIlC;;;OAGG;IACH,SAAS,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAKvC,OAAO,CAAC,aAAa;IAQrB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA+BxB;;OAEG;IACH,OAAO,CAAC,eAAe;IAIvB,KAAK,IAAI,IAAI;IAIb,KAAK,IAAI,IAAI;IAMb,OAAO,IAAI,IAAI;CAGhB"}
1
+ {"version":3,"file":"AudioL1Cache.d.ts","sourceRoot":"","sources":["../../../src/cache/l1/AudioL1Cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAIhD,eAAO,MAAM,8BAA8B,OAAO,CAAC;AACnD,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAElD,UAAU,aAAa;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B,qBAAa,YAAY;IAEvB,OAAO,CAAC,eAAe,CAAsC;IAI7D,OAAO,CAAC,YAAY,CAAa;IAGjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAa;IAC3C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAO;IACzC,OAAO,CAAC,aAAa,CAAK;IAE1B,gBAAgB,CACd,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,SAAS,EACpB,eAAe,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,MAAM,GACpB,IAAI;IAiEP,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE,GAAG,IAAI;IAmBxF,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,GAAG,IAAI;IA8F7E,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;IAoBlF,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAInC;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAE,MAAa,GAAG,OAAO;IAmBnF;;;;;;OAMG;IACH,aAAa,CACX,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,OAAe,GAC1B,OAAO;IAKV;;;;;;;OAOG;IACH,qBAAqB,CACnB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,SAAS,SAAiC,GACzC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE;IAa9C,OAAO,CAAC,4BAA4B;IAsCpC,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIlC;;;OAGG;IACH,SAAS,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAKvC,OAAO,CAAC,aAAa;IAQrB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA+BxB;;OAEG;IACH,OAAO,CAAC,eAAe;IAIvB,KAAK,IAAI,IAAI;IAIb,KAAK,IAAI,IAAI;IAMb,OAAO,IAAI,IAAI;CAGhB"}
@@ -1,5 +1,7 @@
1
1
  import { binarySearchOverlapping, binarySearchFirst } from "../../utils/binary-search.js";
2
2
  import { extractPlanesFromAudioData } from "../../utils/audio-data.js";
3
+ const PREVIEW_PCM_COVERAGE_THRESHOLD = 0.95;
4
+ const EXPORT_PCM_COVERAGE_THRESHOLD = 0.99;
3
5
  class AudioL1Cache {
4
6
  // Aligned with VideoL1Cache: array of discrete audio data slots per clip
5
7
  audioDataByClip = /* @__PURE__ */ new Map();
@@ -175,29 +177,55 @@ class AudioL1Cache {
175
177
  * @param strictMode - If true, require 99% coverage (export). If false, accept 95% (preview)
176
178
  */
177
179
  hasWindowData(clipId, startUs, endUs, strictMode = false) {
180
+ const threshold = strictMode ? EXPORT_PCM_COVERAGE_THRESHOLD : PREVIEW_PCM_COVERAGE_THRESHOLD;
181
+ return this.getAudioRangeCoverage(clipId, startUs, endUs, threshold).covered;
182
+ }
183
+ /**
184
+ * Get audio range coverage information for incremental decoding
185
+ * @param clipId - Clip identifier
186
+ * @param startUs - Range start time in microseconds
187
+ * @param endUs - Range end time in microseconds
188
+ * @param threshold - Coverage threshold (0-1), default 0.95
189
+ * @returns Coverage info { covered: boolean, coverageRatio: number }
190
+ */
191
+ getAudioRangeCoverage(clipId, startUs, endUs, threshold = PREVIEW_PCM_COVERAGE_THRESHOLD) {
192
+ const { coveredDurationUs, requestedDurationUs } = this.computeRangeCoverageDuration(
193
+ clipId,
194
+ startUs,
195
+ endUs
196
+ );
197
+ const coverageRatio = requestedDurationUs > 0 ? coveredDurationUs / requestedDurationUs : 0;
198
+ return {
199
+ covered: coverageRatio >= threshold,
200
+ coverageRatio
201
+ };
202
+ }
203
+ computeRangeCoverageDuration(clipId, startUs, endUs) {
204
+ const requestedDurationUs = Math.max(0, endUs - startUs);
205
+ if (requestedDurationUs === 0) {
206
+ return { coveredDurationUs: 0, requestedDurationUs: 0 };
207
+ }
178
208
  const slots = this.audioDataByClip.get(clipId);
179
209
  if (!slots || slots.length === 0) {
180
- return false;
210
+ return { coveredDurationUs: 0, requestedDurationUs };
181
211
  }
182
212
  const overlappingSlots = binarySearchOverlapping(slots, startUs, endUs, (slot) => ({
183
213
  start: slot.timestampUs,
184
214
  end: slot.timestampUs + slot.durationUs
185
215
  }));
186
216
  if (overlappingSlots.length === 0) {
187
- return false;
217
+ return { coveredDurationUs: 0, requestedDurationUs };
188
218
  }
189
219
  let coveredDurationUs = 0;
190
- const requestedDurationUs = endUs - startUs;
191
220
  for (const slot of overlappingSlots) {
192
221
  const slotEndUs = slot.timestampUs + slot.durationUs;
193
- const overlapStart = Math.max(slot.timestampUs, startUs);
194
- const overlapEnd = Math.min(slotEndUs, endUs);
222
+ const overlapStart = Math.max(startUs, slot.timestampUs);
223
+ const overlapEnd = Math.min(endUs, slotEndUs);
195
224
  if (overlapStart < overlapEnd) {
196
225
  coveredDurationUs += overlapEnd - overlapStart;
197
226
  }
198
227
  }
199
- const threshold = strictMode ? 0.99 : 0.95;
200
- return coveredDurationUs >= requestedDurationUs * threshold;
228
+ return { coveredDurationUs, requestedDurationUs };
201
229
  }
202
230
  clearClipPCM(clipId) {
203
231
  this.audioDataByClip.delete(clipId);
@@ -262,6 +290,8 @@ class AudioL1Cache {
262
290
  }
263
291
  }
264
292
  export {
265
- AudioL1Cache
293
+ AudioL1Cache,
294
+ EXPORT_PCM_COVERAGE_THRESHOLD,
295
+ PREVIEW_PCM_COVERAGE_THRESHOLD
266
296
  };
267
297
  //# sourceMappingURL=AudioL1Cache.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AudioL1Cache.js","sources":["../../../src/cache/l1/AudioL1Cache.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport { binarySearchFirst, binarySearchOverlapping } from '../../utils/binary-search';\nimport { extractPlanesFromAudioData } from '../../utils/audio-data';\n\ninterface AudioDataSlot {\n timestampUs: TimeUs; // Resource timestamp (aligned with VideoL1Cache)\n durationUs: TimeUs;\n planes: Float32Array[]; // PCM data for this slot\n sampleRate: number;\n numberOfChannels: number;\n globalTimeUs?: TimeUs; // Global timeline time (for window management)\n}\n\nexport type { AudioDataSlot };\n\nexport class AudioL1Cache {\n // Aligned with VideoL1Cache: array of discrete audio data slots per clip\n private audioDataByClip = new Map<string, AudioDataSlot[]>();\n\n // Unified window management (aligned with VideoL1Cache)\n // All clips share the same global window center\n private windowCenter: TimeUs = 0;\n\n // Window radius aligned with video (±3.5s, but we use 5s for audio safety margin)\n private readonly WINDOW_RADIUS = 5_000_000; // ±5s\n private readonly EVICT_THROTTLE_MS = 500;\n private lastEvictTime = 0;\n\n putClipAudioData(\n clipId: string,\n audioData: AudioData,\n _clipDurationUs: TimeUs,\n globalTimeUs?: TimeUs\n ): void {\n const numberOfChannels = audioData.numberOfChannels ?? 2;\n const numberOfFrames = audioData.numberOfFrames ?? 0;\n const sampleRate = audioData.sampleRate ?? 48_000;\n const audioTimestampUs = audioData.timestamp ?? 0;\n const audioDurationUs =\n audioData.duration ?? Math.round((numberOfFrames / sampleRate) * 1_000_000);\n\n if (!numberOfChannels || !numberOfFrames) {\n audioData.close();\n return;\n }\n\n // Extract PCM data\n const planes = extractPlanesFromAudioData(audioData, numberOfChannels, numberOfFrames);\n audioData.close();\n\n // Create audio data slot (aligned with video architecture)\n const slot: AudioDataSlot = {\n timestampUs: audioTimestampUs,\n durationUs: audioDurationUs,\n planes,\n sampleRate,\n numberOfChannels,\n globalTimeUs,\n };\n // Get or create slots array for this clip\n let slots = this.audioDataByClip.get(clipId);\n if (!slots) {\n slots = [];\n this.audioDataByClip.set(clipId, slots);\n }\n\n // Insert slot in sorted order (aligned with VideoL1Cache.addFrame)\n const insertIndex = this.findInsertIndex(slots, audioTimestampUs);\n\n // Deduplication: Check for duplicate or near-duplicate timestamp\n // Tolerance of 1ms to handle floating-point precision and concurrent decode attempts\n const DUPLICATE_THRESHOLD_US = 0.5 * audioDurationUs;\n\n if (insertIndex < slots.length) {\n const existingSlot = slots[insertIndex]!;\n const timeDiff = Math.abs(existingSlot.timestampUs - audioTimestampUs);\n\n if (timeDiff < DUPLICATE_THRESHOLD_US) {\n // Near-duplicate detected, skip insertion\n return;\n }\n }\n\n // Also check previous slot to catch overlapping duplicates\n if (insertIndex > 0) {\n const prevSlot = slots[insertIndex - 1]!;\n const timeDiff = Math.abs(prevSlot.timestampUs - audioTimestampUs);\n\n if (timeDiff < DUPLICATE_THRESHOLD_US) {\n // Near-duplicate detected, skip insertion\n return;\n }\n }\n\n // Insert new slot\n slots.splice(insertIndex, 0, slot);\n }\n\n getSlotsInWindow(clipId: string, startUs: TimeUs, endUs: TimeUs): AudioDataSlot[] | null {\n const slots = this.audioDataByClip.get(clipId);\n if (!slots || slots.length === 0) {\n return null;\n }\n\n // Use binary search to find overlapping slots\n const overlappingSlots = binarySearchOverlapping(slots, startUs, endUs, (slot) => ({\n start: slot.timestampUs,\n end: slot.timestampUs + slot.durationUs,\n }));\n\n if (overlappingSlots.length === 0) {\n return null;\n }\n\n return overlappingSlots;\n }\n\n getPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null {\n const slots = this.audioDataByClip.get(clipId);\n if (!slots || slots.length === 0) {\n return null;\n }\n\n // Use binary search to find overlapping slots (O(log n + k) vs O(n))\n // Aligned with video GOP/frame search\n const overlappingSlots = binarySearchOverlapping(slots, startUs, endUs, (slot) => ({\n start: slot.timestampUs,\n end: slot.timestampUs + slot.durationUs,\n }));\n\n if (overlappingSlots.length === 0) {\n return null;\n }\n\n const requestedDurationUs = endUs - startUs;\n\n // Validate sample rate consistency across all slots\n const firstSlot = overlappingSlots[0]!;\n const uniformSampleRate = firstSlot.sampleRate;\n const uniformChannels = firstSlot.numberOfChannels;\n\n // Check if all slots have the same sample rate and channel count\n const hasUniformRate = overlappingSlots.every(\n (s) => s.sampleRate === uniformSampleRate && s.numberOfChannels === uniformChannels\n );\n\n if (!hasUniformRate) {\n console.error(\n `[AudioL1Cache] Inconsistent sample rates detected for clip ${clipId}:`,\n overlappingSlots.map((s) => ({\n timestamp: s.timestampUs,\n sampleRate: s.sampleRate,\n channels: s.numberOfChannels,\n }))\n );\n // Return null to avoid corrupted audio data\n // This will trigger re-decode with correct sample rate\n return null;\n }\n\n // Calculate total frame count needed\n const totalFrames = Math.ceil((requestedDurationUs / 1_000_000) * uniformSampleRate);\n\n // Initialize result arrays\n const result: Float32Array[] = Array.from(\n { length: uniformChannels },\n () => new Float32Array(totalFrames)\n );\n\n // Copy data from each overlapping slot\n // Note: Slots are guaranteed non-overlapping by putClipAudioData deduplication\n for (const slot of overlappingSlots) {\n const slotStartUs = slot.timestampUs;\n const slotEndUs = slotStartUs + slot.durationUs;\n\n // Calculate intersection with requested range\n const copyStartUs = Math.max(slotStartUs, startUs);\n const copyEndUs = Math.min(slotEndUs, endUs);\n\n if (copyStartUs >= copyEndUs) continue;\n\n // Convert time to frame indices (all slots have same sample rate after validation)\n const srcOffsetFrames = Math.floor(\n ((copyStartUs - slotStartUs) / 1_000_000) * uniformSampleRate\n );\n const dstOffsetFrames = Math.floor(((copyStartUs - startUs) / 1_000_000) * uniformSampleRate);\n const copyFrameCount = Math.ceil(((copyEndUs - copyStartUs) / 1_000_000) * uniformSampleRate);\n\n // Copy each channel\n for (let ch = 0; ch < uniformChannels; ch++) {\n const srcPlane = slot.planes[ch];\n const dstPlane = result[ch];\n if (!srcPlane || !dstPlane) continue;\n\n // Boundary check\n const actualCopyFrames = Math.min(\n copyFrameCount,\n srcPlane.length - srcOffsetFrames,\n dstPlane.length - dstOffsetFrames\n );\n\n if (actualCopyFrames > 0) {\n const srcSlice = srcPlane.subarray(srcOffsetFrames, srcOffsetFrames + actualCopyFrames);\n dstPlane.set(srcSlice, dstOffsetFrames);\n }\n }\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 slots = this.audioDataByClip.get(clipId);\n if (!slots || slots.length === 0) {\n return null;\n }\n\n const planes = this.getPCM(clipId, startUs, endUs);\n if (!planes) {\n return null;\n }\n\n // Use first slot's metadata\n const firstSlot = slots[0]!;\n return {\n planes,\n sampleRate: firstSlot.sampleRate,\n numberOfChannels: firstSlot.numberOfChannels,\n };\n }\n\n hasClipPCM(clipId: string): boolean {\n return this.audioDataByClip.has(clipId);\n }\n\n /**\n * Check if a slot with specific timestamp exists (used for incremental decoding)\n */\n hasSlotAt(clipId: string, timestampUs: TimeUs, toleranceUs: number = 1000): boolean {\n const slots = this.audioDataByClip.get(clipId);\n if (!slots) return false;\n\n // Since slots are sorted, we can use binary search\n const index = this.findInsertIndex(slots, timestampUs);\n\n // Check index and index-1 (in case findInsertIndex lands after)\n if (index < slots.length) {\n const slot = slots[index];\n if (slot && Math.abs(slot.timestampUs - timestampUs) < toleranceUs) return true;\n }\n if (index > 0) {\n const slot = slots[index - 1];\n if (slot && Math.abs(slot.timestampUs - timestampUs) < toleranceUs) return true;\n }\n return false;\n }\n\n /**\n * Check if sufficient PCM data exists for the requested time window\n * @param clipId - Clip identifier\n * @param startUs - Window start time\n * @param endUs - Window end time\n * @param strictMode - If true, require 99% coverage (export). If false, accept 95% (preview)\n */\n hasWindowData(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs,\n strictMode: boolean = false\n ): boolean {\n const slots = this.audioDataByClip.get(clipId);\n if (!slots || slots.length === 0) {\n return false;\n }\n\n // Use binary search to find overlapping slots (performance optimization)\n const overlappingSlots = binarySearchOverlapping(slots, startUs, endUs, (slot) => ({\n start: slot.timestampUs,\n end: slot.timestampUs + slot.durationUs,\n }));\n\n if (overlappingSlots.length === 0) {\n return false;\n }\n\n // Calculate total duration covered\n let coveredDurationUs = 0;\n const requestedDurationUs = endUs - startUs;\n\n for (const slot of overlappingSlots) {\n const slotEndUs = slot.timestampUs + slot.durationUs;\n\n // Calculate overlap with requested range\n const overlapStart = Math.max(slot.timestampUs, startUs);\n const overlapEnd = Math.min(slotEndUs, endUs);\n\n if (overlapStart < overlapEnd) {\n coveredDurationUs += overlapEnd - overlapStart;\n }\n }\n\n // Adaptive threshold based on mode:\n // - Export (strictMode=true): 99% - ensures complete audio, critical for final output\n // - Preview (strictMode=false): 95% - balances performance and quality, tolerates chunk misalignment\n const threshold = strictMode ? 0.99 : 0.95;\n return coveredDurationUs >= requestedDurationUs * threshold;\n }\n\n clearClipPCM(clipId: string): void {\n this.audioDataByClip.delete(clipId);\n }\n\n /**\n * Update window center (unified global window)\n * Aligned with VideoL1Cache strategy: maintains a window of ±RADIUS around center\n */\n setWindow(centerGlobalUs: TimeUs): void {\n this.windowCenter = centerGlobalUs;\n this.checkEviction();\n }\n\n private checkEviction(): void {\n const now = Date.now();\n if (now - this.lastEvictTime > this.EVICT_THROTTLE_MS) {\n this.evictOutOfWindow();\n this.lastEvictTime = now;\n }\n }\n\n /**\n * Evict audio slots outside the global window (aligned with VideoL1Cache)\n * Skip if eviction is disabled (e.g., during export)\n */\n private evictOutOfWindow(): void {\n const windowStart = Math.max(0, this.windowCenter - this.WINDOW_RADIUS);\n const windowEnd = this.windowCenter + this.WINDOW_RADIUS;\n\n for (const [clipId, slots] of this.audioDataByClip) {\n const toKeep: AudioDataSlot[] = [];\n\n for (const slot of slots) {\n const globalTime = slot.globalTimeUs;\n\n // Slots without globalTimeUs are kept (legacy)\n if (globalTime === undefined) {\n toKeep.push(slot);\n continue;\n }\n\n // Keep slots within window\n if (globalTime >= windowStart && globalTime <= windowEnd) {\n toKeep.push(slot);\n }\n // Slots outside window are discarded (no close needed for Float32Array)\n }\n\n if (toKeep.length > 0) {\n this.audioDataByClip.set(clipId, toKeep);\n } else {\n this.audioDataByClip.delete(clipId);\n }\n }\n }\n\n /**\n * Find insertion index for a new slot (aligned with VideoL1Cache)\n */\n private findInsertIndex(slots: AudioDataSlot[], timestamp: TimeUs): number {\n return binarySearchFirst(slots, (slot) => slot.timestampUs >= timestamp);\n }\n\n flush(): void {\n this.audioDataByClip.clear();\n }\n\n clear(): void {\n this.flush();\n this.audioDataByClip.clear();\n this.windowCenter = 0;\n }\n\n dispose(): void {\n this.clear();\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AudioL1Cache.js","sources":["../../../src/cache/l1/AudioL1Cache.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport { binarySearchFirst, binarySearchOverlapping } from '../../utils/binary-search';\nimport { extractPlanesFromAudioData } from '../../utils/audio-data';\n\nexport const PREVIEW_PCM_COVERAGE_THRESHOLD = 0.95;\nexport const EXPORT_PCM_COVERAGE_THRESHOLD = 0.99;\n\ninterface AudioDataSlot {\n timestampUs: TimeUs; // Resource timestamp (aligned with VideoL1Cache)\n durationUs: TimeUs;\n planes: Float32Array[]; // PCM data for this slot\n sampleRate: number;\n numberOfChannels: number;\n globalTimeUs?: TimeUs; // Global timeline time (for window management)\n}\n\nexport type { AudioDataSlot };\n\nexport class AudioL1Cache {\n // Aligned with VideoL1Cache: array of discrete audio data slots per clip\n private audioDataByClip = new Map<string, AudioDataSlot[]>();\n\n // Unified window management (aligned with VideoL1Cache)\n // All clips share the same global window center\n private windowCenter: TimeUs = 0;\n\n // Window radius aligned with video (±3.5s, but we use 5s for audio safety margin)\n private readonly WINDOW_RADIUS = 5_000_000; // ±5s\n private readonly EVICT_THROTTLE_MS = 500;\n private lastEvictTime = 0;\n\n putClipAudioData(\n clipId: string,\n audioData: AudioData,\n _clipDurationUs: TimeUs,\n globalTimeUs?: TimeUs\n ): void {\n const numberOfChannels = audioData.numberOfChannels ?? 2;\n const numberOfFrames = audioData.numberOfFrames ?? 0;\n const sampleRate = audioData.sampleRate ?? 48_000;\n const audioTimestampUs = audioData.timestamp ?? 0;\n const audioDurationUs =\n audioData.duration ?? Math.round((numberOfFrames / sampleRate) * 1_000_000);\n\n if (!numberOfChannels || !numberOfFrames) {\n audioData.close();\n return;\n }\n\n // Extract PCM data\n const planes = extractPlanesFromAudioData(audioData, numberOfChannels, numberOfFrames);\n audioData.close();\n\n // Create audio data slot (aligned with video architecture)\n const slot: AudioDataSlot = {\n timestampUs: audioTimestampUs,\n durationUs: audioDurationUs,\n planes,\n sampleRate,\n numberOfChannels,\n globalTimeUs,\n };\n // Get or create slots array for this clip\n let slots = this.audioDataByClip.get(clipId);\n if (!slots) {\n slots = [];\n this.audioDataByClip.set(clipId, slots);\n }\n\n // Insert slot in sorted order (aligned with VideoL1Cache.addFrame)\n const insertIndex = this.findInsertIndex(slots, audioTimestampUs);\n\n // Deduplication: Check for duplicate or near-duplicate timestamp\n // Tolerance of 1ms to handle floating-point precision and concurrent decode attempts\n const DUPLICATE_THRESHOLD_US = 0.5 * audioDurationUs;\n\n if (insertIndex < slots.length) {\n const existingSlot = slots[insertIndex]!;\n const timeDiff = Math.abs(existingSlot.timestampUs - audioTimestampUs);\n\n if (timeDiff < DUPLICATE_THRESHOLD_US) {\n // Near-duplicate detected, skip insertion\n return;\n }\n }\n\n // Also check previous slot to catch overlapping duplicates\n if (insertIndex > 0) {\n const prevSlot = slots[insertIndex - 1]!;\n const timeDiff = Math.abs(prevSlot.timestampUs - audioTimestampUs);\n\n if (timeDiff < DUPLICATE_THRESHOLD_US) {\n // Near-duplicate detected, skip insertion\n return;\n }\n }\n\n // Insert new slot\n slots.splice(insertIndex, 0, slot);\n }\n\n getSlotsInWindow(clipId: string, startUs: TimeUs, endUs: TimeUs): AudioDataSlot[] | null {\n const slots = this.audioDataByClip.get(clipId);\n if (!slots || slots.length === 0) {\n return null;\n }\n\n // Use binary search to find overlapping slots\n const overlappingSlots = binarySearchOverlapping(slots, startUs, endUs, (slot) => ({\n start: slot.timestampUs,\n end: slot.timestampUs + slot.durationUs,\n }));\n\n if (overlappingSlots.length === 0) {\n return null;\n }\n\n return overlappingSlots;\n }\n\n getPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null {\n const slots = this.audioDataByClip.get(clipId);\n if (!slots || slots.length === 0) {\n return null;\n }\n\n // Use binary search to find overlapping slots (O(log n + k) vs O(n))\n // Aligned with video GOP/frame search\n const overlappingSlots = binarySearchOverlapping(slots, startUs, endUs, (slot) => ({\n start: slot.timestampUs,\n end: slot.timestampUs + slot.durationUs,\n }));\n\n if (overlappingSlots.length === 0) {\n return null;\n }\n\n const requestedDurationUs = endUs - startUs;\n\n // Validate sample rate consistency across all slots\n const firstSlot = overlappingSlots[0]!;\n const uniformSampleRate = firstSlot.sampleRate;\n const uniformChannels = firstSlot.numberOfChannels;\n\n // Check if all slots have the same sample rate and channel count\n const hasUniformRate = overlappingSlots.every(\n (s) => s.sampleRate === uniformSampleRate && s.numberOfChannels === uniformChannels\n );\n\n if (!hasUniformRate) {\n console.error(\n `[AudioL1Cache] Inconsistent sample rates detected for clip ${clipId}:`,\n overlappingSlots.map((s) => ({\n timestamp: s.timestampUs,\n sampleRate: s.sampleRate,\n channels: s.numberOfChannels,\n }))\n );\n // Return null to avoid corrupted audio data\n // This will trigger re-decode with correct sample rate\n return null;\n }\n\n // Calculate total frame count needed\n const totalFrames = Math.ceil((requestedDurationUs / 1_000_000) * uniformSampleRate);\n\n // Initialize result arrays\n const result: Float32Array[] = Array.from(\n { length: uniformChannels },\n () => new Float32Array(totalFrames)\n );\n\n // Copy data from each overlapping slot\n // Note: Slots are guaranteed non-overlapping by putClipAudioData deduplication\n for (const slot of overlappingSlots) {\n const slotStartUs = slot.timestampUs;\n const slotEndUs = slotStartUs + slot.durationUs;\n\n // Calculate intersection with requested range\n const copyStartUs = Math.max(slotStartUs, startUs);\n const copyEndUs = Math.min(slotEndUs, endUs);\n\n if (copyStartUs >= copyEndUs) continue;\n\n // Convert time to frame indices (all slots have same sample rate after validation)\n const srcOffsetFrames = Math.floor(\n ((copyStartUs - slotStartUs) / 1_000_000) * uniformSampleRate\n );\n const dstOffsetFrames = Math.floor(((copyStartUs - startUs) / 1_000_000) * uniformSampleRate);\n const copyFrameCount = Math.ceil(((copyEndUs - copyStartUs) / 1_000_000) * uniformSampleRate);\n\n // Copy each channel\n for (let ch = 0; ch < uniformChannels; ch++) {\n const srcPlane = slot.planes[ch];\n const dstPlane = result[ch];\n if (!srcPlane || !dstPlane) continue;\n\n // Boundary check\n const actualCopyFrames = Math.min(\n copyFrameCount,\n srcPlane.length - srcOffsetFrames,\n dstPlane.length - dstOffsetFrames\n );\n\n if (actualCopyFrames > 0) {\n const srcSlice = srcPlane.subarray(srcOffsetFrames, srcOffsetFrames + actualCopyFrames);\n dstPlane.set(srcSlice, dstOffsetFrames);\n }\n }\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 slots = this.audioDataByClip.get(clipId);\n if (!slots || slots.length === 0) {\n return null;\n }\n\n const planes = this.getPCM(clipId, startUs, endUs);\n if (!planes) {\n return null;\n }\n\n // Use first slot's metadata\n const firstSlot = slots[0]!;\n return {\n planes,\n sampleRate: firstSlot.sampleRate,\n numberOfChannels: firstSlot.numberOfChannels,\n };\n }\n\n hasClipPCM(clipId: string): boolean {\n return this.audioDataByClip.has(clipId);\n }\n\n /**\n * Check if a slot with specific timestamp exists (used for incremental decoding)\n */\n hasSlotAt(clipId: string, timestampUs: TimeUs, toleranceUs: number = 1000): boolean {\n const slots = this.audioDataByClip.get(clipId);\n if (!slots) return false;\n\n // Since slots are sorted, we can use binary search\n const index = this.findInsertIndex(slots, timestampUs);\n\n // Check index and index-1 (in case findInsertIndex lands after)\n if (index < slots.length) {\n const slot = slots[index];\n if (slot && Math.abs(slot.timestampUs - timestampUs) < toleranceUs) return true;\n }\n if (index > 0) {\n const slot = slots[index - 1];\n if (slot && Math.abs(slot.timestampUs - timestampUs) < toleranceUs) return true;\n }\n return false;\n }\n\n /**\n * Check if sufficient PCM data exists for the requested time window\n * @param clipId - Clip identifier\n * @param startUs - Window start time\n * @param endUs - Window end time\n * @param strictMode - If true, require 99% coverage (export). If false, accept 95% (preview)\n */\n hasWindowData(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs,\n strictMode: boolean = false\n ): boolean {\n const threshold = strictMode ? EXPORT_PCM_COVERAGE_THRESHOLD : PREVIEW_PCM_COVERAGE_THRESHOLD;\n return this.getAudioRangeCoverage(clipId, startUs, endUs, threshold).covered;\n }\n\n /**\n * Get audio range coverage information for incremental decoding\n * @param clipId - Clip identifier\n * @param startUs - Range start time in microseconds\n * @param endUs - Range end time in microseconds\n * @param threshold - Coverage threshold (0-1), default 0.95\n * @returns Coverage info { covered: boolean, coverageRatio: number }\n */\n getAudioRangeCoverage(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs,\n threshold = PREVIEW_PCM_COVERAGE_THRESHOLD\n ): { covered: boolean; coverageRatio: number } {\n const { coveredDurationUs, requestedDurationUs } = this.computeRangeCoverageDuration(\n clipId,\n startUs,\n endUs\n );\n const coverageRatio = requestedDurationUs > 0 ? coveredDurationUs / requestedDurationUs : 0;\n return {\n covered: coverageRatio >= threshold,\n coverageRatio,\n };\n }\n\n private computeRangeCoverageDuration(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs\n ): { coveredDurationUs: TimeUs; requestedDurationUs: TimeUs } {\n const requestedDurationUs = Math.max(0, endUs - startUs);\n if (requestedDurationUs === 0) {\n return { coveredDurationUs: 0, requestedDurationUs: 0 };\n }\n\n const slots = this.audioDataByClip.get(clipId);\n if (!slots || slots.length === 0) {\n return { coveredDurationUs: 0, requestedDurationUs };\n }\n\n // Use binary search to find overlapping slots (O(log n + k))\n const overlappingSlots = binarySearchOverlapping(slots, startUs, endUs, (slot) => ({\n start: slot.timestampUs,\n end: slot.timestampUs + slot.durationUs,\n }));\n\n if (overlappingSlots.length === 0) {\n return { coveredDurationUs: 0, requestedDurationUs };\n }\n\n let coveredDurationUs = 0;\n for (const slot of overlappingSlots) {\n const slotEndUs = slot.timestampUs + slot.durationUs;\n const overlapStart = Math.max(startUs, slot.timestampUs);\n const overlapEnd = Math.min(endUs, slotEndUs);\n if (overlapStart < overlapEnd) {\n coveredDurationUs += overlapEnd - overlapStart;\n }\n }\n\n return { coveredDurationUs, requestedDurationUs };\n }\n\n clearClipPCM(clipId: string): void {\n this.audioDataByClip.delete(clipId);\n }\n\n /**\n * Update window center (unified global window)\n * Aligned with VideoL1Cache strategy: maintains a window of ±RADIUS around center\n */\n setWindow(centerGlobalUs: TimeUs): void {\n this.windowCenter = centerGlobalUs;\n this.checkEviction();\n }\n\n private checkEviction(): void {\n const now = Date.now();\n if (now - this.lastEvictTime > this.EVICT_THROTTLE_MS) {\n this.evictOutOfWindow();\n this.lastEvictTime = now;\n }\n }\n\n /**\n * Evict audio slots outside the global window (aligned with VideoL1Cache)\n * Skip if eviction is disabled (e.g., during export)\n */\n private evictOutOfWindow(): void {\n const windowStart = Math.max(0, this.windowCenter - this.WINDOW_RADIUS);\n const windowEnd = this.windowCenter + this.WINDOW_RADIUS;\n\n for (const [clipId, slots] of this.audioDataByClip) {\n const toKeep: AudioDataSlot[] = [];\n\n for (const slot of slots) {\n const globalTime = slot.globalTimeUs;\n\n // Slots without globalTimeUs are kept (legacy)\n if (globalTime === undefined) {\n toKeep.push(slot);\n continue;\n }\n\n // Keep slots within window\n if (globalTime >= windowStart && globalTime <= windowEnd) {\n toKeep.push(slot);\n }\n // Slots outside window are discarded (no close needed for Float32Array)\n }\n\n if (toKeep.length > 0) {\n this.audioDataByClip.set(clipId, toKeep);\n } else {\n this.audioDataByClip.delete(clipId);\n }\n }\n }\n\n /**\n * Find insertion index for a new slot (aligned with VideoL1Cache)\n */\n private findInsertIndex(slots: AudioDataSlot[], timestamp: TimeUs): number {\n return binarySearchFirst(slots, (slot) => slot.timestampUs >= timestamp);\n }\n\n flush(): void {\n this.audioDataByClip.clear();\n }\n\n clear(): void {\n this.flush();\n this.audioDataByClip.clear();\n this.windowCenter = 0;\n }\n\n dispose(): void {\n this.clear();\n }\n}\n"],"names":[],"mappings":"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"}
@@ -11,7 +11,6 @@ export declare class PlaybackController implements IPlaybackController, PreviewH
11
11
  private canvas;
12
12
  private videoComposer;
13
13
  currentTimeUs: TimeUs;
14
- private state;
15
14
  private playbackRate;
16
15
  private volume;
17
16
  private loop;
@@ -22,18 +21,17 @@ export declare class PlaybackController implements IPlaybackController, PreviewH
22
21
  private fps;
23
22
  private audioContext;
24
23
  private audioSession;
25
- private currentSeekId;
26
- private wasPlayingBeforeSeek;
24
+ private lastAudioScheduleTime;
25
+ private readonly AUDIO_SCHEDULE_INTERVAL;
26
+ private fsm;
27
27
  private windowEnd;
28
28
  private readonly WINDOW_DURATION;
29
+ private readonly AUDIO_READY_PROBE_WINDOW;
29
30
  private readonly PREHEAT_DISTANCE;
30
31
  private preheatInProgress;
31
- private lastAudioScheduleTime;
32
- private readonly AUDIO_SCHEDULE_INTERVAL;
33
32
  constructor(orchestrator: Orchestrator, eventBus: IEventBus, options: PlaybackOptions);
34
33
  renderCover(): Promise<void>;
35
34
  play(): void;
36
- private startPlayback;
37
35
  pause(): void;
38
36
  stop(): void;
39
37
  seek(timeUs: TimeUs): Promise<void>;
@@ -46,34 +44,26 @@ export declare class PlaybackController implements IPlaybackController, PreviewH
46
44
  resume(): void;
47
45
  on(event: string, handler: (payload: any) => void): void;
48
46
  off(event: string, handler: (payload: any) => void): void;
49
- private playbackLoop;
50
- private updateTime;
51
- /**
52
- * Initialize window at given time (called on play/seek)
53
- * Sets unified window for both video and audio
54
- */
47
+ private dispatch;
48
+ private executeCommands;
49
+ private executeSeq;
50
+ private executePar;
51
+ private executeCommand;
52
+ private cancelRaf;
53
+ private isCurrentToken;
54
+ private startPlaybackLoop;
55
+ private onRafTick;
56
+ private updateFps;
55
57
  private initWindow;
56
- /**
57
- * Check if approaching window end and trigger preheat for next window
58
- *
59
- * Strategy: Unified sliding window for both video and audio
60
- * - Current window: [windowStart, windowEnd] (3s duration)
61
- * - When playback reaches windowEnd - 1s, preheat next window
62
- * - Next window: [windowEnd, windowEnd + 3s]
63
- */
64
58
  private checkAndPreheatWindow;
65
- /**
66
- * Preheat next window by decoding from current playback time
67
- * Supports cross-clip window preheating for seamless playback
68
- * Preheats both video and audio in parallel
69
- */
70
59
  preheatNextWindow(): Promise<void>;
71
- renderCurrentFrame(timeUs: TimeUs): Promise<void>;
72
- private handlePlaybackBuffering;
73
- private clampTime;
60
+ private renderCurrentFrame;
61
+ private compose;
62
+ private clearCanvas;
74
63
  dispose(): void;
75
64
  private onCacheCover;
76
65
  private onModelSet;
77
66
  private setupEventListeners;
67
+ private isVideoResourceReadyAtTime;
78
68
  }
79
69
  //# 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,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAKpD;;;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,aAAa,CAA8B;IAGnD,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,WAAW,CAAa;IAGhC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,YAAY,CAAqB;IAGzC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,oBAAoB,CAAS;IAGrC,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAa;IAC7C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAa;IAC9C,OAAO,CAAC,iBAAiB,CAAS;IAGlC,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAW;gBAEvC,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe;IA4C/E,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAKlC,IAAI,IAAI,IAAI;YAYE,aAAa;IAgC3B,KAAK,IAAI,IAAI;IAiBb,IAAI,IAAI,IAAI;IAwBN,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+FzC,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;IAGD,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;IAKzD,OAAO,CAAC,YAAY;IAqEpB,OAAO,CAAC,UAAU;IAkClB;;;OAGG;IACH,OAAO,CAAC,UAAU;IAQlB;;;;;;;OAOG;IACH,OAAO,CAAC,qBAAqB;IAmB7B;;;;OAIG;IACG,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IA0ClC,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAiCzC,uBAAuB;IA0DrC,OAAO,CAAC,SAAS;IAKjB,OAAO,IAAI,IAAI;IAUf,OAAO,CAAC,YAAY,CASlB;IAEF,OAAO,CAAC,UAAU,CAiBhB;IAEF,OAAO,CAAC,mBAAmB;CAI5B"}
1
+ {"version":3,"file":"PlaybackController.d.ts","sourceRoot":"","sources":["../../src/controllers/PlaybackController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EACf,SAAS,EACT,aAAa,EACb,MAAM,EACP,MAAM,SAAS,CAAC;AAWjB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAQpD;;;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,aAAa,CAA8B;IAGnD,aAAa,EAAE,MAAM,CAAK;IAC1B,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,MAAM,CAAO;IACrB,OAAO,CAAC,IAAI,CAAS;IAGrB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,WAAW,CAAa;IAGhC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,GAAG,CAAK;IAGhB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAW;IAGnD,OAAO,CAAC,GAAG,CAA8B;IAGzC,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAa;IAC7C,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAW;IACpD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAa;IAC9C,OAAO,CAAC,iBAAiB,CAAS;gBAEtB,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe;IAoC/E,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAMlC,IAAI,IAAI,IAAI;IAIZ,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI;IAIN,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASzC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAW3B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAM/B,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAU7B,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAI5B,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,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;IAMzD,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,UAAU;IAkBlB,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,cAAc;IAoMtB,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,iBAAiB;YAMX,SAAS;IAiEvB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,qBAAqB;IAgBvB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;YAoC1B,kBAAkB;YAwBlB,OAAO;IAYrB,OAAO,CAAC,WAAW;IAYnB,OAAO,IAAI,IAAI;IAUf,OAAO,CAAC,YAAY,CAIlB;IAEF,OAAO,CAAC,UAAU,CAchB;IAEF,OAAO,CAAC,mBAAmB;IAK3B,OAAO,CAAC,0BAA0B;CAUnC"}