UNPKG

@remotion/media-parser

Version:

A pure JavaScript library for parsing video files

130 lines (128 loc) 5.02 kB
"use strict"; /* https://discord.com/channels/809501355504959528/1001500302375125055/1364798934832119870 Android 13 produces MP4 videos where first, all video samples are at the beginning, then all audio samples are at the end. This causes issues with our video decoder: "Waited too long for VideoDecoder" because the overall progress is stuck. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateJumpMarks = void 0; // In WebCodecs, we require the tracks to deviate by at most 10 seconds // Therefore, we need to emit them to be less than 10 seconds apart const MAX_SPREAD_IN_SECONDS = 8; const getKey = (samplePositionTrack) => { return `${samplePositionTrack.track.trackId}-${samplePositionTrack.samplePosition.decodingTimestamp}.${samplePositionTrack.samplePosition.offset}`; }; const findBestJump = ({ sampleMap, offsetsSorted, visited, progresses, }) => { var _a; const minProgress = Math.min(...Object.values(progresses)); const trackNumberWithLowestProgress = (_a = Object.entries(progresses).find(([, progress]) => progress === minProgress)) === null || _a === void 0 ? void 0 : _a[0]; const firstSampleAboveMinProgress = offsetsSorted.findIndex((offset) => sampleMap.get(offset).track.trackId === Number(trackNumberWithLowestProgress) && !visited.has(getKey(sampleMap.get(offset)))); return firstSampleAboveMinProgress; }; const calculateJumpMarks = ({ sampleMap, offsetsSorted, trackIds, endOfMdat, }) => { const progresses = {}; for (const trackId of trackIds) { progresses[trackId] = 0; } const jumpMarks = []; let indexToVisit = 0; const visited = new Set(); let rollOverToProcess = false; const increaseIndex = () => { indexToVisit++; if (indexToVisit >= offsetsSorted.length) { rollOverToProcess = true; indexToVisit = 0; } }; let lastVisitedSample = null; const addJumpMark = ({ firstSampleAboveMinProgress, }) => { if (!lastVisitedSample) { throw new Error('no last visited sample'); } const jumpMark = { afterSampleWithOffset: lastVisitedSample.samplePosition.offset, jumpToOffset: offsetsSorted[firstSampleAboveMinProgress], }; indexToVisit = firstSampleAboveMinProgress; jumpMarks.push(jumpMark); }; const addFinalJumpIfNecessary = () => { if (indexToVisit === offsetsSorted.length - 1) { return; } jumpMarks.push({ afterSampleWithOffset: offsetsSorted[indexToVisit], jumpToOffset: endOfMdat, }); }; const considerJump = () => { const firstSampleAboveMinProgress = findBestJump({ sampleMap, offsetsSorted, visited, progresses, }); if (firstSampleAboveMinProgress > -1 && firstSampleAboveMinProgress !== indexToVisit + 1) { addJumpMark({ firstSampleAboveMinProgress }); indexToVisit = firstSampleAboveMinProgress; } else { while (true) { increaseIndex(); if (!visited.has(getKey(sampleMap.get(offsetsSorted[indexToVisit])))) { break; } } } }; while (true) { const currentSamplePosition = sampleMap.get(offsetsSorted[indexToVisit]); const sampleKey = getKey(currentSamplePosition); if (visited.has(sampleKey)) { considerJump(); continue; } visited.add(sampleKey); if (rollOverToProcess) { if (!lastVisitedSample) { throw new Error('no last visited sample'); } jumpMarks.push({ afterSampleWithOffset: lastVisitedSample.samplePosition.offset, jumpToOffset: currentSamplePosition.samplePosition.offset, }); rollOverToProcess = false; } lastVisitedSample = currentSamplePosition; if (visited.size === offsetsSorted.length) { addFinalJumpIfNecessary(); break; } const timestamp = currentSamplePosition.samplePosition.decodingTimestamp / currentSamplePosition.track.originalTimescale; progresses[currentSamplePosition.track.trackId] = timestamp; const progressValues = Object.values(progresses); const maxProgress = Math.max(...progressValues); const minProgress = Math.min(...progressValues); const spread = maxProgress - minProgress; if (visited.size === offsetsSorted.length) { addFinalJumpIfNecessary(); break; } // Also don't allow audio progress to go more if (spread > MAX_SPREAD_IN_SECONDS) { considerJump(); } else { increaseIndex(); } } return jumpMarks; }; exports.calculateJumpMarks = calculateJumpMarks;