@meframe/core 0.0.1
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/CHANGELOG.md +42 -0
- package/LICENSE +22 -0
- package/README.md +396 -0
- package/dist/Meframe.d.ts +82 -0
- package/dist/Meframe.d.ts.map +1 -0
- package/dist/Meframe.js +290 -0
- package/dist/Meframe.js.map +1 -0
- package/dist/_virtual/mp4box.all.js +5 -0
- package/dist/_virtual/mp4box.all.js.map +1 -0
- package/dist/cache/BatchWriter.d.ts +25 -0
- package/dist/cache/BatchWriter.d.ts.map +1 -0
- package/dist/cache/CacheManager.d.ts +115 -0
- package/dist/cache/CacheManager.d.ts.map +1 -0
- package/dist/cache/CacheManager.js +388 -0
- package/dist/cache/CacheManager.js.map +1 -0
- package/dist/cache/CacheStatsDecorator.d.ts +27 -0
- package/dist/cache/CacheStatsDecorator.d.ts.map +1 -0
- package/dist/cache/L2Cache.d.ts +39 -0
- package/dist/cache/L2Cache.d.ts.map +1 -0
- package/dist/cache/L2Cache.js +282 -0
- package/dist/cache/L2Cache.js.map +1 -0
- package/dist/cache/index.d.ts +7 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/l1/AudioL1Cache.d.ts +30 -0
- package/dist/cache/l1/AudioL1Cache.d.ts.map +1 -0
- package/dist/cache/l1/AudioL1Cache.js +306 -0
- package/dist/cache/l1/AudioL1Cache.js.map +1 -0
- package/dist/cache/l1/MixedAudioL1Cache.d.ts +13 -0
- package/dist/cache/l1/MixedAudioL1Cache.d.ts.map +1 -0
- package/dist/cache/l1/MixedAudioL1Cache.js +52 -0
- package/dist/cache/l1/MixedAudioL1Cache.js.map +1 -0
- package/dist/cache/l1/VideoL1Cache.d.ts +69 -0
- package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -0
- package/dist/cache/l1/VideoL1Cache.js +318 -0
- package/dist/cache/l1/VideoL1Cache.js.map +1 -0
- package/dist/cache/l1/gop-utils.d.ts +10 -0
- package/dist/cache/l1/gop-utils.d.ts.map +1 -0
- package/dist/cache/l1/gop-utils.js +78 -0
- package/dist/cache/l1/gop-utils.js.map +1 -0
- package/dist/cache/l1/index.d.ts +4 -0
- package/dist/cache/l1/index.d.ts.map +1 -0
- package/dist/cache/l1/types.d.ts +17 -0
- package/dist/cache/l1/types.d.ts.map +1 -0
- package/dist/cache/types.d.ts +93 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/config/ConfigLoader.d.ts +69 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +133 -0
- package/dist/config/ConfigLoader.js.map +1 -0
- package/dist/config/defaults.d.ts +125 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +191 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/presets.d.ts +32 -0
- package/dist/config/presets.d.ts.map +1 -0
- package/dist/config/presets.js +11 -0
- package/dist/config/presets.js.map +1 -0
- package/dist/config/types.d.ts +199 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/validation.d.ts +19 -0
- package/dist/config/validation.d.ts.map +1 -0
- package/dist/config/validation.js +232 -0
- package/dist/config/validation.js.map +1 -0
- package/dist/controllers/PlaybackController.d.ts +55 -0
- package/dist/controllers/PlaybackController.d.ts.map +1 -0
- package/dist/controllers/PlaybackController.js +369 -0
- package/dist/controllers/PlaybackController.js.map +1 -0
- package/dist/controllers/PreRenderService.d.ts +34 -0
- package/dist/controllers/PreRenderService.d.ts.map +1 -0
- package/dist/controllers/PreRenderService.js +83 -0
- package/dist/controllers/PreRenderService.js.map +1 -0
- package/dist/controllers/PreRenderTaskQueue.d.ts +21 -0
- package/dist/controllers/PreRenderTaskQueue.d.ts.map +1 -0
- package/dist/controllers/PreviewHandle.d.ts +23 -0
- package/dist/controllers/PreviewHandle.d.ts.map +1 -0
- package/dist/controllers/PreviewHandle.js +39 -0
- package/dist/controllers/PreviewHandle.js.map +1 -0
- package/dist/controllers/index.d.ts +8 -0
- package/dist/controllers/index.d.ts.map +1 -0
- package/dist/controllers/types.d.ts +102 -0
- package/dist/controllers/types.d.ts.map +1 -0
- package/dist/event/EventBus.d.ts +42 -0
- package/dist/event/EventBus.d.ts.map +1 -0
- package/dist/event/EventBus.js +94 -0
- package/dist/event/EventBus.js.map +1 -0
- package/dist/event/events.d.ts +371 -0
- package/dist/event/events.d.ts.map +1 -0
- package/dist/event/events.js +71 -0
- package/dist/event/events.js.map +1 -0
- package/dist/event/index.d.ts +4 -0
- package/dist/event/index.d.ts.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/model/CompositionModel.d.ts +48 -0
- package/dist/model/CompositionModel.d.ts.map +1 -0
- package/dist/model/CompositionModel.js +197 -0
- package/dist/model/CompositionModel.js.map +1 -0
- package/dist/model/RcFrame.d.ts +34 -0
- package/dist/model/RcFrame.d.ts.map +1 -0
- package/dist/model/RcFrame.js +97 -0
- package/dist/model/RcFrame.js.map +1 -0
- package/dist/model/dirty-range.d.ts +5 -0
- package/dist/model/dirty-range.d.ts.map +1 -0
- package/dist/model/dirty-range.js +220 -0
- package/dist/model/dirty-range.js.map +1 -0
- package/dist/model/index.d.ts +7 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/patch.d.ts +5 -0
- package/dist/model/patch.d.ts.map +1 -0
- package/dist/model/patch.js +250 -0
- package/dist/model/patch.js.map +1 -0
- package/dist/model/types.d.ts +135 -0
- package/dist/model/types.d.ts.map +1 -0
- package/dist/model/types.js +5 -0
- package/dist/model/types.js.map +1 -0
- package/dist/model/validation.d.ts +15 -0
- package/dist/model/validation.d.ts.map +1 -0
- package/dist/model/validation.js +74 -0
- package/dist/model/validation.js.map +1 -0
- package/dist/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js +7046 -0
- package/dist/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js.map +1 -0
- package/dist/orchestrator/ClipSessionManager.d.ts +75 -0
- package/dist/orchestrator/ClipSessionManager.d.ts.map +1 -0
- package/dist/orchestrator/ClipSessionManager.js +160 -0
- package/dist/orchestrator/ClipSessionManager.js.map +1 -0
- package/dist/orchestrator/CompositionPlanner.d.ts +55 -0
- package/dist/orchestrator/CompositionPlanner.d.ts.map +1 -0
- package/dist/orchestrator/CompositionPlanner.js +411 -0
- package/dist/orchestrator/CompositionPlanner.js.map +1 -0
- package/dist/orchestrator/Orchestrator.d.ts +59 -0
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -0
- package/dist/orchestrator/Orchestrator.js +390 -0
- package/dist/orchestrator/Orchestrator.js.map +1 -0
- package/dist/orchestrator/VideoClipSession.d.ts +64 -0
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -0
- package/dist/orchestrator/VideoClipSession.js +309 -0
- package/dist/orchestrator/VideoClipSession.js.map +1 -0
- package/dist/orchestrator/index.d.ts +5 -0
- package/dist/orchestrator/index.d.ts.map +1 -0
- package/dist/orchestrator/types.d.ts +64 -0
- package/dist/orchestrator/types.d.ts.map +1 -0
- package/dist/plugins/BackpressureMonitor.d.ts +33 -0
- package/dist/plugins/BackpressureMonitor.d.ts.map +1 -0
- package/dist/plugins/BackpressureMonitor.js +62 -0
- package/dist/plugins/BackpressureMonitor.js.map +1 -0
- package/dist/plugins/PluginManager.d.ts +37 -0
- package/dist/plugins/PluginManager.d.ts.map +1 -0
- package/dist/plugins/PluginManager.js +66 -0
- package/dist/plugins/PluginManager.js.map +1 -0
- package/dist/plugins/types.d.ts +60 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/stages/compose/AudioDucker.d.ts +59 -0
- package/dist/stages/compose/AudioDucker.d.ts.map +1 -0
- package/dist/stages/compose/AudioDucker.js +161 -0
- package/dist/stages/compose/AudioDucker.js.map +1 -0
- package/dist/stages/compose/AudioMixer.d.ts +29 -0
- package/dist/stages/compose/AudioMixer.d.ts.map +1 -0
- package/dist/stages/compose/AudioMixer.js +373 -0
- package/dist/stages/compose/AudioMixer.js.map +1 -0
- package/dist/stages/compose/FilterProcessor.d.ts +41 -0
- package/dist/stages/compose/FilterProcessor.d.ts.map +1 -0
- package/dist/stages/compose/FilterProcessor.js +226 -0
- package/dist/stages/compose/FilterProcessor.js.map +1 -0
- package/dist/stages/compose/GlobalAudioSession.d.ts +38 -0
- package/dist/stages/compose/GlobalAudioSession.d.ts.map +1 -0
- package/dist/stages/compose/GlobalAudioSession.js +122 -0
- package/dist/stages/compose/GlobalAudioSession.js.map +1 -0
- package/dist/stages/compose/LayerRenderer.d.ts +30 -0
- package/dist/stages/compose/LayerRenderer.d.ts.map +1 -0
- package/dist/stages/compose/LayerRenderer.js +215 -0
- package/dist/stages/compose/LayerRenderer.js.map +1 -0
- package/dist/stages/compose/OfflineAudioMixer.d.ts +14 -0
- package/dist/stages/compose/OfflineAudioMixer.d.ts.map +1 -0
- package/dist/stages/compose/OfflineAudioMixer.js +68 -0
- package/dist/stages/compose/OfflineAudioMixer.js.map +1 -0
- package/dist/stages/compose/TransitionProcessor.d.ts +30 -0
- package/dist/stages/compose/TransitionProcessor.d.ts.map +1 -0
- package/dist/stages/compose/TransitionProcessor.js +189 -0
- package/dist/stages/compose/TransitionProcessor.js.map +1 -0
- package/dist/stages/compose/VideoComposer.d.ts +30 -0
- package/dist/stages/compose/VideoComposer.d.ts.map +1 -0
- package/dist/stages/compose/VideoComposer.js +186 -0
- package/dist/stages/compose/VideoComposer.js.map +1 -0
- package/dist/stages/compose/audio-compose.worker.d.ts +79 -0
- package/dist/stages/compose/audio-compose.worker.d.ts.map +1 -0
- package/dist/stages/compose/audio-compose.worker.js +541 -0
- package/dist/stages/compose/audio-compose.worker.js.map +1 -0
- package/dist/stages/compose/instructions.d.ts +95 -0
- package/dist/stages/compose/instructions.d.ts.map +1 -0
- package/dist/stages/compose/types.d.ts +245 -0
- package/dist/stages/compose/types.d.ts.map +1 -0
- package/dist/stages/compose/video-compose.worker.d.ts +60 -0
- package/dist/stages/compose/video-compose.worker.d.ts.map +1 -0
- package/dist/stages/compose/video-compose.worker.js +369 -0
- package/dist/stages/compose/video-compose.worker.js.map +1 -0
- package/dist/stages/decode/AudioChunkDecoder.d.ts +41 -0
- package/dist/stages/decode/AudioChunkDecoder.d.ts.map +1 -0
- package/dist/stages/decode/AudioChunkDecoder.js +83 -0
- package/dist/stages/decode/AudioChunkDecoder.js.map +1 -0
- package/dist/stages/decode/BaseDecoder.d.ts +35 -0
- package/dist/stages/decode/BaseDecoder.d.ts.map +1 -0
- package/dist/stages/decode/BaseDecoder.js +130 -0
- package/dist/stages/decode/BaseDecoder.js.map +1 -0
- package/dist/stages/decode/VideoChunkDecoder.d.ts +54 -0
- package/dist/stages/decode/VideoChunkDecoder.d.ts.map +1 -0
- package/dist/stages/decode/VideoChunkDecoder.js +209 -0
- package/dist/stages/decode/VideoChunkDecoder.js.map +1 -0
- package/dist/stages/decode/decode.worker.d.ts +70 -0
- package/dist/stages/decode/decode.worker.d.ts.map +1 -0
- package/dist/stages/decode/decode.worker.js +436 -0
- package/dist/stages/decode/decode.worker.js.map +1 -0
- package/dist/stages/decode/index.d.ts +5 -0
- package/dist/stages/decode/index.d.ts.map +1 -0
- package/dist/stages/decode/types.d.ts +108 -0
- package/dist/stages/decode/types.d.ts.map +1 -0
- package/dist/stages/demux/MP3FrameParser.d.ts +33 -0
- package/dist/stages/demux/MP3FrameParser.d.ts.map +1 -0
- package/dist/stages/demux/MP3FrameParser.js +186 -0
- package/dist/stages/demux/MP3FrameParser.js.map +1 -0
- package/dist/stages/demux/MP4Demuxer.d.ts +45 -0
- package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -0
- package/dist/stages/demux/MP4Demuxer.js +227 -0
- package/dist/stages/demux/MP4Demuxer.js.map +1 -0
- package/dist/stages/demux/aac-esds-extractor.d.ts +7 -0
- package/dist/stages/demux/aac-esds-extractor.d.ts.map +1 -0
- package/dist/stages/demux/audio-demux.worker.d.ts +51 -0
- package/dist/stages/demux/audio-demux.worker.d.ts.map +1 -0
- package/dist/stages/demux/audio-demux.worker.js +312 -0
- package/dist/stages/demux/audio-demux.worker.js.map +1 -0
- package/dist/stages/demux/types.d.ts +77 -0
- package/dist/stages/demux/types.d.ts.map +1 -0
- package/dist/stages/demux/video-demux.worker.d.ts +48 -0
- package/dist/stages/demux/video-demux.worker.d.ts.map +1 -0
- package/dist/stages/demux/video-demux.worker.js +173 -0
- package/dist/stages/demux/video-demux.worker.js.map +1 -0
- package/dist/stages/encode/AudioChunkEncoder.d.ts +21 -0
- package/dist/stages/encode/AudioChunkEncoder.d.ts.map +1 -0
- package/dist/stages/encode/AudioChunkEncoder.js +37 -0
- package/dist/stages/encode/AudioChunkEncoder.js.map +1 -0
- package/dist/stages/encode/BaseEncoder.d.ts +44 -0
- package/dist/stages/encode/BaseEncoder.d.ts.map +1 -0
- package/dist/stages/encode/BaseEncoder.js +164 -0
- package/dist/stages/encode/BaseEncoder.js.map +1 -0
- package/dist/stages/encode/EncoderPool.d.ts +28 -0
- package/dist/stages/encode/EncoderPool.d.ts.map +1 -0
- package/dist/stages/encode/VideoChunkEncoder.d.ts +26 -0
- package/dist/stages/encode/VideoChunkEncoder.d.ts.map +1 -0
- package/dist/stages/encode/VideoChunkEncoder.js +50 -0
- package/dist/stages/encode/VideoChunkEncoder.js.map +1 -0
- package/dist/stages/encode/encode.worker.d.ts +3 -0
- package/dist/stages/encode/encode.worker.d.ts.map +1 -0
- package/dist/stages/encode/encode.worker.js +318 -0
- package/dist/stages/encode/encode.worker.js.map +1 -0
- package/dist/stages/encode/index.d.ts +6 -0
- package/dist/stages/encode/index.d.ts.map +1 -0
- package/dist/stages/encode/types.d.ts +127 -0
- package/dist/stages/encode/types.d.ts.map +1 -0
- package/dist/stages/load/EventHandlers.d.ts +35 -0
- package/dist/stages/load/EventHandlers.d.ts.map +1 -0
- package/dist/stages/load/EventHandlers.js +65 -0
- package/dist/stages/load/EventHandlers.js.map +1 -0
- package/dist/stages/load/ResourceLoader.d.ts +36 -0
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -0
- package/dist/stages/load/ResourceLoader.js +184 -0
- package/dist/stages/load/ResourceLoader.js.map +1 -0
- package/dist/stages/load/StreamFactory.d.ts +42 -0
- package/dist/stages/load/StreamFactory.d.ts.map +1 -0
- package/dist/stages/load/StreamFactory.js +201 -0
- package/dist/stages/load/StreamFactory.js.map +1 -0
- package/dist/stages/load/TaskManager.d.ts +50 -0
- package/dist/stages/load/TaskManager.d.ts.map +1 -0
- package/dist/stages/load/TaskManager.js +103 -0
- package/dist/stages/load/TaskManager.js.map +1 -0
- package/dist/stages/load/WindowByteRangeResolver.d.ts +47 -0
- package/dist/stages/load/WindowByteRangeResolver.d.ts.map +1 -0
- package/dist/stages/load/WindowByteRangeResolver.js +270 -0
- package/dist/stages/load/WindowByteRangeResolver.js.map +1 -0
- package/dist/stages/load/index.d.ts +11 -0
- package/dist/stages/load/index.d.ts.map +1 -0
- package/dist/stages/load/types.d.ts +177 -0
- package/dist/stages/load/types.d.ts.map +1 -0
- package/dist/stages/mux/MP4Muxer.d.ts +44 -0
- package/dist/stages/mux/MP4Muxer.d.ts.map +1 -0
- package/dist/stages/mux/MP4Muxer.js +262 -0
- package/dist/stages/mux/MP4Muxer.js.map +1 -0
- package/dist/stages/mux/OPFSWriter.d.ts +46 -0
- package/dist/stages/mux/OPFSWriter.d.ts.map +1 -0
- package/dist/stages/mux/index.d.ts +5 -0
- package/dist/stages/mux/index.d.ts.map +1 -0
- package/dist/stages/mux/mux.worker.d.ts +65 -0
- package/dist/stages/mux/mux.worker.d.ts.map +1 -0
- package/dist/stages/mux/mux.worker.js +219 -0
- package/dist/stages/mux/mux.worker.js.map +1 -0
- package/dist/stages/mux/types.d.ts +95 -0
- package/dist/stages/mux/types.d.ts.map +1 -0
- package/dist/stages/mux/utils.d.ts +32 -0
- package/dist/stages/mux/utils.d.ts.map +1 -0
- package/dist/stages/mux/utils.js +34 -0
- package/dist/stages/mux/utils.js.map +1 -0
- package/dist/types.d.ts +25 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/BackpressureAdapter.d.ts +26 -0
- package/dist/utils/BackpressureAdapter.d.ts.map +1 -0
- package/dist/utils/binary-search.d.ts +33 -0
- package/dist/utils/binary-search.d.ts.map +1 -0
- package/dist/utils/binary-search.js +62 -0
- package/dist/utils/binary-search.js.map +1 -0
- package/dist/utils/canvas-utils.d.ts +96 -0
- package/dist/utils/canvas-utils.d.ts.map +1 -0
- package/dist/utils/canvas-utils.js +58 -0
- package/dist/utils/canvas-utils.js.map +1 -0
- package/dist/utils/object-utils.d.ts +34 -0
- package/dist/utils/object-utils.d.ts.map +1 -0
- package/dist/utils/object-utils.js +22 -0
- package/dist/utils/object-utils.js.map +1 -0
- package/dist/utils/time-utils.d.ts +10 -0
- package/dist/utils/time-utils.d.ts.map +1 -0
- package/dist/utils/time-utils.js +60 -0
- package/dist/utils/time-utils.js.map +1 -0
- package/dist/worker/BaseWorker.d.ts +44 -0
- package/dist/worker/BaseWorker.d.ts.map +1 -0
- package/dist/worker/BaseWorker.js +98 -0
- package/dist/worker/BaseWorker.js.map +1 -0
- package/dist/worker/WorkerChannel.d.ts +105 -0
- package/dist/worker/WorkerChannel.d.ts.map +1 -0
- package/dist/worker/WorkerChannel.js +355 -0
- package/dist/worker/WorkerChannel.js.map +1 -0
- package/dist/worker/WorkerPool.d.ts +52 -0
- package/dist/worker/WorkerPool.d.ts.map +1 -0
- package/dist/worker/WorkerPool.js +124 -0
- package/dist/worker/WorkerPool.js.map +1 -0
- package/dist/worker/index.d.ts +11 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/transferable-helper.d.ts +89 -0
- package/dist/worker/transferable-helper.d.ts.map +1 -0
- package/dist/worker/transferable-helper.js +44 -0
- package/dist/worker/transferable-helper.js.map +1 -0
- package/dist/worker/types.d.ts +179 -0
- package/dist/worker/types.d.ts.map +1 -0
- package/dist/worker/types.js +50 -0
- package/dist/worker/types.js.map +1 -0
- package/dist/worker/worker-event-whitelist.d.ts +23 -0
- package/dist/worker/worker-event-whitelist.d.ts.map +1 -0
- package/dist/worker/worker-retry.d.ts +36 -0
- package/dist/worker/worker-retry.d.ts.map +1 -0
- package/dist/worker/worker-retry.js +55 -0
- package/dist/worker/worker-retry.js.map +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
const BITRATE_TABLE = {
|
|
2
|
+
"1-3": [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],
|
|
3
|
+
"1-2": [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],
|
|
4
|
+
"1-1": [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],
|
|
5
|
+
"2-3": [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
|
6
|
+
"2-2": [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
|
7
|
+
"2-1": [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0]
|
|
8
|
+
};
|
|
9
|
+
const SAMPLE_RATE_TABLE = {
|
|
10
|
+
1: [44100, 48e3, 32e3],
|
|
11
|
+
2: [22050, 24e3, 16e3],
|
|
12
|
+
2.5: [11025, 12e3, 8e3]
|
|
13
|
+
};
|
|
14
|
+
class MP3FrameParser {
|
|
15
|
+
buffer = new Uint8Array(0);
|
|
16
|
+
config = null;
|
|
17
|
+
timestampUs = 0;
|
|
18
|
+
push(chunk) {
|
|
19
|
+
this.appendToBuffer(chunk);
|
|
20
|
+
this.skipId3Headers();
|
|
21
|
+
const frames = [];
|
|
22
|
+
let configEmitted;
|
|
23
|
+
let offset = 0;
|
|
24
|
+
while (offset <= this.buffer.length - 4) {
|
|
25
|
+
const header = this.parseHeader(this.buffer, offset);
|
|
26
|
+
if (!header) {
|
|
27
|
+
offset += 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (offset + header.frameSize > this.buffer.length) {
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
const frameBytes = this.buffer.slice(offset, offset + header.frameSize);
|
|
34
|
+
const durationUs = Math.round(header.samplesPerFrame * 1e6 / header.sampleRate);
|
|
35
|
+
if (!this.config) {
|
|
36
|
+
this.config = {
|
|
37
|
+
codec: "mp3",
|
|
38
|
+
sampleRate: header.sampleRate,
|
|
39
|
+
channels: header.channels,
|
|
40
|
+
bitrateKbps: header.bitrateKbps,
|
|
41
|
+
samplesPerFrame: header.samplesPerFrame
|
|
42
|
+
};
|
|
43
|
+
configEmitted = this.config;
|
|
44
|
+
}
|
|
45
|
+
frames.push({
|
|
46
|
+
data: frameBytes,
|
|
47
|
+
timestampUs: this.timestampUs,
|
|
48
|
+
durationUs
|
|
49
|
+
});
|
|
50
|
+
this.timestampUs += durationUs;
|
|
51
|
+
offset += header.frameSize;
|
|
52
|
+
}
|
|
53
|
+
if (offset > 0) {
|
|
54
|
+
this.buffer = this.buffer.slice(offset);
|
|
55
|
+
}
|
|
56
|
+
return { frames, config: configEmitted };
|
|
57
|
+
}
|
|
58
|
+
flush() {
|
|
59
|
+
const { frames } = this.push(new Uint8Array(0));
|
|
60
|
+
const remainder = this.buffer;
|
|
61
|
+
this.buffer = new Uint8Array(0);
|
|
62
|
+
if (remainder.length) {
|
|
63
|
+
console.warn("[MP3FrameParser] Remaining unparsed bytes:", remainder.length);
|
|
64
|
+
}
|
|
65
|
+
return frames;
|
|
66
|
+
}
|
|
67
|
+
appendToBuffer(chunk) {
|
|
68
|
+
if (chunk.length === 0) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const combined = new Uint8Array(this.buffer.length + chunk.length);
|
|
72
|
+
combined.set(this.buffer, 0);
|
|
73
|
+
combined.set(chunk, this.buffer.length);
|
|
74
|
+
this.buffer = combined;
|
|
75
|
+
}
|
|
76
|
+
skipId3Headers() {
|
|
77
|
+
let offset = 0;
|
|
78
|
+
while (this.buffer.length - offset >= 10) {
|
|
79
|
+
if (this.buffer[offset] === 73 && this.buffer[offset + 1] === 68 && this.buffer[offset + 2] === 51) {
|
|
80
|
+
const size = this.readSynchsafeInteger(this.buffer.subarray(offset + 6, offset + 10));
|
|
81
|
+
const total = size + 10;
|
|
82
|
+
if (offset + total <= this.buffer.length) {
|
|
83
|
+
offset += total;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
if (offset > 0) {
|
|
90
|
+
this.buffer = this.buffer.slice(offset);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
parseHeader(buffer, offset) {
|
|
94
|
+
if (offset + 3 >= buffer.length) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (buffer[offset] !== 255 || ((buffer[offset + 1] ?? 0) & 224) !== 224) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const versionBits = (buffer[offset + 1] ?? 0) >> 3 & 3;
|
|
101
|
+
const layerBits = (buffer[offset + 1] ?? 0) >> 1 & 3;
|
|
102
|
+
const bitrateIndex = (buffer[offset + 2] ?? 0) >> 4 & 15;
|
|
103
|
+
const sampleRateIndex = (buffer[offset + 2] ?? 0) >> 2 & 3;
|
|
104
|
+
const paddingBit = (buffer[offset + 2] ?? 0) >> 1 & 1;
|
|
105
|
+
const channelMode = (buffer[offset + 3] ?? 0) >> 6 & 3;
|
|
106
|
+
const version = this.getVersion(versionBits);
|
|
107
|
+
const layer = this.getLayer(layerBits);
|
|
108
|
+
if (!version || !layer) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const sampleRate = SAMPLE_RATE_TABLE[version]?.[sampleRateIndex] ?? null;
|
|
112
|
+
if (!sampleRate) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const bitrateKey = `${version === 1 ? 1 : 2}-${layer}`;
|
|
116
|
+
const bitrateKbps = BITRATE_TABLE[bitrateKey]?.[bitrateIndex] ?? null;
|
|
117
|
+
if (bitrateKbps === null) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const samplesPerFrame = this.getSamplesPerFrame(version, layer);
|
|
121
|
+
const frameSize = this.calculateFrameSize(layer, bitrateKbps, sampleRate, paddingBit);
|
|
122
|
+
if (!frameSize || frameSize < 24) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const channels = channelMode === 3 ? 1 : 2;
|
|
126
|
+
return {
|
|
127
|
+
frameSize,
|
|
128
|
+
sampleRate,
|
|
129
|
+
channels,
|
|
130
|
+
bitrateKbps: bitrateKbps || null,
|
|
131
|
+
samplesPerFrame
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
getVersion(bits) {
|
|
135
|
+
switch (bits) {
|
|
136
|
+
case 3:
|
|
137
|
+
return 1;
|
|
138
|
+
case 2:
|
|
139
|
+
return 2;
|
|
140
|
+
case 0:
|
|
141
|
+
return 2.5;
|
|
142
|
+
default:
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
getLayer(bits) {
|
|
147
|
+
switch (bits) {
|
|
148
|
+
case 3:
|
|
149
|
+
return 1;
|
|
150
|
+
case 2:
|
|
151
|
+
return 2;
|
|
152
|
+
case 1:
|
|
153
|
+
return 3;
|
|
154
|
+
default:
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
getSamplesPerFrame(version, layer) {
|
|
159
|
+
if (layer === 1) {
|
|
160
|
+
return 384;
|
|
161
|
+
}
|
|
162
|
+
if (layer === 2) {
|
|
163
|
+
return 1152;
|
|
164
|
+
}
|
|
165
|
+
return version === 1 ? 1152 : 576;
|
|
166
|
+
}
|
|
167
|
+
calculateFrameSize(layer, bitrateKbps, sampleRate, padding) {
|
|
168
|
+
if (bitrateKbps <= 0) {
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
if (layer === 1) {
|
|
172
|
+
return (12 * bitrateKbps * 1e3 / sampleRate + padding) * 4;
|
|
173
|
+
}
|
|
174
|
+
return Math.floor(144 * bitrateKbps * 1e3 / sampleRate + padding);
|
|
175
|
+
}
|
|
176
|
+
readSynchsafeInteger(bytes) {
|
|
177
|
+
if (bytes.length !== 4) {
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
return ((bytes[0] ?? 0) & 127) << 21 | ((bytes[1] ?? 0) & 127) << 14 | ((bytes[2] ?? 0) & 127) << 7 | (bytes[3] ?? 0) & 127;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export {
|
|
184
|
+
MP3FrameParser
|
|
185
|
+
};
|
|
186
|
+
//# sourceMappingURL=MP3FrameParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MP3FrameParser.js","sources":["../../../src/stages/demux/MP3FrameParser.ts"],"sourcesContent":["const BITRATE_TABLE: Record<string, number[]> = {\n '1-3': [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],\n '1-2': [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],\n '1-1': [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],\n '2-3': [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],\n '2-2': [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],\n '2-1': [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],\n};\n\nconst SAMPLE_RATE_TABLE: Record<number, number[]> = {\n 1: [44100, 48000, 32000],\n 2: [22050, 24000, 16000],\n 2.5: [11025, 12000, 8000],\n};\n\nexport interface MP3Config {\n codec: 'mp3';\n sampleRate: number;\n channels: number;\n bitrateKbps: number | null;\n samplesPerFrame: number;\n}\n\nexport interface MP3Frame {\n data: Uint8Array;\n timestampUs: number;\n durationUs: number;\n}\n\ninterface ParseResult {\n frames: MP3Frame[];\n config?: MP3Config;\n}\n\nexport class MP3FrameParser {\n private buffer = new Uint8Array(0);\n private config: MP3Config | null = null;\n private timestampUs = 0;\n\n push(chunk: Uint8Array): ParseResult {\n this.appendToBuffer(chunk);\n this.skipId3Headers();\n\n const frames: MP3Frame[] = [];\n let configEmitted: MP3Config | undefined;\n\n let offset = 0;\n while (offset <= this.buffer.length - 4) {\n const header = this.parseHeader(this.buffer, offset);\n if (!header) {\n offset += 1;\n continue;\n }\n\n if (offset + header.frameSize > this.buffer.length) {\n break;\n }\n\n const frameBytes = this.buffer.slice(offset, offset + header.frameSize);\n const durationUs = Math.round((header.samplesPerFrame * 1_000_000) / header.sampleRate);\n\n if (!this.config) {\n this.config = {\n codec: 'mp3',\n sampleRate: header.sampleRate,\n channels: header.channels,\n bitrateKbps: header.bitrateKbps,\n samplesPerFrame: header.samplesPerFrame,\n };\n configEmitted = this.config;\n }\n\n frames.push({\n data: frameBytes,\n timestampUs: this.timestampUs,\n durationUs,\n });\n\n this.timestampUs += durationUs;\n offset += header.frameSize;\n }\n\n if (offset > 0) {\n this.buffer = this.buffer.slice(offset);\n }\n\n return { frames, config: configEmitted };\n }\n\n flush(): MP3Frame[] {\n const { frames } = this.push(new Uint8Array(0));\n const remainder = this.buffer;\n this.buffer = new Uint8Array(0);\n if (remainder.length) {\n console.warn('[MP3FrameParser] Remaining unparsed bytes:', remainder.length);\n }\n return frames;\n }\n\n private appendToBuffer(chunk: Uint8Array): void {\n if (chunk.length === 0) {\n return;\n }\n const combined = new Uint8Array(this.buffer.length + chunk.length);\n combined.set(this.buffer, 0);\n combined.set(chunk, this.buffer.length);\n this.buffer = combined;\n }\n\n private skipId3Headers(): void {\n let offset = 0;\n while (this.buffer.length - offset >= 10) {\n if (\n this.buffer[offset] === 0x49 &&\n this.buffer[offset + 1] === 0x44 &&\n this.buffer[offset + 2] === 0x33\n ) {\n const size = this.readSynchsafeInteger(this.buffer.subarray(offset + 6, offset + 10));\n const total = size + 10;\n if (offset + total <= this.buffer.length) {\n offset += total;\n continue;\n }\n }\n break;\n }\n if (offset > 0) {\n this.buffer = this.buffer.slice(offset);\n }\n }\n\n private parseHeader(\n buffer: Uint8Array,\n offset: number\n ): {\n frameSize: number;\n sampleRate: number;\n channels: number;\n bitrateKbps: number | null;\n samplesPerFrame: number;\n } | null {\n if (offset + 3 >= buffer.length) {\n return null;\n }\n if (buffer[offset] !== 0xff || ((buffer[offset + 1] ?? 0) & 0xe0) !== 0xe0) {\n return null;\n }\n\n const versionBits = ((buffer[offset + 1] ?? 0) >> 3) & 0x03;\n const layerBits = ((buffer[offset + 1] ?? 0) >> 1) & 0x03;\n const bitrateIndex = ((buffer[offset + 2] ?? 0) >> 4) & 0x0f;\n const sampleRateIndex = ((buffer[offset + 2] ?? 0) >> 2) & 0x03;\n const paddingBit = ((buffer[offset + 2] ?? 0) >> 1) & 0x01;\n const channelMode = ((buffer[offset + 3] ?? 0) >> 6) & 0x03;\n\n const version = this.getVersion(versionBits);\n const layer = this.getLayer(layerBits);\n if (!version || !layer) {\n return null;\n }\n\n const sampleRate = SAMPLE_RATE_TABLE[version]?.[sampleRateIndex] ?? null;\n if (!sampleRate) {\n return null;\n }\n\n const bitrateKey = `${version === 1 ? 1 : 2}-${layer}`;\n const bitrateKbps = BITRATE_TABLE[bitrateKey]?.[bitrateIndex] ?? null;\n if (bitrateKbps === null) {\n return null;\n }\n\n const samplesPerFrame = this.getSamplesPerFrame(version, layer);\n const frameSize = this.calculateFrameSize(layer, bitrateKbps, sampleRate, paddingBit);\n if (!frameSize || frameSize < 24) {\n return null;\n }\n\n const channels = channelMode === 3 ? 1 : 2;\n\n return {\n frameSize,\n sampleRate,\n channels,\n bitrateKbps: bitrateKbps || null,\n samplesPerFrame,\n };\n }\n\n private getVersion(bits: number): 1 | 2 | 2.5 | null {\n switch (bits) {\n case 0b11:\n return 1;\n case 0b10:\n return 2;\n case 0b00:\n return 2.5;\n default:\n return null;\n }\n }\n\n private getLayer(bits: number): 1 | 2 | 3 | null {\n switch (bits) {\n case 0b11:\n return 1;\n case 0b10:\n return 2;\n case 0b01:\n return 3;\n default:\n return null;\n }\n }\n\n private getSamplesPerFrame(version: 1 | 2 | 2.5, layer: 1 | 2 | 3): number {\n if (layer === 1) {\n return 384;\n }\n if (layer === 2) {\n return 1152;\n }\n return version === 1 ? 1152 : 576;\n }\n\n private calculateFrameSize(\n layer: 1 | 2 | 3,\n bitrateKbps: number,\n sampleRate: number,\n padding: number\n ): number {\n if (bitrateKbps <= 0) {\n return 0;\n }\n\n if (layer === 1) {\n return ((12 * bitrateKbps * 1000) / sampleRate + padding) * 4;\n }\n\n return Math.floor((144 * bitrateKbps * 1000) / sampleRate + padding);\n }\n\n private readSynchsafeInteger(bytes: Uint8Array): number {\n if (bytes.length !== 4) {\n return 0;\n }\n return (\n (((bytes[0] ?? 0) & 0x7f) << 21) |\n (((bytes[1] ?? 0) & 0x7f) << 14) |\n (((bytes[2] ?? 0) & 0x7f) << 7) |\n ((bytes[3] ?? 0) & 0x7f)\n );\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { DemuxConfig, TrackInfo } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MP4 Demuxer - Extract encoded chunks from MP4 container
|
|
5
|
+
* Simplified implementation following Stream API pattern
|
|
6
|
+
*/
|
|
7
|
+
export declare class MP4Demuxer {
|
|
8
|
+
private mp4boxFile;
|
|
9
|
+
tracks: Map<number, TrackInfo>;
|
|
10
|
+
isReady: boolean;
|
|
11
|
+
private videoController?;
|
|
12
|
+
private audioController?;
|
|
13
|
+
private backpressureMonitor;
|
|
14
|
+
private demuxHighWaterMark;
|
|
15
|
+
private onReadyCallback?;
|
|
16
|
+
private fileOffset;
|
|
17
|
+
constructor(config?: DemuxConfig & {
|
|
18
|
+
onReady?: () => void;
|
|
19
|
+
});
|
|
20
|
+
updateConfig(config: DemuxConfig): void;
|
|
21
|
+
private setupHandlers;
|
|
22
|
+
private processTracks;
|
|
23
|
+
private processSamples;
|
|
24
|
+
private getVideoDescription;
|
|
25
|
+
private getAudioDescription;
|
|
26
|
+
/**
|
|
27
|
+
* Create transform stream for video track
|
|
28
|
+
*/
|
|
29
|
+
createVideoStream(): TransformStream<Uint8Array, EncodedVideoChunk>;
|
|
30
|
+
/**
|
|
31
|
+
* Create transform stream for audio track
|
|
32
|
+
*/
|
|
33
|
+
createAudioStream(): TransformStream<Uint8Array, EncodedAudioChunk> | null;
|
|
34
|
+
appendBuffer(chunk: Uint8Array): void;
|
|
35
|
+
/**
|
|
36
|
+
* Get video track info if available
|
|
37
|
+
*/
|
|
38
|
+
get videoTrackInfo(): TrackInfo | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Get audio track info if available
|
|
41
|
+
*/
|
|
42
|
+
get audioTrackInfo(): TrackInfo | undefined;
|
|
43
|
+
destroy(): void;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=MP4Demuxer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MP4Demuxer.d.ts","sourceRoot":"","sources":["../../../src/stages/demux/MP4Demuxer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGtD;;;GAGG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAM;IACxB,MAAM,yBAAgC;IACtC,OAAO,UAAS;IAChB,OAAO,CAAC,eAAe,CAAC,CAAsD;IAC9E,OAAO,CAAC,eAAe,CAAC,CAAsD;IAC9E,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,eAAe,CAAC,CAAa;IACrC,OAAO,CAAC,UAAU,CAAK;gBAEX,MAAM,GAAE,WAAW,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;KAAO;IAY/D,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAIvC,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,aAAa;IA4BrB,OAAO,CAAC,cAAc;IAuCtB,OAAO,CAAC,mBAAmB;IA0B3B,OAAO,CAAC,mBAAmB;IAgB3B;;OAEG;IACH,iBAAiB,IAAI,eAAe,CAAC,UAAU,EAAE,iBAAiB,CAAC;IAuCnE;;OAEG;IACH,iBAAiB,IAAI,eAAe,CAAC,UAAU,EAAE,iBAAiB,CAAC,GAAG,IAAI;IAkC1E,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAOrC;;OAEG;IACH,IAAI,cAAc,IAAI,SAAS,GAAG,SAAS,CAE1C;IAED;;OAEG;IACH,IAAI,cAAc,IAAI,SAAS,GAAG,SAAS,CAE1C;IAED,OAAO,IAAI,IAAI;CAOhB"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import "../../node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js";
|
|
2
|
+
import { BackpressureMonitor } from "../../plugins/BackpressureMonitor.js";
|
|
3
|
+
import { __exports as mp4box_all } from "../../_virtual/mp4box.all.js";
|
|
4
|
+
class MP4Demuxer {
|
|
5
|
+
mp4boxFile;
|
|
6
|
+
tracks = /* @__PURE__ */ new Map();
|
|
7
|
+
isReady = false;
|
|
8
|
+
videoController;
|
|
9
|
+
audioController;
|
|
10
|
+
backpressureMonitor;
|
|
11
|
+
demuxHighWaterMark;
|
|
12
|
+
onReadyCallback;
|
|
13
|
+
fileOffset = 0;
|
|
14
|
+
constructor(config = {}) {
|
|
15
|
+
this.mp4boxFile = mp4box_all.createFile();
|
|
16
|
+
this.backpressureMonitor = new BackpressureMonitor();
|
|
17
|
+
this.onReadyCallback = config.onReady;
|
|
18
|
+
const DEFAULT_HIGH_WATER_MARK = 10;
|
|
19
|
+
this.demuxHighWaterMark = config.highWaterMark ?? DEFAULT_HIGH_WATER_MARK;
|
|
20
|
+
this.setupHandlers();
|
|
21
|
+
}
|
|
22
|
+
updateConfig(config) {
|
|
23
|
+
this.demuxHighWaterMark = config.highWaterMark ?? this.demuxHighWaterMark;
|
|
24
|
+
}
|
|
25
|
+
setupHandlers() {
|
|
26
|
+
this.mp4boxFile.onError = (error) => {
|
|
27
|
+
console.error("MP4Box error:", error);
|
|
28
|
+
this.videoController?.error(new Error(error));
|
|
29
|
+
this.audioController?.error(new Error(error));
|
|
30
|
+
};
|
|
31
|
+
this.mp4boxFile.onReady = (info) => {
|
|
32
|
+
this.processTracks(info.tracks);
|
|
33
|
+
this.isReady = true;
|
|
34
|
+
if (this.onReadyCallback) {
|
|
35
|
+
this.onReadyCallback();
|
|
36
|
+
}
|
|
37
|
+
this.mp4boxFile.start();
|
|
38
|
+
};
|
|
39
|
+
this.mp4boxFile.onSamples = (trackId, _user, samples) => {
|
|
40
|
+
this.processSamples(trackId, samples);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
processTracks(tracks) {
|
|
44
|
+
for (const track of tracks) {
|
|
45
|
+
const trackInfo = {
|
|
46
|
+
id: track.id,
|
|
47
|
+
type: track.type === "video" ? "video" : "audio",
|
|
48
|
+
codec: track.codec,
|
|
49
|
+
timescale: track.timescale
|
|
50
|
+
};
|
|
51
|
+
if (track.type === "video") {
|
|
52
|
+
trackInfo.width = track.video?.width;
|
|
53
|
+
trackInfo.height = track.video?.height;
|
|
54
|
+
trackInfo.description = this.getVideoDescription(track);
|
|
55
|
+
} else if (track.type === "audio") {
|
|
56
|
+
trackInfo.sampleRate = track.audio?.sample_rate;
|
|
57
|
+
trackInfo.numberOfChannels = track.audio?.channel_count;
|
|
58
|
+
trackInfo.description = this.getAudioDescription(track);
|
|
59
|
+
}
|
|
60
|
+
this.tracks.set(track.id, trackInfo);
|
|
61
|
+
this.mp4boxFile.setExtractionOptions(track.id, track, {
|
|
62
|
+
nbSamples: 30
|
|
63
|
+
// Batch size per callback (balance between latency and overhead)
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
processSamples(trackId, samples) {
|
|
68
|
+
const track = this.tracks.get(trackId);
|
|
69
|
+
if (!track) return;
|
|
70
|
+
const timescale = track.timescale || 9e4;
|
|
71
|
+
for (const sample of samples) {
|
|
72
|
+
const timestamp = sample.cts * 1e6 / timescale;
|
|
73
|
+
const duration = sample.duration * 1e6 / timescale;
|
|
74
|
+
if (track.type === "video") {
|
|
75
|
+
if (!this.videoController) {
|
|
76
|
+
console.error("[MP4Demuxer] videoController is null when trying to output chunk!");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const chunk = new EncodedVideoChunk({
|
|
80
|
+
type: sample.is_sync ? "key" : "delta",
|
|
81
|
+
timestamp,
|
|
82
|
+
duration,
|
|
83
|
+
data: sample.data
|
|
84
|
+
});
|
|
85
|
+
this.videoController.enqueue(chunk);
|
|
86
|
+
} else if (track.type === "audio" && this.audioController) {
|
|
87
|
+
const chunk = new EncodedAudioChunk({
|
|
88
|
+
type: "key",
|
|
89
|
+
timestamp,
|
|
90
|
+
duration,
|
|
91
|
+
data: sample.data
|
|
92
|
+
});
|
|
93
|
+
this.audioController.enqueue(chunk);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const last = samples[samples.length - 1].number;
|
|
97
|
+
this.mp4boxFile.releaseUsedSamples(trackId, last + 1);
|
|
98
|
+
}
|
|
99
|
+
getVideoDescription(track) {
|
|
100
|
+
try {
|
|
101
|
+
const fullTrack = this.mp4boxFile.getTrackById(track.id);
|
|
102
|
+
for (const entry of fullTrack.mdia.minf.stbl.stsd.entries) {
|
|
103
|
+
const box = entry.avcC ?? entry.hvcC ?? entry.av1C ?? entry.vpcC;
|
|
104
|
+
if (box) {
|
|
105
|
+
const stream = new mp4box_all.DataStream(
|
|
106
|
+
void 0,
|
|
107
|
+
0,
|
|
108
|
+
mp4box_all.DataStream.BIG_ENDIAN
|
|
109
|
+
// IMPORTANT: must be BIG_ENDIAN
|
|
110
|
+
);
|
|
111
|
+
box.write(stream);
|
|
112
|
+
return new Uint8Array(stream.buffer.slice(8)).buffer;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error("Failed to get video description:", error);
|
|
117
|
+
}
|
|
118
|
+
return void 0;
|
|
119
|
+
}
|
|
120
|
+
// private getVideoDescription(track: any): ArrayBuffer | undefined {
|
|
121
|
+
// if (!this.mp4boxFile) return undefined;
|
|
122
|
+
// return getVideoDescription(this.mp4boxFile, track);
|
|
123
|
+
// }
|
|
124
|
+
getAudioDescription(track) {
|
|
125
|
+
try {
|
|
126
|
+
const fullTrack = this.mp4boxFile.getTrackById(track.id);
|
|
127
|
+
for (const entry of fullTrack.mdia.minf.stbl.stsd.entries) {
|
|
128
|
+
if (entry.esds || entry.dOps) {
|
|
129
|
+
const stream = new mp4box_all.DataStream();
|
|
130
|
+
(entry.esds || entry.dOps).write(stream);
|
|
131
|
+
return new Uint8Array(stream.buffer.slice(8)).buffer;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error("Failed to get audio description:", error);
|
|
136
|
+
}
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Create transform stream for video track
|
|
141
|
+
*/
|
|
142
|
+
createVideoStream() {
|
|
143
|
+
return new TransformStream(
|
|
144
|
+
{
|
|
145
|
+
start: (controller) => {
|
|
146
|
+
this.videoController = controller;
|
|
147
|
+
},
|
|
148
|
+
transform: (chunk, controller) => {
|
|
149
|
+
const desiredSize = controller.desiredSize ?? this.demuxHighWaterMark;
|
|
150
|
+
this.backpressureMonitor.updateMetrics("demux-video", desiredSize);
|
|
151
|
+
const chunkData = new Uint8Array(chunk);
|
|
152
|
+
this.appendBuffer(chunkData);
|
|
153
|
+
this.mp4boxFile.flush();
|
|
154
|
+
},
|
|
155
|
+
flush: async () => {
|
|
156
|
+
this.mp4boxFile.flush();
|
|
157
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
158
|
+
console.log("[MP4Demuxer] Video stream flush complete");
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
// Queuing strategy: use configuration
|
|
162
|
+
{
|
|
163
|
+
highWaterMark: this.demuxHighWaterMark,
|
|
164
|
+
size: () => 1
|
|
165
|
+
// Count-based
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Create transform stream for audio track
|
|
171
|
+
*/
|
|
172
|
+
createAudioStream() {
|
|
173
|
+
const hasAudio = Array.from(this.tracks.values()).some((t) => t.type === "audio");
|
|
174
|
+
if (!hasAudio) return null;
|
|
175
|
+
return new TransformStream(
|
|
176
|
+
{
|
|
177
|
+
start: (controller) => {
|
|
178
|
+
this.audioController = controller;
|
|
179
|
+
},
|
|
180
|
+
transform: (chunk, controller) => {
|
|
181
|
+
const desiredSize = controller.desiredSize ?? this.demuxHighWaterMark;
|
|
182
|
+
this.backpressureMonitor.updateMetrics("demux-audio", desiredSize);
|
|
183
|
+
const chunkData = new Uint8Array(chunk);
|
|
184
|
+
this.appendBuffer(chunkData);
|
|
185
|
+
this.mp4boxFile.flush();
|
|
186
|
+
},
|
|
187
|
+
flush: () => {
|
|
188
|
+
this.mp4boxFile.flush();
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
// Queuing strategy: use configuration
|
|
192
|
+
{
|
|
193
|
+
highWaterMark: this.demuxHighWaterMark,
|
|
194
|
+
size: () => 1
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
appendBuffer(chunk) {
|
|
199
|
+
const buffer = chunk.buffer;
|
|
200
|
+
buffer.fileStart = this.fileOffset;
|
|
201
|
+
this.mp4boxFile.appendBuffer(buffer);
|
|
202
|
+
this.fileOffset += chunk.byteLength;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get video track info if available
|
|
206
|
+
*/
|
|
207
|
+
get videoTrackInfo() {
|
|
208
|
+
return Array.from(this.tracks.values()).find((track) => track.type === "video");
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get audio track info if available
|
|
212
|
+
*/
|
|
213
|
+
get audioTrackInfo() {
|
|
214
|
+
return Array.from(this.tracks.values()).find((track) => track.type === "audio");
|
|
215
|
+
}
|
|
216
|
+
destroy() {
|
|
217
|
+
this.mp4boxFile?.stop();
|
|
218
|
+
this.mp4boxFile = null;
|
|
219
|
+
this.tracks.clear();
|
|
220
|
+
this.backpressureMonitor.clear();
|
|
221
|
+
this.isReady = false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
export {
|
|
225
|
+
MP4Demuxer
|
|
226
|
+
};
|
|
227
|
+
//# sourceMappingURL=MP4Demuxer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MP4Demuxer.js","sources":["../../../src/stages/demux/MP4Demuxer.ts"],"sourcesContent":["import * as MP4Box from 'mp4box';\nimport type { DemuxConfig, TrackInfo } from './types';\nimport { BackpressureMonitor } from '../../plugins/BackpressureMonitor';\n\n/**\n * MP4 Demuxer - Extract encoded chunks from MP4 container\n * Simplified implementation following Stream API pattern\n */\nexport class MP4Demuxer {\n private mp4boxFile: any;\n tracks = new Map<number, TrackInfo>();\n isReady = false;\n private videoController?: TransformStreamDefaultController<EncodedVideoChunk>;\n private audioController?: TransformStreamDefaultController<EncodedAudioChunk>;\n private backpressureMonitor: BackpressureMonitor;\n private demuxHighWaterMark: number;\n private onReadyCallback?: () => void;\n private fileOffset = 0;\n\n constructor(config: DemuxConfig & { onReady?: () => void } = {}) {\n this.mp4boxFile = MP4Box.createFile();\n this.backpressureMonitor = new BackpressureMonitor();\n this.onReadyCallback = config.onReady;\n\n // Use provided config with local default as fallback\n const DEFAULT_HIGH_WATER_MARK = 10; // Default for video chunks\n this.demuxHighWaterMark = config.highWaterMark ?? DEFAULT_HIGH_WATER_MARK;\n\n this.setupHandlers();\n }\n\n updateConfig(config: DemuxConfig): void {\n this.demuxHighWaterMark = config.highWaterMark ?? this.demuxHighWaterMark;\n }\n\n private setupHandlers(): void {\n this.mp4boxFile.onError = (error: string) => {\n console.error('MP4Box error:', error);\n this.videoController?.error(new Error(error));\n this.audioController?.error(new Error(error));\n };\n\n this.mp4boxFile.onReady = (info: any) => {\n this.processTracks(info.tracks);\n this.isReady = true;\n\n // Call the ready callback before starting\n if (this.onReadyCallback) {\n this.onReadyCallback();\n }\n\n // Start processing samples after callback\n // Note: start() enables extraction, actual samples will be output\n // when flush() is called (by TransformStream's flush callback)\n this.mp4boxFile.start();\n };\n\n this.mp4boxFile.onSamples = (trackId: number, _user: any, samples: any[]) => {\n this.processSamples(trackId, samples);\n };\n }\n\n private processTracks(tracks: any[]): void {\n for (const track of tracks) {\n const trackInfo: TrackInfo = {\n id: track.id,\n type: track.type === 'video' ? 'video' : 'audio',\n codec: track.codec,\n timescale: track.timescale,\n };\n\n if (track.type === 'video') {\n trackInfo.width = track.video?.width;\n trackInfo.height = track.video?.height;\n trackInfo.description = this.getVideoDescription(track);\n } else if (track.type === 'audio') {\n trackInfo.sampleRate = track.audio?.sample_rate;\n trackInfo.numberOfChannels = track.audio?.channel_count;\n trackInfo.description = this.getAudioDescription(track);\n }\n\n this.tracks.set(track.id, trackInfo);\n\n // Configure extraction\n this.mp4boxFile.setExtractionOptions(track.id, track, {\n nbSamples: 30, // Batch size per callback (balance between latency and overhead)\n });\n }\n }\n\n private processSamples(trackId: number, samples: any[]): void {\n const track = this.tracks.get(trackId);\n if (!track) return;\n\n const timescale = track.timescale || 90000;\n for (const sample of samples) {\n const timestamp = (sample.cts * 1000000) / timescale;\n const duration = (sample.duration * 1000000) / timescale;\n\n if (track.type === 'video') {\n if (!this.videoController) {\n console.error('[MP4Demuxer] videoController is null when trying to output chunk!');\n // Should not happen - stream should be created before appendBuffer\n return;\n }\n\n const chunk = new EncodedVideoChunk({\n type: sample.is_sync ? 'key' : 'delta',\n timestamp,\n duration,\n data: sample.data,\n });\n this.videoController.enqueue(chunk);\n } else if (track.type === 'audio' && this.audioController) {\n const chunk = new EncodedAudioChunk({\n type: 'key',\n timestamp,\n duration,\n data: sample.data,\n });\n this.audioController.enqueue(chunk);\n }\n }\n\n const last = samples[samples.length - 1].number;\n // Release memory immediately\n this.mp4boxFile.releaseUsedSamples(trackId, last + 1);\n }\n\n private getVideoDescription(track: any): ArrayBuffer | undefined {\n try {\n const fullTrack = this.mp4boxFile.getTrackById(track.id);\n for (const entry of fullTrack.mdia.minf.stbl.stsd.entries) {\n const box = entry.avcC ?? entry.hvcC ?? entry.av1C ?? entry.vpcC;\n if (box) {\n const stream = new (MP4Box as any).DataStream(\n undefined,\n 0,\n (MP4Box as any).DataStream.BIG_ENDIAN // IMPORTANT: must be BIG_ENDIAN\n );\n box.write(stream);\n return new Uint8Array(stream.buffer.slice(8)).buffer;\n }\n }\n } catch (error) {\n console.error('Failed to get video description:', error);\n }\n return undefined;\n }\n\n // private getVideoDescription(track: any): ArrayBuffer | undefined {\n // if (!this.mp4boxFile) return undefined;\n // return getVideoDescription(this.mp4boxFile, track);\n // }\n\n private getAudioDescription(track: any): ArrayBuffer | undefined {\n try {\n const fullTrack = this.mp4boxFile.getTrackById(track.id);\n for (const entry of fullTrack.mdia.minf.stbl.stsd.entries) {\n if (entry.esds || entry.dOps) {\n const stream = new (MP4Box as any).DataStream();\n (entry.esds || entry.dOps).write(stream);\n return new Uint8Array(stream.buffer.slice(8)).buffer;\n }\n }\n } catch (error) {\n console.error('Failed to get audio description:', error);\n }\n return undefined;\n }\n\n /**\n * Create transform stream for video track\n */\n createVideoStream(): TransformStream<Uint8Array, EncodedVideoChunk> {\n // const hasVideo = Array.from(this.tracks.values()).some((t) => t.type === 'video');\n return new TransformStream<Uint8Array, EncodedVideoChunk>(\n {\n start: (controller) => {\n this.videoController = controller;\n },\n transform: (chunk, controller) => {\n // Update backpressure metrics\n // desiredSize can be null, negative (backpressure), or positive\n const desiredSize = controller.desiredSize ?? this.demuxHighWaterMark;\n this.backpressureMonitor.updateMetrics('demux-video', desiredSize);\n\n // Create a copy to avoid buffer reuse issues (required for mp4box 0.5.x)\n const chunkData = new Uint8Array(chunk);\n this.appendBuffer(chunkData);\n\n // Flush after each append to trigger sample extraction immediately\n this.mp4boxFile.flush();\n },\n flush: async () => {\n // Trigger MP4Box flush\n this.mp4boxFile.flush();\n\n // Wait for MP4Box to complete sample extraction (onSamples callbacks)\n // Give MP4Box time to process asynchronously\n await new Promise((resolve) => setTimeout(resolve, 100));\n\n console.log('[MP4Demuxer] Video stream flush complete');\n },\n },\n // Queuing strategy: use configuration\n {\n highWaterMark: this.demuxHighWaterMark,\n size: () => 1, // Count-based\n }\n );\n }\n\n /**\n * Create transform stream for audio track\n */\n createAudioStream(): TransformStream<Uint8Array, EncodedAudioChunk> | null {\n const hasAudio = Array.from(this.tracks.values()).some((t) => t.type === 'audio');\n if (!hasAudio) return null;\n\n return new TransformStream<Uint8Array, EncodedAudioChunk>(\n {\n start: (controller) => {\n this.audioController = controller;\n },\n transform: (chunk, controller) => {\n // Update backpressure metrics\n // desiredSize can be null, negative (backpressure), or positive\n const desiredSize = controller.desiredSize ?? this.demuxHighWaterMark;\n this.backpressureMonitor.updateMetrics('demux-audio', desiredSize);\n\n // Create a copy to avoid buffer reuse issues (required for mp4box 0.5.x)\n const chunkData = new Uint8Array(chunk);\n this.appendBuffer(chunkData);\n\n // Flush after each append to trigger sample extraction immediately\n this.mp4boxFile.flush();\n },\n flush: () => {\n this.mp4boxFile.flush();\n },\n },\n // Queuing strategy: use configuration\n {\n highWaterMark: this.demuxHighWaterMark,\n size: () => 1,\n }\n );\n }\n\n appendBuffer(chunk: Uint8Array): void {\n const buffer = chunk.buffer as ArrayBuffer & { fileStart: number };\n buffer.fileStart = this.fileOffset;\n this.mp4boxFile.appendBuffer(buffer);\n this.fileOffset += chunk.byteLength;\n }\n\n /**\n * Get video track info if available\n */\n get videoTrackInfo(): TrackInfo | undefined {\n return Array.from(this.tracks.values()).find((track) => track.type === 'video');\n }\n\n /**\n * Get audio track info if available\n */\n get audioTrackInfo(): TrackInfo | undefined {\n return Array.from(this.tracks.values()).find((track) => track.type === 'audio');\n }\n\n destroy(): void {\n this.mp4boxFile?.stop();\n this.mp4boxFile = null;\n this.tracks.clear();\n this.backpressureMonitor.clear();\n this.isReady = false;\n }\n}\n"],"names":["MP4Box.createFile","MP4Box.DataStream"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aac-esds-extractor.d.ts","sourceRoot":"","sources":["../../../src/stages/demux/aac-esds-extractor.ts"],"names":[],"mappings":"AAEA,UAAU,uBAAuB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,eAAO,MAAM,sBAAsB,GACjC,cAAc,WAAW,KACxB,uBAAuB,GAAG,SAqD5B,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AudioDemuxWorker - First stage for audio processing
|
|
3
|
+
* Extracts audio tracks from various container formats (MP3, MP4/M4A, etc.)
|
|
4
|
+
*
|
|
5
|
+
* Pipeline: ResourceLoader (Main Thread) → AudioDemuxWorker → DecodeWorker
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Multi-format support (MP3, AAC in MP4/M4A)
|
|
9
|
+
* - Stream-based processing with backpressure
|
|
10
|
+
* - Direct streaming to DecodeWorker
|
|
11
|
+
*/
|
|
12
|
+
export declare class AudioDemuxWorker {
|
|
13
|
+
private channel;
|
|
14
|
+
private demuxer;
|
|
15
|
+
private audioStream;
|
|
16
|
+
private mp3Parser;
|
|
17
|
+
private currentFormat;
|
|
18
|
+
private decoderPort;
|
|
19
|
+
constructor();
|
|
20
|
+
private setupHandlers;
|
|
21
|
+
/**
|
|
22
|
+
* Unified connect handler used by stream pipeline
|
|
23
|
+
*/
|
|
24
|
+
private handleConnect;
|
|
25
|
+
/**
|
|
26
|
+
* Configure demuxer with format settings
|
|
27
|
+
* @param payload.config - Demuxer configuration
|
|
28
|
+
* @param payload.initial - If true, initialize worker state; otherwise just update config
|
|
29
|
+
*/
|
|
30
|
+
private handleConfigure;
|
|
31
|
+
/**
|
|
32
|
+
* Detect audio format from configuration
|
|
33
|
+
*/
|
|
34
|
+
private detectFormat;
|
|
35
|
+
/**
|
|
36
|
+
* Handle input stream from ResourceLoader (main thread)
|
|
37
|
+
*/
|
|
38
|
+
private handleInputStream;
|
|
39
|
+
private pipeMp3Stream;
|
|
40
|
+
/**
|
|
41
|
+
* Get demuxer statistics
|
|
42
|
+
*/
|
|
43
|
+
private handleGetStats;
|
|
44
|
+
/**
|
|
45
|
+
* Dispose worker and cleanup resources
|
|
46
|
+
*/
|
|
47
|
+
private handleDispose;
|
|
48
|
+
}
|
|
49
|
+
declare const _default: null;
|
|
50
|
+
export default _default;
|
|
51
|
+
//# sourceMappingURL=audio-demux.worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-demux.worker.d.ts","sourceRoot":"","sources":["../../../src/stages/demux/audio-demux.worker.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,WAAW,CAA+D;IAClF,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,aAAa,CAA8B;IAGnD,OAAO,CAAC,WAAW,CAA4B;;IAY/C,OAAO,CAAC,aAAa;IAarB;;OAEG;YACW,aAAa;IAU3B;;;;OAIG;YACW,eAAe;IAuF7B;;OAEG;IACH,OAAO,CAAC,YAAY;IASpB;;OAEG;YACW,iBAAiB;YAkDjB,aAAa;IAiI3B;;OAEG;YACW,cAAc;IAyB5B;;OAEG;YACW,aAAa;CAgB5B;;AAUD,wBAAoB"}
|