UNPKG

@remotion/media-parser

Version:

A pure JavaScript library for parsing video files

132 lines (130 loc) 5.21 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}`; }; const findBestJump = ({ allSamplesSortedByOffset, 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 = allSamplesSortedByOffset.findIndex((sample) => sample.track.trackId === Number(trackNumberWithLowestProgress) && !visited.has(getKey(sample))); return firstSampleAboveMinProgress; }; const calculateJumpMarks = (samplePositionTracks, endOfMdat) => { const progresses = {}; for (const track of samplePositionTracks) { progresses[track[0].track.trackId] = 0; } const jumpMarks = []; const allSamplesSortedByOffset = samplePositionTracks .flat(1) .sort((a, b) => a.samplePosition.offset - b.samplePosition.offset); let indexToVisit = 0; const visited = new Set(); let rollOverToProcess = false; const increaseIndex = () => { indexToVisit++; if (indexToVisit >= allSamplesSortedByOffset.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: allSamplesSortedByOffset[firstSampleAboveMinProgress].samplePosition .offset, }; indexToVisit = firstSampleAboveMinProgress; jumpMarks.push(jumpMark); }; const addFinalJumpIfNecessary = () => { if (indexToVisit === allSamplesSortedByOffset.length - 1) { return; } jumpMarks.push({ afterSampleWithOffset: allSamplesSortedByOffset[indexToVisit].samplePosition.offset, jumpToOffset: endOfMdat, }); }; const considerJump = () => { const firstSampleAboveMinProgress = findBestJump({ allSamplesSortedByOffset, visited, progresses, }); if (firstSampleAboveMinProgress > -1 && firstSampleAboveMinProgress !== indexToVisit + 1) { addJumpMark({ firstSampleAboveMinProgress }); indexToVisit = firstSampleAboveMinProgress; } else { while (true) { increaseIndex(); if (!visited.has(getKey(allSamplesSortedByOffset[indexToVisit]))) { break; } } } }; while (true) { const currentSamplePosition = allSamplesSortedByOffset[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 === allSamplesSortedByOffset.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 === allSamplesSortedByOffset.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;