diff --git a/externs/shaka/codecs.js b/externs/shaka/codecs.js index e4b89448f3..4e762a9432 100644 --- a/externs/shaka/codecs.js +++ b/externs/shaka/codecs.js @@ -27,6 +27,27 @@ shaka.extern.MPEG_PES; +/** + * @typedef {{ + * data: !Uint8Array, + * frame: boolean, + * isKeyframe: boolean, + * pts: ?number, + * dts: ?number, + * nalus: !Array. + * }} + * + * @summary VideoSample. + * @property {!Uint8Array} data + * @property {boolean} frame + * @property {boolean} isKeyframe + * @property {?number} pts + * @property {?number} dts + * @property {!Array.} nalus + */ +shaka.extern.VideoSample; + + /** * @typedef {{ * data: !Uint8Array, diff --git a/karma.conf.js b/karma.conf.js index cc1feba355..d0565e180c 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -282,6 +282,10 @@ module.exports = (config) => { {pattern: 'test/test/assets/hls-ts-h265/*', included: false}, {pattern: 'test/test/assets/hls-ts-mp3/*', included: false}, {pattern: 'test/test/assets/hls-ts-muxed-aac-h264/*', included: false}, + // eslint-disable-next-line max-len + {pattern: 'test/test/assets/hls-ts-muxed-aac-h264-with-overflow-nalus/*', included: false}, + // eslint-disable-next-line max-len + {pattern: 'test/test/assets/hls-ts-muxed-aac-h264-with-overflow-samples/*', included: false}, {pattern: 'test/test/assets/hls-ts-muxed-aac-h265/*', included: false}, {pattern: 'test/test/assets/hls-ts-muxed-ac3-h264/*', included: false}, {pattern: 'test/test/assets/hls-ts-muxed-mp3-h264/*', included: false}, diff --git a/lib/transmuxer/h264.js b/lib/transmuxer/h264.js index 96094cff00..164918b2c1 100644 --- a/lib/transmuxer/h264.js +++ b/lib/transmuxer/h264.js @@ -196,71 +196,28 @@ shaka.transmuxer.H264 = class { } /** - * @param {!Array.} nalus - * @return {?{data: !Uint8Array, isKeyframe: boolean}} + * @param {!Array.} videoData + * @return {!Array.} */ - static parseFrame(nalus) { + static getVideoSamples(videoData) { const H264 = shaka.transmuxer.H264; - let isKeyframe = false; - const nalusData = []; - const spsNalu = nalus.find((nalu) => { - return nalu.type == H264.NALU_TYPE_SPS_; - }); - let avcSample = false; - for (const nalu of nalus) { - let push = false; - switch (nalu.type) { - case H264.NALU_TYPE_NDR_: { - avcSample = true; - push = true; - const data = nalu.data; - // Only check slice type to detect KF in case SPS found in same packet - // (any keyframe is preceded by SPS ...) - if (spsNalu && data.length > 4) { - // retrieve slice type by parsing beginning of NAL unit (follow - // H264 spec,slice_header definition) to detect keyframe embedded - // in NDR - const sliceType = new shaka.util.ExpGolomb(data).readSliceType(); - // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice - // SI slice : A slice that is coded using intra prediction only and - // using quantisation of the prediction samples. - // An SI slice can be coded such that its decoded samples can be - // constructed identically to an SP slice. - // I slice: A slice that is not an SI slice that is decoded using - // intra prediction only. - if (sliceType === 2 || sliceType === 4 || - sliceType === 7 || sliceType === 9) { - isKeyframe = true; - } - } - break; - } - case H264.NALU_TYPE_IDR_: - avcSample = true; - push = true; - isKeyframe = true; - break; - case H264.NALU_TYPE_SEI_: - push = true; - break; - case H264.NALU_TYPE_SPS_: - push = true; - break; - case H264.NALU_TYPE_PPS_: - push = true; - break; - case H264.NALU_TYPE_AUD_: - push = true; - avcSample = true; - break; - case H264.NALU_TYPE_FILLER_DATA_: - push = true; - break; - default: - push = false; - break; + + /** @type {!Array.} */ + const videoSamples = []; + /** @type {?shaka.extern.VideoSample} */ + let lastVideoSample = null; + /** @type {boolean} */ + let audFound = false; + + const addLastVideoSample = () => { + if (!lastVideoSample) { + return; } - if (avcSample && push) { + if (!lastVideoSample.nalus.length || !lastVideoSample.frame) { + return; + } + const nalusData = []; + for (const nalu of lastVideoSample.nalus) { const size = nalu.fullData.byteLength; const naluLength = new Uint8Array(4); naluLength[0] = (size >> 24) & 0xff; @@ -270,15 +227,131 @@ shaka.transmuxer.H264 = class { nalusData.push(naluLength); nalusData.push(nalu.fullData); } - } - if (!nalusData.length) { - return null; - } - const data = shaka.util.Uint8ArrayUtils.concat(...nalusData); - return { - data, - isKeyframe, + lastVideoSample.data = shaka.util.Uint8ArrayUtils.concat(...nalusData); + videoSamples.push(lastVideoSample); + }; + + const createLastVideoSample = (pes) => { + lastVideoSample = { + data: new Uint8Array([]), + frame: false, + isKeyframe: false, + pts: pes.pts, + dts: pes.dts, + nalus: [], + }; }; + + for (let i = 0; i < videoData.length; i++) { + const pes = videoData[i]; + const nalus = pes.nalus; + let spsFound = false; + + // If new NAL units found and last sample still there, let's push ... + // This helps parsing streams with missing AUD + // (only do this if AUD never found) + if (lastVideoSample && nalus.length && !audFound) { + addLastVideoSample(); + createLastVideoSample(pes); + } + + for (const nalu of pes.nalus) { + let push = false; + switch (nalu.type) { + case H264.NALU_TYPE_NDR_: { + let isKeyframe = false; + push = true; + const data = nalu.data; + // Only check slice type to detect KF in case SPS found in same + // packet (any keyframe is preceded by SPS ...) + if (spsFound && data.length > 4) { + // retrieve slice type by parsing beginning of NAL unit (follow + // H264 spec,slice_header definition) to detect keyframe embedded + // in NDR + const sliceType = new shaka.util.ExpGolomb(data).readSliceType(); + // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice + // SI slice : A slice that is coded using intra prediction only + // and using quantisation of the prediction samples. + // An SI slice can be coded such that its decoded samples can be + // constructed identically to an SP slice. + // I slice: A slice that is not an SI slice that is decoded using + // intra prediction only. + if (sliceType === 2 || sliceType === 4 || + sliceType === 7 || sliceType === 9) { + isKeyframe = true; + } + } + if (isKeyframe) { + // If we have non-keyframe data already, that cannot belong to + // the same frame as a keyframe, so force a push + if (lastVideoSample && + lastVideoSample.frame && !lastVideoSample.isKeyframe) { + addLastVideoSample(); + lastVideoSample = null; + } + } + if (!lastVideoSample) { + createLastVideoSample(pes); + } + lastVideoSample.frame = true; + lastVideoSample.isKeyframe = isKeyframe; + break; + } + case H264.NALU_TYPE_IDR_: { + push = true; + // Handle PES not starting with AUD + // If we have frame data already, that cannot belong to the same + // frame, so force a push + if (lastVideoSample && + lastVideoSample.frame && !lastVideoSample.isKeyframe) { + addLastVideoSample(); + lastVideoSample = null; + } + if (!lastVideoSample) { + createLastVideoSample(pes); + } + lastVideoSample.frame = true; + lastVideoSample.isKeyframe = true; + break; + } + case H264.NALU_TYPE_SEI_: + push = true; + break; + case H264.NALU_TYPE_SPS_: + push = true; + spsFound = true; + break; + case H264.NALU_TYPE_PPS_: + push = true; + break; + case H264.NALU_TYPE_AUD_: + push = true; + audFound = true; + if (lastVideoSample && lastVideoSample.frame) { + addLastVideoSample(); + lastVideoSample = null; + } + if (!lastVideoSample) { + createLastVideoSample(pes); + } + break; + case H264.NALU_TYPE_FILLER_DATA_: + push = true; + break; + default: + push = false; + break; + } + if (lastVideoSample && push) { + lastVideoSample.nalus.push(nalu); + } + } + } + + // If last PES packet, push samples + addLastVideoSample(); + + return videoSamples; } }; diff --git a/lib/transmuxer/ts_transmuxer.js b/lib/transmuxer/ts_transmuxer.js index 37e0ef3046..753a677735 100644 --- a/lib/transmuxer/ts_transmuxer.js +++ b/lib/transmuxer/ts_transmuxer.js @@ -312,6 +312,7 @@ shaka.transmuxer.TsTransmuxer = class { getAacStreamInfo_(tsParser, stream, duration, reference) { const ADTS = shaka.transmuxer.ADTS; const timescale = shaka.util.TsParser.Timescale; + const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils; /** @type {!Array.} */ const samples = []; @@ -320,12 +321,41 @@ shaka.transmuxer.TsTransmuxer = class { let firstPts = null; + /** @type {?number} */ + let nextStartOffset = null; + /** @type {?Uint8Array} */ + let overflowBytes = null; + for (const audioData of tsParser.getAudioData()) { - const data = audioData.data; + let data = audioData.data; if (!data) { continue; } let offset = 0; + if (nextStartOffset == -1 && overflowBytes) { + data = Uint8ArrayUtils.concat(overflowBytes, audioData.data); + nextStartOffset = null; + } else if (nextStartOffset != null && overflowBytes) { + offset = Math.max(0, nextStartOffset); + const missingFrameData = + Uint8ArrayUtils.concat(overflowBytes, data.subarray(0, offset)); + samples.push({ + data: missingFrameData, + size: missingFrameData.byteLength, + duration: ADTS.AAC_SAMPLES_PER_FRAME, + cts: 0, + flags: { + isLeading: 0, + isDependedOn: 0, + hasRedundancy: 0, + degradPrio: 0, + dependsOn: 2, + isNonSync: 0, + }, + }); + overflowBytes = null; + nextStartOffset = null; + } info = ADTS.parseInfo(data, offset); if (!info) { throw new shaka.util.Error( @@ -342,12 +372,16 @@ shaka.transmuxer.TsTransmuxer = class { while (offset < data.length) { const header = ADTS.parseHeader(data, offset); if (!header) { - // We will increment one byte each time until we find the header. - offset++; - continue; + overflowBytes = data.subarray(offset, data.length); + nextStartOffset = -1; + break; } const length = header.headerLength + header.frameLength; - if (offset + length <= data.length) { + nextStartOffset = Math.max(0, offset + length - data.length); + if (nextStartOffset != 0) { + overflowBytes = data.subarray( + offset + header.headerLength, offset + length); + } else if (offset + length <= data.length) { const frameData = data.subarray( offset + header.headerLength, offset + length); @@ -865,50 +899,48 @@ shaka.transmuxer.TsTransmuxer = class { /** @type {?number} */ let baseMediaDecodeTime = null; - const nalus = []; const videoData = tsParser.getVideoData(); - if (!videoData.length) { + const videoSamples = H264.getVideoSamples(videoData); + if (!videoSamples.length) { throw new shaka.util.Error( shaka.util.Error.Severity.CRITICAL, shaka.util.Error.Category.MEDIA, shaka.util.Error.Code.TRANSMUXING_NO_VIDEO_DATA, reference ? reference.getUris()[0] : null); } - for (let i = 0; i < videoData.length; i++) { - const pes = videoData[i]; - const dataNalus = pes.nalus; - nalus.push(...dataNalus); - const frame = H264.parseFrame(dataNalus); - if (!frame) { - continue; - } + for (let i = 0; i < videoSamples.length; i++) { + const videoSample = videoSamples[i]; if (baseMediaDecodeTime == null) { - baseMediaDecodeTime = pes.dts; + baseMediaDecodeTime = videoSample.dts; } let duration; - if (i + 1 < videoData.length) { - duration = (videoData[i + 1].dts || 0) - (pes.dts || 0); - } else if (videoData.length > 1) { - duration = (pes.dts || 0) - (videoData[i - 1].dts || 0); + if (i + 1 < videoSamples.length) { + duration = (videoSamples[i + 1].dts || 0) - (videoSample.dts || 0); + } else if (videoSamples.length > 1) { + duration = (videoSample.dts || 0) - (videoSamples[i - 1].dts || 0); } else { duration = (reference.endTime - reference.startTime) * timescale; } samples.push({ - data: frame.data, - size: frame.data.byteLength, + data: videoSample.data, + size: videoSample.data.byteLength, duration: duration, - cts: Math.round((pes.pts || 0) - (pes.dts || 0)), + cts: Math.round((videoSample.pts || 0) - (videoSample.dts || 0)), flags: { isLeading: 0, isDependedOn: 0, hasRedundancy: 0, degradPrio: 0, - dependsOn: frame.isKeyframe ? 2 : 1, - isNonSync: frame.isKeyframe ? 0 : 1, + dependsOn: videoSample.isKeyframe ? 2 : 1, + isNonSync: videoSample.isKeyframe ? 0 : 1, }, }); } + const nalus = []; + for (const pes of videoData) { + nalus.push(...pes.nalus); + } const info = H264.parseInfo(nalus); if (!info || baseMediaDecodeTime == null) { diff --git a/lib/util/ts_parser.js b/lib/util/ts_parser.js index b7d857c87d..175e42fe65 100644 --- a/lib/util/ts_parser.js +++ b/lib/util/ts_parser.js @@ -517,7 +517,11 @@ shaka.util.TsParser = class { shaka.Deprecate.deprecateFeature(5, 'TsParser.parseAvcNalus', 'Please use parseNalus function instead.'); - return this.parseNalus(pes); + const lastInfo = { + nalu: null, + state: null, + }; + return this.parseNalus(pes, lastInfo); } /** @@ -527,12 +531,11 @@ shaka.util.TsParser = class { * Credit to https://github.com/video-dev/hls.js/blob/master/src/demux/tsdemuxer.ts * * @param {shaka.extern.MPEG_PES} pes - * @param {?shaka.extern.VideoNalu=} lastNalu - * @param {?number=} lastState + * @param {{nalu: ?shaka.extern.VideoNalu, state: ?number}} lastInfo * @return {!Array.} * @export */ - parseNalus(pes, lastNalu, lastState) { + parseNalus(pes, lastInfo) { const timescale = shaka.util.TsParser.Timescale; const time = pes.pts ? pes.pts / timescale : null; const data = pes.data; @@ -548,7 +551,12 @@ shaka.util.TsParser = class { // precede each NALU. A start code is 2 or 3 0x00 bytes followed with a // 0x01 byte. e.g. 0x000001 or 0x00000001. // More info in: https://stackoverflow.com/questions/24884827/possible-locations-for-sequence-picture-parameter-sets-for-h-264-stream/24890903#24890903 - let numZeros = lastState || 0; + let numZeros = lastInfo.state || 0; + + const initialNumZeros = numZeros; + + /** @type {number} */ + let i = 0; /** @type {!Array.} */ const nalus = []; @@ -559,103 +567,119 @@ shaka.util.TsParser = class { // Extracted from the first byte. let lastNaluType = 0; - /** @type {?shaka.extern.VideoNalu} */ - let infoOfLastNalu; + const getNaluType = (offset) => { + if (this.videoCodec_ == 'hvc') { + return (data[offset] >> 1) & 0x3f; + } else { + return data[offset] & 0x1f; + } + }; - for (let i = 0; i < len; ++i) { - const value = data[i]; - if (!value) { - numZeros++; - } else if (numZeros >= 2 && value == 1) { - if (lastNalu && !nalus.length && lastNaluStart == -1) { - // If we are scanning the next PES, we need append the data to the - // previous Nalu and don't scan for more nalus. - const startCodeSize = numZeros > 3 ? 3 : numZeros; - const lastByteToKeep = i - startCodeSize; - // Optimization - if (lastState && lastByteToKeep != 0) { - const prevData = data.subarray(0, lastByteToKeep); - lastNalu.data = shaka.util.Uint8ArrayUtils.concat( - lastNalu.data, prevData); - lastNalu.fullData = shaka.util.Uint8ArrayUtils.concat( - lastNalu.fullData, prevData); - } - } - // We just read a start code. Consume the NALU we passed, if any. - if (lastNaluStart >= 0) { - // Because the start position includes the header size. - const firstByteToKeep = lastNaluStart + naluHeaderSize; + const getLastNalu = () => { + if (nalus.length) { + return nalus[nalus.length - 1]; + } + return lastInfo.nalu; + }; - // Compute the last byte to keep. The start code is at most 3 zeros. - // Any earlier zeros are not part of the start code. - const startCodeSize = (numZeros > 3 ? 3 : numZeros) + 1; - const lastByteToKeep = i - startCodeSize; + if (numZeros == -1) { + // special use case where we found 3 or 4-byte start codes exactly at the + // end of previous PES packet + lastNaluStart = 0; + // NALu type is value read from offset 0 + lastNaluType = getNaluType(0); + numZeros = 0; + i = 1; + } + while (i < len) { + const value = data[i++]; + // Optimization. numZeros 0 and 1 are the predominant case. + if (!numZeros) { + numZeros = value ? 0 : 1; + continue; + } + if (numZeros === 1) { + numZeros = value ? 0 : 2; + continue; + } + if (!value) { + numZeros = 3; + } else if (value == 1) { + const overflow = i - numZeros - 1; + if (lastNaluStart >= 0) { /** @type {shaka.extern.VideoNalu} */ const nalu = { - // subarray's end position is exclusive, so add one. - data: data.subarray(firstByteToKeep, lastByteToKeep + 1), - fullData: data.subarray(lastNaluStart, lastByteToKeep + 1), + data: data.subarray(lastNaluStart + naluHeaderSize, overflow), + fullData: data.subarray(lastNaluStart, overflow), type: lastNaluType, time: time, + state: null, }; nalus.push(nalu); - } else if (lastNalu && !nalus.length) { - const overflow = i - numZeros; - if (overflow > 0) { - const prevData = data.subarray(0, overflow); - lastNalu.data = shaka.util.Uint8ArrayUtils.concat( - lastNalu.data, prevData); - lastNalu.fullData = shaka.util.Uint8ArrayUtils.concat( - lastNalu.fullData, prevData); + } else { + const lastNalu = getLastNalu(); + if (lastNalu) { + if (initialNumZeros && i <= 4 - initialNumZeros) { + // Start delimiter overlapping between PES packets + // strip start delimiter bytes from the end of last NAL unit + // check if lastNalu had a state different from zero + if (lastNalu.state) { + // strip last bytes + lastNalu.data = lastNalu.data.subarray( + 0, lastNalu.data.byteLength - initialNumZeros); + lastNalu.fullData = lastNalu.fullData.subarray( + 0, lastNalu.fullData.byteLength - initialNumZeros); + } + } + // If NAL units are not starting right at the beginning of the PES + // packet, push preceding data into previous NAL unit. + if (overflow > 0) { + const prevData = data.subarray(0, overflow); + lastNalu.data = shaka.util.Uint8ArrayUtils.concat( + lastNalu.data, prevData); + lastNalu.fullData = shaka.util.Uint8ArrayUtils.concat( + lastNalu.fullData, prevData); + lastNalu.state = 0; + } } } - // We just read a start code, so there should be another byte here, at - // least, for the NALU type. Check just in case. - if (i >= len - naluHeaderSize) { - shaka.log.warning('Malformed TS, incomplete NALU, ignoring.'); - return nalus; - } - - // Advance and read the type of the next NALU. - i++; - lastNaluStart = i; - if (this.videoCodec_ == 'hvc') { - lastNaluType = (data[i] >> 1) & 0x3f; + // Check if we can read unit type + if (i < len) { + lastNaluType = getNaluType(i); + lastNaluStart = i; + numZeros = 0; } else { - lastNaluType = data[i] & 0x1f; + // Not enough byte to read unit type. + // Let's read it on next PES parsing. + numZeros = -1; } - numZeros = 0; } else { numZeros = 0; } - // If we have gone through all the data from the PES and we have an - // unfinished Nalu, we will try to use the next PES to complete the - // unfinished Nalu. - if (i >= (len - 1) && lastNaluStart >= 0 && numZeros >= 0) { - // The rest of the buffer was a NALU. - // Because the start position includes the header size. - const firstByteToKeep = lastNaluStart + naluHeaderSize; - infoOfLastNalu = { - data: data.subarray(firstByteToKeep, len), - fullData: data.subarray(lastNaluStart, len), - type: lastNaluType, - time: time, - }; - } } - if (infoOfLastNalu) { - nalus.push(infoOfLastNalu); + if (lastNaluStart >= 0 && numZeros >= 0) { + const nalu = { + data: data.subarray(lastNaluStart + naluHeaderSize, len), + fullData: data.subarray(lastNaluStart, len), + type: lastNaluType, + time: time, + state: numZeros, + }; + nalus.push(nalu); } - if (!nalus.length && lastNalu) { - lastNalu.data = shaka.util.Uint8ArrayUtils.concat( - lastNalu.data, data); - lastNalu.fullData = shaka.util.Uint8ArrayUtils.concat( - lastNalu.fullData, data); + if (!nalus.length && lastInfo.nalu) { + const lastNalu = getLastNalu(); + if (lastNalu) { + lastNalu.data = shaka.util.Uint8ArrayUtils.concat( + lastNalu.data, data); + lastNalu.fullData = shaka.util.Uint8ArrayUtils.concat( + lastNalu.fullData, data); + } } - lastState = Math.min(3, numZeros); + lastInfo.state = numZeros; return nalus; } @@ -759,14 +783,16 @@ shaka.util.TsParser = class { } } if (naluProcessing) { - let lastNalu; - let lastState; + const lastInfo = { + nalu: null, + state: null, + }; const pesWithLength = []; for (const pes of this.videoPes_) { - pes.nalus = this.parseNalus(pes, lastNalu, lastState); + pes.nalus = this.parseNalus(pes, lastInfo); if (pes.nalus.length) { pesWithLength.push(pes); - lastNalu = pes.nalus[pes.nalus.length - 1]; + lastInfo.nalu = pes.nalus[pes.nalus.length - 1]; } } this.videoPes_ = pesWithLength; diff --git a/test/test/assets/hls-ts-muxed-aac-h264-with-overflow-nalus/fileSequence0.ts b/test/test/assets/hls-ts-muxed-aac-h264-with-overflow-nalus/fileSequence0.ts new file mode 100644 index 0000000000..031bfe30d6 Binary files /dev/null and b/test/test/assets/hls-ts-muxed-aac-h264-with-overflow-nalus/fileSequence0.ts differ diff --git a/test/test/assets/hls-ts-muxed-aac-h264-with-overflow-nalus/media.m3u8 b/test/test/assets/hls-ts-muxed-aac-h264-with-overflow-nalus/media.m3u8 new file mode 100644 index 0000000000..7a8ee8df2f --- /dev/null +++ b/test/test/assets/hls-ts-muxed-aac-h264-with-overflow-nalus/media.m3u8 @@ -0,0 +1,8 @@ +#EXTM3U +#EXT-X-TARGETDURATION:10 +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:9.97663, +fileSequence0.ts +#EXT-X-ENDLIST diff --git a/test/test/assets/hls-ts-muxed-aac-h264-with-overflow-samples/fileSequence0.ts b/test/test/assets/hls-ts-muxed-aac-h264-with-overflow-samples/fileSequence0.ts new file mode 100644 index 0000000000..a97a6553d4 Binary files /dev/null and b/test/test/assets/hls-ts-muxed-aac-h264-with-overflow-samples/fileSequence0.ts differ diff --git a/test/test/assets/hls-ts-muxed-aac-h264-with-overflow-samples/media.m3u8 b/test/test/assets/hls-ts-muxed-aac-h264-with-overflow-samples/media.m3u8 new file mode 100644 index 0000000000..7a8ee8df2f --- /dev/null +++ b/test/test/assets/hls-ts-muxed-aac-h264-with-overflow-samples/media.m3u8 @@ -0,0 +1,8 @@ +#EXTM3U +#EXT-X-TARGETDURATION:10 +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PLAYLIST-TYPE:VOD +#EXTINF:9.97663, +fileSequence0.ts +#EXT-X-ENDLIST diff --git a/test/transmuxer/transmuxer_integration.js b/test/transmuxer/transmuxer_integration.js index f0fbc74962..077a9e2521 100644 --- a/test/transmuxer/transmuxer_integration.js +++ b/test/transmuxer/transmuxer_integration.js @@ -309,6 +309,40 @@ describe('Transmuxer Player', () => { await player.unload(); }); + it('H.264+AAC with AAC sample with overflow aac samples', async () => { + // eslint-disable-next-line max-len + await player.load('/base/test/test/assets/hls-ts-muxed-aac-h264-with-overflow-samples/media.m3u8'); + await video.play(); + expect(player.isLive()).toBe(false); + + // Wait for the video to start playback. If it takes longer than 10 + // seconds, fail the test. + await waiter.waitForMovementOrFailOnTimeout(video, 10); + + // Play for e seconds, but stop early if the video ends. If it takes + // longer than 45 seconds, fail the test. + await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 3, 45); + + await player.unload(); + }); + + it('H.264+AAC with AAC sample with overflow nalus', async () => { + // eslint-disable-next-line max-len + await player.load('/base/test/test/assets/hls-ts-muxed-aac-h264-with-overflow-nalus/media.m3u8'); + await video.play(); + expect(player.isLive()).toBe(false); + + // Wait for the video to start playback. If it takes longer than 10 + // seconds, fail the test. + await waiter.waitForMovementOrFailOnTimeout(video, 10); + + // Play for e seconds, but stop early if the video ends. If it takes + // longer than 45 seconds, fail the test. + await waiter.waitUntilPlayheadReachesOrFailOnTimeout(video, 3, 45); + + await player.unload(); + }); + it('H.265+AAC in TS', async () => { if (!await Util.isTypeSupported('video/mp4; codecs="hvc1.2.4.L123.B0"', /* width= */ 720, /* height= */ 1280)) {