diff --git a/src/isofile.ts b/src/isofile.ts index f02594ce..d2d8c334 100644 --- a/src/isofile.ts +++ b/src/isofile.ts @@ -1073,6 +1073,8 @@ export class ISOFile { } time = trak.samples[seek_sample_num].cts; trak.nextSample = seek_sample_num; + this.resetFragmentedTrackStateAfterSeek(trak, seek_sample_num); + this.resetExtractedTrackStateAfterSeek(trak); while (trak.samples[seek_sample_num].alreadyRead === trak.samples[seek_sample_num].size) { // No remaining samples to look for, all are downloaded. if (!trak.samples[seek_sample_num + 1]) { @@ -1098,6 +1100,27 @@ export class ISOFile { return { offset: seek_offset, time: time / timescale }; } + resetFragmentedTrackStateAfterSeek(trak: trakBox, seekSampleNumber: number) { + const fragTrack = this.fragmentedTracks.find(t => t.trak === trak); + if (!fragTrack) { + return; + } + + fragTrack.state.lastFragmentSampleNumber = seekSampleNumber; + fragTrack.state.lastSegmentSampleNumber = seekSampleNumber; + fragTrack.state.accumulatedSize = 0; + fragTrack.segmentStream = undefined; + } + + resetExtractedTrackStateAfterSeek(trak: trakBox) { + const extractTrack = this.extractedTracks.find(t => t.trak === trak); + if (!extractTrack) { + return; + } + + extractTrack.samples = []; + } + getTrackDuration(trak: trakBox) { if (!trak.samples) { return Infinity; @@ -1182,6 +1205,14 @@ export class ISOFile { sampleEnd: number, existingStream: DataStream, ) { + if (sampleEnd < sampleStart) { + Log.warn( + 'ISOFile', + `Skipping fragment creation on track #${track_id}: invalid sample range [${sampleStart}, ${sampleEnd}]`, + ); + return existingStream || new DataStream(); + } + // Check existence of all samples const samples: Array = []; for (let i = sampleStart; i <= sampleEnd; i++) { diff --git a/tests/segmentation.test.ts b/tests/segmentation.test.ts index 87fa22f0..048a3ee1 100644 --- a/tests/segmentation.test.ts +++ b/tests/segmentation.test.ts @@ -206,4 +206,89 @@ describe('File Segmentation', () => { expect(newMP4.getBoxes('moof', false).length).toBe(10); expect(out.getAbsoluteEndPosition()).toBe(203_175); }); + + it('resets segmentation state on seek', async () => { + const { testFile } = getFilePath('isobmff', '01_simple.mp4'); + const { mp4 } = await loadAndGetInfo(testFile, true, true); + + mp4.setSegmentOptions(201, undefined, { + nbSamples: 100, + nbSamplesPerFragment: 50, + rapAlignement: false, + }); + mp4.initializeSegmentation(); + mp4.onSegment = () => undefined; + + mp4.start(); + + const fragTrack = mp4.fragmentedTracks.find(track => track.id === 201); + expect(fragTrack).toBeDefined(); + if (!fragTrack) { + throw new Error('Missing fragmented track #201'); + } + + mp4.seek(0, false); + + expect(fragTrack.state.lastFragmentSampleNumber).toBe(fragTrack.trak.nextSample); + expect(fragTrack.state.lastSegmentSampleNumber).toBe(fragTrack.trak.nextSample); + expect(fragTrack.state.accumulatedSize).toBe(0); + expect(fragTrack.segmentStream).toBeUndefined(); + expect(() => mp4.start()).not.toThrow(); + }); + + it('resets segmentation state on forward seek before processing', async () => { + const { testFile } = getFilePath('isobmff', '01_simple.mp4'); + const { mp4 } = await loadAndGetInfo(testFile, true, true); + + mp4.setSegmentOptions(201, undefined, { + nbSamples: 100, + nbSamplesPerFragment: 50, + rapAlignement: false, + }); + mp4.initializeSegmentation(); + + const fragTrack = mp4.fragmentedTracks.find(track => track.id === 201); + expect(fragTrack).toBeDefined(); + if (!fragTrack) { + throw new Error('Missing fragmented track #201'); + } + + mp4.seek(3, false); + + expect(fragTrack.trak.nextSample).toBeGreaterThan(0); + expect(fragTrack.state.lastFragmentSampleNumber).toBe(fragTrack.trak.nextSample); + expect(fragTrack.state.lastSegmentSampleNumber).toBe(fragTrack.trak.nextSample); + expect(fragTrack.state.accumulatedSize).toBe(0); + expect(fragTrack.segmentStream).toBeUndefined(); + }); + + it('resets segmentation state on backward seek after a forward seek', async () => { + const { testFile } = getFilePath('isobmff', '01_simple.mp4'); + const { mp4 } = await loadAndGetInfo(testFile, true, true); + + mp4.setSegmentOptions(201, undefined, { + nbSamples: 100, + nbSamplesPerFragment: 50, + rapAlignement: false, + }); + mp4.initializeSegmentation(); + + const fragTrack = mp4.fragmentedTracks.find(track => track.id === 201); + expect(fragTrack).toBeDefined(); + if (!fragTrack) { + throw new Error('Missing fragmented track #201'); + } + + mp4.seek(3, false); + const forwardSample = fragTrack.trak.nextSample; + expect(forwardSample).toBeGreaterThan(0); + + mp4.seek(0, false); + + expect(fragTrack.trak.nextSample).toBeLessThan(forwardSample); + expect(fragTrack.state.lastFragmentSampleNumber).toBe(fragTrack.trak.nextSample); + expect(fragTrack.state.lastSegmentSampleNumber).toBe(fragTrack.trak.nextSample); + expect(fragTrack.state.accumulatedSize).toBe(0); + expect(fragTrack.segmentStream).toBeUndefined(); + }); });