diff --git a/api-extractor/report/hls.js.api.md b/api-extractor/report/hls.js.api.md index e720fc8b2a8..9c5341f5b59 100644 --- a/api-extractor/report/hls.js.api.md +++ b/api-extractor/report/hls.js.api.md @@ -860,12 +860,14 @@ export class CaptionScreen { // // @public (undocumented) export class ChunkMetadata { - constructor(level: number, sn: number, id: number, size?: number, part?: number, partial?: boolean); + constructor(level: number, sn: number, id: number, size?: number, part?: number, partial?: boolean, duration?: number | null); // (undocumented) readonly buffering: { [key in SourceBufferName]: HlsChunkPerformanceTiming; }; // (undocumented) + readonly duration: number | null; + // (undocumented) readonly id: number; // (undocumented) readonly level: number; diff --git a/src/controller/audio-stream-controller.ts b/src/controller/audio-stream-controller.ts index 8fa0a8092ad..cf8955b7dc6 100644 --- a/src/controller/audio-stream-controller.ts +++ b/src/controller/audio-stream-controller.ts @@ -660,6 +660,7 @@ class AudioStreamController payload.byteLength, partIndex, partial, + !partial ? frag.duration : null, ); transmuxer.push( payload, diff --git a/src/controller/base-stream-controller.ts b/src/controller/base-stream-controller.ts index 13c60d21c4c..b4be31cabd0 100644 --- a/src/controller/base-stream-controller.ts +++ b/src/controller/base-stream-controller.ts @@ -741,6 +741,7 @@ export default class BaseStreamController 0, part ? part.index : -1, !complete, + complete ? fragLoadedEndData.frag.duration : null, ); transmuxer.flush(chunkMeta); } diff --git a/src/controller/stream-controller.ts b/src/controller/stream-controller.ts index 0d78e18704e..1c464edd220 100644 --- a/src/controller/stream-controller.ts +++ b/src/controller/stream-controller.ts @@ -821,6 +821,7 @@ export default class StreamController payload.byteLength, partIndex, partial, + !partial ? frag.duration : null, ); const initPTS = this.initPTS[frag.cc]; diff --git a/src/demux/transmuxer.ts b/src/demux/transmuxer.ts index 45d8d8db863..bd9da285270 100644 --- a/src/demux/transmuxer.ts +++ b/src/demux/transmuxer.ts @@ -303,6 +303,7 @@ export default class Transmuxer { accurateTimeOffset, true, this.id, + chunkMeta.duration, ); transmuxResults.push({ remuxResult, @@ -411,6 +412,7 @@ export default class Transmuxer { accurateTimeOffset, false, this.id, + chunkMeta.duration, ); return { remuxResult, @@ -437,6 +439,7 @@ export default class Transmuxer { accurateTimeOffset, false, this.id, + chunkMeta.duration, ); return { remuxResult, diff --git a/src/remux/mp4-remuxer.ts b/src/remux/mp4-remuxer.ts index 3e761f585e0..8db181384f2 100644 --- a/src/remux/mp4-remuxer.ts +++ b/src/remux/mp4-remuxer.ts @@ -154,6 +154,7 @@ export default class MP4Remuxer implements Remuxer { accurateTimeOffset: boolean, flush: boolean, playlistType: PlaylistLevelType, + fragmentDuration: number, ): RemuxerResult { let video: RemuxedTrack | undefined; let audio: RemuxedTrack | undefined; @@ -290,6 +291,7 @@ export default class MP4Remuxer implements Remuxer { videoTimeOffset, isVideoContiguous, audioTrackLength, + fragmentDuration, ); } } else if (enoughVideoSamples) { @@ -298,6 +300,7 @@ export default class MP4Remuxer implements Remuxer { videoTimeOffset, isVideoContiguous, 0, + fragmentDuration, ); } if (video) { @@ -468,6 +471,7 @@ export default class MP4Remuxer implements Remuxer { timeOffset: number, contiguous: boolean, audioTrackLength: number, + fragmentDuration: number, ): RemuxedTrack | undefined { const timeScale: number = track.inputTimeScale; const inputSamples: Array = track.samples; @@ -530,9 +534,15 @@ export default class MP4Remuxer implements Remuxer { // Sample duration (as expected by trun MP4 boxes), should be the delta between sample DTS // set this constant duration as being the avg delta between consecutive DTS. const inputDuration = lastDTS - firstDTS; + const audioLengthBasedSampleDuration = audioTrackLength + ? Math.round(audioTrackLength * track.inputTimeScale) + : track.inputTimeScale; + const fragmentLengthOrAudioLengthBasedSampleDuration = fragmentDuration + ? Math.round(fragmentDuration * track.inputTimeScale) + : audioLengthBasedSampleDuration; const averageSampleDuration = inputDuration ? Math.round(inputDuration / (nbSamples - 1)) - : mp4SampleDuration || track.inputTimeScale / 30; + : fragmentLengthOrAudioLengthBasedSampleDuration; // if fragment are contiguous, detect hole/overlapping between fragments if (contiguous) { @@ -737,6 +747,9 @@ export default class MP4Remuxer implements Remuxer { maxDtsDelta = Math.max(maxDtsDelta, mp4SampleDuration); minPtsDelta = Math.min(minPtsDelta, ptsDelta); maxPtsDelta = Math.max(maxPtsDelta, ptsDelta); + this.logger.trace( + `[mp4-remuxer]: audioTrackLength = ${audioTrackLength}, fragmentDuration=${fragmentDuration}, fragmentLengthBasedSampleDuration=${fragmentLengthOrAudioLengthBasedSampleDuration}, audioLengthBasedSampleDuration=${audioLengthBasedSampleDuration}, averageSampleDuration=${averageSampleDuration}, mp4SampleDuration=${mp4SampleDuration}`, + ); outputSamples.push( createMp4Sample( diff --git a/src/types/remuxer.ts b/src/types/remuxer.ts index 8478a1f80c2..c75c3feebe3 100644 --- a/src/types/remuxer.ts +++ b/src/types/remuxer.ts @@ -23,6 +23,7 @@ export interface Remuxer { accurateTimeOffset: boolean, flush: boolean, playlistType: PlaylistLevelType, + fragmentDuration: number | null, ): RemuxerResult; resetInitSegment( initSegment: Uint8Array | undefined, diff --git a/src/types/transmuxer.ts b/src/types/transmuxer.ts index 1553d60daf0..a1215a5cf55 100644 --- a/src/types/transmuxer.ts +++ b/src/types/transmuxer.ts @@ -14,6 +14,7 @@ export class ChunkMetadata { public readonly id: number; public readonly size: number; public readonly partial: boolean; + public readonly duration: number | null; public readonly transmuxing: HlsChunkPerformanceTiming = getNewPerformanceTiming(); public readonly buffering: { @@ -31,6 +32,7 @@ export class ChunkMetadata { size = 0, part = -1, partial = false, + duration: number | null = null, ) { this.level = level; this.sn = sn; @@ -38,6 +40,7 @@ export class ChunkMetadata { this.size = size; this.part = part; this.partial = partial; + this.duration = duration; } } diff --git a/tests/unit/demuxer/transmuxer.ts b/tests/unit/demuxer/transmuxer.ts index fbedb058052..66a750dec59 100644 --- a/tests/unit/demuxer/transmuxer.ts +++ b/tests/unit/demuxer/transmuxer.ts @@ -130,7 +130,12 @@ describe('TransmuxerInterface tests', function () { const videoCodec = ''; const duration = 0; const accurateTimeOffset = true; - let chunkMeta = new ChunkMetadata(currentFrag.level, currentFrag.sn + 1, 0); + let chunkMeta = new ChunkMetadata( + currentFrag.level, + currentFrag.sn + 1, + 0, + currentFrag.duration, + ); let state = new TransmuxState(false, true, true, false, 0, false); transmuxerInterface.push( data, @@ -224,7 +229,12 @@ describe('TransmuxerInterface tests', function () { const videoCodec = ''; const duration = 0; const accurateTimeOffset = true; - const chunkMeta = new ChunkMetadata(newFrag.level, newFrag.sn, 0); + const chunkMeta = new ChunkMetadata( + newFrag.level, + newFrag.sn, + 0, + newFrag.duration, + ); const configureStub = sinon.stub( transmuxerInterfacePrivates.transmuxer,