UNPKG

@remotion/media-parser

Version:

A pure JavaScript library for parsing video files

275 lines (274 loc) 13.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processM3uChunk = void 0; const media_parser_controller_1 = require("../../controller/media-parser-controller"); const forward_controller_pause_resume_abort_1 = require("../../forward-controller-pause-resume-abort"); const parse_media_1 = require("../../parse-media"); const register_track_1 = require("../../register-track"); const with_resolvers_1 = require("../../with-resolvers"); const first_sample_in_m3u_chunk_1 = require("./first-sample-in-m3u-chunk"); const get_chunks_1 = require("./get-chunks"); const get_playlist_1 = require("./get-playlist"); const get_chunk_to_seek_to_1 = require("./seek/get-chunk-to-seek-to"); const processM3uChunk = ({ playlistUrl, state, structure, audioDone, videoDone, }) => { const { promise, reject, resolve } = (0, with_resolvers_1.withResolvers)(); const onGlobalAudioTrack = audioDone ? null : async (track) => { const existingTracks = state.callbacks.tracks.getTracks(); let { trackId } = track; while (existingTracks.find((t) => t.trackId === trackId)) { trackId++; } const onAudioSample = await (0, register_track_1.registerAudioTrack)({ container: 'm3u8', track: { ...track, trackId, }, registerAudioSampleCallback: state.callbacks.registerAudioSampleCallback, tracks: state.callbacks.tracks, logLevel: state.logLevel, onAudioTrack: state.onAudioTrack, }); state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl); if (onAudioSample === null) { return null; } state.m3u.sampleSorter.addAudioStreamToConsider(playlistUrl, onAudioSample); return async (sample) => { await state.m3u.sampleSorter.addAudioSample(playlistUrl, sample); }; }; const onGlobalVideoTrack = videoDone ? null : async (track) => { const existingTracks = state.callbacks.tracks.getTracks(); let { trackId } = track; while (existingTracks.find((t) => t.trackId === trackId)) { trackId++; } const onVideoSample = await (0, register_track_1.registerVideoTrack)({ container: 'm3u8', track: { ...track, trackId, }, logLevel: state.logLevel, onVideoTrack: state.onVideoTrack, registerVideoSampleCallback: state.callbacks.registerVideoSampleCallback, tracks: state.callbacks.tracks, }); state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl); if (onVideoSample === null) { return null; } state.m3u.sampleSorter.addVideoStreamToConsider(playlistUrl, onVideoSample); return async (sample) => { await state.m3u.sampleSorter.addVideoSample(playlistUrl, sample); }; }; // This function will run through the whole playlist step by step, and pause itself // On the next run it will continue const pausableIterator = async () => { const playlist = (0, get_playlist_1.getPlaylist)(structure, playlistUrl); const chunks = (0, get_chunks_1.getChunks)(playlist); const seekToSecondsToProcess = state.m3u.getSeekToSecondsToProcess(playlistUrl); const chunksToSubtract = state.m3u.getNextSeekShouldSubtractChunks(playlistUrl); let chunkIndex = null; if (seekToSecondsToProcess !== null) { chunkIndex = Math.max(0, (0, get_chunk_to_seek_to_1.getChunkToSeekTo)({ chunks, seekToSecondsToProcess: seekToSecondsToProcess.targetTime, }) - chunksToSubtract); } const currentPromise = { resolver: (() => undefined), rejector: reject, }; const requiresHeaderToBeFetched = chunks[0].isHeader; for (const chunk of chunks) { const mp4HeaderSegment = state.m3u.getMp4HeaderSegment(playlistUrl); if (requiresHeaderToBeFetched && mp4HeaderSegment && chunk.isHeader) { continue; } if (chunkIndex !== null && chunks.indexOf(chunk) < chunkIndex && !chunk.isHeader) { continue; } currentPromise.resolver = (newRun) => { state.m3u.setM3uStreamRun(playlistUrl, newRun); resolve(); }; currentPromise.rejector = reject; const childController = (0, media_parser_controller_1.mediaParserController)(); const forwarded = (0, forward_controller_pause_resume_abort_1.forwardMediaParserControllerPauseResume)({ childController, parentController: state.controller, }); const nextChunk = chunks[chunks.indexOf(chunk) + 1]; if (nextChunk) { const nextChunkSource = state.readerInterface.createAdjacentFileSource(nextChunk.url, playlistUrl); state.readerInterface.preload({ logLevel: state.logLevel, range: null, src: nextChunkSource, prefetchCache: state.prefetchCache, }); } const makeContinuationFn = () => { return { continue() { const resolver = (0, with_resolvers_1.withResolvers)(); currentPromise.resolver = resolver.resolve; currentPromise.rejector = resolver.reject; childController.resume(); return resolver.promise; }, abort() { childController.abort(); }, }; }; const isLastChunk = chunk === chunks[chunks.length - 1]; await childController._internals.checkForAbortAndPause(); const src = state.readerInterface.createAdjacentFileSource(chunk.url, playlistUrl); try { const data = await (0, parse_media_1.parseMedia)({ src, acknowledgeRemotionLicense: true, logLevel: state.logLevel, controller: childController, progressIntervalInMs: 0, onParseProgress: () => { childController.pause(); currentPromise.resolver(makeContinuationFn()); }, fields: chunk.isHeader ? { slowStructure: true } : undefined, onTracks: () => { if (!state.m3u.hasEmittedDoneWithTracks(playlistUrl)) { state.m3u.setHasEmittedDoneWithTracks(playlistUrl); const allDone = state.m3u.setTracksDone(playlistUrl); if (allDone) { state.callbacks.tracks.setIsDone(state.logLevel); } return null; } }, onAudioTrack: onGlobalAudioTrack === null ? null : async ({ track }) => { const callbackOrFalse = state.m3u.hasEmittedAudioTrack(playlistUrl); if (callbackOrFalse === false) { const callback = await onGlobalAudioTrack(track); if (!callback) { state.m3u.setHasEmittedAudioTrack(playlistUrl, null); return null; } state.m3u.setHasEmittedAudioTrack(playlistUrl, callback); return async (sample) => { await (0, first_sample_in_m3u_chunk_1.considerSeekBasedOnChunk)({ sample, callback, parentController: state.controller, childController, m3uState: state.m3u, playlistUrl, subtractChunks: chunksToSubtract, chunkIndex, }); }; } if (callbackOrFalse === null) { return null; } return async (sample) => { await (0, first_sample_in_m3u_chunk_1.considerSeekBasedOnChunk)({ sample, m3uState: state.m3u, playlistUrl, callback: callbackOrFalse, parentController: state.controller, childController, subtractChunks: chunksToSubtract, chunkIndex, }); }; }, onVideoTrack: onGlobalVideoTrack === null ? null : async ({ track }) => { const callbackOrFalse = state.m3u.hasEmittedVideoTrack(playlistUrl); if (callbackOrFalse === false) { const callback = await onGlobalVideoTrack({ ...track, m3uStreamFormat: chunk.isHeader || mp4HeaderSegment ? 'mp4' : 'ts', }); if (!callback) { state.m3u.setHasEmittedVideoTrack(playlistUrl, null); return null; } state.m3u.setHasEmittedVideoTrack(playlistUrl, callback); return async (sample) => { await (0, first_sample_in_m3u_chunk_1.considerSeekBasedOnChunk)({ sample, m3uState: state.m3u, playlistUrl, callback, parentController: state.controller, childController, subtractChunks: chunksToSubtract, chunkIndex, }); }; } if (callbackOrFalse === null) { return null; } return async (sample) => { await (0, first_sample_in_m3u_chunk_1.considerSeekBasedOnChunk)({ sample, m3uState: state.m3u, playlistUrl, callback: callbackOrFalse, parentController: state.controller, childController, subtractChunks: chunksToSubtract, chunkIndex, }); }; }, reader: state.readerInterface, makeSamplesStartAtZero: false, m3uPlaylistContext: { mp4HeaderSegment, isLastChunkInPlaylist: isLastChunk, }, }); if (chunk.isHeader) { if (data.slowStructure.type !== 'iso-base-media') { throw new Error('Expected an mp4 file'); } state.m3u.setMp4HeaderSegment(playlistUrl, data.slowStructure); } } catch (e) { currentPromise.rejector(e); throw e; } forwarded.cleanup(); if (!isLastChunk) { childController.pause(); currentPromise.resolver(makeContinuationFn()); } } currentPromise.resolver(null); }; const run = pausableIterator(); run.catch((err) => { reject(err); }); return promise; }; exports.processM3uChunk = processM3uChunk;