@meframe/core 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/CacheManager.d.ts +3 -0
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +16 -2
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/storage/opfs/OPFSManager.d.ts.map +1 -1
- package/dist/cache/storage/opfs/OPFSManager.js +11 -7
- package/dist/cache/storage/opfs/OPFSManager.js.map +1 -1
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +9 -1
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/orchestrator/AudioPreviewSession.d.ts.map +1 -1
- package/dist/orchestrator/AudioPreviewSession.js +3 -1
- package/dist/orchestrator/AudioPreviewSession.js.map +1 -1
- package/dist/orchestrator/ExportScheduler.d.ts.map +1 -1
- package/dist/orchestrator/ExportScheduler.js +3 -0
- package/dist/orchestrator/ExportScheduler.js.map +1 -1
- package/dist/orchestrator/OnDemandVideoSession.d.ts +1 -0
- package/dist/orchestrator/OnDemandVideoSession.d.ts.map +1 -1
- package/dist/orchestrator/OnDemandVideoSession.js +26 -15
- package/dist/orchestrator/OnDemandVideoSession.js.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +34 -8
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/utils/errors.d.ts +1 -0
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +5 -1
- package/dist/utils/errors.js.map +1 -1
- package/package.json +1 -1
|
@@ -35,7 +35,10 @@ export declare class CacheManager {
|
|
|
35
35
|
readonly audioSampleCache: AudioSampleCache;
|
|
36
36
|
private clipReadyWaiters;
|
|
37
37
|
private eventBus?;
|
|
38
|
+
isExporting: boolean;
|
|
38
39
|
constructor(config: CacheManagerConfig, eventBus?: EventBus<EventPayloadMap>);
|
|
40
|
+
beginExport(): void;
|
|
41
|
+
endExport(): void;
|
|
39
42
|
init(): Promise<void>;
|
|
40
43
|
/**
|
|
41
44
|
* Set window center for L1 cache management (unified for video and audio)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CacheManager.d.ts","sourceRoot":"","sources":["../../src/cache/CacheManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;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;
|
|
1
|
+
{"version":3,"file":"CacheManager.d.ts","sourceRoot":"","sources":["../../src/cache/CacheManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { VideoL1Cache } from "./l1/VideoL1Cache.js";
|
|
2
2
|
import { MeframeEvent } from "../event/events.js";
|
|
3
3
|
import { AudioL1Cache, EXPORT_PCM_COVERAGE_THRESHOLD, PREVIEW_PCM_COVERAGE_THRESHOLD } from "./l1/AudioL1Cache.js";
|
|
4
|
-
import { WaiterReplacedError } from "../utils/errors.js";
|
|
4
|
+
import { isDOMException, ResourceCorruptedError, WaiterReplacedError } from "../utils/errors.js";
|
|
5
5
|
import { OPFSManager } from "./storage/opfs/OPFSManager.js";
|
|
6
6
|
import { ResourceCache } from "./resource/ResourceCache.js";
|
|
7
7
|
import { MP4IndexCache } from "./resource/MP4IndexCache.js";
|
|
@@ -14,6 +14,7 @@ class CacheManager {
|
|
|
14
14
|
audioSampleCache;
|
|
15
15
|
clipReadyWaiters = /* @__PURE__ */ new Map();
|
|
16
16
|
eventBus;
|
|
17
|
+
isExporting = false;
|
|
17
18
|
constructor(config, eventBus) {
|
|
18
19
|
this.videoL1Cache = new VideoL1Cache(config.l1);
|
|
19
20
|
this.audioL1Cache = new AudioL1Cache();
|
|
@@ -23,6 +24,12 @@ class CacheManager {
|
|
|
23
24
|
this.audioSampleCache = new AudioSampleCache();
|
|
24
25
|
this.eventBus = eventBus;
|
|
25
26
|
}
|
|
27
|
+
beginExport() {
|
|
28
|
+
this.isExporting = true;
|
|
29
|
+
}
|
|
30
|
+
endExport() {
|
|
31
|
+
this.isExporting = false;
|
|
32
|
+
}
|
|
26
33
|
async init() {
|
|
27
34
|
await this.resourceCache.init();
|
|
28
35
|
}
|
|
@@ -43,7 +50,14 @@ class CacheManager {
|
|
|
43
50
|
* Read byte range from resource (for GOP-level access)
|
|
44
51
|
*/
|
|
45
52
|
async readResourceRange(resourceId, start, end) {
|
|
46
|
-
|
|
53
|
+
try {
|
|
54
|
+
return await this.resourceCache.readRange(resourceId, start, end);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (isDOMException(error, "NotFoundError")) {
|
|
57
|
+
throw new ResourceCorruptedError(resourceId, "OPFS file missing");
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
47
61
|
}
|
|
48
62
|
/**
|
|
49
63
|
* Get MP4 index for a resource
|
|
@@ -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 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":"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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 { isDOMException, ResourceCorruptedError, 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 isExporting = false;\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 beginExport(): void {\n this.isExporting = true;\n }\n\n endExport(): void {\n this.isExporting = false;\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 try {\n return await this.resourceCache.readRange(resourceId, start, end);\n } catch (error) {\n if (isDOMException(error, 'NotFoundError')) {\n throw new ResourceCorruptedError(resourceId, 'OPFS file missing');\n }\n throw error;\n }\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":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OPFSManager.d.ts","sourceRoot":"","sources":["../../../../src/cache/storage/opfs/OPFSManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"OPFSManager.d.ts","sourceRoot":"","sources":["../../../../src/cache/storage/opfs/OPFSManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAGrE;;;;;;;;GAQG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAA0C;IAC1D,OAAO,CAAC,WAAW,CAA8B;IACjD,YAAY,EAAE,MAAM,CAAY;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;gBAEpB,qBAAqB,GAAE,MAAa;IAI1C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAOb,WAAW;IAWzB;;OAEG;IACG,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAS9F;;;OAGG;IACG,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,GAAG,cAAc,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB9F;;OAEG;YACW,eAAe;IAU7B;;;OAGG;YACW,WAAW;IAYzB;;OAEG;IACG,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;YAKzC,iBAAiB;IAgC/B;;OAEG;IACG,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC;IAOpD;;OAEG;IACG,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAQjF;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAW/C;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAa9C;;OAEG;IACG,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAOlD;;OAEG;IACG,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,4BAA4B,CAAC;IAMjF;;OAEG;IACG,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAalF;;;OAGG;IACG,wBAAwB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA2C9E;;;OAGG;IACG,mBAAmB,CACvB,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC;CAiCnB"}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import { OPFSQuotaExceededError } from "../../../utils/errors.js";
|
|
2
|
-
function isDOMException(error, name) {
|
|
3
|
-
return error instanceof Error && "name" in error && error.name === name;
|
|
4
|
-
}
|
|
1
|
+
import { isDOMException, OPFSQuotaExceededError } from "../../../utils/errors.js";
|
|
5
2
|
class OPFSManager {
|
|
6
3
|
opfsRoot = null;
|
|
7
4
|
initPromise = null;
|
|
@@ -207,9 +204,16 @@ class OPFSManager {
|
|
|
207
204
|
let maxLastModified = 0;
|
|
208
205
|
for await (const [_fileName, fileHandle] of projectDir.entries()) {
|
|
209
206
|
if (fileHandle.kind === "file") {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
207
|
+
try {
|
|
208
|
+
const file = await fileHandle.getFile();
|
|
209
|
+
totalSize += file.size;
|
|
210
|
+
maxLastModified = Math.max(maxLastModified, file.lastModified);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (isDOMException(error, "NotFoundError")) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
213
217
|
}
|
|
214
218
|
}
|
|
215
219
|
if (totalSize > 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OPFSManager.js","sources":["../../../../src/cache/storage/opfs/OPFSManager.ts"],"sourcesContent":["import type { OPFSPrefix, OPFSPath, ProjectMetadata } from './types';\nimport { OPFSQuotaExceededError } from '../../../utils/errors';\n\nfunction isDOMException(error: unknown, name: string): boolean {\n return error instanceof Error && 'name' in error && error.name === name;\n}\n\n/**\n * OPFSManager - Unified OPFS management infrastructure\n *\n * Supports:\n * - Directory isolation by prefix (l2/resource)\n * - Range reads for on-demand decoding\n * - Streaming writes with automatic quota management\n * - Project-level LRU eviction\n */\nexport class OPFSManager {\n private opfsRoot: FileSystemDirectoryHandle | null = null;\n private initPromise: Promise<void> | null = null;\n maxSizeBytes: number = Infinity;\n readonly quotaThreshold: number;\n\n constructor(quotaThresholdPercent: number = 0.85) {\n this.quotaThreshold = quotaThresholdPercent;\n }\n\n async init(): Promise<void> {\n if (this.initPromise) return this.initPromise;\n\n this.initPromise = this.initStorage();\n return this.initPromise;\n }\n\n private async initStorage(): Promise<void> {\n this.opfsRoot = await navigator.storage.getDirectory();\n\n // Dynamic quota detection\n const estimate = await navigator.storage.estimate();\n const quota = estimate.quota || 5 * 1024 * 1024 * 1024;\n\n // Aggressive strategy: use 80% of browser quota\n this.maxSizeBytes = Math.floor(quota * 0.8);\n }\n\n /**\n * Get project directory by prefix\n */\n async getProjectDir(projectId: string, prefix: OPFSPrefix): Promise<FileSystemDirectoryHandle> {\n if (!this.opfsRoot) {\n throw new Error('[OPFSManager] Not initialized');\n }\n\n const dirName = `meframe-${prefix}-${projectId}`;\n return this.opfsRoot.getDirectoryHandle(dirName, { create: true });\n }\n\n /**\n * Write file with automatic quota management\n * Proactively checks quota before write; evicts old projects if threshold exceeded\n */\n async writeFile(path: OPFSPath, data: ArrayBuffer | ReadableStream<Uint8Array>): Promise<void> {\n await this.ensureQuota(path.projectId, path.prefix);\n\n try {\n await this.writeFileInternal(path, data);\n } catch (error) {\n if (isDOMException(error, 'QuotaExceededError')) {\n const evictedCount = await this.evictOldestProjects(path.projectId, path.prefix, 1);\n\n if (evictedCount === 0) {\n throw new OPFSQuotaExceededError(path.projectId, path.prefix, false);\n }\n\n if (data instanceof ReadableStream) {\n throw new OPFSQuotaExceededError(path.projectId, path.prefix, true);\n }\n\n await this.writeFileInternal(path, data);\n } else {\n throw error;\n }\n }\n }\n\n /**\n * Check if project is locked by any tab (actively being used)\n */\n private async isProjectLocked(projectId: string, prefix: OPFSPrefix): Promise<boolean> {\n if (!navigator.locks) {\n return false;\n }\n\n const lockName = `meframe-${prefix}-${projectId}`;\n const locks = await navigator.locks.query();\n return locks.held?.some((lock) => lock.name === lockName) ?? false;\n }\n\n /**\n * Ensure quota is within threshold before write\n * Proactively evicts old projects if usage exceeds threshold\n */\n private async ensureQuota(currentProjectId: string, prefix: OPFSPrefix): Promise<void> {\n const totalSize = await this.getTotalSize(prefix);\n const usagePercent = totalSize / this.maxSizeBytes;\n\n if (usagePercent > this.quotaThreshold) {\n const needToFreeBytes = totalSize - this.maxSizeBytes * this.quotaThreshold;\n const projectsToEvict = Math.ceil(needToFreeBytes / (totalSize / 10));\n\n await this.evictOldestProjects(currentProjectId, prefix, Math.max(1, projectsToEvict));\n }\n }\n\n /**\n * Get total size of all projects for a prefix\n */\n async getTotalSize(prefix: OPFSPrefix): Promise<number> {\n const projects = await this.listProjectsWithMetadata(prefix);\n return projects.reduce((sum, p) => sum + p.size, 0);\n }\n\n private async writeFileInternal(\n path: OPFSPath,\n data: ArrayBuffer | ReadableStream<Uint8Array>\n ): Promise<void> {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n const fileHandle = await projectDir.getFileHandle(path.fileName, { create: true });\n const writable = await fileHandle.createWritable();\n\n if (data instanceof ArrayBuffer) {\n await writable.write(data);\n } else {\n const reader = data.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n const buffer = value.buffer.slice(\n value.byteOffset,\n value.byteOffset + value.byteLength\n );\n await writable.write(buffer as ArrayBuffer);\n }\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n await writable.close();\n }\n\n /**\n * Read entire file\n */\n async readFile(path: OPFSPath): Promise<ArrayBuffer> {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n const fileHandle = await projectDir.getFileHandle(path.fileName, { create: false });\n const file = await fileHandle.getFile();\n return await file.arrayBuffer();\n }\n\n /**\n * Read byte range from file (for on-demand decoding)\n */\n async readRange(path: OPFSPath, start: number, end: number): Promise<ArrayBuffer> {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n const fileHandle = await projectDir.getFileHandle(path.fileName, { create: false });\n const file = await fileHandle.getFile();\n const slice = file.slice(start, end);\n return await slice.arrayBuffer();\n }\n\n /**\n * Delete file\n */\n async deleteFile(path: OPFSPath): Promise<void> {\n try {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n await projectDir.removeEntry(path.fileName);\n } catch (error) {\n if (!isDOMException(error, 'NotFoundError')) {\n console.warn(`[OPFSManager] Failed to delete file ${path.fileName}:`, error);\n }\n }\n }\n\n /**\n * Check if file exists\n */\n async exists(path: OPFSPath): Promise<boolean> {\n try {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n await projectDir.getFileHandle(path.fileName, { create: false });\n return true;\n } catch (error) {\n if (isDOMException(error, 'NotFoundError')) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Get file size\n */\n async getFileSize(path: OPFSPath): Promise<number> {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n const fileHandle = await projectDir.getFileHandle(path.fileName, { create: false });\n const file = await fileHandle.getFile();\n return file.size;\n }\n\n /**\n * Create writable stream for streaming writes\n */\n async createWritableStream(path: OPFSPath): Promise<FileSystemWritableFileStream> {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n const fileHandle = await projectDir.getFileHandle(path.fileName, { create: true });\n return await fileHandle.createWritable();\n }\n\n /**\n * Delete entire project directory\n */\n async deleteProjectDirectory(projectId: string, prefix: OPFSPrefix): Promise<void> {\n if (!this.opfsRoot) return;\n\n try {\n const dirName = `meframe-${prefix}-${projectId}`;\n await this.opfsRoot.removeEntry(dirName, { recursive: true });\n } catch (error) {\n if (!isDOMException(error, 'NotFoundError')) {\n console.warn(`[OPFSManager] Failed to remove directory ${prefix}-${projectId}:`, error);\n }\n }\n }\n\n /**\n * List all projects with size and lastModified metadata\n * Used for LRU eviction\n */\n async listProjectsWithMetadata(prefix: OPFSPrefix): Promise<ProjectMetadata[]> {\n if (!this.opfsRoot) {\n throw new Error('[OPFSManager] Not initialized');\n }\n\n const projects: ProjectMetadata[] = [];\n const searchPrefix = `meframe-${prefix}-`;\n\n // @ts-expect-error - AsyncIterator type not well-supported\n for await (const [name, handle] of this.opfsRoot.entries()) {\n if (handle.kind === 'directory' && name.startsWith(searchPrefix)) {\n const projectId = name.slice(searchPrefix.length);\n const projectDir = handle as FileSystemDirectoryHandle;\n let totalSize = 0;\n let maxLastModified = 0;\n\n // @ts-expect-error - AsyncIterator type not well-supported\n for await (const [_fileName, fileHandle] of projectDir.entries()) {\n if (fileHandle.kind === 'file') {\n const file = await (fileHandle as FileSystemFileHandle).getFile();\n totalSize += file.size;\n maxLastModified = Math.max(maxLastModified, file.lastModified);\n }\n }\n\n if (totalSize > 0) {\n projects.push({ projectId, size: totalSize, lastModified: maxLastModified });\n }\n }\n }\n\n return projects;\n }\n\n /**\n * Evict oldest projects (by lastModified) excluding current project and locked projects\n * Returns number of projects evicted\n */\n async evictOldestProjects(\n currentProjectId: string,\n prefix: OPFSPrefix,\n count: number\n ): Promise<number> {\n const projects = await this.listProjectsWithMetadata(prefix);\n\n // Filter candidates: exclude current project and locked projects\n const candidates = [];\n for (const project of projects) {\n if (project.projectId === currentProjectId) continue;\n\n const isLocked = await this.isProjectLocked(project.projectId, prefix);\n if (isLocked) continue;\n\n candidates.push(project);\n }\n\n if (candidates.length === 0) {\n return 0;\n }\n\n candidates.sort((a, b) => a.lastModified - b.lastModified);\n const toEvict = candidates.slice(0, count);\n\n let freedBytes = 0;\n for (const project of toEvict) {\n await this.deleteProjectDirectory(project.projectId, prefix);\n freedBytes += project.size;\n }\n\n console.log(\n `[OPFSManager] Evicted ${toEvict.length} project(s), freed ${(freedBytes / 1024 / 1024).toFixed(0)}MB`\n );\n\n return toEvict.length;\n }\n}\n"],"names":[],"mappings":";AAGA,SAAS,eAAe,OAAgB,MAAuB;AAC7D,SAAO,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS;AACrE;AAWO,MAAM,YAAY;AAAA,EACf,WAA6C;AAAA,EAC7C,cAAoC;AAAA,EAC5C,eAAuB;AAAA,EACd;AAAA,EAET,YAAY,wBAAgC,MAAM;AAChD,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEA,MAAM,OAAsB;AAC1B,QAAI,KAAK,YAAa,QAAO,KAAK;AAElC,SAAK,cAAc,KAAK,YAAA;AACxB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,cAA6B;AACzC,SAAK,WAAW,MAAM,UAAU,QAAQ,aAAA;AAGxC,UAAM,WAAW,MAAM,UAAU,QAAQ,SAAA;AACzC,UAAM,QAAQ,SAAS,SAAS,IAAI,OAAO,OAAO;AAGlD,SAAK,eAAe,KAAK,MAAM,QAAQ,GAAG;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,WAAmB,QAAwD;AAC7F,QAAI,CAAC,KAAK,UAAU;AAClB,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AAEA,UAAM,UAAU,WAAW,MAAM,IAAI,SAAS;AAC9C,WAAO,KAAK,SAAS,mBAAmB,SAAS,EAAE,QAAQ,MAAM;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UAAU,MAAgB,MAA+D;AAC7F,UAAM,KAAK,YAAY,KAAK,WAAW,KAAK,MAAM;AAElD,QAAI;AACF,YAAM,KAAK,kBAAkB,MAAM,IAAI;AAAA,IACzC,SAAS,OAAO;AACd,UAAI,eAAe,OAAO,oBAAoB,GAAG;AAC/C,cAAM,eAAe,MAAM,KAAK,oBAAoB,KAAK,WAAW,KAAK,QAAQ,CAAC;AAElF,YAAI,iBAAiB,GAAG;AACtB,gBAAM,IAAI,uBAAuB,KAAK,WAAW,KAAK,QAAQ,KAAK;AAAA,QACrE;AAEA,YAAI,gBAAgB,gBAAgB;AAClC,gBAAM,IAAI,uBAAuB,KAAK,WAAW,KAAK,QAAQ,IAAI;AAAA,QACpE;AAEA,cAAM,KAAK,kBAAkB,MAAM,IAAI;AAAA,MACzC,OAAO;AACL,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,gBAAgB,WAAmB,QAAsC;AACrF,QAAI,CAAC,UAAU,OAAO;AACpB,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,WAAW,MAAM,IAAI,SAAS;AAC/C,UAAM,QAAQ,MAAM,UAAU,MAAM,MAAA;AACpC,WAAO,MAAM,MAAM,KAAK,CAAC,SAAS,KAAK,SAAS,QAAQ,KAAK;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,YAAY,kBAA0B,QAAmC;AACrF,UAAM,YAAY,MAAM,KAAK,aAAa,MAAM;AAChD,UAAM,eAAe,YAAY,KAAK;AAEtC,QAAI,eAAe,KAAK,gBAAgB;AACtC,YAAM,kBAAkB,YAAY,KAAK,eAAe,KAAK;AAC7D,YAAM,kBAAkB,KAAK,KAAK,mBAAmB,YAAY,GAAG;AAEpE,YAAM,KAAK,oBAAoB,kBAAkB,QAAQ,KAAK,IAAI,GAAG,eAAe,CAAC;AAAA,IACvF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,QAAqC;AACtD,UAAM,WAAW,MAAM,KAAK,yBAAyB,MAAM;AAC3D,WAAO,SAAS,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,MAAM,CAAC;AAAA,EACpD;AAAA,EAEA,MAAc,kBACZ,MACA,MACe;AACf,UAAM,aAAa,MAAM,KAAK,cAAc,KAAK,WAAW,KAAK,MAAM;AACvE,UAAM,aAAa,MAAM,WAAW,cAAc,KAAK,UAAU,EAAE,QAAQ,MAAM;AACjF,UAAM,WAAW,MAAM,WAAW,eAAA;AAElC,QAAI,gBAAgB,aAAa;AAC/B,YAAM,SAAS,MAAM,IAAI;AAAA,IAC3B,OAAO;AACL,YAAM,SAAS,KAAK,UAAA;AACpB,UAAI;AACF,eAAO,MAAM;AACX,gBAAM,EAAE,MAAM,MAAA,IAAU,MAAM,OAAO,KAAA;AACrC,cAAI,KAAM;AACV,cAAI,OAAO;AACT,kBAAM,SAAS,MAAM,OAAO;AAAA,cAC1B,MAAM;AAAA,cACN,MAAM,aAAa,MAAM;AAAA,YAAA;AAE3B,kBAAM,SAAS,MAAM,MAAqB;AAAA,UAC5C;AAAA,QACF;AAAA,MACF,UAAA;AACE,eAAO,YAAA;AAAA,MACT;AAAA,IACF;AAEA,UAAM,SAAS,MAAA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,MAAsC;AACnD,UAAM,aAAa,MAAM,KAAK,cAAc,KAAK,WAAW,KAAK,MAAM;AACvE,UAAM,aAAa,MAAM,WAAW,cAAc,KAAK,UAAU,EAAE,QAAQ,OAAO;AAClF,UAAM,OAAO,MAAM,WAAW,QAAA;AAC9B,WAAO,MAAM,KAAK,YAAA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,MAAgB,OAAe,KAAmC;AAChF,UAAM,aAAa,MAAM,KAAK,cAAc,KAAK,WAAW,KAAK,MAAM;AACvE,UAAM,aAAa,MAAM,WAAW,cAAc,KAAK,UAAU,EAAE,QAAQ,OAAO;AAClF,UAAM,OAAO,MAAM,WAAW,QAAA;AAC9B,UAAM,QAAQ,KAAK,MAAM,OAAO,GAAG;AACnC,WAAO,MAAM,MAAM,YAAA;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,MAA+B;AAC9C,QAAI;AACF,YAAM,aAAa,MAAM,KAAK,cAAc,KAAK,WAAW,KAAK,MAAM;AACvE,YAAM,WAAW,YAAY,KAAK,QAAQ;AAAA,IAC5C,SAAS,OAAO;AACd,UAAI,CAAC,eAAe,OAAO,eAAe,GAAG;AAC3C,gBAAQ,KAAK,uCAAuC,KAAK,QAAQ,KAAK,KAAK;AAAA,MAC7E;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,MAAkC;AAC7C,QAAI;AACF,YAAM,aAAa,MAAM,KAAK,cAAc,KAAK,WAAW,KAAK,MAAM;AACvE,YAAM,WAAW,cAAc,KAAK,UAAU,EAAE,QAAQ,OAAO;AAC/D,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UAAI,eAAe,OAAO,eAAe,GAAG;AAC1C,eAAO;AAAA,MACT;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,MAAiC;AACjD,UAAM,aAAa,MAAM,KAAK,cAAc,KAAK,WAAW,KAAK,MAAM;AACvE,UAAM,aAAa,MAAM,WAAW,cAAc,KAAK,UAAU,EAAE,QAAQ,OAAO;AAClF,UAAM,OAAO,MAAM,WAAW,QAAA;AAC9B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBAAqB,MAAuD;AAChF,UAAM,aAAa,MAAM,KAAK,cAAc,KAAK,WAAW,KAAK,MAAM;AACvE,UAAM,aAAa,MAAM,WAAW,cAAc,KAAK,UAAU,EAAE,QAAQ,MAAM;AACjF,WAAO,MAAM,WAAW,eAAA;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,uBAAuB,WAAmB,QAAmC;AACjF,QAAI,CAAC,KAAK,SAAU;AAEpB,QAAI;AACF,YAAM,UAAU,WAAW,MAAM,IAAI,SAAS;AAC9C,YAAM,KAAK,SAAS,YAAY,SAAS,EAAE,WAAW,MAAM;AAAA,IAC9D,SAAS,OAAO;AACd,UAAI,CAAC,eAAe,OAAO,eAAe,GAAG;AAC3C,gBAAQ,KAAK,4CAA4C,MAAM,IAAI,SAAS,KAAK,KAAK;AAAA,MACxF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,yBAAyB,QAAgD;AAC7E,QAAI,CAAC,KAAK,UAAU;AAClB,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AAEA,UAAM,WAA8B,CAAA;AACpC,UAAM,eAAe,WAAW,MAAM;AAGtC,qBAAiB,CAAC,MAAM,MAAM,KAAK,KAAK,SAAS,WAAW;AAC1D,UAAI,OAAO,SAAS,eAAe,KAAK,WAAW,YAAY,GAAG;AAChE,cAAM,YAAY,KAAK,MAAM,aAAa,MAAM;AAChD,cAAM,aAAa;AACnB,YAAI,YAAY;AAChB,YAAI,kBAAkB;AAGtB,yBAAiB,CAAC,WAAW,UAAU,KAAK,WAAW,WAAW;AAChE,cAAI,WAAW,SAAS,QAAQ;AAC9B,kBAAM,OAAO,MAAO,WAAoC,QAAA;AACxD,yBAAa,KAAK;AAClB,8BAAkB,KAAK,IAAI,iBAAiB,KAAK,YAAY;AAAA,UAC/D;AAAA,QACF;AAEA,YAAI,YAAY,GAAG;AACjB,mBAAS,KAAK,EAAE,WAAW,MAAM,WAAW,cAAc,iBAAiB;AAAA,QAC7E;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,oBACJ,kBACA,QACA,OACiB;AACjB,UAAM,WAAW,MAAM,KAAK,yBAAyB,MAAM;AAG3D,UAAM,aAAa,CAAA;AACnB,eAAW,WAAW,UAAU;AAC9B,UAAI,QAAQ,cAAc,iBAAkB;AAE5C,YAAM,WAAW,MAAM,KAAK,gBAAgB,QAAQ,WAAW,MAAM;AACrE,UAAI,SAAU;AAEd,iBAAW,KAAK,OAAO;AAAA,IACzB;AAEA,QAAI,WAAW,WAAW,GAAG;AAC3B,aAAO;AAAA,IACT;AAEA,eAAW,KAAK,CAAC,GAAG,MAAM,EAAE,eAAe,EAAE,YAAY;AACzD,UAAM,UAAU,WAAW,MAAM,GAAG,KAAK;AAEzC,QAAI,aAAa;AACjB,eAAW,WAAW,SAAS;AAC7B,YAAM,KAAK,uBAAuB,QAAQ,WAAW,MAAM;AAC3D,oBAAc,QAAQ;AAAA,IACxB;AAEA,YAAQ;AAAA,MACN,yBAAyB,QAAQ,MAAM,uBAAuB,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC;AAAA,IAAA;AAGpG,WAAO,QAAQ;AAAA,EACjB;AACF;"}
|
|
1
|
+
{"version":3,"file":"OPFSManager.js","sources":["../../../../src/cache/storage/opfs/OPFSManager.ts"],"sourcesContent":["import type { OPFSPrefix, OPFSPath, ProjectMetadata } from './types';\nimport { OPFSQuotaExceededError, isDOMException } from '../../../utils/errors';\n\n/**\n * OPFSManager - Unified OPFS management infrastructure\n *\n * Supports:\n * - Directory isolation by prefix (l2/resource)\n * - Range reads for on-demand decoding\n * - Streaming writes with automatic quota management\n * - Project-level LRU eviction\n */\nexport class OPFSManager {\n private opfsRoot: FileSystemDirectoryHandle | null = null;\n private initPromise: Promise<void> | null = null;\n maxSizeBytes: number = Infinity;\n readonly quotaThreshold: number;\n\n constructor(quotaThresholdPercent: number = 0.85) {\n this.quotaThreshold = quotaThresholdPercent;\n }\n\n async init(): Promise<void> {\n if (this.initPromise) return this.initPromise;\n\n this.initPromise = this.initStorage();\n return this.initPromise;\n }\n\n private async initStorage(): Promise<void> {\n this.opfsRoot = await navigator.storage.getDirectory();\n\n // Dynamic quota detection\n const estimate = await navigator.storage.estimate();\n const quota = estimate.quota || 5 * 1024 * 1024 * 1024;\n\n // Aggressive strategy: use 80% of browser quota\n this.maxSizeBytes = Math.floor(quota * 0.8);\n }\n\n /**\n * Get project directory by prefix\n */\n async getProjectDir(projectId: string, prefix: OPFSPrefix): Promise<FileSystemDirectoryHandle> {\n if (!this.opfsRoot) {\n throw new Error('[OPFSManager] Not initialized');\n }\n\n const dirName = `meframe-${prefix}-${projectId}`;\n return this.opfsRoot.getDirectoryHandle(dirName, { create: true });\n }\n\n /**\n * Write file with automatic quota management\n * Proactively checks quota before write; evicts old projects if threshold exceeded\n */\n async writeFile(path: OPFSPath, data: ArrayBuffer | ReadableStream<Uint8Array>): Promise<void> {\n await this.ensureQuota(path.projectId, path.prefix);\n\n try {\n await this.writeFileInternal(path, data);\n } catch (error) {\n if (isDOMException(error, 'QuotaExceededError')) {\n const evictedCount = await this.evictOldestProjects(path.projectId, path.prefix, 1);\n\n if (evictedCount === 0) {\n throw new OPFSQuotaExceededError(path.projectId, path.prefix, false);\n }\n\n if (data instanceof ReadableStream) {\n throw new OPFSQuotaExceededError(path.projectId, path.prefix, true);\n }\n\n await this.writeFileInternal(path, data);\n } else {\n throw error;\n }\n }\n }\n\n /**\n * Check if project is locked by any tab (actively being used)\n */\n private async isProjectLocked(projectId: string, prefix: OPFSPrefix): Promise<boolean> {\n if (!navigator.locks) {\n return false;\n }\n\n const lockName = `meframe-${prefix}-${projectId}`;\n const locks = await navigator.locks.query();\n return locks.held?.some((lock) => lock.name === lockName) ?? false;\n }\n\n /**\n * Ensure quota is within threshold before write\n * Proactively evicts old projects if usage exceeds threshold\n */\n private async ensureQuota(currentProjectId: string, prefix: OPFSPrefix): Promise<void> {\n const totalSize = await this.getTotalSize(prefix);\n const usagePercent = totalSize / this.maxSizeBytes;\n\n if (usagePercent > this.quotaThreshold) {\n const needToFreeBytes = totalSize - this.maxSizeBytes * this.quotaThreshold;\n const projectsToEvict = Math.ceil(needToFreeBytes / (totalSize / 10));\n\n await this.evictOldestProjects(currentProjectId, prefix, Math.max(1, projectsToEvict));\n }\n }\n\n /**\n * Get total size of all projects for a prefix\n */\n async getTotalSize(prefix: OPFSPrefix): Promise<number> {\n const projects = await this.listProjectsWithMetadata(prefix);\n return projects.reduce((sum, p) => sum + p.size, 0);\n }\n\n private async writeFileInternal(\n path: OPFSPath,\n data: ArrayBuffer | ReadableStream<Uint8Array>\n ): Promise<void> {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n const fileHandle = await projectDir.getFileHandle(path.fileName, { create: true });\n const writable = await fileHandle.createWritable();\n\n if (data instanceof ArrayBuffer) {\n await writable.write(data);\n } else {\n const reader = data.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n const buffer = value.buffer.slice(\n value.byteOffset,\n value.byteOffset + value.byteLength\n );\n await writable.write(buffer as ArrayBuffer);\n }\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n await writable.close();\n }\n\n /**\n * Read entire file\n */\n async readFile(path: OPFSPath): Promise<ArrayBuffer> {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n const fileHandle = await projectDir.getFileHandle(path.fileName, { create: false });\n const file = await fileHandle.getFile();\n return await file.arrayBuffer();\n }\n\n /**\n * Read byte range from file (for on-demand decoding)\n */\n async readRange(path: OPFSPath, start: number, end: number): Promise<ArrayBuffer> {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n const fileHandle = await projectDir.getFileHandle(path.fileName, { create: false });\n const file = await fileHandle.getFile();\n const slice = file.slice(start, end);\n return await slice.arrayBuffer();\n }\n\n /**\n * Delete file\n */\n async deleteFile(path: OPFSPath): Promise<void> {\n try {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n await projectDir.removeEntry(path.fileName);\n } catch (error) {\n if (!isDOMException(error, 'NotFoundError')) {\n console.warn(`[OPFSManager] Failed to delete file ${path.fileName}:`, error);\n }\n }\n }\n\n /**\n * Check if file exists\n */\n async exists(path: OPFSPath): Promise<boolean> {\n try {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n await projectDir.getFileHandle(path.fileName, { create: false });\n return true;\n } catch (error) {\n if (isDOMException(error, 'NotFoundError')) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Get file size\n */\n async getFileSize(path: OPFSPath): Promise<number> {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n const fileHandle = await projectDir.getFileHandle(path.fileName, { create: false });\n const file = await fileHandle.getFile();\n return file.size;\n }\n\n /**\n * Create writable stream for streaming writes\n */\n async createWritableStream(path: OPFSPath): Promise<FileSystemWritableFileStream> {\n const projectDir = await this.getProjectDir(path.projectId, path.prefix);\n const fileHandle = await projectDir.getFileHandle(path.fileName, { create: true });\n return await fileHandle.createWritable();\n }\n\n /**\n * Delete entire project directory\n */\n async deleteProjectDirectory(projectId: string, prefix: OPFSPrefix): Promise<void> {\n if (!this.opfsRoot) return;\n\n try {\n const dirName = `meframe-${prefix}-${projectId}`;\n await this.opfsRoot.removeEntry(dirName, { recursive: true });\n } catch (error) {\n if (!isDOMException(error, 'NotFoundError')) {\n console.warn(`[OPFSManager] Failed to remove directory ${prefix}-${projectId}:`, error);\n }\n }\n }\n\n /**\n * List all projects with size and lastModified metadata\n * Used for LRU eviction\n */\n async listProjectsWithMetadata(prefix: OPFSPrefix): Promise<ProjectMetadata[]> {\n if (!this.opfsRoot) {\n throw new Error('[OPFSManager] Not initialized');\n }\n\n const projects: ProjectMetadata[] = [];\n const searchPrefix = `meframe-${prefix}-`;\n\n // @ts-expect-error - AsyncIterator type not well-supported\n for await (const [name, handle] of this.opfsRoot.entries()) {\n if (handle.kind === 'directory' && name.startsWith(searchPrefix)) {\n const projectId = name.slice(searchPrefix.length);\n const projectDir = handle as FileSystemDirectoryHandle;\n let totalSize = 0;\n let maxLastModified = 0;\n\n // @ts-expect-error - AsyncIterator type not well-supported\n for await (const [_fileName, fileHandle] of projectDir.entries()) {\n if (fileHandle.kind === 'file') {\n try {\n const file = await (fileHandle as FileSystemFileHandle).getFile();\n totalSize += file.size;\n maxLastModified = Math.max(maxLastModified, file.lastModified);\n } catch (error) {\n // Race: file may be deleted/evicted between directory iteration and getFile().\n // Under CPU throttling this becomes much more likely; ignore and continue.\n if (isDOMException(error, 'NotFoundError')) {\n continue;\n }\n throw error;\n }\n }\n }\n\n if (totalSize > 0) {\n projects.push({ projectId, size: totalSize, lastModified: maxLastModified });\n }\n }\n }\n\n return projects;\n }\n\n /**\n * Evict oldest projects (by lastModified) excluding current project and locked projects\n * Returns number of projects evicted\n */\n async evictOldestProjects(\n currentProjectId: string,\n prefix: OPFSPrefix,\n count: number\n ): Promise<number> {\n const projects = await this.listProjectsWithMetadata(prefix);\n\n // Filter candidates: exclude current project and locked projects\n const candidates = [];\n for (const project of projects) {\n if (project.projectId === currentProjectId) continue;\n\n const isLocked = await this.isProjectLocked(project.projectId, prefix);\n if (isLocked) continue;\n\n candidates.push(project);\n }\n\n if (candidates.length === 0) {\n return 0;\n }\n\n candidates.sort((a, b) => a.lastModified - b.lastModified);\n const toEvict = candidates.slice(0, count);\n\n let freedBytes = 0;\n for (const project of toEvict) {\n await this.deleteProjectDirectory(project.projectId, prefix);\n freedBytes += project.size;\n }\n\n console.log(\n `[OPFSManager] Evicted ${toEvict.length} project(s), freed ${(freedBytes / 1024 / 1024).toFixed(0)}MB`\n );\n\n return toEvict.length;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -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;AAUjB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AASpD;;;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,CAAsB;IAC1C,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;
|
|
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;AAUjB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AASpD;;;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,CAAsB;IAC1C,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;IAoBvB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;YAuC1B,kBAAkB;YAqBlB,OAAO;IAYrB,OAAO,CAAC,WAAW;IAYnB,OAAO,IAAI,IAAI;IAUf,OAAO,CAAC,YAAY,CAIlB;IAEF,OAAO,CAAC,UAAU,CAoBhB;IAEF,OAAO,CAAC,mBAAmB;IAK3B,OAAO,CAAC,0BAA0B;CAUnC"}
|
|
@@ -402,6 +402,9 @@ class PlaybackController {
|
|
|
402
402
|
this.orchestrator.cacheManager.setWindow(timeUs);
|
|
403
403
|
}
|
|
404
404
|
checkAndPreheatWindow() {
|
|
405
|
+
if (this.orchestrator.cacheManager.isExporting) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
405
408
|
if (this.preheatInProgress || this.fsm.snapshot.state !== PlaybackState.Playing) {
|
|
406
409
|
return;
|
|
407
410
|
}
|
|
@@ -415,6 +418,9 @@ class PlaybackController {
|
|
|
415
418
|
}
|
|
416
419
|
}
|
|
417
420
|
async preheatNextWindow() {
|
|
421
|
+
if (this.orchestrator.cacheManager.isExporting) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
418
424
|
if (this.preheatInProgress) return;
|
|
419
425
|
this.preheatInProgress = true;
|
|
420
426
|
try {
|
|
@@ -492,7 +498,9 @@ class PlaybackController {
|
|
|
492
498
|
fps: model.fps || 30,
|
|
493
499
|
backgroundColor: model.renderConfig?.backgroundColor || "#000"
|
|
494
500
|
});
|
|
495
|
-
|
|
501
|
+
if (!this.orchestrator.cacheManager.isExporting) {
|
|
502
|
+
void this.audioSession.ensureAudioForTime(this.currentTimeUs, { mode: "probe" });
|
|
503
|
+
}
|
|
496
504
|
void this.renderCurrentFrame(this.currentTimeUs, { mode: "blocking" });
|
|
497
505
|
};
|
|
498
506
|
setupEventListeners() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlaybackController.js","sources":["../../src/controllers/PlaybackController.ts"],"sourcesContent":["import type {\n IPlaybackController,\n PlaybackOptions,\n IEventBus,\n PreviewHandle,\n TimeUs,\n} from './types';\nimport {\n PlaybackActionType,\n PlaybackCommandType,\n PlaybackState,\n type OpToken,\n type PlaybackAction,\n type PlaybackCommand,\n} from './types';\nimport { MeframeEvent } from '../event/events';\nimport type { Orchestrator } from '../orchestrator';\nimport type { RequestMode } from '../orchestrator/types';\nimport { WaiterReplacedError } from '../utils/errors';\nimport { VideoComposer } from '../stages/compose/VideoComposer';\nimport { isVideoClip } from '../model/types';\nimport type { AudioPreviewSession } from '../orchestrator/AudioPreviewSession';\n\nimport { PlaybackStateMachine } from './PlaybackStateMachine';\n\n/**\n * Playback controller for preview\n * Internal implementation - not exposed directly to external consumers\n */\nexport class PlaybackController implements IPlaybackController, PreviewHandle {\n private orchestrator: Orchestrator;\n private eventBus: IEventBus;\n private canvas: HTMLCanvasElement | OffscreenCanvas;\n private videoComposer: VideoComposer | null = null;\n\n // Playback time (external)\n currentTimeUs: TimeUs = 0;\n private playbackRate = 1.0;\n private volume = 1.0;\n private loop = false;\n\n // Time base\n private rafId: number | null = null;\n private startTimeUs: TimeUs = 0; // AudioContext timeline origin (microseconds)\n\n // Frame stats\n private frameCount = 0;\n private lastFrameTime = 0;\n private fps = 0;\n\n // Audio\n private audioContext: AudioContext;\n private audioSession: AudioPreviewSession;\n private lastAudioScheduleTime = 0;\n private readonly AUDIO_SCHEDULE_INTERVAL = 250_000; // 250ms\n\n // State machine\n private fsm = new PlaybackStateMachine();\n\n // Unified window management for both video and audio\n private windowEnd: TimeUs = 0;\n private readonly WINDOW_DURATION = 3_000_000; // 3s decode window\n private readonly PREHEAT_DISTANCE = 1_000_000; // 1s preheat trigger distance\n private preheatInProgress = false;\n\n constructor(orchestrator: Orchestrator, eventBus: IEventBus, options: PlaybackOptions) {\n this.orchestrator = orchestrator;\n this.audioSession = orchestrator.audio.preview;\n this.eventBus = eventBus;\n this.canvas = options.canvas;\n this.audioContext = new AudioContext();\n\n const model = orchestrator.compositionModel;\n const width = model?.renderConfig?.width || this.canvas.width || 720;\n const height = model?.renderConfig?.height || this.canvas.height || 1280;\n\n this.videoComposer = new VideoComposer({\n width,\n height,\n fps: model?.fps || 30,\n backgroundColor: model?.renderConfig?.backgroundColor || '#000',\n externalCanvas: this.canvas,\n });\n\n if (options.startUs !== undefined) {\n this.currentTimeUs = options.startUs;\n }\n if (options.rate !== undefined) {\n this.playbackRate = options.rate;\n }\n if (options.loop !== undefined) {\n this.loop = options.loop;\n }\n\n this.setupEventListeners();\n\n if (options.autoStart) {\n this.play();\n }\n }\n\n async renderCover(): Promise<void> {\n await this.renderCurrentFrame(0, { mode: 'blocking' });\n }\n\n // ========= Public API =========\n\n play(): void {\n this.dispatch({ type: PlaybackActionType.Play });\n }\n\n pause(): void {\n this.dispatch({ type: PlaybackActionType.Pause });\n }\n\n stop(): void {\n this.dispatch({ type: PlaybackActionType.Stop });\n }\n\n async seek(timeUs: TimeUs): Promise<void> {\n const { done } = this.dispatch({\n type: PlaybackActionType.Seek,\n timeUs,\n durationUs: this.duration,\n });\n await done;\n }\n\n setRate(rate: number): void {\n const currentTimeUs = this.currentTimeUs;\n this.playbackRate = rate;\n\n // Keep currentTimeUs stable; update the time base for AudioContext clock mapping.\n this.startTimeUs = this.audioContext.currentTime * 1_000_000 - currentTimeUs / rate;\n this.audioSession.setPlaybackRate(this.playbackRate);\n\n this.eventBus.emit(MeframeEvent.PlaybackRateChange, { rate });\n }\n\n setVolume(volume: number): void {\n this.volume = Math.max(0, Math.min(1, volume));\n this.audioSession.setVolume(this.volume);\n this.eventBus.emit(MeframeEvent.PlaybackVolumeChange, { volume: this.volume });\n }\n\n setMute(muted: boolean): void {\n if (muted) {\n this.audioSession.stopPlayback();\n return;\n }\n if (this.fsm.snapshot.state === PlaybackState.Playing) {\n void this.audioSession.startPlayback(this.currentTimeUs, this.audioContext);\n }\n }\n\n setLoop(loop: boolean): void {\n this.loop = loop;\n }\n\n get duration(): TimeUs {\n return this.orchestrator.compositionModel?.durationUs ?? 0;\n }\n\n get isPlaying(): boolean {\n return this.fsm.snapshot.state === PlaybackState.Playing;\n }\n\n resume(): void {\n this.play();\n }\n\n on(event: string, handler: (payload: any) => void): void {\n this.eventBus.on(event as MeframeEvent, handler);\n }\n\n off(event: string, handler: (payload: any) => void): void {\n this.eventBus.off(event as MeframeEvent, handler);\n }\n\n // ========= State machine wiring =========\n\n private dispatch(action: PlaybackAction): { token: OpToken; done: Promise<void> } {\n const { token, commands } = this.fsm.dispatch(action, { currentTimeUs: this.currentTimeUs });\n const done = this.executeCommands(commands, token);\n return { token, done };\n }\n\n private executeCommands(commands: PlaybackCommand[], token: OpToken): Promise<void> {\n const maybe = this.executeSeq(commands, token, 0);\n return maybe ?? Promise.resolve();\n }\n\n private executeSeq(\n commands: PlaybackCommand[],\n token: OpToken,\n startIndex: number\n ): Promise<void> | void {\n for (let i = startIndex; i < commands.length; i++) {\n if (!this.isCurrentToken(token)) return;\n const maybe = this.executeCommand(commands[i]!, token);\n if (maybe) {\n return maybe.then(() => {\n if (!this.isCurrentToken(token)) return;\n const cont = this.executeSeq(commands, token, i + 1);\n return cont ?? Promise.resolve();\n });\n }\n }\n }\n\n private executePar(commands: PlaybackCommand[], token: OpToken): Promise<any> | void {\n const promises: Promise<void>[] = [];\n for (const c of commands) {\n if (!this.isCurrentToken(token)) return;\n const maybe = this.executeCommand(c, token);\n if (maybe) promises.push(maybe);\n }\n if (promises.length === 0) return;\n return Promise.all(promises);\n }\n\n private executeCommand(command: PlaybackCommand, token: OpToken): Promise<any> | void {\n if (!this.isCurrentToken(token)) return;\n\n switch (command.type) {\n case PlaybackCommandType.Seq:\n return this.executeSeq(command.commands, token, 0);\n case PlaybackCommandType.Par:\n return this.executePar(command.commands, token);\n case PlaybackCommandType.Try: {\n const handleError = (error: unknown): Promise<void> | void => {\n if (!this.isCurrentToken(token)) return;\n if (command.ignoreWaiterReplacedError && error instanceof WaiterReplacedError) return;\n if (command.logPrefix) console.error(command.logPrefix, error);\n const onErrorDone = command.onError ? this.dispatch(command.onError).done : undefined;\n const normalizeError = (e: unknown): Error => {\n if (e instanceof Error) return e;\n return new Error(typeof e === 'string' ? e : JSON.stringify(e));\n };\n const emit = () => {\n if (command.emitPlaybackError) {\n const err = normalizeError(error);\n // PlaybackError: direct playback channel error for advanced consumers.\n this.eventBus.emit(MeframeEvent.PlaybackError, err);\n // Error: generic error channel expected by higher-level wrappers (e.g. @meframe/axii).\n this.eventBus.emit(MeframeEvent.Error, {\n source: 'playback',\n error: err,\n context: {\n command: command.logPrefix,\n onError: command.onError?.type,\n },\n recoverable: false,\n });\n }\n };\n if (onErrorDone) {\n return onErrorDone.then(() => {\n emit();\n });\n }\n emit();\n };\n\n try {\n const maybe = this.executeCommand(command.command, token);\n if (maybe) {\n return maybe.catch(handleError);\n }\n return;\n } catch (error) {\n return handleError(error) ?? Promise.resolve();\n }\n }\n case PlaybackCommandType.Dispatch:\n return this.dispatch(command.action).done;\n case PlaybackCommandType.SetTime: {\n this.currentTimeUs = command.timeUs;\n return;\n }\n case PlaybackCommandType.SetFrozenTime:\n case PlaybackCommandType.SetWantsPlay:\n case PlaybackCommandType.SetState: {\n // managed inside fsm\n return;\n }\n case PlaybackCommandType.CancelRaf: {\n this.cancelRaf();\n return;\n }\n case PlaybackCommandType.StopAudio: {\n this.audioSession.stopPlayback();\n return;\n }\n case PlaybackCommandType.ResetAudioPlaybackStates: {\n this.audioSession.resetPlaybackStates();\n return;\n }\n case PlaybackCommandType.ResetAudioSession: {\n this.audioSession.reset();\n return;\n }\n case PlaybackCommandType.ClearCanvas: {\n this.clearCanvas();\n return;\n }\n case PlaybackCommandType.SetLastAudioScheduleTime: {\n this.lastAudioScheduleTime = command.timeUs;\n return;\n }\n case PlaybackCommandType.SetStartTimeBase: {\n this.startTimeUs = command.startTimeUs;\n return;\n }\n case PlaybackCommandType.SyncTimeBaseToAudioClock: {\n this.startTimeUs =\n this.audioContext.currentTime * 1_000_000 - command.timeUs / this.playbackRate;\n return;\n }\n case PlaybackCommandType.InitWindow: {\n this.initWindow(command.timeUs);\n return;\n }\n case PlaybackCommandType.SetCacheWindow: {\n this.orchestrator.cacheManager.setWindow(command.timeUs);\n return;\n }\n case PlaybackCommandType.Emit: {\n if (command.payload === undefined) {\n this.eventBus.emit(command.event as any);\n } else {\n this.eventBus.emit(command.event as any, command.payload);\n }\n return;\n }\n case PlaybackCommandType.RenderFrame: {\n return this.renderCurrentFrame(command.timeUs, {\n mode: command.mode,\n relativeTimeUs: command.relativeTimeUs,\n });\n }\n case PlaybackCommandType.MaybeRenderKeyframePreview: {\n return this.orchestrator.tryRenderKeyframe(command.timeUs).then((keyframeTimeUs) => {\n if (!this.isCurrentToken(token)) return;\n if (keyframeTimeUs === null) return;\n return this.orchestrator\n .getRenderState(command.timeUs, {\n mode: 'probe',\n relativeTimeUs: keyframeTimeUs,\n })\n .then((keyframeRenderState) => {\n if (!this.isCurrentToken(token)) return;\n if (!keyframeRenderState) return;\n return this.compose(command.timeUs, keyframeRenderState);\n });\n });\n }\n case PlaybackCommandType.EnsureAudio: {\n return this.audioSession.ensureAudioForTime(command.timeUs, {\n mode: command.mode,\n });\n }\n case PlaybackCommandType.GetFrame: {\n return this.orchestrator.getFrame(command.timeUs, {\n mode: command.mode,\n preheat: command.preheat,\n });\n }\n case PlaybackCommandType.StartAudioPlayback: {\n return this.audioSession.startPlayback(command.timeUs, this.audioContext);\n }\n case PlaybackCommandType.ProbeStartReady: {\n const audioReady = this.audioSession.isPreviewMixBlockCached(command.timeUs);\n const videoReady = this.isVideoResourceReadyAtTime(command.timeUs);\n\n if (audioReady && videoReady) return;\n\n // Kick background preparation (best-effort).\n if (!audioReady) {\n void this.audioSession.ensureAudioForTime(command.timeUs, { mode: 'probe' });\n }\n if (!videoReady) {\n void this.orchestrator.getFrame(command.timeUs, { mode: 'probe' });\n }\n\n // Enter buffering and bump token to cancel the remaining start sequence.\n this.dispatch({\n type: PlaybackActionType.EnterBuffering,\n timeUs: command.timeUs,\n bumpToken: true,\n reason: 'startup',\n });\n return;\n }\n case PlaybackCommandType.StartRafLoop: {\n this.startPlaybackLoop(token);\n return;\n }\n }\n }\n\n private cancelRaf(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n }\n\n private isCurrentToken(token: OpToken): boolean {\n return token === this.fsm.snapshot.token;\n }\n\n private startPlaybackLoop(token: OpToken): void {\n this.rafId = requestAnimationFrame(() => {\n void this.onRafTick(token);\n });\n }\n\n private async onRafTick(token: OpToken): Promise<void> {\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) {\n return;\n }\n\n const candidateTimeUs =\n (this.audioContext.currentTime * 1_000_000 - this.startTimeUs) * this.playbackRate;\n this.dispatch({\n type: PlaybackActionType.ClockTick,\n candidateTimeUs,\n durationUs: this.duration,\n loop: this.loop,\n audioNowUs: this.audioContext.currentTime * 1_000_000,\n });\n\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) {\n return;\n }\n\n // Audio probe: if the audio window isn't ready, enter buffering (freeze timeline)\n // and kick background audio preparation.\n // Note: preview audio readiness is driven by 60s mix-block cache inside AudioPreviewSession.\n // We intentionally avoid additional resource-window probing here to prevent buffering oscillation.\n\n // Throttle audio scheduling.\n if (this.currentTimeUs - this.lastAudioScheduleTime >= this.AUDIO_SCHEDULE_INTERVAL) {\n // Fire-and-forget: AudioPreviewSession runs its own background scheduling loop.\n // We avoid awaiting here to keep the render loop responsive.\n void this.audioSession.scheduleAudio(this.currentTimeUs, this.audioContext);\n this.lastAudioScheduleTime = this.currentTimeUs;\n }\n\n // If we're close to the 60s block boundary and the next mixed block isn't ready yet,\n // enter buffering to avoid hard silence at the boundary.\n if (this.audioSession.shouldEnterBufferingForUpcomingPreviewAudio(this.currentTimeUs)) {\n // Best-effort preheat; buffering path will call EnsureAudio(blocking).\n void this.audioSession.ensureAudioForTime(this.currentTimeUs, { mode: 'probe' });\n this.dispatch({\n type: PlaybackActionType.EnterBuffering,\n timeUs: this.currentTimeUs,\n reason: 'audio',\n });\n return;\n }\n\n const renderState = await this.orchestrator.getRenderState(this.currentTimeUs, {\n mode: 'probe',\n });\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) {\n return;\n }\n\n if (!renderState) {\n this.dispatch({ type: PlaybackActionType.EnterBuffering, timeUs: this.currentTimeUs });\n return;\n }\n\n await this.compose(this.currentTimeUs, renderState);\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) return;\n\n this.updateFps();\n this.frameCount++;\n\n // Unified cache window update.\n this.orchestrator.cacheManager.setWindow(this.currentTimeUs);\n\n this.checkAndPreheatWindow();\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) return;\n\n this.startPlaybackLoop(token);\n }\n\n private updateFps(): void {\n const now = performance.now();\n if (this.lastFrameTime > 0) {\n const deltaTime = now - this.lastFrameTime;\n const instantFps = 1000 / deltaTime;\n this.fps = this.fps > 0 ? this.fps * 0.9 + instantFps * 0.1 : instantFps;\n }\n this.lastFrameTime = now;\n }\n\n private initWindow(timeUs: TimeUs): void {\n this.windowEnd = timeUs + this.WINDOW_DURATION;\n this.preheatInProgress = false;\n this.orchestrator.cacheManager.setWindow(timeUs);\n }\n\n private checkAndPreheatWindow(): void {\n if (this.preheatInProgress || this.fsm.snapshot.state !== PlaybackState.Playing) {\n return;\n }\n\n const distanceToWindowEnd = this.windowEnd - this.currentTimeUs;\n if (distanceToWindowEnd < 0) {\n this.initWindow(this.currentTimeUs);\n return;\n }\n\n if (distanceToWindowEnd > 0 && distanceToWindowEnd <= this.PREHEAT_DISTANCE) {\n void this.preheatNextWindow();\n }\n }\n\n async preheatNextWindow(): Promise<void> {\n if (this.preheatInProgress) return;\n\n this.preheatInProgress = true;\n try {\n const windowStart = this.currentTimeUs;\n const windowEnd = windowStart + this.WINDOW_DURATION;\n\n const clipsInWindow =\n this.orchestrator.compositionModel?.getClipsInRange(windowStart, windowEnd) ?? [];\n const preheatPromises: Promise<any>[] = [];\n\n for (const clip of clipsInWindow) {\n if (!isVideoClip(clip)) continue;\n\n const clipWindowStart = Math.max(0, windowStart - clip.startUs);\n const clipWindowEnd = Math.min(clip.durationUs, windowEnd - clip.startUs);\n if (clipWindowStart >= clipWindowEnd) continue;\n\n preheatPromises.push(\n this.orchestrator.preheatClipWindow(clip.id, clipWindowStart, clipWindowEnd, windowStart)\n );\n }\n\n // Audio preheat is handled inside AudioPreviewSession via mix-block scheduling & cache.\n\n await Promise.all(preheatPromises);\n this.windowEnd = windowEnd;\n } catch (error) {\n console.warn('[PlaybackController] Preheat failed:', error);\n } finally {\n this.preheatInProgress = false;\n }\n }\n\n private async renderCurrentFrame(\n timeUs: TimeUs,\n options: { mode: RequestMode; relativeTimeUs?: TimeUs }\n ): Promise<void> {\n if (!this.videoComposer) {\n console.error('[PlaybackController] VideoComposer not initialized');\n return;\n }\n\n const renderState = await this.orchestrator.getRenderState(timeUs, {\n mode: options.mode,\n relativeTimeUs: options.relativeTimeUs,\n });\n\n if (!renderState) {\n return;\n }\n\n await this.compose(timeUs, renderState);\n }\n\n private async compose(\n timeUs: TimeUs,\n renderState: { layers: any[]; transition?: any }\n ): Promise<void> {\n if (!this.videoComposer) return;\n await this.videoComposer.composeFrame({\n timeUs,\n layers: renderState.layers,\n transition: renderState.transition,\n });\n }\n\n private clearCanvas(): void {\n const ctx = this.canvas.getContext('2d') as\n | CanvasRenderingContext2D\n | OffscreenCanvasRenderingContext2D\n | null;\n if (ctx && 'clearRect' in ctx) {\n ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n }\n }\n\n // ========= Cleanup / event handlers =========\n\n dispose(): void {\n this.stop();\n this.eventBus.off(MeframeEvent.CacheCover, this.onCacheCover);\n this.eventBus.off(MeframeEvent.ModelSet, this.onModelSet);\n if (this.videoComposer) {\n this.videoComposer.dispose();\n this.videoComposer = null;\n }\n }\n\n private onCacheCover = (): void => {\n if (this.fsm.snapshot.state === PlaybackState.Idle && this.currentTimeUs === 0) {\n void this.renderCurrentFrame(0, { mode: 'blocking' });\n }\n };\n\n private onModelSet = (): void => {\n if (!this.videoComposer || !this.orchestrator.compositionModel) return;\n\n // Model switching cancels in-flight async chains, but must NOT enter buffering or reset time/state.\n // Buffering runs a long async sequence; if cancelled mid-way it can leave the FSM stuck.\n this.dispatch({ type: PlaybackActionType.ModelSet });\n\n const model = this.orchestrator.compositionModel;\n this.videoComposer.updateConfig({\n width: model.renderConfig?.width || 720,\n height: model.renderConfig?.height || 1280,\n fps: model.fps || 30,\n backgroundColor: model.renderConfig?.backgroundColor || '#000',\n });\n\n // Best-effort background audio preheat (non-blocking).\n void this.audioSession.ensureAudioForTime(this.currentTimeUs, { mode: 'probe' });\n void this.renderCurrentFrame(this.currentTimeUs, { mode: 'blocking' });\n };\n\n private setupEventListeners(): void {\n this.eventBus.on(MeframeEvent.CacheCover, this.onCacheCover);\n this.eventBus.on(MeframeEvent.ModelSet, this.onModelSet);\n }\n\n private isVideoResourceReadyAtTime(timeUs: TimeUs): boolean {\n const model = this.orchestrator.compositionModel;\n if (!model) return true;\n const clip = model.getClipsAtTime(timeUs, model.mainTrackId)[0];\n if (!clip || !('resourceId' in clip) || typeof (clip as any).resourceId !== 'string')\n return true;\n const resourceId = (clip as any).resourceId as string;\n const resource = model.getResource(resourceId);\n return resource?.state === 'ready';\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlaybackController.js","sources":["../../src/controllers/PlaybackController.ts"],"sourcesContent":["import type {\n IPlaybackController,\n PlaybackOptions,\n IEventBus,\n PreviewHandle,\n TimeUs,\n} from './types';\nimport {\n PlaybackActionType,\n PlaybackCommandType,\n PlaybackState,\n type OpToken,\n type PlaybackAction,\n type PlaybackCommand,\n} from './types';\nimport { MeframeEvent } from '../event/events';\nimport type { Orchestrator } from '../orchestrator';\nimport type { RequestMode } from '../orchestrator/types';\nimport { WaiterReplacedError } from '../utils/errors';\nimport { VideoComposer } from '../stages/compose/VideoComposer';\nimport { isVideoClip } from '../model/types';\nimport type { AudioPreviewSession } from '../orchestrator/AudioPreviewSession';\n\nimport { PlaybackStateMachine } from './PlaybackStateMachine';\n\n/**\n * Playback controller for preview\n * Internal implementation - not exposed directly to external consumers\n */\nexport class PlaybackController implements IPlaybackController, PreviewHandle {\n private orchestrator: Orchestrator;\n private eventBus: IEventBus;\n private canvas: HTMLCanvasElement | OffscreenCanvas;\n private videoComposer: VideoComposer | null = null;\n\n // Playback time (external)\n currentTimeUs: TimeUs = 0;\n private playbackRate = 1.0;\n private volume = 1.0;\n private loop = false;\n\n // Time base\n private rafId: number | null = null;\n private startTimeUs: TimeUs = 0; // AudioContext timeline origin (microseconds)\n\n // Frame stats\n private frameCount = 0;\n private lastFrameTime = 0;\n private fps = 0;\n\n // Audio\n private audioContext: AudioContext;\n private audioSession: AudioPreviewSession;\n private lastAudioScheduleTime = 0;\n private readonly AUDIO_SCHEDULE_INTERVAL = 250_000; // 250ms\n\n // State machine\n private fsm = new PlaybackStateMachine();\n\n // Unified window management for both video and audio\n private windowEnd: TimeUs = 0;\n private readonly WINDOW_DURATION = 3_000_000; // 3s decode window\n private readonly PREHEAT_DISTANCE = 1_000_000; // 1s preheat trigger distance\n private preheatInProgress = false;\n\n constructor(orchestrator: Orchestrator, eventBus: IEventBus, options: PlaybackOptions) {\n this.orchestrator = orchestrator;\n this.audioSession = orchestrator.audio.preview;\n this.eventBus = eventBus;\n this.canvas = options.canvas;\n this.audioContext = new AudioContext();\n\n const model = orchestrator.compositionModel;\n const width = model?.renderConfig?.width || this.canvas.width || 720;\n const height = model?.renderConfig?.height || this.canvas.height || 1280;\n\n this.videoComposer = new VideoComposer({\n width,\n height,\n fps: model?.fps || 30,\n backgroundColor: model?.renderConfig?.backgroundColor || '#000',\n externalCanvas: this.canvas,\n });\n\n if (options.startUs !== undefined) {\n this.currentTimeUs = options.startUs;\n }\n if (options.rate !== undefined) {\n this.playbackRate = options.rate;\n }\n if (options.loop !== undefined) {\n this.loop = options.loop;\n }\n\n this.setupEventListeners();\n\n if (options.autoStart) {\n this.play();\n }\n }\n\n async renderCover(): Promise<void> {\n await this.renderCurrentFrame(0, { mode: 'blocking' });\n }\n\n // ========= Public API =========\n\n play(): void {\n this.dispatch({ type: PlaybackActionType.Play });\n }\n\n pause(): void {\n this.dispatch({ type: PlaybackActionType.Pause });\n }\n\n stop(): void {\n this.dispatch({ type: PlaybackActionType.Stop });\n }\n\n async seek(timeUs: TimeUs): Promise<void> {\n const { done } = this.dispatch({\n type: PlaybackActionType.Seek,\n timeUs,\n durationUs: this.duration,\n });\n await done;\n }\n\n setRate(rate: number): void {\n const currentTimeUs = this.currentTimeUs;\n this.playbackRate = rate;\n\n // Keep currentTimeUs stable; update the time base for AudioContext clock mapping.\n this.startTimeUs = this.audioContext.currentTime * 1_000_000 - currentTimeUs / rate;\n this.audioSession.setPlaybackRate(this.playbackRate);\n\n this.eventBus.emit(MeframeEvent.PlaybackRateChange, { rate });\n }\n\n setVolume(volume: number): void {\n this.volume = Math.max(0, Math.min(1, volume));\n this.audioSession.setVolume(this.volume);\n this.eventBus.emit(MeframeEvent.PlaybackVolumeChange, { volume: this.volume });\n }\n\n setMute(muted: boolean): void {\n if (muted) {\n this.audioSession.stopPlayback();\n return;\n }\n if (this.fsm.snapshot.state === PlaybackState.Playing) {\n void this.audioSession.startPlayback(this.currentTimeUs, this.audioContext);\n }\n }\n\n setLoop(loop: boolean): void {\n this.loop = loop;\n }\n\n get duration(): TimeUs {\n return this.orchestrator.compositionModel?.durationUs ?? 0;\n }\n\n get isPlaying(): boolean {\n return this.fsm.snapshot.state === PlaybackState.Playing;\n }\n\n resume(): void {\n this.play();\n }\n\n on(event: string, handler: (payload: any) => void): void {\n this.eventBus.on(event as MeframeEvent, handler);\n }\n\n off(event: string, handler: (payload: any) => void): void {\n this.eventBus.off(event as MeframeEvent, handler);\n }\n\n // ========= State machine wiring =========\n\n private dispatch(action: PlaybackAction): { token: OpToken; done: Promise<void> } {\n const { token, commands } = this.fsm.dispatch(action, { currentTimeUs: this.currentTimeUs });\n const done = this.executeCommands(commands, token);\n return { token, done };\n }\n\n private executeCommands(commands: PlaybackCommand[], token: OpToken): Promise<void> {\n const maybe = this.executeSeq(commands, token, 0);\n return maybe ?? Promise.resolve();\n }\n\n private executeSeq(\n commands: PlaybackCommand[],\n token: OpToken,\n startIndex: number\n ): Promise<void> | void {\n for (let i = startIndex; i < commands.length; i++) {\n if (!this.isCurrentToken(token)) return;\n const maybe = this.executeCommand(commands[i]!, token);\n if (maybe) {\n return maybe.then(() => {\n if (!this.isCurrentToken(token)) return;\n const cont = this.executeSeq(commands, token, i + 1);\n return cont ?? Promise.resolve();\n });\n }\n }\n }\n\n private executePar(commands: PlaybackCommand[], token: OpToken): Promise<any> | void {\n const promises: Promise<void>[] = [];\n for (const c of commands) {\n if (!this.isCurrentToken(token)) return;\n const maybe = this.executeCommand(c, token);\n if (maybe) promises.push(maybe);\n }\n if (promises.length === 0) return;\n return Promise.all(promises);\n }\n\n private executeCommand(command: PlaybackCommand, token: OpToken): Promise<any> | void {\n if (!this.isCurrentToken(token)) return;\n\n switch (command.type) {\n case PlaybackCommandType.Seq:\n return this.executeSeq(command.commands, token, 0);\n case PlaybackCommandType.Par:\n return this.executePar(command.commands, token);\n case PlaybackCommandType.Try: {\n const handleError = (error: unknown): Promise<void> | void => {\n if (!this.isCurrentToken(token)) return;\n if (command.ignoreWaiterReplacedError && error instanceof WaiterReplacedError) return;\n if (command.logPrefix) console.error(command.logPrefix, error);\n const onErrorDone = command.onError ? this.dispatch(command.onError).done : undefined;\n const normalizeError = (e: unknown): Error => {\n if (e instanceof Error) return e;\n return new Error(typeof e === 'string' ? e : JSON.stringify(e));\n };\n const emit = () => {\n if (command.emitPlaybackError) {\n const err = normalizeError(error);\n // PlaybackError: direct playback channel error for advanced consumers.\n this.eventBus.emit(MeframeEvent.PlaybackError, err);\n // Error: generic error channel expected by higher-level wrappers (e.g. @meframe/axii).\n this.eventBus.emit(MeframeEvent.Error, {\n source: 'playback',\n error: err,\n context: {\n command: command.logPrefix,\n onError: command.onError?.type,\n },\n recoverable: false,\n });\n }\n };\n if (onErrorDone) {\n return onErrorDone.then(() => {\n emit();\n });\n }\n emit();\n };\n\n try {\n const maybe = this.executeCommand(command.command, token);\n if (maybe) {\n return maybe.catch(handleError);\n }\n return;\n } catch (error) {\n return handleError(error) ?? Promise.resolve();\n }\n }\n case PlaybackCommandType.Dispatch:\n return this.dispatch(command.action).done;\n case PlaybackCommandType.SetTime: {\n this.currentTimeUs = command.timeUs;\n return;\n }\n case PlaybackCommandType.SetFrozenTime:\n case PlaybackCommandType.SetWantsPlay:\n case PlaybackCommandType.SetState: {\n // managed inside fsm\n return;\n }\n case PlaybackCommandType.CancelRaf: {\n this.cancelRaf();\n return;\n }\n case PlaybackCommandType.StopAudio: {\n this.audioSession.stopPlayback();\n return;\n }\n case PlaybackCommandType.ResetAudioPlaybackStates: {\n this.audioSession.resetPlaybackStates();\n return;\n }\n case PlaybackCommandType.ResetAudioSession: {\n this.audioSession.reset();\n return;\n }\n case PlaybackCommandType.ClearCanvas: {\n this.clearCanvas();\n return;\n }\n case PlaybackCommandType.SetLastAudioScheduleTime: {\n this.lastAudioScheduleTime = command.timeUs;\n return;\n }\n case PlaybackCommandType.SetStartTimeBase: {\n this.startTimeUs = command.startTimeUs;\n return;\n }\n case PlaybackCommandType.SyncTimeBaseToAudioClock: {\n this.startTimeUs =\n this.audioContext.currentTime * 1_000_000 - command.timeUs / this.playbackRate;\n return;\n }\n case PlaybackCommandType.InitWindow: {\n this.initWindow(command.timeUs);\n return;\n }\n case PlaybackCommandType.SetCacheWindow: {\n this.orchestrator.cacheManager.setWindow(command.timeUs);\n return;\n }\n case PlaybackCommandType.Emit: {\n if (command.payload === undefined) {\n this.eventBus.emit(command.event as any);\n } else {\n this.eventBus.emit(command.event as any, command.payload);\n }\n return;\n }\n case PlaybackCommandType.RenderFrame: {\n return this.renderCurrentFrame(command.timeUs, {\n mode: command.mode,\n relativeTimeUs: command.relativeTimeUs,\n });\n }\n case PlaybackCommandType.MaybeRenderKeyframePreview: {\n return this.orchestrator.tryRenderKeyframe(command.timeUs).then((keyframeTimeUs) => {\n if (!this.isCurrentToken(token)) return;\n if (keyframeTimeUs === null) return;\n return this.orchestrator\n .getRenderState(command.timeUs, {\n mode: 'probe',\n relativeTimeUs: keyframeTimeUs,\n })\n .then((keyframeRenderState) => {\n if (!this.isCurrentToken(token)) return;\n if (!keyframeRenderState) return;\n return this.compose(command.timeUs, keyframeRenderState);\n });\n });\n }\n case PlaybackCommandType.EnsureAudio: {\n return this.audioSession.ensureAudioForTime(command.timeUs, {\n mode: command.mode,\n });\n }\n case PlaybackCommandType.GetFrame: {\n return this.orchestrator.getFrame(command.timeUs, {\n mode: command.mode,\n preheat: command.preheat,\n });\n }\n case PlaybackCommandType.StartAudioPlayback: {\n return this.audioSession.startPlayback(command.timeUs, this.audioContext);\n }\n case PlaybackCommandType.ProbeStartReady: {\n const audioReady = this.audioSession.isPreviewMixBlockCached(command.timeUs);\n const videoReady = this.isVideoResourceReadyAtTime(command.timeUs);\n\n if (audioReady && videoReady) return;\n\n // Kick background preparation (best-effort).\n if (!audioReady) {\n void this.audioSession.ensureAudioForTime(command.timeUs, { mode: 'probe' });\n }\n if (!videoReady) {\n void this.orchestrator.getFrame(command.timeUs, { mode: 'probe' });\n }\n\n // Enter buffering and bump token to cancel the remaining start sequence.\n this.dispatch({\n type: PlaybackActionType.EnterBuffering,\n timeUs: command.timeUs,\n bumpToken: true,\n reason: 'startup',\n });\n return;\n }\n case PlaybackCommandType.StartRafLoop: {\n this.startPlaybackLoop(token);\n return;\n }\n }\n }\n\n private cancelRaf(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n }\n\n private isCurrentToken(token: OpToken): boolean {\n return token === this.fsm.snapshot.token;\n }\n\n private startPlaybackLoop(token: OpToken): void {\n this.rafId = requestAnimationFrame(() => {\n void this.onRafTick(token);\n });\n }\n\n private async onRafTick(token: OpToken): Promise<void> {\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) {\n return;\n }\n\n const candidateTimeUs =\n (this.audioContext.currentTime * 1_000_000 - this.startTimeUs) * this.playbackRate;\n this.dispatch({\n type: PlaybackActionType.ClockTick,\n candidateTimeUs,\n durationUs: this.duration,\n loop: this.loop,\n audioNowUs: this.audioContext.currentTime * 1_000_000,\n });\n\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) {\n return;\n }\n\n // Audio probe: if the audio window isn't ready, enter buffering (freeze timeline)\n // and kick background audio preparation.\n // Note: preview audio readiness is driven by 60s mix-block cache inside AudioPreviewSession.\n // We intentionally avoid additional resource-window probing here to prevent buffering oscillation.\n\n // Throttle audio scheduling.\n if (this.currentTimeUs - this.lastAudioScheduleTime >= this.AUDIO_SCHEDULE_INTERVAL) {\n // Fire-and-forget: AudioPreviewSession runs its own background scheduling loop.\n // We avoid awaiting here to keep the render loop responsive.\n void this.audioSession.scheduleAudio(this.currentTimeUs, this.audioContext);\n this.lastAudioScheduleTime = this.currentTimeUs;\n }\n\n // If we're close to the 60s block boundary and the next mixed block isn't ready yet,\n // enter buffering to avoid hard silence at the boundary.\n if (this.audioSession.shouldEnterBufferingForUpcomingPreviewAudio(this.currentTimeUs)) {\n // Best-effort preheat; buffering path will call EnsureAudio(blocking).\n void this.audioSession.ensureAudioForTime(this.currentTimeUs, { mode: 'probe' });\n this.dispatch({\n type: PlaybackActionType.EnterBuffering,\n timeUs: this.currentTimeUs,\n reason: 'audio',\n });\n return;\n }\n\n const renderState = await this.orchestrator.getRenderState(this.currentTimeUs, {\n mode: 'probe',\n });\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) {\n return;\n }\n\n if (!renderState) {\n this.dispatch({ type: PlaybackActionType.EnterBuffering, timeUs: this.currentTimeUs });\n return;\n }\n\n await this.compose(this.currentTimeUs, renderState);\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) return;\n\n this.updateFps();\n this.frameCount++;\n\n // Unified cache window update.\n this.orchestrator.cacheManager.setWindow(this.currentTimeUs);\n\n this.checkAndPreheatWindow();\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) return;\n\n this.startPlaybackLoop(token);\n }\n\n private updateFps(): void {\n const now = performance.now();\n if (this.lastFrameTime > 0) {\n const deltaTime = now - this.lastFrameTime;\n const instantFps = 1000 / deltaTime;\n this.fps = this.fps > 0 ? this.fps * 0.9 + instantFps * 0.1 : instantFps;\n }\n this.lastFrameTime = now;\n }\n\n private initWindow(timeUs: TimeUs): void {\n this.windowEnd = timeUs + this.WINDOW_DURATION;\n this.preheatInProgress = false;\n this.orchestrator.cacheManager.setWindow(timeUs);\n }\n\n private checkAndPreheatWindow(): void {\n // Export uses main-thread decode + heavy OPFS IO; disable preview preheat to avoid contention.\n if (this.orchestrator.cacheManager.isExporting) {\n return;\n }\n if (this.preheatInProgress || this.fsm.snapshot.state !== PlaybackState.Playing) {\n return;\n }\n\n const distanceToWindowEnd = this.windowEnd - this.currentTimeUs;\n if (distanceToWindowEnd < 0) {\n this.initWindow(this.currentTimeUs);\n return;\n }\n\n if (distanceToWindowEnd > 0 && distanceToWindowEnd <= this.PREHEAT_DISTANCE) {\n void this.preheatNextWindow();\n }\n }\n\n async preheatNextWindow(): Promise<void> {\n // Export uses main-thread decode + heavy OPFS IO; disable preview preheat to avoid contention.\n if (this.orchestrator.cacheManager.isExporting) {\n return;\n }\n if (this.preheatInProgress) return;\n\n this.preheatInProgress = true;\n try {\n const windowStart = this.currentTimeUs;\n const windowEnd = windowStart + this.WINDOW_DURATION;\n\n const clipsInWindow =\n this.orchestrator.compositionModel?.getClipsInRange(windowStart, windowEnd) ?? [];\n const preheatPromises: Promise<any>[] = [];\n\n for (const clip of clipsInWindow) {\n if (!isVideoClip(clip)) continue;\n\n const clipWindowStart = Math.max(0, windowStart - clip.startUs);\n const clipWindowEnd = Math.min(clip.durationUs, windowEnd - clip.startUs);\n if (clipWindowStart >= clipWindowEnd) continue;\n\n preheatPromises.push(\n this.orchestrator.preheatClipWindow(clip.id, clipWindowStart, clipWindowEnd, windowStart)\n );\n }\n\n // Audio preheat is handled inside AudioPreviewSession via mix-block scheduling & cache.\n\n await Promise.all(preheatPromises);\n this.windowEnd = windowEnd;\n } catch (error) {\n console.warn('[PlaybackController] Preheat failed:', error);\n } finally {\n this.preheatInProgress = false;\n }\n }\n\n private async renderCurrentFrame(\n timeUs: TimeUs,\n options: { mode: RequestMode; relativeTimeUs?: TimeUs }\n ): Promise<void> {\n if (!this.videoComposer) {\n console.error('[PlaybackController] VideoComposer not initialized');\n return;\n }\n\n const renderState = await this.orchestrator.getRenderState(timeUs, {\n mode: options.mode,\n relativeTimeUs: options.relativeTimeUs,\n });\n\n if (!renderState) {\n return;\n }\n\n await this.compose(timeUs, renderState);\n }\n\n private async compose(\n timeUs: TimeUs,\n renderState: { layers: any[]; transition?: any }\n ): Promise<void> {\n if (!this.videoComposer) return;\n await this.videoComposer.composeFrame({\n timeUs,\n layers: renderState.layers,\n transition: renderState.transition,\n });\n }\n\n private clearCanvas(): void {\n const ctx = this.canvas.getContext('2d') as\n | CanvasRenderingContext2D\n | OffscreenCanvasRenderingContext2D\n | null;\n if (ctx && 'clearRect' in ctx) {\n ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n }\n }\n\n // ========= Cleanup / event handlers =========\n\n dispose(): void {\n this.stop();\n this.eventBus.off(MeframeEvent.CacheCover, this.onCacheCover);\n this.eventBus.off(MeframeEvent.ModelSet, this.onModelSet);\n if (this.videoComposer) {\n this.videoComposer.dispose();\n this.videoComposer = null;\n }\n }\n\n private onCacheCover = (): void => {\n if (this.fsm.snapshot.state === PlaybackState.Idle && this.currentTimeUs === 0) {\n void this.renderCurrentFrame(0, { mode: 'blocking' });\n }\n };\n\n private onModelSet = (): void => {\n if (!this.videoComposer || !this.orchestrator.compositionModel) return;\n\n // Model switching cancels in-flight async chains, but must NOT enter buffering or reset time/state.\n // Buffering runs a long async sequence; if cancelled mid-way it can leave the FSM stuck.\n this.dispatch({ type: PlaybackActionType.ModelSet });\n\n const model = this.orchestrator.compositionModel;\n this.videoComposer.updateConfig({\n width: model.renderConfig?.width || 720,\n height: model.renderConfig?.height || 1280,\n fps: model.fps || 30,\n backgroundColor: model.renderConfig?.backgroundColor || '#000',\n });\n\n // Best-effort background audio preheat (non-blocking).\n if (!this.orchestrator.cacheManager.isExporting) {\n void this.audioSession.ensureAudioForTime(this.currentTimeUs, { mode: 'probe' });\n }\n void this.renderCurrentFrame(this.currentTimeUs, { mode: 'blocking' });\n };\n\n private setupEventListeners(): void {\n this.eventBus.on(MeframeEvent.CacheCover, this.onCacheCover);\n this.eventBus.on(MeframeEvent.ModelSet, this.onModelSet);\n }\n\n private isVideoResourceReadyAtTime(timeUs: TimeUs): boolean {\n const model = this.orchestrator.compositionModel;\n if (!model) return true;\n const clip = model.getClipsAtTime(timeUs, model.mainTrackId)[0];\n if (!clip || !('resourceId' in clip) || typeof (clip as any).resourceId !== 'string')\n return true;\n const resourceId = (clip as any).resourceId as string;\n const resource = model.getResource(resourceId);\n return resource?.state === 'ready';\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioPreviewSession.d.ts","sourceRoot":"","sources":["../../src/orchestrator/AudioPreviewSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAG7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,qBAAa,mBAAmB;IA+BlB,OAAO,CAAC,IAAI;IA9BxB,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,MAAM,CAAO;IACrB,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,SAAS,CAAS;IAM1B,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAA0B;IACpE,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAK;IAC9C,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAQ;IACnD,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAqB;IAC7D,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAQ;IAE/C,OAAO,CAAC,iBAAiB,CAAyD;IAElF,OAAO,CAAC,mBAAmB,CAA8B;IACzD,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,uBAAuB,CAAK;IACpC,OAAO,CAAC,yBAAyB,CAAa;IAC9C,OAAO,CAAC,qBAAqB,CAAa;IAC1C,OAAO,CAAC,sBAAsB,CAAK;IAEnC,OAAO,CAAC,uBAAuB,CAAgE;IAC/F,OAAO,CAAC,eAAe,CAAuC;gBAE1C,IAAI,EAAE;QAAE,YAAY,EAAE,YAAY,CAAC;QAAC,QAAQ,EAAE,mBAAmB,CAAA;KAAE;IAIvF,yBAAyB,IAAI,IAAI;IASjC,OAAO,CAAC,iBAAiB;IAyBnB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"AudioPreviewSession.d.ts","sourceRoot":"","sources":["../../src/orchestrator/AudioPreviewSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAG7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,qBAAa,mBAAmB;IA+BlB,OAAO,CAAC,IAAI;IA9BxB,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,MAAM,CAAO;IACrB,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,SAAS,CAAS;IAM1B,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAA0B;IACpE,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAK;IAC9C,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAQ;IACnD,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAqB;IAC7D,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAQ;IAE/C,OAAO,CAAC,iBAAiB,CAAyD;IAElF,OAAO,CAAC,mBAAmB,CAA8B;IACzD,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,uBAAuB,CAAK;IACpC,OAAO,CAAC,yBAAyB,CAAa;IAC9C,OAAO,CAAC,qBAAqB,CAAa;IAC1C,OAAO,CAAC,sBAAsB,CAAK;IAEnC,OAAO,CAAC,uBAAuB,CAAgE;IAC/F,OAAO,CAAC,eAAe,CAAuC;gBAE1C,IAAI,EAAE;QAAE,YAAY,EAAE,YAAY,CAAC;QAAC,QAAQ,EAAE,mBAAmB,CAAA;KAAE;IAIvF,yBAAyB,IAAI,IAAI;IASjC,OAAO,CAAC,iBAAiB;IAyBnB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAqCzF,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAKhD,2CAA2C,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAgB9D,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB9E,YAAY,IAAI,IAAI;IAOpB;;OAEG;IACG,aAAa,CAAC,iBAAiB,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBzF;;OAEG;IACH,mBAAmB,IAAI,IAAI;IAM3B,KAAK,IAAI,IAAI;IAOb,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAkB/B,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAMnC,OAAO,CAAC,2BAA2B;IAcnC,OAAO,CAAC,4BAA4B;IASpC,OAAO,CAAC,kCAAkC;YAe5B,6BAA6B;YA2C7B,qBAAqB;YA2BrB,wBAAwB;IA2FtC,OAAO,CAAC,qBAAqB;CAoB9B"}
|
|
@@ -64,6 +64,7 @@ class AudioPreviewSession {
|
|
|
64
64
|
return next;
|
|
65
65
|
}
|
|
66
66
|
async ensureAudioForTime(timeUs, options) {
|
|
67
|
+
if (this.deps.cacheManager.isExporting) return;
|
|
67
68
|
const model = this.deps.preparer.getModel();
|
|
68
69
|
if (!model) return;
|
|
69
70
|
const mode = options?.mode ?? "blocking";
|
|
@@ -235,6 +236,7 @@ class AudioPreviewSession {
|
|
|
235
236
|
const token = this.previewMixToken;
|
|
236
237
|
return await this.previewBlockCache.getOrCreate(blockIndex, async () => {
|
|
237
238
|
return await this.enqueuePreviewMix(async () => {
|
|
239
|
+
if (this.deps.cacheManager.isExporting) return null;
|
|
238
240
|
const startUs = blockIndex * this.PREVIEW_BLOCK_DURATION_US;
|
|
239
241
|
const endUs = Math.min(startUs + this.PREVIEW_BLOCK_DURATION_US, model.durationUs);
|
|
240
242
|
await this.deps.preparer.ensureAudioForTimeRange(startUs, endUs, {
|
|
@@ -242,7 +244,7 @@ class AudioPreviewSession {
|
|
|
242
244
|
loadResource: true
|
|
243
245
|
});
|
|
244
246
|
const mixed = await this.mixer.mix(startUs, endUs);
|
|
245
|
-
this.deps.cacheManager.clearAudioCache();
|
|
247
|
+
if (!this.deps.cacheManager.isExporting) this.deps.cacheManager.clearAudioCache();
|
|
246
248
|
return mixed;
|
|
247
249
|
}, token);
|
|
248
250
|
});
|