@meframe/core 0.0.3 → 0.0.5
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/Meframe.d.ts +16 -7
- package/dist/Meframe.d.ts.map +1 -1
- package/dist/Meframe.js +75 -90
- package/dist/Meframe.js.map +1 -1
- package/dist/cache/CacheManager.d.ts +28 -11
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +93 -30
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/L2Cache.d.ts +31 -2
- package/dist/cache/L2Cache.d.ts.map +1 -1
- package/dist/cache/L2Cache.js +245 -44
- package/dist/cache/L2Cache.js.map +1 -1
- package/dist/cache/l1/VideoL1Cache.d.ts +3 -3
- package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
- package/dist/cache/l1/VideoL1Cache.js +13 -8
- package/dist/cache/l1/VideoL1Cache.js.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +2 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/types.d.ts +3 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.d.ts +7 -8
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +56 -76
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/controllers/PreRenderService.d.ts +21 -4
- package/dist/controllers/PreRenderService.d.ts.map +1 -1
- package/dist/controllers/PreRenderService.js +67 -5
- package/dist/controllers/PreRenderService.js.map +1 -1
- package/dist/controllers/types.d.ts +2 -3
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/event/events.d.ts +1 -4
- package/dist/event/events.d.ts.map +1 -1
- package/dist/event/events.js.map +1 -1
- package/dist/model/CompositionModel.d.ts +2 -1
- package/dist/model/CompositionModel.d.ts.map +1 -1
- package/dist/model/CompositionModel.js +3 -1
- package/dist/model/CompositionModel.js.map +1 -1
- package/dist/model/patch.d.ts +6 -2
- package/dist/model/patch.d.ts.map +1 -1
- package/dist/model/patch.js +76 -2
- package/dist/model/patch.js.map +1 -1
- package/dist/model/types.d.ts +1 -0
- package/dist/model/types.d.ts.map +1 -1
- package/dist/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js +1858 -0
- package/dist/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js.map +1 -0
- package/dist/orchestrator/ClipSessionManager.d.ts +1 -2
- package/dist/orchestrator/ClipSessionManager.d.ts.map +1 -1
- package/dist/orchestrator/ClipSessionManager.js +1 -0
- package/dist/orchestrator/ClipSessionManager.js.map +1 -1
- package/dist/orchestrator/CompositionPlanner.d.ts +8 -7
- package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -1
- package/dist/orchestrator/CompositionPlanner.js +33 -56
- package/dist/orchestrator/CompositionPlanner.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts +9 -2
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +100 -50
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts +14 -9
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +108 -85
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/orchestrator/types.d.ts +1 -0
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/stages/compose/GlobalAudioSession.d.ts +34 -1
- package/dist/stages/compose/GlobalAudioSession.d.ts.map +1 -1
- package/dist/stages/compose/GlobalAudioSession.js +149 -5
- package/dist/stages/compose/GlobalAudioSession.js.map +1 -1
- package/dist/stages/compose/VideoComposer.d.ts +1 -0
- package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
- package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
- package/dist/stages/encode/AudioChunkEncoder.d.ts +2 -1
- package/dist/stages/encode/AudioChunkEncoder.d.ts.map +1 -1
- package/dist/stages/encode/AudioChunkEncoder.js +41 -0
- package/dist/stages/encode/AudioChunkEncoder.js.map +1 -0
- package/dist/stages/encode/BaseEncoder.d.ts +7 -3
- package/dist/stages/encode/BaseEncoder.d.ts.map +1 -1
- package/dist/stages/encode/BaseEncoder.js +173 -0
- package/dist/stages/encode/BaseEncoder.js.map +1 -0
- package/dist/stages/encode/ClipEncoderManager.d.ts +64 -0
- package/dist/stages/encode/ClipEncoderManager.d.ts.map +1 -0
- package/dist/stages/encode/index.d.ts +1 -1
- package/dist/stages/encode/index.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts +22 -1
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +80 -29
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/stages/load/TaskManager.d.ts +1 -1
- package/dist/stages/load/TaskManager.d.ts.map +1 -1
- package/dist/stages/load/TaskManager.js +3 -2
- package/dist/stages/load/TaskManager.js.map +1 -1
- package/dist/stages/load/types.d.ts +4 -2
- package/dist/stages/load/types.d.ts.map +1 -1
- package/dist/stages/mux/MP4Muxer.d.ts +19 -38
- package/dist/stages/mux/MP4Muxer.d.ts.map +1 -1
- package/dist/stages/mux/MP4Muxer.js +60 -0
- package/dist/stages/mux/MP4Muxer.js.map +1 -0
- package/dist/stages/mux/MuxManager.d.ts +27 -0
- package/dist/stages/mux/MuxManager.d.ts.map +1 -0
- package/dist/stages/mux/MuxManager.js +148 -0
- package/dist/stages/mux/MuxManager.js.map +1 -0
- package/dist/stages/mux/index.d.ts +1 -0
- package/dist/stages/mux/index.d.ts.map +1 -1
- package/dist/stages/mux/types.d.ts +1 -0
- package/dist/stages/mux/types.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/worker/WorkerPool.d.ts +2 -0
- package/dist/worker/WorkerPool.d.ts.map +1 -1
- package/dist/worker/WorkerPool.js +6 -5
- package/dist/worker/WorkerPool.js.map +1 -1
- package/dist/worker/types.d.ts +1 -4
- package/dist/worker/types.d.ts.map +1 -1
- package/dist/worker/types.js +0 -3
- package/dist/worker/types.js.map +1 -1
- package/dist/worker/worker-event-whitelist.d.ts.map +1 -1
- package/dist/workers/MP4Demuxer.js +7049 -6
- package/dist/workers/MP4Demuxer.js.map +1 -1
- package/dist/workers/WorkerChannel.js +0 -3
- package/dist/workers/WorkerChannel.js.map +1 -1
- package/dist/workers/stages/compose/video-compose.worker.js +126 -83
- package/dist/workers/stages/compose/video-compose.worker.js.map +1 -1
- package/dist/workers/stages/decode/decode.worker.js +25 -16
- package/dist/workers/stages/decode/decode.worker.js.map +1 -1
- package/dist/workers/stages/demux/audio-demux.worker.js +4 -4
- package/dist/workers/stages/demux/audio-demux.worker.js.map +1 -1
- package/dist/workers/stages/demux/video-demux.worker.js +9 -7
- package/dist/workers/stages/demux/video-demux.worker.js.map +1 -1
- package/dist/workers/stages/encode/encode.worker.js +191 -195
- package/dist/workers/stages/encode/encode.worker.js.map +1 -1
- package/package.json +2 -1
- package/dist/controllers/PreviewHandle.d.ts +0 -25
- package/dist/controllers/PreviewHandle.d.ts.map +0 -1
- package/dist/controllers/PreviewHandle.js +0 -45
- package/dist/controllers/PreviewHandle.js.map +0 -1
- package/dist/model/dirty-range.js +0 -220
- package/dist/model/dirty-range.js.map +0 -1
- package/dist/stages/encode/EncoderPool.d.ts +0 -28
- package/dist/stages/encode/EncoderPool.d.ts.map +0 -1
- package/dist/workers/mp4box.all.js +0 -7049
- package/dist/workers/mp4box.all.js.map +0 -1
- package/dist/workers/stages/mux/mux.worker.js +0 -501
- package/dist/workers/stages/mux/mux.worker.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"L2Cache.js","sources":["../../src/cache/L2Cache.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\n// import type { ClipMetadata } from './types';\nimport { binarySearchRange } from '../utils/binary-search';\n\ninterface ChunkBatch {\n startUs: TimeUs;\n durationUs: TimeUs;\n byteOffset: number;\n byteLength: number;\n}\n\ninterface ChunkRecord {\n clipId: string;\n track: 'video' | 'audio';\n fileName: string;\n batches: ChunkBatch[];\n lastAccess: number;\n totalBytes: number;\n}\n\ninterface L2Config {\n maxSizeMB: number;\n projectId: string;\n}\n\nexport class L2Cache {\n private db: IDBDatabase | null = null;\n private opfsRoot: FileSystemDirectoryHandle | null = null;\n readonly maxSize: number;\n readonly projectId: string;\n private initPromise: Promise<void> | null = null;\n\n constructor(config: L2Config) {\n this.maxSize = config.maxSizeMB * 1024 * 1024;\n this.projectId = config.projectId;\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 async get(timeUs: TimeUs, clipId: string): Promise<EncodedVideoChunk | EncodedAudioChunk | null> {\n await this.init();\n\n if (!this.db) return null;\n\n // Query IndexedDB for chunk metadata\n const tx = this.db.transaction('chunks', 'readonly');\n const store = tx.objectStore('chunks');\n const records = await this.collectRecords(store, clipId);\n\n for (const record of records) {\n const batch = binarySearchRange(record.batches, timeUs, (b) => ({\n start: b.startUs,\n end: b.startUs + b.durationUs,\n }));\n\n if (!batch) {\n continue;\n }\n\n const chunkData = await this.readFromOPFS(record.fileName, batch);\n if (!chunkData) continue;\n\n this.updateLastAccess(record.clipId, record.track);\n\n return this.createChunk(chunkData, timeUs, record.track);\n }\n\n return null;\n }\n\n async put(\n clipId: string,\n chunks: Array<EncodedVideoChunk | EncodedAudioChunk>,\n track: 'video' | 'audio'\n ): Promise<void> {\n await this.init();\n\n if (!this.db || !this.opfsRoot) return;\n\n const fileName = `clip-${clipId}-${track[0]}1.${track === 'video' ? 'webm' : 'm4a'}`;\n\n // Write chunks to OPFS\n const batches = await this.writeToOPFS(fileName, chunks);\n\n // Calculate total size\n const totalBytes = batches.reduce((sum, b) => sum + b.byteLength, 0);\n\n // Update IndexedDB index\n const tx = this.db.transaction('chunks', 'readwrite');\n const store = tx.objectStore('chunks');\n\n const record: ChunkRecord = {\n clipId,\n track,\n fileName,\n batches,\n lastAccess: Date.now(),\n totalBytes,\n };\n\n await this.promisifyRequest(store.put(record));\n\n // Check and enforce quota\n await this.enforceQuota();\n }\n\n async invalidateRange(startUs: TimeUs, endUs: TimeUs, clipId?: string): Promise<void> {\n await this.init();\n\n if (!this.db) return;\n\n const tx = this.db.transaction('chunks', 'readwrite');\n const store = tx.objectStore('chunks');\n const keysToDelete: Array<[string, string]> = [];\n\n // Iterate through all records\n const cursor = store.openCursor();\n await new Promise<void>((resolve) => {\n cursor.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest).result;\n if (!cursor) {\n resolve();\n return;\n }\n\n const record: ChunkRecord = cursor.value;\n\n if (clipId && record.clipId !== clipId) {\n cursor.continue();\n return;\n }\n\n // Check if any batch overlaps with invalidation range\n const hasOverlap = record.batches.some((batch) => {\n const batchEnd = batch.startUs + batch.durationUs;\n return batch.startUs < endUs && batchEnd > startUs;\n });\n\n if (hasOverlap) {\n keysToDelete.push([record.clipId, record.track]);\n }\n\n cursor.continue();\n };\n });\n\n // Delete invalidated entries\n for (const key of keysToDelete) {\n await this.deleteEntry(key[0], key[1]);\n }\n }\n\n async invalidateClip(clipId: string): Promise<void> {\n await this.init();\n\n if (!this.db) return;\n\n const tx = this.db.transaction('chunks', 'readwrite');\n const store = tx.objectStore('chunks');\n const records = await this.collectRecords(store, clipId);\n\n for (const record of records) {\n await this.deleteEntry(record.clipId, record.track);\n }\n }\n\n async clear(): Promise<void> {\n await this.init();\n\n if (!this.db || !this.opfsRoot) return;\n\n // Clear IndexedDB\n const tx = this.db.transaction(['chunks', 'meta'], 'readwrite');\n await this.promisifyRequest(tx.objectStore('chunks').clear());\n await this.promisifyRequest(tx.objectStore('meta').clear());\n\n // Clear OPFS files\n const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${this.projectId}`, {\n create: false,\n });\n await this.opfsRoot.removeEntry(projectDir.name, { recursive: true });\n }\n\n private async initStorage(): Promise<void> {\n // Initialize OPFS\n this.opfsRoot = await navigator.storage.getDirectory();\n\n // Initialize IndexedDB\n const request = indexedDB.open('meframe_cache', 1);\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n\n // chunks store with composite key\n if (!db.objectStoreNames.contains('chunks')) {\n const store = db.createObjectStore('chunks', {\n keyPath: ['clipId', 'track'],\n });\n store.createIndex('lastAccess', 'lastAccess');\n }\n\n // meta store\n if (!db.objectStoreNames.contains('meta')) {\n db.createObjectStore('meta', { keyPath: 'projectId' });\n }\n };\n\n this.db = await new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n }\n\n private async readFromOPFS(fileName: string, batch: ChunkBatch): Promise<ArrayBuffer | null> {\n if (!this.opfsRoot) return null;\n\n const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${this.projectId}`, {\n create: false,\n });\n const fileHandle = await projectDir.getFileHandle(fileName);\n const file = await fileHandle.getFile();\n const slice = file.slice(batch.byteOffset, batch.byteOffset + batch.byteLength);\n return await slice.arrayBuffer();\n }\n\n private async writeToOPFS(\n fileName: string,\n chunks: Array<EncodedVideoChunk | EncodedAudioChunk>\n ): Promise<ChunkBatch[]> {\n if (!this.opfsRoot) return [];\n\n const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${this.projectId}`, {\n create: true,\n });\n const fileHandle = await projectDir.getFileHandle(fileName, { create: true });\n const writable = await fileHandle.createWritable();\n\n const batches: ChunkBatch[] = [];\n let offset = 0;\n\n for (const chunk of chunks) {\n const data = await this.chunkToArrayBuffer(chunk);\n await writable.write({ type: 'write', position: offset, data });\n\n batches.push({\n startUs: chunk.timestamp,\n durationUs: chunk.duration || 0,\n byteOffset: offset,\n byteLength: data.byteLength,\n });\n\n offset += data.byteLength;\n }\n\n await writable.close();\n return batches;\n }\n\n private async chunkToArrayBuffer(\n chunk: EncodedVideoChunk | EncodedAudioChunk\n ): Promise<ArrayBuffer> {\n const buffer = new ArrayBuffer(chunk.byteLength);\n chunk.copyTo(buffer);\n return buffer;\n }\n\n private createChunk(\n data: ArrayBuffer,\n timeUs: TimeUs,\n track: 'video' | 'audio'\n ): EncodedVideoChunk | EncodedAudioChunk {\n if (track === 'video') {\n return new EncodedVideoChunk({\n type: 'key', // Should be determined from metadata\n timestamp: timeUs,\n data,\n });\n } else {\n return new EncodedAudioChunk({\n type: 'key',\n timestamp: timeUs,\n data,\n });\n }\n }\n\n private async updateLastAccess(clipId: string, track: string): Promise<void> {\n if (!this.db) return;\n\n const tx = this.db.transaction('chunks', 'readwrite');\n const store = tx.objectStore('chunks');\n const record = await this.promisifyRequest<ChunkRecord>(store.get([clipId, track]));\n\n if (record) {\n record.lastAccess = Date.now();\n await this.promisifyRequest(store.put(record));\n }\n }\n\n private async deleteEntry(clipId: string, track: string): Promise<void> {\n if (!this.db) return;\n\n const tx = this.db.transaction('chunks', 'readwrite');\n const store = tx.objectStore('chunks');\n const record = await this.promisifyRequest<ChunkRecord>(store.get([clipId, track]));\n\n if (record && this.opfsRoot) {\n // Delete OPFS file\n const projectDir = await this.opfsRoot.getDirectoryHandle(\n `meframe-project-${this.projectId}`,\n { create: false }\n );\n await projectDir.removeEntry(record.fileName);\n }\n\n // Delete IndexedDB record\n await this.promisifyRequest(store.delete([clipId, track]));\n }\n\n private async enforceQuota(): Promise<void> {\n const estimate = await navigator.storage.estimate();\n const usage = estimate.usage || 0;\n\n if (usage <= this.maxSize) return;\n\n if (!this.db) return;\n\n // Delete oldest entries until under quota\n const tx = this.db.transaction('chunks', 'readwrite');\n const store = tx.objectStore('chunks');\n const index = store.index('lastAccess');\n\n let bytesDeleted = 0;\n const toDelete = usage - this.maxSize;\n\n const cursor = index.openCursor();\n await new Promise<void>((resolve) => {\n cursor.onsuccess = async (event) => {\n const cursor = (event.target as IDBRequest).result;\n if (!cursor || bytesDeleted >= toDelete) {\n resolve();\n return;\n }\n\n const record: ChunkRecord = cursor.value;\n await this.deleteEntry(record.clipId, record.track);\n bytesDeleted += record.totalBytes;\n\n cursor.continue();\n };\n });\n }\n\n private async collectRecords(store: IDBObjectStore, clipId: string): Promise<ChunkRecord[]> {\n const records: ChunkRecord[] = [];\n const cursor = store.openCursor();\n await new Promise<void>((resolve) => {\n cursor.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest).result;\n if (!cursor) {\n resolve();\n return;\n }\n\n const record: ChunkRecord = cursor.value;\n if (record.clipId === clipId) {\n records.push(record);\n }\n\n cursor.continue();\n };\n });\n return records;\n }\n\n private promisifyRequest<T>(request: IDBRequest): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n }\n\n getMetadata(): {\n maxSizeMB: number;\n usedSizeMB: number;\n entries: number;\n hitRate: number;\n } {\n // This is a simplified implementation\n // In a real implementation, we would track actual usage\n return {\n maxSizeMB: this.maxSize / (1024 * 1024),\n usedSizeMB: 0, // Would need to track actual usage\n entries: 0, // Would need to track actual entries\n hitRate: 0, // Would need to track hits and misses\n };\n }\n\n async hasAvailableQuota(sizeMB: number): Promise<boolean> {\n if (typeof navigator === 'undefined' || !navigator.storage?.estimate) {\n // L2Cache requires storage API to function\n throw new Error('Storage API not available');\n }\n\n const estimate = await navigator.storage.estimate();\n const availableMB = ((estimate.quota || 0) - (estimate.usage || 0)) / (1024 * 1024);\n return availableMB >= sizeMB;\n }\n}\n"],"names":["cursor"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"L2Cache.js","sources":["../../src/cache/L2Cache.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\n// import type { ClipMetadata } from './types';\nimport { binarySearchRange } from '../utils/binary-search';\n\ninterface ChunkBatch {\n startUs: TimeUs;\n durationUs: TimeUs;\n byteOffset: number;\n byteLength: number;\n type: 'key' | 'delta'; // Store chunk type for correct reconstruction\n}\n\ninterface ChunkRecord {\n clipId: string; // IndexedDB keyPath uses clipId\n track: 'video' | 'audio';\n fileName: string;\n batches: ChunkBatch[];\n lastAccess: number;\n totalBytes: number;\n isComplete: boolean; // Mark if clip is fully rendered\n expectedDurationUs?: number; // Expected duration for validation\n metadata?: {\n codec?: string;\n description?: Uint8Array;\n codedWidth?: number;\n codedHeight?: number;\n displayAspectWidth?: number;\n displayAspectHeight?: number;\n colorSpace?: VideoColorSpaceInit;\n hardwareAcceleration?: HardwareAcceleration;\n optimizeForLatency?: boolean;\n sampleRate?: number;\n numberOfChannels?: number;\n };\n}\n\ninterface L2Config {\n maxSizeMB: number;\n projectId: string;\n}\n\nexport class L2Cache {\n private db: IDBDatabase | null = null;\n private opfsRoot: FileSystemDirectoryHandle | null = null;\n readonly maxSize: number;\n readonly projectId: string;\n private initPromise: Promise<void> | null = null;\n\n constructor(config: L2Config) {\n this.maxSize = config.maxSizeMB * 1024 * 1024;\n this.projectId = config.projectId;\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 async get(timeUs: TimeUs, clipId: string): Promise<EncodedVideoChunk | EncodedAudioChunk | null> {\n await this.init();\n\n if (!this.db) return null;\n\n // Query IndexedDB for chunk metadata\n const tx = this.db.transaction('chunks', 'readonly');\n const store = tx.objectStore('chunks');\n const records = await this.collectRecords(store, clipId);\n\n for (const record of records) {\n const batch = binarySearchRange(record.batches, timeUs, (b) => ({\n start: b.startUs,\n end: b.startUs + b.durationUs,\n }));\n\n if (!batch) {\n continue;\n }\n\n const chunkData = await this.readFromOPFS(record.fileName, batch);\n if (!chunkData) continue;\n\n this.updateLastAccess(record.clipId, record.track);\n\n return this.createChunk(chunkData, timeUs, record.track, batch.type, batch.durationUs);\n }\n\n return null;\n }\n\n async put(\n clipId: string,\n chunks: Array<EncodedVideoChunk | EncodedAudioChunk>,\n track: 'video' | 'audio',\n options?: {\n isComplete?: boolean;\n expectedDurationUs?: number;\n metadata?: any;\n }\n ): Promise<void> {\n await this.init();\n\n if (!this.db || !this.opfsRoot) {\n console.warn(`[L2Cache] put aborted: db=${!!this.db}, opfsRoot=${!!this.opfsRoot}`);\n return;\n }\n if (chunks.length === 0) return;\n\n const fileName = `clip-${clipId}-${track[0]}1.${track === 'video' ? 'webm' : 'm4a'}`;\n\n // Step 1: Read existing record (separate transaction)\n let existingRecord: ChunkRecord | undefined;\n {\n const tx = this.db.transaction('chunks', 'readonly');\n const store = tx.objectStore('chunks');\n existingRecord = await this.promisifyRequest<ChunkRecord>(store.get([clipId, track]));\n }\n\n // Step 2: Validate consistency - if IndexedDB has record but OPFS file missing, delete the record\n if (existingRecord) {\n const projectDir = await this.opfsRoot.getDirectoryHandle(\n `meframe-project-${this.projectId}`,\n {\n create: true,\n }\n );\n try {\n await projectDir.getFileHandle(existingRecord.fileName, { create: false });\n } catch (error) {\n if ((error as any)?.name === 'NotFoundError') {\n await this.deleteEntry(clipId, track);\n existingRecord = undefined;\n }\n }\n }\n\n // Step 3: Deduplicate based on timestamp\n let chunksToWrite = chunks;\n if (existingRecord && existingRecord.batches.length > 0) {\n const lastBatch = existingRecord.batches[existingRecord.batches.length - 1];\n if (lastBatch) {\n const lastTimestamp = lastBatch.startUs;\n // Filter out chunks with timestamp <= lastTimestamp\n chunksToWrite = chunks.filter((chunk) => chunk.timestamp > lastTimestamp);\n\n if (chunksToWrite.length === 0) {\n return;\n }\n }\n }\n\n // Step 3: Write to OPFS (no active transaction)\n const newBatches = await this.appendToOPFS(fileName, chunksToWrite, existingRecord?.batches);\n\n // Step 4: Update IndexedDB (new transaction)\n {\n const tx = this.db.transaction('chunks', 'readwrite');\n const store = tx.objectStore('chunks');\n\n const record: ChunkRecord = {\n clipId,\n track,\n fileName,\n batches: existingRecord?.batches ? [...existingRecord.batches, ...newBatches] : newBatches,\n lastAccess: Date.now(),\n totalBytes:\n (existingRecord?.totalBytes || 0) + newBatches.reduce((sum, b) => sum + b.byteLength, 0),\n isComplete: options?.isComplete ?? existingRecord?.isComplete ?? false,\n expectedDurationUs: options?.expectedDurationUs ?? existingRecord?.expectedDurationUs,\n metadata: options?.metadata ?? existingRecord?.metadata,\n };\n\n store.put(record);\n // Wait for transaction to complete, not just the request\n await new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => {\n resolve();\n };\n tx.onerror = () => {\n console.error(`[L2Cache] Transaction error for ${clipId} ${track}:`, tx.error);\n reject(tx.error);\n };\n });\n }\n\n // Check and enforce quota\n await this.enforceQuota();\n }\n\n async invalidateRange(startUs: TimeUs, endUs: TimeUs, clipId?: string): Promise<void> {\n await this.init();\n\n if (!this.db) return;\n\n const tx = this.db.transaction('chunks', 'readwrite');\n const store = tx.objectStore('chunks');\n const keysToDelete: Array<[string, string]> = [];\n\n // Iterate through all records\n const cursor = store.openCursor();\n await new Promise<void>((resolve) => {\n cursor.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest).result;\n if (!cursor) {\n resolve();\n return;\n }\n\n const record: ChunkRecord = cursor.value;\n\n if (clipId && record.clipId !== clipId) {\n cursor.continue();\n return;\n }\n\n // Check if any batch overlaps with invalidation range\n const hasOverlap = record.batches.some((batch) => {\n const batchEnd = batch.startUs + batch.durationUs;\n return batch.startUs < endUs && batchEnd > startUs;\n });\n\n if (hasOverlap) {\n keysToDelete.push([record.clipId, record.track]);\n }\n\n cursor.continue();\n };\n });\n\n // Delete invalidated entries\n for (const key of keysToDelete) {\n await this.deleteEntry(key[0], key[1]);\n }\n }\n\n /**\n * Check if clip has cached data in L2\n */\n async hasClip(clipId: string, track: 'video' | 'audio'): Promise<boolean> {\n await this.init();\n\n if (!this.db) return false;\n\n const tx = this.db.transaction('chunks', 'readonly');\n const store = tx.objectStore('chunks');\n const record = await this.promisifyRequest<ChunkRecord>(store.get([clipId, track]));\n\n return record !== undefined && record.batches && record.batches.length > 0;\n }\n\n /**\n * Check if clip has complete cached data in L2\n */\n async hasCompleteClip(clipId: string, track: 'video' | 'audio'): Promise<boolean> {\n await this.init();\n\n if (!this.db) {\n console.warn(`[L2Cache] hasCompleteClip: db not initialized`);\n return false;\n }\n\n const tx = this.db.transaction('chunks', 'readonly');\n const store = tx.objectStore('chunks');\n const record = await this.promisifyRequest<ChunkRecord>(store.get([clipId, track]));\n\n const result = record?.isComplete === true;\n return result;\n }\n\n /**\n * Mark clip as complete in L2 cache\n */\n async markComplete(clipId: string, track: 'video' | 'audio'): Promise<void> {\n await this.init();\n\n if (!this.db) return;\n\n const tx = this.db.transaction('chunks', 'readwrite');\n const store = tx.objectStore('chunks');\n const record = await this.promisifyRequest<ChunkRecord>(store.get([clipId, track]));\n\n if (record) {\n record.isComplete = true;\n record.lastAccess = Date.now();\n store.put(record);\n // Wait for transaction to complete\n await new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n console.log(`[L2Cache] markComplete(${clipId}, ${track}): marked successfully`);\n } else {\n console.warn(`[L2Cache] markComplete(${clipId}, ${track}): no record found, cannot mark`);\n }\n }\n\n async invalidateClip(clipId: string): Promise<void> {\n await this.init();\n\n if (!this.db) return;\n\n // Collect records to delete\n const recordsToDelete: ChunkRecord[] = [];\n {\n const tx = this.db.transaction('chunks', 'readonly');\n const store = tx.objectStore('chunks');\n recordsToDelete.push(...(await this.collectRecords(store, clipId)));\n }\n\n // Delete each record\n for (const record of recordsToDelete) {\n await this.deleteEntry(record.clipId, record.track);\n }\n }\n\n /**\n * Create a readable stream of encoded chunks for export\n * Reads chunks in timestamp order from OPFS\n */\n async createReadStream(\n clipId: string,\n track: 'video' | 'audio'\n ): Promise<ReadableStream<EncodedVideoChunk | EncodedAudioChunk> | null> {\n await this.init();\n\n if (!this.db || !this.opfsRoot) return null;\n\n // Get chunk record\n const tx = this.db.transaction('chunks', 'readonly');\n const store = tx.objectStore('chunks');\n const record = await this.promisifyRequest<ChunkRecord>(store.get([clipId, track]));\n\n if (!record || record.batches.length === 0) {\n return null;\n }\n\n // Clone batches array for stream iteration\n const batches = [...record.batches];\n let batchIndex = 0;\n\n return new ReadableStream<EncodedVideoChunk | EncodedAudioChunk>({\n pull: async (controller) => {\n if (batchIndex >= batches.length) {\n controller.close();\n return;\n }\n\n const batch = batches[batchIndex];\n if (!batch) {\n controller.close();\n return;\n }\n\n // Read chunk data from OPFS\n const chunkData = await this.readFromOPFS(record.fileName, batch);\n if (!chunkData) {\n controller.error(new Error(`Failed to read chunk at index ${batchIndex}`));\n return;\n }\n\n // Create encoded chunk with correct type and duration\n const chunk = this.createChunk(\n chunkData,\n batch.startUs,\n track,\n batch.type,\n batch.durationUs\n );\n controller.enqueue(chunk);\n\n batchIndex++;\n },\n });\n }\n\n async clear(): Promise<void> {\n await this.init();\n\n if (!this.db || !this.opfsRoot) {\n console.warn('[L2Cache] clear() called but db or opfsRoot not available');\n return;\n }\n\n // Clear IndexedDB\n try {\n const tx = this.db.transaction(['chunks', 'meta'], 'readwrite');\n await this.promisifyRequest(tx.objectStore('chunks').clear());\n await this.promisifyRequest(tx.objectStore('meta').clear());\n } catch (error) {\n console.error('[L2Cache] Failed to clear IndexedDB:', error);\n throw error;\n }\n\n // Clear OPFS files\n try {\n const projectDir = await this.opfsRoot.getDirectoryHandle(\n `meframe-project-${this.projectId}`,\n {\n create: false,\n }\n );\n await this.opfsRoot.removeEntry(projectDir.name, { recursive: true });\n } catch (error) {\n if ((error as any)?.name !== 'NotFoundError') {\n console.warn('[L2Cache] Failed to clear OPFS:', error);\n }\n }\n }\n\n private async initStorage(): Promise<void> {\n // Initialize OPFS\n this.opfsRoot = await navigator.storage.getDirectory();\n\n // Initialize IndexedDB\n const request = indexedDB.open('meframe_cache', 1);\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n\n // chunks store with composite key [clipId, track]\n if (!db.objectStoreNames.contains('chunks')) {\n const store = db.createObjectStore('chunks', {\n keyPath: ['clipId', 'track'],\n });\n store.createIndex('lastAccess', 'lastAccess');\n }\n\n // meta store\n if (!db.objectStoreNames.contains('meta')) {\n db.createObjectStore('meta', { keyPath: 'projectId' });\n }\n };\n\n this.db = await new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n }\n\n private async readFromOPFS(fileName: string, batch: ChunkBatch): Promise<ArrayBuffer | null> {\n if (!this.opfsRoot) return null;\n\n const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${this.projectId}`, {\n create: false,\n });\n const fileHandle = await projectDir.getFileHandle(fileName);\n const file = await fileHandle.getFile();\n const slice = file.slice(batch.byteOffset, batch.byteOffset + batch.byteLength);\n return await slice.arrayBuffer();\n }\n\n /**\n * Append chunks to OPFS file (or create new file)\n * Supports incremental writing for streaming scenarios\n */\n private async appendToOPFS(\n fileName: string,\n chunks: Array<EncodedVideoChunk | EncodedAudioChunk>,\n existingBatches?: ChunkBatch[]\n ): Promise<ChunkBatch[]> {\n if (!this.opfsRoot) return [];\n\n const projectDir = await this.opfsRoot.getDirectoryHandle(`meframe-project-${this.projectId}`, {\n create: true,\n });\n const fileHandle = await projectDir.getFileHandle(fileName, { create: true });\n\n // Calculate starting offset from existing batches\n let offset = 0;\n if (existingBatches && existingBatches.length > 0) {\n const lastBatch = existingBatches[existingBatches.length - 1];\n if (lastBatch) {\n offset = lastBatch.byteOffset + lastBatch.byteLength;\n }\n }\n\n const writable = await fileHandle.createWritable({ keepExistingData: true });\n\n const batches: ChunkBatch[] = [];\n\n for (const chunk of chunks) {\n const data = await this.chunkToArrayBuffer(chunk);\n await writable.write({ type: 'write', position: offset, data });\n\n batches.push({\n startUs: chunk.timestamp,\n durationUs: chunk.duration || 0,\n byteOffset: offset,\n byteLength: data.byteLength,\n type: chunk.type,\n });\n\n offset += data.byteLength;\n }\n\n await writable.close();\n return batches;\n }\n\n private async chunkToArrayBuffer(\n chunk: EncodedVideoChunk | EncodedAudioChunk\n ): Promise<ArrayBuffer> {\n const buffer = new ArrayBuffer(chunk.byteLength);\n chunk.copyTo(buffer);\n return buffer;\n }\n\n private createChunk(\n data: ArrayBuffer,\n timeUs: TimeUs,\n track: 'video' | 'audio',\n chunkType: 'key' | 'delta' = 'key',\n durationUs: TimeUs = 0\n ): EncodedVideoChunk | EncodedAudioChunk {\n if (track === 'video') {\n return new EncodedVideoChunk({\n type: chunkType,\n timestamp: timeUs,\n duration: durationUs,\n data,\n });\n } else {\n return new EncodedAudioChunk({\n type: chunkType,\n timestamp: timeUs,\n duration: durationUs,\n data,\n });\n }\n }\n\n private async updateLastAccess(clipId: string, track: string): Promise<void> {\n if (!this.db) return;\n\n const tx = this.db.transaction('chunks', 'readwrite');\n const store = tx.objectStore('chunks');\n const record = await this.promisifyRequest<ChunkRecord>(store.get([clipId, track]));\n\n if (record) {\n record.lastAccess = Date.now();\n await this.promisifyRequest(store.put(record));\n }\n }\n\n private async deleteEntry(clipId: string, track: string): Promise<void> {\n if (!this.db) return;\n\n // Step 1: Get record info\n let record: ChunkRecord | undefined;\n {\n const tx = this.db.transaction('chunks', 'readonly');\n const store = tx.objectStore('chunks');\n record = await this.promisifyRequest<ChunkRecord>(store.get([clipId, track]));\n }\n\n // Step 2: Delete OPFS file (outside transaction)\n if (record && this.opfsRoot) {\n try {\n const projectDir = await this.opfsRoot.getDirectoryHandle(\n `meframe-project-${this.projectId}`,\n { create: false }\n );\n await projectDir.removeEntry(record.fileName);\n } catch (error) {\n console.warn(`[L2Cache] Failed to delete OPFS file ${record.fileName}:`, error);\n }\n }\n\n // Step 3: Delete IndexedDB record (new transaction)\n {\n const tx = this.db.transaction('chunks', 'readwrite');\n const store = tx.objectStore('chunks');\n await this.promisifyRequest(store.delete([clipId, track]));\n }\n }\n\n private async enforceQuota(): Promise<void> {\n const estimate = await navigator.storage.estimate();\n const usage = estimate.usage || 0;\n\n console.log(\n `[L2Cache] enforceQuota: usage=${(usage / 1024 / 1024).toFixed(2)}MB, maxSize=${(this.maxSize / 1024 / 1024).toFixed(2)}MB`\n );\n\n if (usage <= this.maxSize) return;\n\n console.warn(\n `[L2Cache] Quota exceeded! Deleting oldest entries: usage=${usage}, maxSize=${this.maxSize}`\n );\n\n if (!this.db) return;\n\n // Delete oldest entries until under quota\n const tx = this.db.transaction('chunks', 'readwrite');\n const store = tx.objectStore('chunks');\n const index = store.index('lastAccess');\n\n let bytesDeleted = 0;\n const toDelete = usage - this.maxSize;\n\n const cursor = index.openCursor();\n await new Promise<void>((resolve) => {\n cursor.onsuccess = async (event) => {\n const cursor = (event.target as IDBRequest).result;\n if (!cursor || bytesDeleted >= toDelete) {\n resolve();\n return;\n }\n\n const record: ChunkRecord = cursor.value;\n await this.deleteEntry(record.clipId, record.track);\n bytesDeleted += record.totalBytes;\n\n cursor.continue();\n };\n });\n }\n\n private async collectRecords(store: IDBObjectStore, clipId: string): Promise<ChunkRecord[]> {\n const records: ChunkRecord[] = [];\n const cursor = store.openCursor();\n await new Promise<void>((resolve) => {\n cursor.onsuccess = (event) => {\n const cursor = (event.target as IDBRequest).result;\n if (!cursor) {\n resolve();\n return;\n }\n\n const record: ChunkRecord = cursor.value;\n if (record.clipId === clipId) {\n records.push(record);\n }\n\n cursor.continue();\n };\n });\n return records;\n }\n\n private promisifyRequest<T>(request: IDBRequest): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n }\n\n getMetadata(): {\n maxSizeMB: number;\n usedSizeMB: number;\n entries: number;\n hitRate: number;\n } {\n // This is a simplified implementation\n // In a real implementation, we would track actual usage\n return {\n maxSizeMB: this.maxSize / (1024 * 1024),\n usedSizeMB: 0, // Would need to track actual usage\n entries: 0, // Would need to track actual entries\n hitRate: 0, // Would need to track hits and misses\n };\n }\n\n async hasAvailableQuota(sizeMB: number): Promise<boolean> {\n if (typeof navigator === 'undefined' || !navigator.storage?.estimate) {\n // L2Cache requires storage API to function\n throw new Error('Storage API not available');\n }\n\n const estimate = await navigator.storage.estimate();\n const availableMB = ((estimate.quota || 0) - (estimate.usage || 0)) / (1024 * 1024);\n return availableMB >= sizeMB;\n }\n\n /**\n * Get chunk metadata (decoderConfig) for a specific clip\n */\n async getClipMetadata(clipId: string, track: 'video' | 'audio'): Promise<any | null> {\n await this.init();\n\n if (!this.db) return null;\n\n const tx = this.db.transaction('chunks', 'readonly');\n const store = tx.objectStore('chunks');\n const record = await this.promisifyRequest<ChunkRecord>(store.get([clipId, track]));\n\n return record?.metadata || null;\n }\n}\n"],"names":["cursor"],"mappings":"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"}
|
|
@@ -14,7 +14,7 @@ export interface L1CacheMetadata {
|
|
|
14
14
|
clipCount: number;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Simplified VideoL1Cache for
|
|
17
|
+
* Simplified VideoL1Cache for 2-Clip strategy
|
|
18
18
|
*
|
|
19
19
|
* Clip lifecycle is managed by ClipSessionManager:
|
|
20
20
|
* - No LRU eviction (clips evicted explicitly)
|
|
@@ -28,8 +28,8 @@ export declare class VideoL1Cache {
|
|
|
28
28
|
private currentBytes;
|
|
29
29
|
constructor(config: L1Config);
|
|
30
30
|
get(timeUs: TimeUs, clipId: string): RcFrame | null;
|
|
31
|
-
putGOP(gop: GOP, clipId: string): void;
|
|
32
|
-
addFrame(frame: VideoFrame, clipId: string, frameDuration: TimeUs, gopSerial?: number, isKeyframe?: boolean): RcFrame | null;
|
|
31
|
+
putGOP(gop: GOP, clipId: string, trackId: string): void;
|
|
32
|
+
addFrame(frame: VideoFrame, clipId: string, frameDuration: TimeUs, trackId: string, gopSerial?: number, isKeyframe?: boolean): RcFrame | null;
|
|
33
33
|
/**
|
|
34
34
|
* Evict all cache entries for a specific clip
|
|
35
35
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoL1Cache.d.ts","sourceRoot":"","sources":["../../../src/cache/l1/VideoL1Cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAMtC,UAAU,QAAQ;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAYD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqC;IACnE,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,YAAY,CAAK;gBAEb,MAAM,EAAE,QAAQ;IAK5B,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IA6BnD,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"VideoL1Cache.d.ts","sourceRoot":"","sources":["../../../src/cache/l1/VideoL1Cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAMtC,UAAU,QAAQ;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAYD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqC;IACnE,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,YAAY,CAAK;gBAEb,MAAM,EAAE,QAAQ;IAK5B,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IA6BnD,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAwBvD,QAAQ,CACN,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,OAAO,GACnB,OAAO,GAAG,IAAI;IA0BjB;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAY/B;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAKrC;;OAEG;IACH,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAMzC,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAatE,KAAK,IAAI,IAAI;IAUb,WAAW,IAAI,eAAe;IAS9B,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,WAAW;IAyBnB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,WAAW;IAgBnB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,UAAU;IAmBlB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,sBAAsB;CAM/B"}
|
|
@@ -35,10 +35,10 @@ class VideoL1Cache {
|
|
|
35
35
|
}
|
|
36
36
|
return entry.frames[frameIndex] ?? null;
|
|
37
37
|
}
|
|
38
|
-
putGOP(gop, clipId) {
|
|
38
|
+
putGOP(gop, clipId, trackId) {
|
|
39
39
|
const gopIndex = gop.index ?? gop.startUs;
|
|
40
40
|
const existing = this.getEntryExact(clipId, gopIndex);
|
|
41
|
-
const frames = this.wrapFrames(gop.frames, clipId, gop.durationUs);
|
|
41
|
+
const frames = this.wrapFrames(gop.frames, clipId, gop.durationUs, trackId);
|
|
42
42
|
if (existing) {
|
|
43
43
|
existing.gopStartUs = gop.startUs;
|
|
44
44
|
this.mergeFrames(existing, frames, gop.durationUs);
|
|
@@ -54,11 +54,11 @@ class VideoL1Cache {
|
|
|
54
54
|
this.registerEntry(entry);
|
|
55
55
|
this.currentBytes += entry.size;
|
|
56
56
|
}
|
|
57
|
-
addFrame(frame, clipId, frameDuration, gopSerial, isKeyframe) {
|
|
57
|
+
addFrame(frame, clipId, frameDuration, trackId, gopSerial, isKeyframe) {
|
|
58
58
|
const timestamp = frame.timestamp ?? 0;
|
|
59
59
|
const gopIndex = typeof gopSerial === "number" ? gopSerial : findGopIndex(timestamp, this.gopIntervalUs);
|
|
60
60
|
const existing = this.getEntryExact(clipId, gopIndex);
|
|
61
|
-
const rcFrame = this.wrapFrame(frame, clipId, frameDuration, gopSerial, isKeyframe);
|
|
61
|
+
const rcFrame = this.wrapFrame(frame, clipId, frameDuration, trackId, gopSerial, isKeyframe);
|
|
62
62
|
if (existing) {
|
|
63
63
|
this.mergeFrames(existing, [rcFrame], frameDuration);
|
|
64
64
|
return rcFrame;
|
|
@@ -195,9 +195,9 @@ class VideoL1Cache {
|
|
|
195
195
|
composeKey(clipId, gopIndex) {
|
|
196
196
|
return `${clipId}:${gopIndex}`;
|
|
197
197
|
}
|
|
198
|
-
wrapFrame(frame, clipId, frameDuration, gopSerial, isKeyframe) {
|
|
198
|
+
wrapFrame(frame, clipId, frameDuration, trackId, gopSerial, isKeyframe) {
|
|
199
199
|
return RcFrame.wrap(frame, {
|
|
200
|
-
trackId
|
|
200
|
+
trackId,
|
|
201
201
|
clipId,
|
|
202
202
|
timestampUs: frame.timestamp ?? 0,
|
|
203
203
|
durationUs: frame.duration ?? frameDuration,
|
|
@@ -205,9 +205,14 @@ class VideoL1Cache {
|
|
|
205
205
|
isKeyframe
|
|
206
206
|
});
|
|
207
207
|
}
|
|
208
|
-
wrapFrames(frames, clipId, fallbackDuration) {
|
|
208
|
+
wrapFrames(frames, clipId, fallbackDuration, trackId) {
|
|
209
209
|
const wrapped = frames.map(
|
|
210
|
-
(frame) => frame instanceof RcFrame ? frame : this.wrapFrame(
|
|
210
|
+
(frame) => frame instanceof RcFrame ? frame : this.wrapFrame(
|
|
211
|
+
frame,
|
|
212
|
+
clipId,
|
|
213
|
+
fallbackDuration / Math.max(frames.length, 1),
|
|
214
|
+
trackId
|
|
215
|
+
)
|
|
211
216
|
);
|
|
212
217
|
return this.normalizeFrames(wrapped);
|
|
213
218
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoL1Cache.js","sources":["../../../src/cache/l1/VideoL1Cache.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport type { GOP } from '../types';\nimport { RcFrame } from '../../model';\nimport { findFrameIndex, findGopIndex } from './gop-utils';\n\nconst DEFAULT_GOP_INTERVAL_US = 2_000_000;\nconst BYTES_PER_MB = 1024 * 1024;\n\ninterface L1Config {\n maxMemoryMB: number;\n maxGOPs?: number;\n gopIntervalUs?: number;\n}\n\ninterface L1CacheEntry {\n key: string;\n clipId: string;\n gopIndex: number;\n gopStartUs: TimeUs;\n durationUs: TimeUs;\n frames: RcFrame[];\n size: number;\n}\n\nexport interface L1CacheMetadata {\n size: number;\n maxSize: number;\n entries: number;\n clipCount: number;\n}\n\n/**\n * Simplified VideoL1Cache for 3-Clip strategy\n *\n * Clip lifecycle is managed by ClipSessionManager:\n * - No LRU eviction (clips evicted explicitly)\n * - No capacity limits (fixed 3 clips)\n * - Simple GOP storage per clip\n */\nexport class VideoL1Cache {\n private readonly entriesByClip = new Map<string, L1CacheEntry[]>();\n private maxMemoryBytes: number;\n private gopIntervalUs: number;\n private currentBytes = 0;\n\n constructor(config: L1Config) {\n this.maxMemoryBytes = config.maxMemoryMB * BYTES_PER_MB;\n this.gopIntervalUs = config.gopIntervalUs ?? DEFAULT_GOP_INTERVAL_US;\n }\n\n get(timeUs: TimeUs, clipId: string): RcFrame | null {\n const clipEntries = this.entriesByClip.get(clipId);\n if (!clipEntries || clipEntries.length === 0) {\n return null;\n }\n\n const entry = this.findEntryByTime(clipEntries, timeUs);\n if (!entry) {\n return null;\n }\n\n const frameIndex = findFrameIndex(\n {\n index: entry.gopIndex,\n startUs: entry.gopStartUs,\n durationUs: entry.durationUs,\n frames: entry.frames,\n isKeyframe: true,\n clipId: entry.clipId,\n },\n timeUs\n );\n if (frameIndex === -1) {\n return null;\n }\n\n return entry.frames[frameIndex] ?? null;\n }\n\n putGOP(gop: GOP, clipId: string): void {\n const gopIndex = gop.index ?? gop.startUs;\n const existing = this.getEntryExact(clipId, gopIndex);\n\n const frames = this.wrapFrames(gop.frames, clipId, gop.durationUs);\n\n if (existing) {\n existing.gopStartUs = gop.startUs;\n this.mergeFrames(existing, frames, gop.durationUs);\n return;\n }\n\n const entry = this.createEntry(clipId, {\n gopIndex,\n startUs: gop.startUs,\n durationUs: gop.durationUs,\n frames,\n isKeyframe: gop.isKeyframe,\n });\n\n this.registerEntry(entry);\n this.currentBytes += entry.size;\n }\n\n addFrame(\n frame: VideoFrame,\n clipId: string,\n frameDuration: TimeUs,\n gopSerial?: number,\n isKeyframe?: boolean\n ): RcFrame | null {\n const timestamp = frame.timestamp ?? 0;\n const gopIndex =\n typeof gopSerial === 'number' ? gopSerial : findGopIndex(timestamp, this.gopIntervalUs);\n const existing = this.getEntryExact(clipId, gopIndex);\n\n const rcFrame = this.wrapFrame(frame, clipId, frameDuration, gopSerial, isKeyframe);\n\n if (existing) {\n this.mergeFrames(existing, [rcFrame], frameDuration);\n return rcFrame;\n }\n\n const entry = this.createEntry(clipId, {\n gopIndex,\n startUs: timestamp,\n durationUs: frameDuration,\n frames: [rcFrame],\n isKeyframe: isKeyframe ?? true,\n });\n\n this.registerEntry(entry);\n this.currentBytes += entry.size;\n return rcFrame;\n }\n\n /**\n * Evict all cache entries for a specific clip\n */\n evictClip(clipId: string): void {\n const entries = this.entriesByClip.get(clipId);\n if (!entries) return;\n\n for (const entry of entries) {\n this.closeFrames(entry);\n this.currentBytes -= entry.size;\n }\n\n this.entriesByClip.delete(clipId);\n }\n\n /**\n * Check if a clip has any cached entries\n */\n isClipCached(clipId: string): boolean {\n const entries = this.entriesByClip.get(clipId);\n return !!entries && entries.length > 0;\n }\n\n /**\n * Get total frame count for a clip\n */\n getClipFrameCount(clipId: string): number {\n const entries = this.entriesByClip.get(clipId);\n if (!entries) return 0;\n return entries.reduce((sum, entry) => sum + entry.frames.length, 0);\n }\n\n invalidateRange(startUs: TimeUs, endUs: TimeUs, clipId?: string): void {\n for (const entry of this.iterateEntries()) {\n if (clipId && entry.clipId !== clipId) {\n continue;\n }\n\n const gopEnd = entry.gopStartUs + entry.durationUs;\n if (entry.gopStartUs < endUs && gopEnd > startUs) {\n this.removeEntry(entry);\n }\n }\n }\n\n clear(): void {\n for (const entries of this.entriesByClip.values()) {\n for (const entry of entries) {\n this.closeFrames(entry);\n }\n }\n this.entriesByClip.clear();\n this.currentBytes = 0;\n }\n\n getMetadata(): L1CacheMetadata {\n return {\n size: this.currentBytes,\n maxSize: this.maxMemoryBytes,\n entries: this.countEntries(),\n clipCount: this.entriesByClip.size,\n };\n }\n\n private registerEntry(entry: L1CacheEntry): void {\n const entries = this.ensureClipEntries(entry.clipId);\n const insertIndex = this.findInsertIndex(entries, entry.gopIndex);\n entries.splice(insertIndex, 0, entry);\n }\n\n private createEntry(\n clipId: string,\n gop: {\n gopIndex: number;\n startUs: TimeUs;\n durationUs: TimeUs;\n frames: RcFrame[];\n isKeyframe: boolean;\n }\n ): L1CacheEntry {\n const frames = this.normalizeFrames(gop.frames);\n const entry: L1CacheEntry = {\n key: this.composeKey(clipId, gop.gopIndex),\n clipId,\n gopIndex: gop.gopIndex,\n gopStartUs: gop.startUs,\n durationUs: gop.durationUs,\n frames,\n size: 0,\n };\n\n this.updateEntryStats(entry, this.deriveFallbackDuration(gop.durationUs, frames.length));\n return entry;\n }\n\n private mergeFrames(entry: L1CacheEntry, frames: RcFrame[], fallbackDuration: TimeUs): void {\n const durationFallback = this.deriveFallbackDuration(\n fallbackDuration,\n entry.frames.length + frames.length\n );\n for (const rcFrame of frames) {\n this.insertFrame(entry, rcFrame, durationFallback);\n }\n this.updateEntryStats(entry, durationFallback);\n }\n\n private insertFrame(entry: L1CacheEntry, frame: RcFrame, fallbackDuration: TimeUs): void {\n const timestamp = frame.timestampUs ?? entry.gopStartUs;\n const frames = entry.frames;\n const insertIndex = this.findFrameInsertIndex(frames, timestamp);\n\n if (\n insertIndex < frames.length &&\n (frames[insertIndex]?.timestampUs ?? entry.gopStartUs) === timestamp\n ) {\n const oldFrame = frames[insertIndex];\n frames[insertIndex] = frame;\n oldFrame?.close?.();\n } else {\n frames.splice(insertIndex, 0, frame);\n }\n\n entry.size += frame.sizeEstimate;\n const duration = frame.durationUs || fallbackDuration;\n entry.durationUs = Math.max(entry.durationUs, timestamp + duration - entry.gopStartUs);\n }\n\n private removeEntry(entry: L1CacheEntry): void {\n const clipEntries = this.entriesByClip.get(entry.clipId);\n if (clipEntries) {\n const index = clipEntries.findIndex((item) => item.gopIndex === entry.gopIndex);\n if (index !== -1) {\n clipEntries.splice(index, 1);\n }\n if (clipEntries.length === 0) {\n this.entriesByClip.delete(entry.clipId);\n }\n }\n\n this.closeFrames(entry);\n this.currentBytes -= entry.size;\n }\n\n private closeFrames(entry: L1CacheEntry): void {\n for (const frame of entry.frames) {\n frame?.close?.();\n }\n }\n\n private composeKey(clipId: string, gopIndex: number): string {\n return `${clipId}:${gopIndex}`;\n }\n\n private wrapFrame(\n frame: VideoFrame,\n clipId: string,\n frameDuration: TimeUs,\n gopSerial?: number,\n isKeyframe?: boolean\n ): RcFrame {\n return RcFrame.wrap(frame, {\n trackId: 'main',\n clipId,\n timestampUs: frame.timestamp ?? 0,\n durationUs: frame.duration ?? frameDuration,\n gopSerial,\n isKeyframe,\n });\n }\n\n private wrapFrames(\n frames: (RcFrame | VideoFrame)[],\n clipId: string,\n fallbackDuration: TimeUs\n ): RcFrame[] {\n const wrapped = frames.map((frame) =>\n frame instanceof RcFrame\n ? frame\n : this.wrapFrame(frame as VideoFrame, clipId, fallbackDuration / Math.max(frames.length, 1))\n );\n return this.normalizeFrames(wrapped);\n }\n\n private getEntryExact(clipId: string, gopIndex: number): L1CacheEntry | undefined {\n const entries = this.entriesByClip.get(clipId);\n if (!entries) return undefined;\n return entries.find((e) => e.gopIndex === gopIndex);\n }\n\n private findEntryByTime(entries: L1CacheEntry[], timeUs: TimeUs): L1CacheEntry | undefined {\n let low = 0;\n let high = entries.length - 1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const entry = entries[mid];\n if (!entry) break;\n\n if (timeUs < entry.gopStartUs) {\n high = mid - 1;\n } else if (timeUs >= entry.gopStartUs + entry.durationUs) {\n low = mid + 1;\n } else {\n return entry;\n }\n }\n\n return undefined;\n }\n\n private countEntries(): number {\n let count = 0;\n for (const clipEntries of this.entriesByClip.values()) {\n count += clipEntries.length;\n }\n return count;\n }\n\n private iterateEntries(): Iterable<L1CacheEntry> {\n const entries: L1CacheEntry[] = [];\n for (const clipEntries of this.entriesByClip.values()) {\n entries.push(...clipEntries);\n }\n return entries;\n }\n\n private ensureClipEntries(clipId: string): L1CacheEntry[] {\n let entries = this.entriesByClip.get(clipId);\n if (!entries) {\n entries = [];\n this.entriesByClip.set(clipId, entries);\n }\n return entries;\n }\n\n private findInsertIndex(entries: L1CacheEntry[], gopIndex: number): number {\n let low = 0;\n let high = entries.length;\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const entry = entries[mid];\n if (entry && entry.gopIndex < gopIndex) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n }\n\n private findFrameInsertIndex(frames: RcFrame[], timestamp: TimeUs): number {\n let low = 0;\n let high = frames.length;\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const midTs = frames[mid]?.timestampUs ?? 0;\n if (midTs < timestamp) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n }\n\n private normalizeFrames(frames: RcFrame[]): RcFrame[] {\n const seen = new Set<number>();\n const sorted = [...frames].sort((a, b) => (a.timestampUs ?? 0) - (b.timestampUs ?? 0));\n const result: RcFrame[] = [];\n for (const frame of sorted) {\n const ts = frame.timestampUs ?? 0;\n if (seen.has(ts)) {\n frame?.close?.();\n } else {\n seen.add(ts);\n result.push(frame);\n }\n }\n return result;\n }\n\n private updateEntryStats(entry: L1CacheEntry, fallbackDuration: TimeUs): void {\n entry.size = entry.frames.reduce((acc, frame) => acc + frame.sizeEstimate, 0);\n entry.durationUs = entry.frames.reduce((acc, frame) => {\n const duration = frame.durationUs || fallbackDuration;\n return Math.max(acc, (frame.timestampUs ?? entry.gopStartUs) + duration - entry.gopStartUs);\n }, entry.durationUs);\n }\n\n private deriveFallbackDuration(durationUs: TimeUs, frameCount: number): TimeUs {\n if (frameCount <= 1) {\n return durationUs || this.gopIntervalUs;\n }\n return Math.max(durationUs / frameCount, this.gopIntervalUs / frameCount);\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VideoL1Cache.js","sources":["../../../src/cache/l1/VideoL1Cache.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport type { GOP } from '../types';\nimport { RcFrame } from '../../model';\nimport { findFrameIndex, findGopIndex } from './gop-utils';\n\nconst DEFAULT_GOP_INTERVAL_US = 2_000_000;\nconst BYTES_PER_MB = 1024 * 1024;\n\ninterface L1Config {\n maxMemoryMB: number;\n maxGOPs?: number;\n gopIntervalUs?: number;\n}\n\ninterface L1CacheEntry {\n key: string;\n clipId: string;\n gopIndex: number;\n gopStartUs: TimeUs;\n durationUs: TimeUs;\n frames: RcFrame[];\n size: number;\n}\n\nexport interface L1CacheMetadata {\n size: number;\n maxSize: number;\n entries: number;\n clipCount: number;\n}\n\n/**\n * Simplified VideoL1Cache for 2-Clip strategy\n *\n * Clip lifecycle is managed by ClipSessionManager:\n * - No LRU eviction (clips evicted explicitly)\n * - No capacity limits (fixed 3 clips)\n * - Simple GOP storage per clip\n */\nexport class VideoL1Cache {\n private readonly entriesByClip = new Map<string, L1CacheEntry[]>();\n private maxMemoryBytes: number;\n private gopIntervalUs: number;\n private currentBytes = 0;\n\n constructor(config: L1Config) {\n this.maxMemoryBytes = config.maxMemoryMB * BYTES_PER_MB;\n this.gopIntervalUs = config.gopIntervalUs ?? DEFAULT_GOP_INTERVAL_US;\n }\n\n get(timeUs: TimeUs, clipId: string): RcFrame | null {\n const clipEntries = this.entriesByClip.get(clipId);\n if (!clipEntries || clipEntries.length === 0) {\n return null;\n }\n\n const entry = this.findEntryByTime(clipEntries, timeUs);\n if (!entry) {\n return null;\n }\n\n const frameIndex = findFrameIndex(\n {\n index: entry.gopIndex,\n startUs: entry.gopStartUs,\n durationUs: entry.durationUs,\n frames: entry.frames,\n isKeyframe: true,\n clipId: entry.clipId,\n },\n timeUs\n );\n if (frameIndex === -1) {\n return null;\n }\n\n return entry.frames[frameIndex] ?? null;\n }\n\n putGOP(gop: GOP, clipId: string, trackId: string): void {\n const gopIndex = gop.index ?? gop.startUs;\n const existing = this.getEntryExact(clipId, gopIndex);\n\n const frames = this.wrapFrames(gop.frames, clipId, gop.durationUs, trackId);\n\n if (existing) {\n existing.gopStartUs = gop.startUs;\n this.mergeFrames(existing, frames, gop.durationUs);\n return;\n }\n\n const entry = this.createEntry(clipId, {\n gopIndex,\n startUs: gop.startUs,\n durationUs: gop.durationUs,\n frames,\n isKeyframe: gop.isKeyframe,\n });\n\n this.registerEntry(entry);\n this.currentBytes += entry.size;\n }\n\n addFrame(\n frame: VideoFrame,\n clipId: string,\n frameDuration: TimeUs,\n trackId: string,\n gopSerial?: number,\n isKeyframe?: boolean\n ): RcFrame | null {\n const timestamp = frame.timestamp ?? 0;\n const gopIndex =\n typeof gopSerial === 'number' ? gopSerial : findGopIndex(timestamp, this.gopIntervalUs);\n const existing = this.getEntryExact(clipId, gopIndex);\n\n const rcFrame = this.wrapFrame(frame, clipId, frameDuration, trackId, gopSerial, isKeyframe);\n\n if (existing) {\n this.mergeFrames(existing, [rcFrame], frameDuration);\n return rcFrame;\n }\n\n const entry = this.createEntry(clipId, {\n gopIndex,\n startUs: timestamp,\n durationUs: frameDuration,\n frames: [rcFrame],\n isKeyframe: isKeyframe ?? true,\n });\n\n this.registerEntry(entry);\n this.currentBytes += entry.size;\n return rcFrame;\n }\n\n /**\n * Evict all cache entries for a specific clip\n */\n evictClip(clipId: string): void {\n const entries = this.entriesByClip.get(clipId);\n if (!entries) return;\n\n for (const entry of entries) {\n this.closeFrames(entry);\n this.currentBytes -= entry.size;\n }\n\n this.entriesByClip.delete(clipId);\n }\n\n /**\n * Check if a clip has any cached entries\n */\n isClipCached(clipId: string): boolean {\n const entries = this.entriesByClip.get(clipId);\n return !!entries && entries.length > 0;\n }\n\n /**\n * Get total frame count for a clip\n */\n getClipFrameCount(clipId: string): number {\n const entries = this.entriesByClip.get(clipId);\n if (!entries) return 0;\n return entries.reduce((sum, entry) => sum + entry.frames.length, 0);\n }\n\n invalidateRange(startUs: TimeUs, endUs: TimeUs, clipId?: string): void {\n for (const entry of this.iterateEntries()) {\n if (clipId && entry.clipId !== clipId) {\n continue;\n }\n\n const gopEnd = entry.gopStartUs + entry.durationUs;\n if (entry.gopStartUs < endUs && gopEnd > startUs) {\n this.removeEntry(entry);\n }\n }\n }\n\n clear(): void {\n for (const entries of this.entriesByClip.values()) {\n for (const entry of entries) {\n this.closeFrames(entry);\n }\n }\n this.entriesByClip.clear();\n this.currentBytes = 0;\n }\n\n getMetadata(): L1CacheMetadata {\n return {\n size: this.currentBytes,\n maxSize: this.maxMemoryBytes,\n entries: this.countEntries(),\n clipCount: this.entriesByClip.size,\n };\n }\n\n private registerEntry(entry: L1CacheEntry): void {\n const entries = this.ensureClipEntries(entry.clipId);\n const insertIndex = this.findInsertIndex(entries, entry.gopIndex);\n entries.splice(insertIndex, 0, entry);\n }\n\n private createEntry(\n clipId: string,\n gop: {\n gopIndex: number;\n startUs: TimeUs;\n durationUs: TimeUs;\n frames: RcFrame[];\n isKeyframe: boolean;\n }\n ): L1CacheEntry {\n const frames = this.normalizeFrames(gop.frames);\n const entry: L1CacheEntry = {\n key: this.composeKey(clipId, gop.gopIndex),\n clipId,\n gopIndex: gop.gopIndex,\n gopStartUs: gop.startUs,\n durationUs: gop.durationUs,\n frames,\n size: 0,\n };\n\n this.updateEntryStats(entry, this.deriveFallbackDuration(gop.durationUs, frames.length));\n return entry;\n }\n\n private mergeFrames(entry: L1CacheEntry, frames: RcFrame[], fallbackDuration: TimeUs): void {\n const durationFallback = this.deriveFallbackDuration(\n fallbackDuration,\n entry.frames.length + frames.length\n );\n for (const rcFrame of frames) {\n this.insertFrame(entry, rcFrame, durationFallback);\n }\n this.updateEntryStats(entry, durationFallback);\n }\n\n private insertFrame(entry: L1CacheEntry, frame: RcFrame, fallbackDuration: TimeUs): void {\n const timestamp = frame.timestampUs ?? entry.gopStartUs;\n const frames = entry.frames;\n const insertIndex = this.findFrameInsertIndex(frames, timestamp);\n\n if (\n insertIndex < frames.length &&\n (frames[insertIndex]?.timestampUs ?? entry.gopStartUs) === timestamp\n ) {\n const oldFrame = frames[insertIndex];\n frames[insertIndex] = frame;\n oldFrame?.close?.();\n } else {\n frames.splice(insertIndex, 0, frame);\n }\n\n entry.size += frame.sizeEstimate;\n const duration = frame.durationUs || fallbackDuration;\n entry.durationUs = Math.max(entry.durationUs, timestamp + duration - entry.gopStartUs);\n }\n\n private removeEntry(entry: L1CacheEntry): void {\n const clipEntries = this.entriesByClip.get(entry.clipId);\n if (clipEntries) {\n const index = clipEntries.findIndex((item) => item.gopIndex === entry.gopIndex);\n if (index !== -1) {\n clipEntries.splice(index, 1);\n }\n if (clipEntries.length === 0) {\n this.entriesByClip.delete(entry.clipId);\n }\n }\n\n this.closeFrames(entry);\n this.currentBytes -= entry.size;\n }\n\n private closeFrames(entry: L1CacheEntry): void {\n for (const frame of entry.frames) {\n frame?.close?.();\n }\n }\n\n private composeKey(clipId: string, gopIndex: number): string {\n return `${clipId}:${gopIndex}`;\n }\n\n private wrapFrame(\n frame: VideoFrame,\n clipId: string,\n frameDuration: TimeUs,\n trackId: string,\n gopSerial?: number,\n isKeyframe?: boolean\n ): RcFrame {\n return RcFrame.wrap(frame, {\n trackId,\n clipId,\n timestampUs: frame.timestamp ?? 0,\n durationUs: frame.duration ?? frameDuration,\n gopSerial,\n isKeyframe,\n });\n }\n\n private wrapFrames(\n frames: (RcFrame | VideoFrame)[],\n clipId: string,\n fallbackDuration: TimeUs,\n trackId: string\n ): RcFrame[] {\n const wrapped = frames.map((frame) =>\n frame instanceof RcFrame\n ? frame\n : this.wrapFrame(\n frame as VideoFrame,\n clipId,\n fallbackDuration / Math.max(frames.length, 1),\n trackId\n )\n );\n return this.normalizeFrames(wrapped);\n }\n\n private getEntryExact(clipId: string, gopIndex: number): L1CacheEntry | undefined {\n const entries = this.entriesByClip.get(clipId);\n if (!entries) return undefined;\n return entries.find((e) => e.gopIndex === gopIndex);\n }\n\n private findEntryByTime(entries: L1CacheEntry[], timeUs: TimeUs): L1CacheEntry | undefined {\n let low = 0;\n let high = entries.length - 1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const entry = entries[mid];\n if (!entry) break;\n\n if (timeUs < entry.gopStartUs) {\n high = mid - 1;\n } else if (timeUs >= entry.gopStartUs + entry.durationUs) {\n low = mid + 1;\n } else {\n return entry;\n }\n }\n\n return undefined;\n }\n\n private countEntries(): number {\n let count = 0;\n for (const clipEntries of this.entriesByClip.values()) {\n count += clipEntries.length;\n }\n return count;\n }\n\n private iterateEntries(): Iterable<L1CacheEntry> {\n const entries: L1CacheEntry[] = [];\n for (const clipEntries of this.entriesByClip.values()) {\n entries.push(...clipEntries);\n }\n return entries;\n }\n\n private ensureClipEntries(clipId: string): L1CacheEntry[] {\n let entries = this.entriesByClip.get(clipId);\n if (!entries) {\n entries = [];\n this.entriesByClip.set(clipId, entries);\n }\n return entries;\n }\n\n private findInsertIndex(entries: L1CacheEntry[], gopIndex: number): number {\n let low = 0;\n let high = entries.length;\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const entry = entries[mid];\n if (entry && entry.gopIndex < gopIndex) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n }\n\n private findFrameInsertIndex(frames: RcFrame[], timestamp: TimeUs): number {\n let low = 0;\n let high = frames.length;\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const midTs = frames[mid]?.timestampUs ?? 0;\n if (midTs < timestamp) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n }\n\n private normalizeFrames(frames: RcFrame[]): RcFrame[] {\n const seen = new Set<number>();\n const sorted = [...frames].sort((a, b) => (a.timestampUs ?? 0) - (b.timestampUs ?? 0));\n const result: RcFrame[] = [];\n for (const frame of sorted) {\n const ts = frame.timestampUs ?? 0;\n if (seen.has(ts)) {\n frame?.close?.();\n } else {\n seen.add(ts);\n result.push(frame);\n }\n }\n return result;\n }\n\n private updateEntryStats(entry: L1CacheEntry, fallbackDuration: TimeUs): void {\n entry.size = entry.frames.reduce((acc, frame) => acc + frame.sizeEstimate, 0);\n entry.durationUs = entry.frames.reduce((acc, frame) => {\n const duration = frame.durationUs || fallbackDuration;\n return Math.max(acc, (frame.timestampUs ?? entry.gopStartUs) + duration - entry.gopStartUs);\n }, entry.durationUs);\n }\n\n private deriveFallbackDuration(durationUs: TimeUs, frameCount: number): TimeUs {\n if (frameCount <= 1) {\n return durationUs || this.gopIntervalUs;\n }\n return Math.max(durationUs / frameCount, this.gopIntervalUs / frameCount);\n }\n}\n"],"names":[],"mappings":";;AAKA,MAAM,0BAA0B;AAChC,MAAM,eAAe,OAAO;AAiCrB,MAAM,aAAa;AAAA,EACP,oCAAoB,IAAA;AAAA,EAC7B;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EAEvB,YAAY,QAAkB;AAC5B,SAAK,iBAAiB,OAAO,cAAc;AAC3C,SAAK,gBAAgB,OAAO,iBAAiB;AAAA,EAC/C;AAAA,EAEA,IAAI,QAAgB,QAAgC;AAClD,UAAM,cAAc,KAAK,cAAc,IAAI,MAAM;AACjD,QAAI,CAAC,eAAe,YAAY,WAAW,GAAG;AAC5C,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,KAAK,gBAAgB,aAAa,MAAM;AACtD,QAAI,CAAC,OAAO;AACV,aAAO;AAAA,IACT;AAEA,UAAM,aAAa;AAAA,MACjB;AAAA,QACE,OAAO,MAAM;AAAA,QACb,SAAS,MAAM;AAAA,QACf,YAAY,MAAM;AAAA,QAClB,QAAQ,MAAM;AAAA,QAEd,QAAQ,MAAM;AAAA,MAAA;AAAA,MAEhB;AAAA,IAAA;AAEF,QAAI,eAAe,IAAI;AACrB,aAAO;AAAA,IACT;AAEA,WAAO,MAAM,OAAO,UAAU,KAAK;AAAA,EACrC;AAAA,EAEA,OAAO,KAAU,QAAgB,SAAuB;AACtD,UAAM,WAAW,IAAI,SAAS,IAAI;AAClC,UAAM,WAAW,KAAK,cAAc,QAAQ,QAAQ;AAEpD,UAAM,SAAS,KAAK,WAAW,IAAI,QAAQ,QAAQ,IAAI,YAAY,OAAO;AAE1E,QAAI,UAAU;AACZ,eAAS,aAAa,IAAI;AAC1B,WAAK,YAAY,UAAU,QAAQ,IAAI,UAAU;AACjD;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,YAAY,QAAQ;AAAA,MACrC;AAAA,MACA,SAAS,IAAI;AAAA,MACb,YAAY,IAAI;AAAA,MAChB;AAAA,MACA,YAAY,IAAI;AAAA,IAAA,CACjB;AAED,SAAK,cAAc,KAAK;AACxB,SAAK,gBAAgB,MAAM;AAAA,EAC7B;AAAA,EAEA,SACE,OACA,QACA,eACA,SACA,WACA,YACgB;AAChB,UAAM,YAAY,MAAM,aAAa;AACrC,UAAM,WACJ,OAAO,cAAc,WAAW,YAAY,aAAa,WAAW,KAAK,aAAa;AACxF,UAAM,WAAW,KAAK,cAAc,QAAQ,QAAQ;AAEpD,UAAM,UAAU,KAAK,UAAU,OAAO,QAAQ,eAAe,SAAS,WAAW,UAAU;AAE3F,QAAI,UAAU;AACZ,WAAK,YAAY,UAAU,CAAC,OAAO,GAAG,aAAa;AACnD,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,KAAK,YAAY,QAAQ;AAAA,MACrC;AAAA,MACA,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,QAAQ,CAAC,OAAO;AAAA,MAChB,YAAY,cAAc;AAAA,IAAA,CAC3B;AAED,SAAK,cAAc,KAAK;AACxB,SAAK,gBAAgB,MAAM;AAC3B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAAsB;AAC9B,UAAM,UAAU,KAAK,cAAc,IAAI,MAAM;AAC7C,QAAI,CAAC,QAAS;AAEd,eAAW,SAAS,SAAS;AAC3B,WAAK,YAAY,KAAK;AACtB,WAAK,gBAAgB,MAAM;AAAA,IAC7B;AAEA,SAAK,cAAc,OAAO,MAAM;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,QAAyB;AACpC,UAAM,UAAU,KAAK,cAAc,IAAI,MAAM;AAC7C,WAAO,CAAC,CAAC,WAAW,QAAQ,SAAS;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,QAAwB;AACxC,UAAM,UAAU,KAAK,cAAc,IAAI,MAAM;AAC7C,QAAI,CAAC,QAAS,QAAO;AACrB,WAAO,QAAQ,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,OAAO,QAAQ,CAAC;AAAA,EACpE;AAAA,EAEA,gBAAgB,SAAiB,OAAe,QAAuB;AACrE,eAAW,SAAS,KAAK,kBAAkB;AACzC,UAAI,UAAU,MAAM,WAAW,QAAQ;AACrC;AAAA,MACF;AAEA,YAAM,SAAS,MAAM,aAAa,MAAM;AACxC,UAAI,MAAM,aAAa,SAAS,SAAS,SAAS;AAChD,aAAK,YAAY,KAAK;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,eAAW,WAAW,KAAK,cAAc,OAAA,GAAU;AACjD,iBAAW,SAAS,SAAS;AAC3B,aAAK,YAAY,KAAK;AAAA,MACxB;AAAA,IACF;AACA,SAAK,cAAc,MAAA;AACnB,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,cAA+B;AAC7B,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,SAAS,KAAK,aAAA;AAAA,MACd,WAAW,KAAK,cAAc;AAAA,IAAA;AAAA,EAElC;AAAA,EAEQ,cAAc,OAA2B;AAC/C,UAAM,UAAU,KAAK,kBAAkB,MAAM,MAAM;AACnD,UAAM,cAAc,KAAK,gBAAgB,SAAS,MAAM,QAAQ;AAChE,YAAQ,OAAO,aAAa,GAAG,KAAK;AAAA,EACtC;AAAA,EAEQ,YACN,QACA,KAOc;AACd,UAAM,SAAS,KAAK,gBAAgB,IAAI,MAAM;AAC9C,UAAM,QAAsB;AAAA,MAC1B,KAAK,KAAK,WAAW,QAAQ,IAAI,QAAQ;AAAA,MACzC;AAAA,MACA,UAAU,IAAI;AAAA,MACd,YAAY,IAAI;AAAA,MAChB,YAAY,IAAI;AAAA,MAChB;AAAA,MACA,MAAM;AAAA,IAAA;AAGR,SAAK,iBAAiB,OAAO,KAAK,uBAAuB,IAAI,YAAY,OAAO,MAAM,CAAC;AACvF,WAAO;AAAA,EACT;AAAA,EAEQ,YAAY,OAAqB,QAAmB,kBAAgC;AAC1F,UAAM,mBAAmB,KAAK;AAAA,MAC5B;AAAA,MACA,MAAM,OAAO,SAAS,OAAO;AAAA,IAAA;AAE/B,eAAW,WAAW,QAAQ;AAC5B,WAAK,YAAY,OAAO,SAAS,gBAAgB;AAAA,IACnD;AACA,SAAK,iBAAiB,OAAO,gBAAgB;AAAA,EAC/C;AAAA,EAEQ,YAAY,OAAqB,OAAgB,kBAAgC;AACvF,UAAM,YAAY,MAAM,eAAe,MAAM;AAC7C,UAAM,SAAS,MAAM;AACrB,UAAM,cAAc,KAAK,qBAAqB,QAAQ,SAAS;AAE/D,QACE,cAAc,OAAO,WACpB,OAAO,WAAW,GAAG,eAAe,MAAM,gBAAgB,WAC3D;AACA,YAAM,WAAW,OAAO,WAAW;AACnC,aAAO,WAAW,IAAI;AACtB,gBAAU,QAAA;AAAA,IACZ,OAAO;AACL,aAAO,OAAO,aAAa,GAAG,KAAK;AAAA,IACrC;AAEA,UAAM,QAAQ,MAAM;AACpB,UAAM,WAAW,MAAM,cAAc;AACrC,UAAM,aAAa,KAAK,IAAI,MAAM,YAAY,YAAY,WAAW,MAAM,UAAU;AAAA,EACvF;AAAA,EAEQ,YAAY,OAA2B;AAC7C,UAAM,cAAc,KAAK,cAAc,IAAI,MAAM,MAAM;AACvD,QAAI,aAAa;AACf,YAAM,QAAQ,YAAY,UAAU,CAAC,SAAS,KAAK,aAAa,MAAM,QAAQ;AAC9E,UAAI,UAAU,IAAI;AAChB,oBAAY,OAAO,OAAO,CAAC;AAAA,MAC7B;AACA,UAAI,YAAY,WAAW,GAAG;AAC5B,aAAK,cAAc,OAAO,MAAM,MAAM;AAAA,MACxC;AAAA,IACF;AAEA,SAAK,YAAY,KAAK;AACtB,SAAK,gBAAgB,MAAM;AAAA,EAC7B;AAAA,EAEQ,YAAY,OAA2B;AAC7C,eAAW,SAAS,MAAM,QAAQ;AAChC,aAAO,QAAA;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,WAAW,QAAgB,UAA0B;AAC3D,WAAO,GAAG,MAAM,IAAI,QAAQ;AAAA,EAC9B;AAAA,EAEQ,UACN,OACA,QACA,eACA,SACA,WACA,YACS;AACT,WAAO,QAAQ,KAAK,OAAO;AAAA,MACzB;AAAA,MACA;AAAA,MACA,aAAa,MAAM,aAAa;AAAA,MAChC,YAAY,MAAM,YAAY;AAAA,MAC9B;AAAA,MACA;AAAA,IAAA,CACD;AAAA,EACH;AAAA,EAEQ,WACN,QACA,QACA,kBACA,SACW;AACX,UAAM,UAAU,OAAO;AAAA,MAAI,CAAC,UAC1B,iBAAiB,UACb,QACA,KAAK;AAAA,QACH;AAAA,QACA;AAAA,QACA,mBAAmB,KAAK,IAAI,OAAO,QAAQ,CAAC;AAAA,QAC5C;AAAA,MAAA;AAAA,IACF;AAEN,WAAO,KAAK,gBAAgB,OAAO;AAAA,EACrC;AAAA,EAEQ,cAAc,QAAgB,UAA4C;AAChF,UAAM,UAAU,KAAK,cAAc,IAAI,MAAM;AAC7C,QAAI,CAAC,QAAS,QAAO;AACrB,WAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,aAAa,QAAQ;AAAA,EACpD;AAAA,EAEQ,gBAAgB,SAAyB,QAA0C;AACzF,QAAI,MAAM;AACV,QAAI,OAAO,QAAQ,SAAS;AAE5B,WAAO,OAAO,MAAM;AAClB,YAAM,MAAM,KAAK,OAAO,MAAM,QAAQ,CAAC;AACvC,YAAM,QAAQ,QAAQ,GAAG;AACzB,UAAI,CAAC,MAAO;AAEZ,UAAI,SAAS,MAAM,YAAY;AAC7B,eAAO,MAAM;AAAA,MACf,WAAW,UAAU,MAAM,aAAa,MAAM,YAAY;AACxD,cAAM,MAAM;AAAA,MACd,OAAO;AACL,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAuB;AAC7B,QAAI,QAAQ;AACZ,eAAW,eAAe,KAAK,cAAc,OAAA,GAAU;AACrD,eAAS,YAAY;AAAA,IACvB;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,iBAAyC;AAC/C,UAAM,UAA0B,CAAA;AAChC,eAAW,eAAe,KAAK,cAAc,OAAA,GAAU;AACrD,cAAQ,KAAK,GAAG,WAAW;AAAA,IAC7B;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,kBAAkB,QAAgC;AACxD,QAAI,UAAU,KAAK,cAAc,IAAI,MAAM;AAC3C,QAAI,CAAC,SAAS;AACZ,gBAAU,CAAA;AACV,WAAK,cAAc,IAAI,QAAQ,OAAO;AAAA,IACxC;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,gBAAgB,SAAyB,UAA0B;AACzE,QAAI,MAAM;AACV,QAAI,OAAO,QAAQ;AACnB,WAAO,MAAM,MAAM;AACjB,YAAM,MAAM,KAAK,OAAO,MAAM,QAAQ,CAAC;AACvC,YAAM,QAAQ,QAAQ,GAAG;AACzB,UAAI,SAAS,MAAM,WAAW,UAAU;AACtC,cAAM,MAAM;AAAA,MACd,OAAO;AACL,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,qBAAqB,QAAmB,WAA2B;AACzE,QAAI,MAAM;AACV,QAAI,OAAO,OAAO;AAClB,WAAO,MAAM,MAAM;AACjB,YAAM,MAAM,KAAK,OAAO,MAAM,QAAQ,CAAC;AACvC,YAAM,QAAQ,OAAO,GAAG,GAAG,eAAe;AAC1C,UAAI,QAAQ,WAAW;AACrB,cAAM,MAAM;AAAA,MACd,OAAO;AACL,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,gBAAgB,QAA8B;AACpD,UAAM,2BAAW,IAAA;AACjB,UAAM,SAAS,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,OAAO,EAAE,eAAe,MAAM,EAAE,eAAe,EAAE;AACrF,UAAM,SAAoB,CAAA;AAC1B,eAAW,SAAS,QAAQ;AAC1B,YAAM,KAAK,MAAM,eAAe;AAChC,UAAI,KAAK,IAAI,EAAE,GAAG;AAChB,eAAO,QAAA;AAAA,MACT,OAAO;AACL,aAAK,IAAI,EAAE;AACX,eAAO,KAAK,KAAK;AAAA,MACnB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,iBAAiB,OAAqB,kBAAgC;AAC5E,UAAM,OAAO,MAAM,OAAO,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,cAAc,CAAC;AAC5E,UAAM,aAAa,MAAM,OAAO,OAAO,CAAC,KAAK,UAAU;AACrD,YAAM,WAAW,MAAM,cAAc;AACrC,aAAO,KAAK,IAAI,MAAM,MAAM,eAAe,MAAM,cAAc,WAAW,MAAM,UAAU;AAAA,IAC5F,GAAG,MAAM,UAAU;AAAA,EACrB;AAAA,EAEQ,uBAAuB,YAAoB,YAA4B;AAC7E,QAAI,cAAc,GAAG;AACnB,aAAO,cAAc,KAAK;AAAA,IAC5B;AACA,WAAO,KAAK,IAAI,aAAa,YAAY,KAAK,gBAAgB,UAAU;AAAA,EAC1E;AACF;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAU9C;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,EAAE,cAkE5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc;IACzB;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA8BH;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;qCAyBmC,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE;;;;;;;;;;;;;;IAezD;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BK,CAAC"}
|
package/dist/config/defaults.js
CHANGED
|
@@ -6,7 +6,8 @@ const DEFAULT_CONFIG = {
|
|
|
6
6
|
defaultCanvasWidth: CANVAS_PRESETS.MOBILE_PORTRAIT.width,
|
|
7
7
|
defaultCanvasHeight: CANVAS_PRESETS.MOBILE_PORTRAIT.height,
|
|
8
8
|
defaultFps: 30,
|
|
9
|
-
workerPath: "/meframe-workers"
|
|
9
|
+
workerPath: "/meframe-workers",
|
|
10
|
+
workerExtension: ".js"
|
|
10
11
|
},
|
|
11
12
|
load: {
|
|
12
13
|
backpressure: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","sources":["../../src/config/defaults.ts"],"sourcesContent":["import type { ResolvedConfig } from './types';\nimport { CANVAS_PRESETS } from './presets';\n\n/**\n * Default configuration values based on mobile 1080p30 memory budget\n *\n * Note: Canvas dimensions are fixed for the entire project.\n * All video clips will be scaled/fitted to this resolution.\n * Choose based on your primary content type:\n * - 720×1280: Mobile vertical video\n * - 1080×1920: HD vertical video (TikTok, Instagram Reels) ← default\n * - 1920×1080: HD horizontal video (YouTube)\n */\nexport const DEFAULT_CONFIG: ResolvedConfig = {\n global: {\n logLevel: 'info',\n enablePerfMonitor: false,\n defaultCanvasWidth: CANVAS_PRESETS.MOBILE_PORTRAIT.width,\n defaultCanvasHeight: CANVAS_PRESETS.MOBILE_PORTRAIT.height,\n defaultFps: 30,\n workerPath: '/meframe-workers',\n },\n\n load: {\n backpressure: {\n highWaterMark: 64 * 1024, // 64 KB\n stallTimeoutMs: 500,\n },\n retry: {\n maxAttempts: 3,\n baseDelayMs: 500,\n },\n window: {\n maxInflightPerClip: 1,\n maxInflight: 4,\n chunkSize: 1 * 1024 * 1024, // 1 MB\n },\n },\n\n demux: {\n backpressure: {\n highWaterMark: 10, // 10 EncodedChunks\n },\n },\n\n decode: {\n video: {\n backpressure: {\n highWaterMark: 4, // 4 EncodedVideoChunks\n decodeQueueThreshold: 16, // Pause when decoder has 16+ chunks\n },\n maxGOPs: 4, // Cache 4 GOPs for seeking\n },\n audio: {\n backpressure: {\n highWaterMark: 20, // 20 EncodedAudioChunks\n },\n },\n },\n\n compose: {\n visual: {},\n audio: {\n enableDucking: false, // Default: no ducking\n },\n },\n\n encode: {\n video: {},\n audio: {},\n },\n\n cache: {\n l1: {},\n l2: {},\n },\n\n mux: {},\n};\n\n/**\n * Tuning presets for common scenarios\n */\nexport const TUNING_PRESETS = {\n /**\n * Low-latency live streaming\n * Minimize buffering for real-time playback\n */\n lowLatency: {\n global: {\n logLevel: 'info' as const,\n },\n load: {\n backpressure: {\n highWaterMark: 16 * 1024, // 16 KB\n },\n },\n demux: {\n backpressure: {\n highWaterMark: 3,\n },\n },\n decode: {\n video: {\n backpressure: {\n highWaterMark: 2,\n decodeQueueThreshold: 4,\n },\n },\n audio: {\n backpressure: {\n highWaterMark: 10,\n },\n },\n },\n },\n\n /**\n * 4K60 playback/export\n * Larger buffers for high bitrate content\n */\n highQuality: {\n global: {\n logLevel: 'info' as const,\n },\n load: {\n backpressure: {\n highWaterMark: 256 * 1024, // 256 KB\n },\n retry: {\n maxAttempts: 3,\n baseDelayMs: 500,\n },\n },\n demux: {\n backpressure: {\n highWaterMark: 20,\n },\n },\n decode: {\n video: {\n backpressure: {\n highWaterMark: 8,\n decodeQueueThreshold: 24,\n },\n codecHints: ['h264', 'hevc'] as ('h264' | 'hevc')[],\n },\n audio: {\n backpressure: {\n highWaterMark: 30,\n },\n },\n },\n cache: {\n l2: {\n quotaGb: 1,\n },\n },\n },\n\n /**\n * Batch offline transcode\n * Maximum throughput, memory not a concern\n */\n offline: {\n global: {\n logLevel: 'warn' as const,\n },\n load: {\n backpressure: {\n highWaterMark: 512 * 1024, // 512 KB\n },\n },\n demux: {\n backpressure: {\n highWaterMark: 25,\n },\n },\n decode: {\n video: {\n backpressure: {\n highWaterMark: 12,\n decodeQueueThreshold: 32,\n },\n },\n audio: {\n backpressure: {\n highWaterMark: 40,\n },\n },\n },\n },\n} as const;\n"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"defaults.js","sources":["../../src/config/defaults.ts"],"sourcesContent":["import type { ResolvedConfig } from './types';\nimport { CANVAS_PRESETS } from './presets';\n\n/**\n * Detect if running in development mode\n * In dev mode, workers are loaded from source (Vite dev server)\n * In prod mode, workers are loaded from dist\n */\nconst isDev = import.meta.env?.DEV ?? false;\n\n/**\n * Default configuration values based on mobile 1080p30 memory budget\n *\n * Note: Canvas dimensions are fixed for the entire project.\n * All video clips will be scaled/fitted to this resolution.\n * Choose based on your primary content type:\n * - 720×1280: Mobile vertical video\n * - 1080×1920: HD vertical video (TikTok, Instagram Reels) ← default\n * - 1920×1080: HD horizontal video (YouTube)\n */\nexport const DEFAULT_CONFIG: ResolvedConfig = {\n global: {\n logLevel: 'info',\n enablePerfMonitor: false,\n defaultCanvasWidth: CANVAS_PRESETS.MOBILE_PORTRAIT.width,\n defaultCanvasHeight: CANVAS_PRESETS.MOBILE_PORTRAIT.height,\n defaultFps: 30,\n workerPath: isDev ? '/src' : '/meframe-workers',\n workerExtension: isDev ? '.ts' : '.js',\n },\n\n load: {\n backpressure: {\n highWaterMark: 64 * 1024, // 64 KB\n stallTimeoutMs: 500,\n },\n retry: {\n maxAttempts: 3,\n baseDelayMs: 500,\n },\n window: {\n maxInflightPerClip: 1,\n maxInflight: 4,\n chunkSize: 1 * 1024 * 1024, // 1 MB\n },\n },\n\n demux: {\n backpressure: {\n highWaterMark: 10, // 10 EncodedChunks\n },\n },\n\n decode: {\n video: {\n backpressure: {\n highWaterMark: 4, // 4 EncodedVideoChunks\n decodeQueueThreshold: 16, // Pause when decoder has 16+ chunks\n },\n maxGOPs: 4, // Cache 4 GOPs for seeking\n },\n audio: {\n backpressure: {\n highWaterMark: 20, // 20 EncodedAudioChunks\n },\n },\n },\n\n compose: {\n visual: {},\n audio: {\n enableDucking: false, // Default: no ducking\n },\n },\n\n encode: {\n video: {},\n audio: {},\n },\n\n cache: {\n l1: {},\n l2: {},\n },\n\n mux: {},\n};\n\n/**\n * Tuning presets for common scenarios\n */\nexport const TUNING_PRESETS = {\n /**\n * Low-latency live streaming\n * Minimize buffering for real-time playback\n */\n lowLatency: {\n global: {\n logLevel: 'info' as const,\n },\n load: {\n backpressure: {\n highWaterMark: 16 * 1024, // 16 KB\n },\n },\n demux: {\n backpressure: {\n highWaterMark: 3,\n },\n },\n decode: {\n video: {\n backpressure: {\n highWaterMark: 2,\n decodeQueueThreshold: 4,\n },\n },\n audio: {\n backpressure: {\n highWaterMark: 10,\n },\n },\n },\n },\n\n /**\n * 4K60 playback/export\n * Larger buffers for high bitrate content\n */\n highQuality: {\n global: {\n logLevel: 'info' as const,\n },\n load: {\n backpressure: {\n highWaterMark: 256 * 1024, // 256 KB\n },\n retry: {\n maxAttempts: 3,\n baseDelayMs: 500,\n },\n },\n demux: {\n backpressure: {\n highWaterMark: 20,\n },\n },\n decode: {\n video: {\n backpressure: {\n highWaterMark: 8,\n decodeQueueThreshold: 24,\n },\n codecHints: ['h264', 'hevc'] as ('h264' | 'hevc')[],\n },\n audio: {\n backpressure: {\n highWaterMark: 30,\n },\n },\n },\n cache: {\n l2: {\n quotaGb: 1,\n },\n },\n },\n\n /**\n * Batch offline transcode\n * Maximum throughput, memory not a concern\n */\n offline: {\n global: {\n logLevel: 'warn' as const,\n },\n load: {\n backpressure: {\n highWaterMark: 512 * 1024, // 512 KB\n },\n },\n demux: {\n backpressure: {\n highWaterMark: 25,\n },\n },\n decode: {\n video: {\n backpressure: {\n highWaterMark: 12,\n decodeQueueThreshold: 32,\n },\n },\n audio: {\n backpressure: {\n highWaterMark: 40,\n },\n },\n },\n },\n} as const;\n"],"names":[],"mappings":"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"}
|
package/dist/config/types.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export interface MeframeConfig {
|
|
|
29
29
|
defaultFps?: number;
|
|
30
30
|
/** Worker files base path (must match meframePlugin workerPath) */
|
|
31
31
|
workerPath?: string;
|
|
32
|
+
/** Worker file extension (.ts in dev, .js in prod) */
|
|
33
|
+
workerExtension?: string;
|
|
32
34
|
};
|
|
33
35
|
/** Resource loading stage - fetch → ReadableStream */
|
|
34
36
|
load?: {
|
|
@@ -134,6 +136,7 @@ export interface ResolvedConfig {
|
|
|
134
136
|
defaultCanvasHeight: number;
|
|
135
137
|
defaultFps: number;
|
|
136
138
|
workerPath: string;
|
|
139
|
+
workerExtension: string;
|
|
137
140
|
};
|
|
138
141
|
load: {
|
|
139
142
|
backpressure: Required<LoadBackpressure>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,MAAM,CAAC,EAAE;QACP,QAAQ,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;QACjD,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,mEAAmE;QACnE,UAAU,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { IPlaybackController,
|
|
2
|
-
import { TimeUs } from '../model/types';
|
|
1
|
+
import { IPlaybackController, PlaybackOptions, IEventBus, PreviewHandle, TimeUs } from './types';
|
|
3
2
|
import { GlobalAudioSession } from '../stages/compose/GlobalAudioSession';
|
|
4
3
|
import { Orchestrator } from '../orchestrator';
|
|
5
4
|
|
|
@@ -7,13 +6,13 @@ import { Orchestrator } from '../orchestrator';
|
|
|
7
6
|
* Playback controller for preview
|
|
8
7
|
* Internal implementation - not exposed directly to external consumers
|
|
9
8
|
*/
|
|
10
|
-
export declare class PlaybackController implements IPlaybackController {
|
|
9
|
+
export declare class PlaybackController implements IPlaybackController, PreviewHandle {
|
|
11
10
|
private orchestrator;
|
|
12
11
|
private eventBus;
|
|
13
12
|
private canvas;
|
|
14
13
|
private ctx;
|
|
15
|
-
|
|
16
|
-
private
|
|
14
|
+
currentTimeUs: TimeUs;
|
|
15
|
+
private state;
|
|
17
16
|
private playbackRate;
|
|
18
17
|
private volume;
|
|
19
18
|
private loop;
|
|
@@ -37,12 +36,12 @@ export declare class PlaybackController implements IPlaybackController {
|
|
|
37
36
|
setVolume(volume: number): void;
|
|
38
37
|
setMute(muted: boolean): void;
|
|
39
38
|
setLoop(loop: boolean): void;
|
|
40
|
-
get currentTime(): TimeUs;
|
|
41
39
|
get duration(): TimeUs;
|
|
42
|
-
get
|
|
43
|
-
get playing(): boolean;
|
|
40
|
+
get isPlaying(): boolean;
|
|
44
41
|
setAudioSession(session: GlobalAudioSession): void;
|
|
45
42
|
resume(): void;
|
|
43
|
+
on(event: string, handler: (payload: any) => void): void;
|
|
44
|
+
off(event: string, handler: (payload: any) => void): void;
|
|
46
45
|
private setupListeners;
|
|
47
46
|
private playbackLoop;
|
|
48
47
|
private updateTime;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlaybackController.d.ts","sourceRoot":"","sources":["../../src/controllers/PlaybackController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"PlaybackController.d.ts","sourceRoot":"","sources":["../../src/controllers/PlaybackController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EAEnB,eAAe,EACf,SAAS,EACT,aAAa,EACb,MAAM,EACP,MAAM,SAAS,CAAC;AAGjB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,mBAAmB,EAAE,aAAa;IAC3E,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAY;IAC5B,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,GAAG,CAA+D;IAG1E,aAAa,EAAE,MAAM,CAAK;IAC1B,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,MAAM,CAAO;IACrB,OAAO,CAAC,IAAI,CAAS;IAGrB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,SAAS,CAAK;IAGtB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,WAAW,CAAsC;IACzD,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,YAAY,CAAmC;IAGvD,OAAO,CAAC,WAAW,CAAS;gBAEhB,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe;IAyCrF,IAAI,IAAI,IAAI;YAME,aAAa;IAgC3B,KAAK,IAAI,IAAI;IAeb,IAAI,IAAI,IAAI;IAiBN,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BzC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAa3B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAS/B,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAQ7B,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAI5B,IAAI,QAAQ,IAAI,MAAM,CAOrB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAKlD,MAAM,IAAI,IAAI;IAId,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAIxD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAIzD,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,YAAY;IAwCpB,OAAO,CAAC,UAAU;YAuBJ,kBAAkB;YAwBlB,uBAAuB;IA8CrC,OAAO,CAAC,SAAS;IAKjB,OAAO,IAAI,IAAI;YAID,kBAAkB;IAWhC,OAAO,CAAC,kBAAkB;IAiC1B,OAAO,CAAC,iBAAiB;CAW1B"}
|