@meframe/core 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/AudioMixBlockCache.d.ts +18 -0
- package/dist/cache/AudioMixBlockCache.d.ts.map +1 -0
- package/dist/cache/AudioMixBlockCache.js +57 -0
- package/dist/cache/AudioMixBlockCache.js.map +1 -0
- package/dist/cache/CacheManager.d.ts +1 -1
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +1 -2
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/l1/AudioL1Cache.d.ts +0 -15
- package/dist/cache/l1/AudioL1Cache.d.ts.map +1 -1
- package/dist/cache/l1/AudioL1Cache.js +57 -65
- package/dist/cache/l1/AudioL1Cache.js.map +1 -1
- package/dist/controllers/PlaybackController.d.ts +0 -1
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +16 -35
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/controllers/PlaybackStateMachine.d.ts.map +1 -1
- package/dist/controllers/PlaybackStateMachine.js +22 -2
- package/dist/controllers/PlaybackStateMachine.js.map +1 -1
- package/dist/controllers/types.d.ts +3 -0
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/controllers/types.js +1 -0
- package/dist/controllers/types.js.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.d.ts +27 -7
- package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.js +292 -103
- package/dist/orchestrator/GlobalAudioSession.js.map +1 -1
- package/dist/orchestrator/OnDemandVideoSession.d.ts.map +1 -1
- package/dist/orchestrator/OnDemandVideoSession.js +13 -0
- package/dist/orchestrator/OnDemandVideoSession.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts +1 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +44 -17
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/stages/compose/OfflineAudioMixer.d.ts.map +1 -1
- package/dist/stages/compose/OfflineAudioMixer.js +4 -1
- package/dist/stages/compose/OfflineAudioMixer.js.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts +6 -5
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +53 -39
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/stages/load/TaskManager.d.ts +5 -12
- package/dist/stages/load/TaskManager.d.ts.map +1 -1
- package/dist/stages/load/TaskManager.js +60 -46
- package/dist/stages/load/TaskManager.js.map +1 -1
- package/dist/stages/mux/MP4Muxer.d.ts.map +1 -1
- package/dist/stages/mux/MP4Muxer.js +3 -0
- package/dist/stages/mux/MP4Muxer.js.map +1 -1
- package/dist/utils/platform-utils.d.ts +1 -0
- package/dist/utils/platform-utils.d.ts.map +1 -1
- package/dist/utils/platform-utils.js +19 -6
- package/dist/utils/platform-utils.js.map +1 -1
- package/dist/workers/stages/decode/video-decode.worker.BnWVUkng.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare class AudioMixBlockCache {
|
|
2
|
+
private cache;
|
|
3
|
+
private inFlight;
|
|
4
|
+
private maxEntries;
|
|
5
|
+
constructor(maxEntries: number);
|
|
6
|
+
clear(): void;
|
|
7
|
+
/**
|
|
8
|
+
* Get a cached block by index.
|
|
9
|
+
* Uses Map insertion order as LRU order: on hit, delete+set refreshes recency.
|
|
10
|
+
*/
|
|
11
|
+
get(blockIndex: number): AudioBuffer | null;
|
|
12
|
+
/**
|
|
13
|
+
* Get-or-create a block with in-flight de-duplication.
|
|
14
|
+
*/
|
|
15
|
+
getOrCreate(blockIndex: number, producer: () => Promise<AudioBuffer | null>): Promise<AudioBuffer | null>;
|
|
16
|
+
private evictIfNeeded;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=AudioMixBlockCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioMixBlockCache.d.ts","sourceRoot":"","sources":["../../src/cache/AudioMixBlockCache.ts"],"names":[],"mappings":"AAAA,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,QAAQ,CAA2C;IAC3D,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,EAAE,MAAM;IAM9B,KAAK,IAAI,IAAI;IAKb;;;OAGG;IACH,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAQ3C;;OAEG;IACG,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,GAC1C,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAsB9B,OAAO,CAAC,aAAa;CAUtB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
class AudioMixBlockCache {
|
|
2
|
+
cache;
|
|
3
|
+
inFlight;
|
|
4
|
+
maxEntries;
|
|
5
|
+
constructor(maxEntries) {
|
|
6
|
+
this.maxEntries = Math.max(1, Math.floor(maxEntries));
|
|
7
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
8
|
+
this.inFlight = /* @__PURE__ */ new Map();
|
|
9
|
+
}
|
|
10
|
+
clear() {
|
|
11
|
+
this.cache.clear();
|
|
12
|
+
this.inFlight.clear();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get a cached block by index.
|
|
16
|
+
* Uses Map insertion order as LRU order: on hit, delete+set refreshes recency.
|
|
17
|
+
*/
|
|
18
|
+
get(blockIndex) {
|
|
19
|
+
const hit = this.cache.get(blockIndex);
|
|
20
|
+
if (!hit) return null;
|
|
21
|
+
this.cache.delete(blockIndex);
|
|
22
|
+
this.cache.set(blockIndex, hit);
|
|
23
|
+
return hit;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get-or-create a block with in-flight de-duplication.
|
|
27
|
+
*/
|
|
28
|
+
async getOrCreate(blockIndex, producer) {
|
|
29
|
+
const cached = this.get(blockIndex);
|
|
30
|
+
if (cached) return cached;
|
|
31
|
+
const existing = this.inFlight.get(blockIndex);
|
|
32
|
+
if (existing) return await existing;
|
|
33
|
+
const promise = producer().then((buffer) => {
|
|
34
|
+
if (!buffer) return null;
|
|
35
|
+
this.cache.set(blockIndex, buffer);
|
|
36
|
+
this.evictIfNeeded();
|
|
37
|
+
return buffer;
|
|
38
|
+
});
|
|
39
|
+
this.inFlight.set(blockIndex, promise);
|
|
40
|
+
try {
|
|
41
|
+
return await promise;
|
|
42
|
+
} finally {
|
|
43
|
+
this.inFlight.delete(blockIndex);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
evictIfNeeded() {
|
|
47
|
+
while (this.cache.size > this.maxEntries) {
|
|
48
|
+
const oldestKey = this.cache.keys().next().value;
|
|
49
|
+
if (oldestKey === void 0) break;
|
|
50
|
+
this.cache.delete(oldestKey);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
AudioMixBlockCache
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=AudioMixBlockCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioMixBlockCache.js","sources":["../../src/cache/AudioMixBlockCache.ts"],"sourcesContent":["export class AudioMixBlockCache {\n private cache: Map<number, AudioBuffer>;\n private inFlight: Map<number, Promise<AudioBuffer | null>>;\n private maxEntries: number;\n\n constructor(maxEntries: number) {\n this.maxEntries = Math.max(1, Math.floor(maxEntries));\n this.cache = new Map();\n this.inFlight = new Map();\n }\n\n clear(): void {\n this.cache.clear();\n this.inFlight.clear();\n }\n\n /**\n * Get a cached block by index.\n * Uses Map insertion order as LRU order: on hit, delete+set refreshes recency.\n */\n get(blockIndex: number): AudioBuffer | null {\n const hit = this.cache.get(blockIndex);\n if (!hit) return null;\n this.cache.delete(blockIndex);\n this.cache.set(blockIndex, hit);\n return hit;\n }\n\n /**\n * Get-or-create a block with in-flight de-duplication.\n */\n async getOrCreate(\n blockIndex: number,\n producer: () => Promise<AudioBuffer | null>\n ): Promise<AudioBuffer | null> {\n const cached = this.get(blockIndex);\n if (cached) return cached;\n\n const existing = this.inFlight.get(blockIndex);\n if (existing) return await existing;\n\n const promise = producer().then((buffer) => {\n if (!buffer) return null;\n this.cache.set(blockIndex, buffer);\n this.evictIfNeeded();\n return buffer;\n });\n\n this.inFlight.set(blockIndex, promise);\n try {\n return await promise;\n } finally {\n this.inFlight.delete(blockIndex);\n }\n }\n\n private evictIfNeeded(): void {\n while (this.cache.size > this.maxEntries) {\n // Evict least-recently-used entry:\n // Map preserves insertion order; because we refresh hits via delete+set,\n // the first key is the oldest (least recently used).\n const oldestKey = this.cache.keys().next().value as number | undefined;\n if (oldestKey === undefined) break;\n this.cache.delete(oldestKey);\n }\n }\n}\n"],"names":[],"mappings":"AAAO,MAAM,mBAAmB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,YAAoB;AAC9B,SAAK,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,UAAU,CAAC;AACpD,SAAK,4BAAY,IAAA;AACjB,SAAK,+BAAe,IAAA;AAAA,EACtB;AAAA,EAEA,QAAc;AACZ,SAAK,MAAM,MAAA;AACX,SAAK,SAAS,MAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,YAAwC;AAC1C,UAAM,MAAM,KAAK,MAAM,IAAI,UAAU;AACrC,QAAI,CAAC,IAAK,QAAO;AACjB,SAAK,MAAM,OAAO,UAAU;AAC5B,SAAK,MAAM,IAAI,YAAY,GAAG;AAC9B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YACJ,YACA,UAC6B;AAC7B,UAAM,SAAS,KAAK,IAAI,UAAU;AAClC,QAAI,OAAQ,QAAO;AAEnB,UAAM,WAAW,KAAK,SAAS,IAAI,UAAU;AAC7C,QAAI,iBAAiB,MAAM;AAE3B,UAAM,UAAU,SAAA,EAAW,KAAK,CAAC,WAAW;AAC1C,UAAI,CAAC,OAAQ,QAAO;AACpB,WAAK,MAAM,IAAI,YAAY,MAAM;AACjC,WAAK,cAAA;AACL,aAAO;AAAA,IACT,CAAC;AAED,SAAK,SAAS,IAAI,YAAY,OAAO;AACrC,QAAI;AACF,aAAO,MAAM;AAAA,IACf,UAAA;AACE,WAAK,SAAS,OAAO,UAAU;AAAA,IACjC;AAAA,EACF;AAAA,EAEQ,gBAAsB;AAC5B,WAAO,KAAK,MAAM,OAAO,KAAK,YAAY;AAIxC,YAAM,YAAY,KAAK,MAAM,KAAA,EAAO,OAAO;AAC3C,UAAI,cAAc,OAAW;AAC7B,WAAK,MAAM,OAAO,SAAS;AAAA,IAC7B;AAAA,EACF;AACF;"}
|
|
@@ -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":"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"}
|
|
@@ -32,7 +32,6 @@ class CacheManager {
|
|
|
32
32
|
*/
|
|
33
33
|
setWindow(centerTimeUs) {
|
|
34
34
|
this.videoL1Cache.setWindow(centerTimeUs);
|
|
35
|
-
this.audioL1Cache.setWindow(centerTimeUs);
|
|
36
35
|
}
|
|
37
36
|
/**
|
|
38
37
|
* Check if resource exists in OPFS cache
|
|
@@ -187,7 +186,7 @@ class CacheManager {
|
|
|
187
186
|
clearVideoCache() {
|
|
188
187
|
this.videoL1Cache.clear();
|
|
189
188
|
}
|
|
190
|
-
|
|
189
|
+
clear() {
|
|
191
190
|
this.clearVideoCache();
|
|
192
191
|
this.clearAudioCache();
|
|
193
192
|
}
|
|
@@ -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 {\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
|
+
{"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 clear(): 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":";;;;;;;;AAgDO,MAAM,aAAa;AAAA,EACf;AAAA,EACQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACD,uCAAuB,IAAA;AAAA,EACvB;AAAA,EAER,YAAY,QAA4B,UAAsC;AAC5E,SAAK,eAAe,IAAI,aAAa,OAAO,EAAE;AAC9C,SAAK,eAAe,IAAI,aAAA;AAExB,UAAM,cAAc,IAAI,YAAA;AACxB,SAAK,gBAAgB,IAAI,cAAc,aAAa,OAAO,SAAS,SAAS;AAC7E,SAAK,gBAAgB,IAAI,cAAA;AACzB,SAAK,mBAAmB,IAAI,iBAAA;AAC5B,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,OAAsB;AAC1B,UAAM,KAAK,cAAc,KAAA;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,cAA4B;AACpC,SAAK,aAAa,UAAU,YAAY;AAAA,EAE1C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,mBAAmB,YAAsC;AAC7D,WAAO,MAAM,KAAK,cAAc,YAAY,UAAU;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAkB,YAAoB,OAAe,KAAmC;AAC5F,WAAO,MAAM,KAAK,cAAc,UAAU,YAAY,OAAO,GAAG;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,YAAqC;AAC/C,WAAO,KAAK,cAAc,IAAI,UAAU;AAAA,EAC1C;AAAA,EAEA,iBACE,QACA,WACA,gBACA,cACM;AACN,SAAK,aAAa,iBAAiB,QAAQ,WAAW,gBAAgB,YAAY;AAAA,EACpF;AAAA,EAEA,uBACE,QACA,SACA,OACiF;AACjF,WAAO,KAAK,aAAa,mBAAmB,QAAQ,SAAS,KAAK;AAAA,EACpE;AAAA,EAEA,WAAW,QAAyB;AAClC,WAAO,KAAK,aAAa,WAAW,MAAM;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,QAAgB,aAA8B;AAC3D,WAAO,KAAK,aAAa,UAAU,QAAQ,WAAW;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,QAAgB,SAAiB,OAAe,YAA+B;AAC1F,UAAM,YAAY,aAAa,gCAAgC;AAC/D,WAAO,KAAK,aAAa,sBAAsB,QAAQ,SAAS,OAAO,SAAS,EAAE;AAAA,EACpF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,sBACE,QACA,SACA,OACA,WAC6C;AAC7C,WAAO,KAAK,aAAa,sBAAsB,QAAQ,SAAS,OAAO,SAAS;AAAA,EAClF;AAAA,EAEA,kBAAwB;AACtB,SAAK,aAAa,MAAA;AAAA,EACpB;AAAA,EAEA,mBAAmB,QAAsB;AACvC,SAAK,aAAa,aAAa,MAAM;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,SACE,OACA,QACA,eACA,SACA,cACS;AACT,UAAM,UAAU,KAAK,aAAa,SAAS,OAAO,QAAQ,eAAe,SAAS,YAAY;AAE9F,UAAM,iBAAiB,MAAM,aAAa;AAG1C,SAAK,wBAAwB,QAAQ,cAAc;AAGnD,QAAI,iBAAiB,GAAG;AACtB,WAAK,UAAU,KAAK,aAAa,YAAY;AAAA,QAC3C,QAAQ;AAAA,QACR;AAAA,QACA,OAAO;AAAA,QACP,MAAM,QAAQ,gBAAgB;AAAA,MAAA,CAC/B;AAAA,IACH;AAGA,SAAK,UAAU,KAAK,aAAa,mBAAmB;AAAA,MAClD,QAAQ;AAAA,MACR,aAAa,KAAK,MAAM,eAAe,aAAa;AAAA,MACpD,cAAc;AAAA,MACd;AAAA,MACA;AAAA,IAAA,CACD;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAS,QAAgB,QAAgC;AACvD,WAAO,KAAK,aAAa,IAAI,QAAQ,MAAM;AAAA,EAC7C;AAAA,EAEA,MAAM,eAAe,QAA+B;AAClD,SAAK,aAAa,eAAe,MAAM;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,QAAsB;AACvC,SAAK,aAAa,eAAe,MAAM;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,QAAyB;AACnC,WAAO,KAAK,aAAa,QAAQ,MAAM;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBACE,QACA,UAAgF,IAC9D;AAClB,UAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,UAAM,cAAc,QAAQ,eAAe;AAG3C,UAAM,oBAAoB,KAAK,aAAa,kBAAkB,QAAQ,WAAW;AACjF,QAAI,qBAAqB,eAAe;AACtC,aAAO,QAAQ,QAAQ,IAAI;AAAA,IAC7B;AAGA,UAAM,YAAY,KAAK,iBAAiB,IAAI,MAAM;AAClD,QAAI,WAAW;AACb,UAAI,UAAU,WAAW;AACvB,qBAAa,UAAU,SAAS;AAAA,MAClC;AACA,gBAAU,OAAO,IAAI,oBAAoB,MAAM,CAAC;AAAA,IAClD;AAEA,WAAO,IAAI,QAAiB,CAAC,SAAS,WAAW;AAC/C,YAAM,SAA0B;AAAA,QAC9B;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAc;AAAA,QACd;AAAA,QACA;AAAA,MAAA;AAGF,WAAK,iBAAiB,IAAI,QAAQ,MAAM;AAExC,UAAI,QAAQ,aAAa,QAAQ,YAAY,GAAG;AAC9C,eAAO,YAAY,WAAW,MAAM;AAClC,cAAI,KAAK,iBAAiB,IAAI,MAAM,MAAM,QAAQ;AAChD,iBAAK,iBAAiB,OAAO,MAAM;AAAA,UACrC;AACA,kBAAQ,KAAK;AAAA,QACf,GAAG,QAAQ,SAAS;AAAA,MACtB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,kBAAwB;AACtB,SAAK,aAAa,MAAA;AAAA,EACpB;AAAA,EAEA,QAAc;AACZ,SAAK,gBAAA;AACL,SAAK,gBAAA;AAAA,EACP;AAAA,EAEA,cAAc;AACZ,WAAO;AAAA,MACL,IAAI,KAAK,aAAa,YAAA;AAAA,IAAY;AAAA,EAEtC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,wBAAwB,QAAgB,kBAAgC;AACtE,UAAM,SAAS,KAAK,iBAAiB,IAAI,MAAM;AAC/C,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AAIA,UAAM,cAAc,OAAO,gBAAgB,KAAK,oBAAoB,OAAO;AAE3E,QAAI,aAAa;AACf,aAAO;AAEP,UAAI,OAAO,gBAAgB,OAAO,eAAe;AAC/C,YAAI,OAAO,WAAW;AACpB,uBAAa,OAAO,SAAS;AAAA,QAC/B;AACA,eAAO,QAAQ,IAAI;AACnB,aAAK,iBAAiB,OAAO,MAAM;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AACF;"}
|
|
@@ -13,10 +13,6 @@ interface AudioDataSlot {
|
|
|
13
13
|
export type { AudioDataSlot };
|
|
14
14
|
export declare class AudioL1Cache {
|
|
15
15
|
private audioDataByClip;
|
|
16
|
-
private windowCenter;
|
|
17
|
-
private readonly WINDOW_RADIUS;
|
|
18
|
-
private readonly EVICT_THROTTLE_MS;
|
|
19
|
-
private lastEvictTime;
|
|
20
16
|
putClipAudioData(clipId: string, audioData: AudioData, _clipDurationUs: TimeUs, globalTimeUs?: TimeUs): void;
|
|
21
17
|
getSlotsInWindow(clipId: string, startUs: TimeUs, endUs: TimeUs): AudioDataSlot[] | null;
|
|
22
18
|
getPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null;
|
|
@@ -30,14 +26,6 @@ export declare class AudioL1Cache {
|
|
|
30
26
|
* Check if a slot with specific timestamp exists (used for incremental decoding)
|
|
31
27
|
*/
|
|
32
28
|
hasSlotAt(clipId: string, timestampUs: TimeUs, toleranceUs?: number): boolean;
|
|
33
|
-
/**
|
|
34
|
-
* Check if sufficient PCM data exists for the requested time window
|
|
35
|
-
* @param clipId - Clip identifier
|
|
36
|
-
* @param startUs - Window start time
|
|
37
|
-
* @param endUs - Window end time
|
|
38
|
-
* @param strictMode - If true, require 99% coverage (export). If false, accept 95% (preview)
|
|
39
|
-
*/
|
|
40
|
-
hasWindowData(clipId: string, startUs: TimeUs, endUs: TimeUs, strictMode?: boolean): boolean;
|
|
41
29
|
/**
|
|
42
30
|
* Get audio range coverage information for incremental decoding
|
|
43
31
|
* @param clipId - Clip identifier
|
|
@@ -56,13 +44,10 @@ export declare class AudioL1Cache {
|
|
|
56
44
|
* Update window center (unified global window)
|
|
57
45
|
* Aligned with VideoL1Cache strategy: maintains a window of ±RADIUS around center
|
|
58
46
|
*/
|
|
59
|
-
setWindow(centerGlobalUs: TimeUs): void;
|
|
60
|
-
private checkEviction;
|
|
61
47
|
/**
|
|
62
48
|
* Evict audio slots outside the global window (aligned with VideoL1Cache)
|
|
63
49
|
* Skip if eviction is disabled (e.g., during export)
|
|
64
50
|
*/
|
|
65
|
-
private evictOutOfWindow;
|
|
66
51
|
/**
|
|
67
52
|
* Find insertion index for a new slot (aligned with VideoL1Cache)
|
|
68
53
|
*/
|
|
@@ -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,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;
|
|
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;IAW7D,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;IAiG7E,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;;;;;;;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;;;OAGG;IAgCH;;OAEG;IACH,OAAO,CAAC,eAAe;IAIvB,KAAK,IAAI,IAAI;IAIb,KAAK,IAAI,IAAI;IAMb,OAAO,IAAI,IAAI;CAGhB"}
|
|
@@ -7,12 +7,11 @@ class AudioL1Cache {
|
|
|
7
7
|
audioDataByClip = /* @__PURE__ */ new Map();
|
|
8
8
|
// Unified window management (aligned with VideoL1Cache)
|
|
9
9
|
// All clips share the same global window center
|
|
10
|
-
windowCenter = 0;
|
|
11
|
-
// Window radius aligned with video (±3.5s, but we use 5s for audio safety margin)
|
|
12
|
-
WINDOW_RADIUS =
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
lastEvictTime = 0;
|
|
10
|
+
// private windowCenter: TimeUs = 0;
|
|
11
|
+
// // Window radius aligned with video (±3.5s, but we use 5s for audio safety margin)
|
|
12
|
+
// private readonly WINDOW_RADIUS = 5_000_000; // ±5s
|
|
13
|
+
// private readonly EVICT_THROTTLE_MS = 500;
|
|
14
|
+
// private lastEvictTime = 0;
|
|
16
15
|
putClipAudioData(clipId, audioData, _clipDurationUs, globalTimeUs) {
|
|
17
16
|
const numberOfChannels = audioData.numberOfChannels ?? 2;
|
|
18
17
|
const numberOfFrames = audioData.numberOfFrames ?? 0;
|
|
@@ -106,16 +105,17 @@ class AudioL1Cache {
|
|
|
106
105
|
() => new Float32Array(totalFrames)
|
|
107
106
|
);
|
|
108
107
|
for (const slot of overlappingSlots) {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
if (copyStartUs >= copyEndUs) continue;
|
|
114
|
-
const srcOffsetFrames = Math.floor(
|
|
115
|
-
(copyStartUs - slotStartUs) / 1e6 * uniformSampleRate
|
|
108
|
+
const slotFrames = slot.planes[0]?.length ?? 0;
|
|
109
|
+
if (slotFrames <= 0) continue;
|
|
110
|
+
const slotStartFrame = Math.round(
|
|
111
|
+
(slot.timestampUs - startUs) / 1e6 * uniformSampleRate
|
|
116
112
|
);
|
|
117
|
-
const
|
|
118
|
-
const
|
|
113
|
+
const slotEndFrame = slotStartFrame + slotFrames;
|
|
114
|
+
const dstStartFrame = Math.max(0, slotStartFrame);
|
|
115
|
+
const dstEndFrame = Math.min(totalFrames, slotEndFrame);
|
|
116
|
+
const copyFrameCount = dstEndFrame - dstStartFrame;
|
|
117
|
+
if (copyFrameCount <= 0) continue;
|
|
118
|
+
const srcOffsetFrames = dstStartFrame - slotStartFrame;
|
|
119
119
|
for (let ch = 0; ch < uniformChannels; ch++) {
|
|
120
120
|
const srcPlane = slot.planes[ch];
|
|
121
121
|
const dstPlane = result[ch];
|
|
@@ -123,12 +123,13 @@ class AudioL1Cache {
|
|
|
123
123
|
const actualCopyFrames = Math.min(
|
|
124
124
|
copyFrameCount,
|
|
125
125
|
srcPlane.length - srcOffsetFrames,
|
|
126
|
-
dstPlane.length -
|
|
126
|
+
dstPlane.length - dstStartFrame
|
|
127
|
+
);
|
|
128
|
+
if (actualCopyFrames <= 0) continue;
|
|
129
|
+
dstPlane.set(
|
|
130
|
+
srcPlane.subarray(srcOffsetFrames, srcOffsetFrames + actualCopyFrames),
|
|
131
|
+
dstStartFrame
|
|
127
132
|
);
|
|
128
|
-
if (actualCopyFrames > 0) {
|
|
129
|
-
const srcSlice = srcPlane.subarray(srcOffsetFrames, srcOffsetFrames + actualCopyFrames);
|
|
130
|
-
dstPlane.set(srcSlice, dstOffsetFrames);
|
|
131
|
-
}
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
return result;
|
|
@@ -169,17 +170,6 @@ class AudioL1Cache {
|
|
|
169
170
|
}
|
|
170
171
|
return false;
|
|
171
172
|
}
|
|
172
|
-
/**
|
|
173
|
-
* Check if sufficient PCM data exists for the requested time window
|
|
174
|
-
* @param clipId - Clip identifier
|
|
175
|
-
* @param startUs - Window start time
|
|
176
|
-
* @param endUs - Window end time
|
|
177
|
-
* @param strictMode - If true, require 99% coverage (export). If false, accept 95% (preview)
|
|
178
|
-
*/
|
|
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
173
|
/**
|
|
184
174
|
* Get audio range coverage information for incremental decoding
|
|
185
175
|
* @param clipId - Clip identifier
|
|
@@ -234,43 +224,46 @@ class AudioL1Cache {
|
|
|
234
224
|
* Update window center (unified global window)
|
|
235
225
|
* Aligned with VideoL1Cache strategy: maintains a window of ±RADIUS around center
|
|
236
226
|
*/
|
|
237
|
-
setWindow(centerGlobalUs) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
checkEviction() {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
227
|
+
// setWindow(centerGlobalUs: TimeUs): void {
|
|
228
|
+
// this.windowCenter = centerGlobalUs;
|
|
229
|
+
// this.checkEviction();
|
|
230
|
+
// }
|
|
231
|
+
// private checkEviction(): void {
|
|
232
|
+
// const now = Date.now();
|
|
233
|
+
// if (now - this.lastEvictTime > this.EVICT_THROTTLE_MS) {
|
|
234
|
+
// this.evictOutOfWindow();
|
|
235
|
+
// this.lastEvictTime = now;
|
|
236
|
+
// }
|
|
237
|
+
// }
|
|
248
238
|
/**
|
|
249
239
|
* Evict audio slots outside the global window (aligned with VideoL1Cache)
|
|
250
240
|
* Skip if eviction is disabled (e.g., during export)
|
|
251
241
|
*/
|
|
252
|
-
evictOutOfWindow() {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
242
|
+
// private evictOutOfWindow(): void {
|
|
243
|
+
// const windowStart = Math.max(0, this.windowCenter - this.WINDOW_RADIUS);
|
|
244
|
+
// const windowEnd = this.windowCenter + this.WINDOW_RADIUS;
|
|
245
|
+
// for (const [clipId, slots] of this.audioDataByClip) {
|
|
246
|
+
// const toKeep: AudioDataSlot[] = [];
|
|
247
|
+
// for (const slot of slots) {
|
|
248
|
+
// const globalTime = slot.globalTimeUs;
|
|
249
|
+
// // Slots without globalTimeUs are kept (legacy)
|
|
250
|
+
// if (globalTime === undefined) {
|
|
251
|
+
// toKeep.push(slot);
|
|
252
|
+
// continue;
|
|
253
|
+
// }
|
|
254
|
+
// // Keep slots within window
|
|
255
|
+
// if (globalTime >= windowStart && globalTime <= windowEnd) {
|
|
256
|
+
// toKeep.push(slot);
|
|
257
|
+
// }
|
|
258
|
+
// // Slots outside window are discarded (no close needed for Float32Array)
|
|
259
|
+
// }
|
|
260
|
+
// if (toKeep.length > 0) {
|
|
261
|
+
// this.audioDataByClip.set(clipId, toKeep);
|
|
262
|
+
// } else {
|
|
263
|
+
// this.audioDataByClip.delete(clipId);
|
|
264
|
+
// }
|
|
265
|
+
// }
|
|
266
|
+
// }
|
|
274
267
|
/**
|
|
275
268
|
* Find insertion index for a new slot (aligned with VideoL1Cache)
|
|
276
269
|
*/
|
|
@@ -283,7 +276,6 @@ class AudioL1Cache {
|
|
|
283
276
|
clear() {
|
|
284
277
|
this.flush();
|
|
285
278
|
this.audioDataByClip.clear();
|
|
286
|
-
this.windowCenter = 0;
|
|
287
279
|
}
|
|
288
280
|
dispose() {
|
|
289
281
|
this.clear();
|
|
@@ -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\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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"}
|
|
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 // Note: We intentionally keep ceil here to avoid truncating the requested range.\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 // IMPORTANT: Use frame-aligned mapping based on slot plane length instead of time-based floor/ceil.\n // This avoids off-by-one discontinuities at MP3/AAC frame boundaries caused by microsecond rounding.\n for (const slot of overlappingSlots) {\n const slotFrames = slot.planes[0]?.length ?? 0;\n if (slotFrames <= 0) continue;\n\n // Map slot start time to destination frame index (frame-aligned).\n const slotStartFrame = Math.round(\n ((slot.timestampUs - startUs) / 1_000_000) * uniformSampleRate\n );\n const slotEndFrame = slotStartFrame + slotFrames;\n\n // Intersect with requested destination buffer [0, totalFrames)\n const dstStartFrame = Math.max(0, slotStartFrame);\n const dstEndFrame = Math.min(totalFrames, slotEndFrame);\n const copyFrameCount = dstEndFrame - dstStartFrame;\n if (copyFrameCount <= 0) continue;\n\n // Source offset is derived from the chosen destination start.\n const srcOffsetFrames = dstStartFrame - slotStartFrame;\n\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 const actualCopyFrames = Math.min(\n copyFrameCount,\n srcPlane.length - srcOffsetFrames,\n dstPlane.length - dstStartFrame\n );\n if (actualCopyFrames <= 0) continue;\n\n dstPlane.set(\n srcPlane.subarray(srcOffsetFrames, srcOffsetFrames + actualCopyFrames),\n dstStartFrame\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 * 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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"}
|
|
@@ -26,7 +26,6 @@ export declare class PlaybackController implements IPlaybackController, PreviewH
|
|
|
26
26
|
private fsm;
|
|
27
27
|
private windowEnd;
|
|
28
28
|
private readonly WINDOW_DURATION;
|
|
29
|
-
private readonly AUDIO_READY_PROBE_WINDOW;
|
|
30
29
|
private readonly PREHEAT_DISTANCE;
|
|
31
30
|
private preheatInProgress;
|
|
32
31
|
constructor(orchestrator: Orchestrator, eventBus: IEventBus, options: PlaybackOptions);
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,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;IAoLtB,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,iBAAiB;YAMX,SAAS;IAwEvB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,qBAAqB;IAgBvB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;YAmC1B,kBAAkB;YAqBlB,OAAO;IAYrB,OAAO,CAAC,WAAW;IAYnB,OAAO,IAAI,IAAI;IAUf,OAAO,CAAC,YAAY,CAIlB;IAEF,OAAO,CAAC,UAAU,CAkBhB;IAEF,OAAO,CAAC,mBAAmB;IAK3B,OAAO,CAAC,0BAA0B;CAUnC"}
|