Keep initPTS across the chunks of a progressive multi-moof segment
In progressive mode every chunk of a segment is remuxed with the segment's timeOffset, so only the first chunk's decode time lines up with it. A second moof starting further into the segment was treated as a new initPTS and appended over the chunks before it, stalling playback. Fixes #5697
This commit is contained in:
1 parent
2ac5311ec5
commit
155a6b6c90
2 files changed
+65
-2
No files matched your search
@@ -49,6 +49,7 @@ class PassThroughRemuxer extends Logger implements Remuxer {
|
|||||||
private initPTS: TimestampOffset | null = null;
|
private initPTS: TimestampOffset | null = null;
|
||||||
private initTracks?: TrackSet;
|
private initTracks?: TrackSet;
|
||||||
private lastEndTime: number | null = null;
|
private lastEndTime: number | null = null;
|
||||||
|
private lastChunkKey: string | null = null;
|
||||||
private isVideoContiguous: boolean = false;
|
private isVideoContiguous: boolean = false;
|
||||||
private videoOnlyRemux: boolean = false;
|
private videoOnlyRemux: boolean = false;
|
||||||
private decryptdata: DecryptData | null = null;
|
private decryptdata: DecryptData | null = null;
|
||||||
@@ -74,6 +75,7 @@ class PassThroughRemuxer extends Logger implements Remuxer {
|
|||||||
|
|
||||||
public resetTimeStamp(defaultInitPTS: TimestampOffset | null) {
|
public resetTimeStamp(defaultInitPTS: TimestampOffset | null) {
|
||||||
this.lastEndTime = null;
|
this.lastEndTime = null;
|
||||||
|
this.lastChunkKey = null;
|
||||||
const initPTS = this.initPTS;
|
const initPTS = this.initPTS;
|
||||||
if (initPTS && defaultInitPTS) {
|
if (initPTS && defaultInitPTS) {
|
||||||
if (
|
if (
|
||||||
@@ -89,6 +91,7 @@ class PassThroughRemuxer extends Logger implements Remuxer {
|
|||||||
public resetNextTimestamp() {
|
public resetNextTimestamp() {
|
||||||
this.isVideoContiguous = false;
|
this.isVideoContiguous = false;
|
||||||
this.lastEndTime = null;
|
this.lastEndTime = null;
|
||||||
|
this.lastChunkKey = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public resetInitSegment(
|
public resetInitSegment(
|
||||||
@@ -441,8 +444,14 @@ class PassThroughRemuxer extends Logger implements Remuxer {
|
|||||||
|
|
||||||
decodeTime = baseOffsetSamples.start / timescale;
|
decodeTime = baseOffsetSamples.start / timescale;
|
||||||
|
|
||||||
|
// Every chunk of a progressive segment is remuxed with the segment's
|
||||||
|
// timeOffset, so only the first chunk's decode time lines up with it
|
||||||
|
const chunkKey = `${chunkMeta.level}_${chunkMeta.sn}_${chunkMeta.part}`;
|
||||||
|
const segmentStart = chunkKey !== this.lastChunkKey;
|
||||||
|
this.lastChunkKey = chunkKey;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(accurateTimeOffset || !initPTS) &&
|
((accurateTimeOffset && segmentStart) || !initPTS) &&
|
||||||
(isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) ||
|
(isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) ||
|
||||||
timescale !== initPTS.timescale)
|
timescale !== initPTS.timescale)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -104,6 +104,24 @@ describe('passthrough-remuxer', function () {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function remuxProgressiveChunk(
|
||||||
|
fragmentData: Uint8Array<ArrayBuffer>,
|
||||||
|
timeOffset: number,
|
||||||
|
flush: boolean,
|
||||||
|
) {
|
||||||
|
return remuxer.remux(
|
||||||
|
audioTrack(),
|
||||||
|
passthroughTrack(fragmentData),
|
||||||
|
metadataTrack(),
|
||||||
|
userdataTrack(),
|
||||||
|
timeOffset,
|
||||||
|
true,
|
||||||
|
flush,
|
||||||
|
PlaylistLevelType.MAIN,
|
||||||
|
new ChunkMetadata(0, 0, 0, fragmentData.byteLength, -1, true, 4, false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
it('remuxes moof+mdat to stretch a single-keyframe iframe to the EXTINF duration', function () {
|
it('remuxes moof+mdat to stretch a single-keyframe iframe to the EXTINF duration', function () {
|
||||||
const extinfDuration = 4;
|
const extinfDuration = 4;
|
||||||
const fragmentData = mp4Fragment([sample(3003, 4, 0)]);
|
const fragmentData = mp4Fragment([sample(3003, 4, 0)]);
|
||||||
@@ -410,6 +428,35 @@ describe('passthrough-remuxer', function () {
|
|||||||
extinfDuration,
|
extinfDuration,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps initPTS across the chunks of a progressively loaded multi-moof segment', function () {
|
||||||
|
remuxer.resetInitSegment(
|
||||||
|
MP4.initSegment([videoInitTrack()]),
|
||||||
|
undefined,
|
||||||
|
'avc1.42001e',
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
// One 4s segment delivered as two 2s moof+mdat chunks
|
||||||
|
const firstChunk = mp4FragmentAt(0, [sample(180000, 4, 0)]);
|
||||||
|
const secondChunk = mp4FragmentAt(180000, [sample(180000, 4, 0)]);
|
||||||
|
|
||||||
|
const first = remuxProgressiveChunk(firstChunk, 0, false);
|
||||||
|
const second = remuxProgressiveChunk(secondChunk, 0, true);
|
||||||
|
|
||||||
|
expect(first.video, 'first chunk video track').to.exist;
|
||||||
|
expect(first.video!.startDTS, 'first chunk startDTS').to.equal(0);
|
||||||
|
expect(first.video!.endDTS, 'first chunk endDTS').to.equal(2);
|
||||||
|
|
||||||
|
// The second moof starts 2s into the segment. Its decode time must not be
|
||||||
|
// mistaken for a new initPTS, or the chunk is appended over the first one.
|
||||||
|
expect(second.video, 'second chunk video track').to.exist;
|
||||||
|
expect(second.video!.startDTS, 'second chunk startDTS').to.equal(2);
|
||||||
|
expect(second.video!.endDTS, 'second chunk endDTS').to.equal(4);
|
||||||
|
expect(second.initSegment?.initPTS, 'second chunk initPTS').to.not.equal(
|
||||||
|
180000,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function markVideoInitSegmentEncrypted(
|
function markVideoInitSegmentEncrypted(
|
||||||
@@ -573,11 +620,18 @@ function withSecondVideoTraf(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mp4Fragment(samples: TrackFragmentSample[]): Uint8Array<ArrayBuffer> {
|
function mp4Fragment(samples: TrackFragmentSample[]): Uint8Array<ArrayBuffer> {
|
||||||
|
return mp4FragmentAt(0, samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mp4FragmentAt(
|
||||||
|
baseMediaDecodeTime: number,
|
||||||
|
samples: TrackFragmentSample[],
|
||||||
|
): Uint8Array<ArrayBuffer> {
|
||||||
const mdatPayload = new Uint8Array(
|
const mdatPayload = new Uint8Array(
|
||||||
samples.reduce((total, sample) => total + sample.size, 0),
|
samples.reduce((total, sample) => total + sample.size, 0),
|
||||||
);
|
);
|
||||||
return appendUint8Array(
|
return appendUint8Array(
|
||||||
MP4.moof(0, 0, { type: 'video', id: 1, samples }),
|
MP4.moof(0, baseMediaDecodeTime, { type: 'video', id: 1, samples }),
|
||||||
MP4.mdat(mdatPayload),
|
MP4.mdat(mdatPayload),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user