diff --git a/demo/chart/timeline-chart.ts b/demo/chart/timeline-chart.ts index ada4baec5b4..d71f015084a 100644 --- a/demo/chart/timeline-chart.ts +++ b/demo/chart/timeline-chart.ts @@ -274,7 +274,7 @@ export class TimelineChart { return; } // eslint-disable-next-line no-restricted-properties - const fragData = arrayFind(levelDataSet.data, fragData => fragData.relurl === frag.relurl); + const fragData = arrayFind(levelDataSet.data, fragData => fragData.relurl === frag.relurl && fragData.sn === frag.sn); if (fragData && fragData !== frag) { Object.assign(fragData, frag); } diff --git a/demo/index.html b/demo/index.html index 2960fa59cb8..6737459fad8 100644 --- a/demo/index.html +++ b/demo/index.html @@ -169,6 +169,7 @@

+ diff --git a/demo/main.js b/demo/main.js index 61881a9b21a..039a5c08386 100644 --- a/demo/main.js +++ b/demo/main.js @@ -58,14 +58,18 @@ $(document).ready(function () { chart = setupTimelineChart(); - Object.keys(testStreams).forEach((key) => { + Object.keys(testStreams).forEach((key, index) => { const stream = testStreams[key]; const option = new Option(stream.description, key); $('#streamSelect').append(option); + if (stream.url === sourceURL) { + $('#streamSelect')[0].selectedIndex = index + 1; + } }); $('#streamSelect').change(function () { - selectedTestStream = testStreams[$('#streamSelect').val()]; + const key = $('#streamSelect').val() || Object.keys(testStreams)[0]; + selectedTestStream = testStreams[key]; const streamUrl = selectedTestStream.url; $('#streamURL').val(streamUrl); loadSelectedStream(); @@ -159,6 +163,9 @@ function setupGlobals () { level: [], bitrate: [] }; + lastAudioTrackSwitchingIdx = undefined; + lastSeekingIdx = undefined; + bufferingIdx = -1; // actual values, only on window self.recoverDecodingErrorDate = null; diff --git a/src/controller/audio-stream-controller.ts b/src/controller/audio-stream-controller.ts index bd047dcd25e..6fe69163edd 100644 --- a/src/controller/audio-stream-controller.ts +++ b/src/controller/audio-stream-controller.ts @@ -99,7 +99,7 @@ class AudioStreamController extends BaseStreamController implements ComponentAPI const cc = frag.cc; this.initPTS[cc] = initPTS; this.videoTrackCC = cc; - this.log(`InitPTS for cc: ${cc} found from video track: ${initPTS}`); + this.log(`InitPTS for cc: ${cc} found from main: ${initPTS}`); // If we are waiting, tick immediately to unblock audio fragment transmuxing if (this.state === State.WAITING_INIT_PTS) { this.tick(); @@ -310,7 +310,7 @@ class AudioStreamController extends BaseStreamController implements ComponentAPI return; } - this.log(`Loading ${frag.sn}, cc: ${frag.cc} of [${trackDetails.startSN} ,${trackDetails.endSN}],track ${this.trackId}, load position: ${pos.toFixed(3)}-${loadPos.toFixed(3)}`); + this.log(`Loading ${frag.sn}, cc: ${frag.cc} of [${trackDetails.startSN}-${trackDetails.endSN}], track ${trackId}, load position: ${pos.toFixed(3)}-${loadPos.toFixed(3)}`); this.loadFragment(frag); } } diff --git a/src/controller/id3-track-controller.ts b/src/controller/id3-track-controller.ts index f29b26a1f75..f2b37acd54d 100644 --- a/src/controller/id3-track-controller.ts +++ b/src/controller/id3-track-controller.ts @@ -11,6 +11,8 @@ declare global { } } +const MIN_CUE_DURATION = 0.25; + class ID3TrackController implements ComponentAPI { private hls: Hls; private id3Track: TextTrack | null = null; @@ -99,11 +101,10 @@ class ID3TrackController implements ComponentAPI { if (!endTime) { endTime = fragment.start + fragment.duration; } - if (startTime === endTime) { - // Give a slight bump to the endTime if it's equal to startTime to avoid a SyntaxError in IE - endTime += 0.0001; - } else if (startTime > endTime) { - endTime = startTime + 0.25; + + const timeDiff = endTime - startTime; + if (timeDiff < MIN_CUE_DURATION) { + endTime += MIN_CUE_DURATION - timeDiff; } for (let j = 0; j < frames.length; j++) { diff --git a/src/controller/stream-controller.ts b/src/controller/stream-controller.ts index 4ea861c1772..04f2f48311f 100644 --- a/src/controller/stream-controller.ts +++ b/src/controller/stream-controller.ts @@ -264,11 +264,13 @@ export default class StreamController extends BaseStreamController implements Ne // We want to load the key if we're dealing with an identity key, because we will decrypt // this content using the key we fetch. Other keys will be handled by the DRM CDM via EME. if (frag.decryptdata?.keyFormat === 'identity' && !frag.decryptdata?.key) { - this.log(`Loading key for ${frag.sn} of [${levelDetails.startSN} ,${levelDetails.endSN}],level ${level}`); + this.log(`Loading key for ${frag.sn} of [${levelDetails.startSN}-${levelDetails.endSN}], level ${level}`); this._loadKey(frag); } else { if (this.fragCurrent !== frag) { - this.log(`Loading fragment ${frag.sn} of [${levelDetails.startSN} ,${levelDetails.endSN}],level ${level}, currentTime:${pos.toFixed(3)},bufferEnd:${bufferInfo.end.toFixed(3)}`); + this.log(`Loading fragment ${frag.sn} of [${levelDetails.startSN}-${levelDetails.endSN}], level ${level}, ${ + this.loadedmetadata ? 'currentTime' : 'nextLoadPosition' + }: ${parseFloat(pos.toFixed(3))}, bufferInfo.end: ${parseFloat(bufferInfo.end.toFixed(3))}`); } this._loadFragment(frag); } @@ -638,6 +640,7 @@ export default class StreamController extends BaseStreamController implements Ne onAudioTrackSwitching (event: Events.AUDIO_TRACK_SWITCHING, data: AudioTrackSwitchingData) { // if any URL found on new audio track, it is an alternate audio track + const fromAltAudio = this.altAudio; const altAudio = !!data.url; const trackId = data.id; // if we switch on main audio, ensure that main fragment scheduling is synced with media.buffered @@ -664,10 +667,17 @@ export default class StreamController extends BaseStreamController implements Ne this.resetTransmuxer(); } const hls = this.hls; - // switching to main audio, flush all audio and trigger track switched - hls.trigger(Events.BUFFER_FLUSHING, { startOffset: 0, endOffset: Number.POSITIVE_INFINITY, type: 'audio' }); - hls.trigger(Events.AUDIO_TRACK_SWITCHED, { id: trackId }); - this.altAudio = false; + // If switching from alt to main audio, flush all audio and trigger track switched + if (fromAltAudio) { + hls.trigger(Events.BUFFER_FLUSHING, { + startOffset: 0, + endOffset: Number.POSITIVE_INFINITY, + type: 'audio' + }); + } + hls.trigger(Events.AUDIO_TRACK_SWITCHED, { + id: trackId + }); } } diff --git a/src/demux/tsdemuxer.ts b/src/demux/tsdemuxer.ts index 3a7afa7d53d..2c4944d79f0 100644 --- a/src/demux/tsdemuxer.ts +++ b/src/demux/tsdemuxer.ts @@ -1154,22 +1154,14 @@ function parsePES (stream) { (frag[11] & 0xFE) * 16384 +// 1 << 14 (frag[12] & 0xFF) * 128 +// 1 << 7 (frag[13] & 0xFE) / 2; - // check if greater than 2^32 -1 - if (pesPts > 4294967295) { - // decrement 2^33 - pesPts -= 8589934592; - } + if (pesFlags & 0x40) { pesDts = (frag[14] & 0x0E) * 536870912 +// 1 << 29 (frag[15] & 0xFF) * 4194304 +// 1 << 22 (frag[16] & 0xFE) * 16384 +// 1 << 14 (frag[17] & 0xFF) * 128 +// 1 << 7 (frag[18] & 0xFE) / 2; - // check if greater than 2^32 -1 - if (pesDts > 4294967295) { - // decrement 2^33 - pesDts -= 8589934592; - } + if (pesPts - pesDts > 60 * 90000) { logger.warn(`${Math.round((pesPts - pesDts) / 90000)}s delta between PTS and DTS, align them`); pesPts = pesDts; diff --git a/src/remux/mp4-remuxer.ts b/src/remux/mp4-remuxer.ts index d4cdd130d42..5a543074d5d 100644 --- a/src/remux/mp4-remuxer.ts +++ b/src/remux/mp4-remuxer.ts @@ -72,6 +72,25 @@ export default class MP4Remuxer implements Remuxer { this.ISGenerated = false; } + getVideoStartPts (videoSamples) { + let rolloverDetected = false; + const startPTS = videoSamples.reduce((minPTS, sample) => { + const delta = sample.pts - minPTS; + if (delta < -4294967296) { // 2^32, see PTSNormalize for reasoning, but we're hitting a rollover here, and we don't want that to impact the timeOffset calculation + rolloverDetected = true; + return minPTS; + } else if (delta > 0) { + return minPTS; + } else { + return sample.pts; + } + }, videoSamples[0].pts); + if (rolloverDetected) { + logger.debug('PTS rollover detected'); + } + return startPTS; + } + remux (audioTrack: DemuxedAudioTrack, videoTrack: DemuxedAvcTrack, id3Track: DemuxedTrack, textTrack: DemuxedTrack, timeOffset: number, accurateTimeOffset: boolean) : RemuxerResult { let video; let audio; @@ -112,7 +131,7 @@ export default class MP4Remuxer implements Remuxer { // if first audio DTS is not aligned with first video DTS then we need to take that into account // when providing timeOffset to remuxAudio / remuxVideo. if we don't do that, there might be a permanent / small // drift between audio and video streams - const startPTS = videoTrack.samples.reduce((minPTS, sample) => Math.min(minPTS, sample.pts), videoTrack.samples[0].pts); + const startPTS = this.getVideoStartPts(videoTrack.samples); const tsDelta = audioTrack.samples[0].pts - startPTS; const audiovideoTimestampDelta = tsDelta / videoTrack.inputTimeScale; audioTimeOffset += Math.max(0, audiovideoTimestampDelta); @@ -148,11 +167,11 @@ export default class MP4Remuxer implements Remuxer { if (this.ISGenerated) { if (id3Track.samples.length) { - id3 = this.remuxID3(id3Track); + id3 = this.remuxID3(id3Track, timeOffset); } if (textTrack.samples.length) { - text = this.remuxText(textTrack); + text = this.remuxText(textTrack, timeOffset); } } @@ -225,7 +244,7 @@ export default class MP4Remuxer implements Remuxer { } }; if (computePTSDTS) { - const startPTS = videoSamples.reduce((minPTS, sample) => Math.min(minPTS, sample.pts), videoSamples[0].pts); + const startPTS = this.getVideoStartPts(videoSamples); const startOffset = Math.round(inputTimeScale * timeOffset); initDTS = Math.min(initDTS, videoSamples[0].dts - startOffset); initPTS = Math.min(initPTS, startPTS - startOffset); @@ -265,8 +284,7 @@ export default class MP4Remuxer implements Remuxer { // if parsed fragment is contiguous with last one, let's use last DTS value as reference if (!contiguous || nextAvcDts === null) { const pts = timeOffset * timeScale; - // TODO: Handle case where pts value is wrapped, but dts is not - const cts = Math.max(0, inputSamples[0].pts - inputSamples[0].dts); + const cts = inputSamples[0].pts - PTSNormalize(inputSamples[0].dts, inputSamples[0].pts); // if not contiguous, let's use target timeOffset nextAvcDts = pts - cts; } @@ -754,7 +772,7 @@ export default class MP4Remuxer implements Remuxer { return this.remuxAudio(track, timeOffset, contiguous, false); } - remuxID3 (track: DemuxedTrack) : RemuxedMetadata | undefined { + remuxID3 (track: DemuxedTrack, timeOffset: number) : RemuxedMetadata | undefined { const length = track.samples.length; if (!length) { return; @@ -766,8 +784,8 @@ export default class MP4Remuxer implements Remuxer { const sample = track.samples[index]; // setting id3 pts, dts to relative time // using this._initPTS and this._initDTS to calculate relative time - sample.pts = ((sample.pts - initPTS) / inputTimeScale); - sample.dts = ((sample.dts - initDTS) / inputTimeScale); + sample.pts = PTSNormalize(sample.pts - initPTS, timeOffset * inputTimeScale) / inputTimeScale; + sample.dts = PTSNormalize(sample.dts - initDTS, timeOffset * inputTimeScale) / inputTimeScale; } const samples = track.samples; track.samples = []; @@ -776,12 +794,11 @@ export default class MP4Remuxer implements Remuxer { }; } - remuxText (track: DemuxedTrack) : RemuxedUserdata | undefined { + remuxText (track: DemuxedTrack, timeOffset: number) : RemuxedUserdata | undefined { const length = track.samples.length; if (!length) { return; } - track.samples.sort((a, b) => a.pts - b.pts); const inputTimeScale = track.inputTimeScale; const initPTS = this._initPTS; @@ -789,8 +806,9 @@ export default class MP4Remuxer implements Remuxer { const sample = track.samples[index]; // setting text pts, dts to relative time // using this._initPTS and this._initDTS to calculate relative time - sample.pts = ((sample.pts - initPTS) / inputTimeScale); + sample.pts = PTSNormalize(sample.pts - initPTS, timeOffset * inputTimeScale) / inputTimeScale; } + track.samples.sort((a, b) => a.pts - b.pts); const samples = track.samples; track.samples = []; return { diff --git a/tests/functional/auto/setup.js b/tests/functional/auto/setup.js index 31396767f41..c6aef14dea8 100644 --- a/tests/functional/auto/setup.js +++ b/tests/functional/auto/setup.js @@ -22,6 +22,8 @@ const browserConfig = { */ let browser; let stream; +const printDebugLogs = false; + // Setup browser config data from env vars if (onTravis) { const UA = process.env.UA; @@ -118,20 +120,20 @@ async function testSmoothSwitch (url, config) { const callback = arguments[arguments.length - 1]; self.startStream(url, config, callback); const video = self.video; - self.hls.once(self.Hls.Events.FRAG_CHANGED, (event, data) => { + self.hls.once(self.Hls.Events.FRAG_CHANGED, function (eventName, data) { + console.log('[test] > ' + eventName + ' frag.level: ' + data.frag.level); self.switchToHighestLevel('next'); }); - self.hls.on(self.Hls.Events.LEVEL_SWITCHED, (event, data) => { - console.log(`[test] > level switched: ${data.level}`); + self.hls.on(self.Hls.Events.LEVEL_SWITCHED, function (eventName, data) { + console.log('[test] > ' + eventName + ' data.level: ' + data.level); const currentTime = video.currentTime; - if (data.level === self.hls.levels.length - 1) { - console.log(`[test] > switched on level: ${data.level}`); + const highestLevel = (self.hls.levels.length - 1); + if (data.level === highestLevel) { self.setTimeout(function () { const newCurrentTime = video.currentTime; - console.log( - `[test] > currentTime delta : ${newCurrentTime - currentTime}` - ); + console.log('[test] > currentTime delta: ' + (newCurrentTime - currentTime)); callback({ + highestLevel: highestLevel, currentTimeDelta: newCurrentTime - currentTime, logs: self.logString }); @@ -318,15 +320,17 @@ describe(`testing hls.js playback in the browser on "${browserDescription}"`, fu beforeEach(async function () { try { await retry(async () => { - console.log('Loading test page...'); + if (printDebugLogs) { + console.log('Loading test page...'); + } try { await browser.get(`http://${hostname}:8000/tests/functional/auto/index.html`); } catch (e) { throw new Error('failed to open test page'); } - console.log('Test page loaded.'); - - console.log('Locating ID \'hlsjs-functional-tests\''); + if (printDebugLogs) { + console.log('Test page loaded.'); + } try { await browser.wait( until.elementLocated(By.css('body#hlsjs-functional-tests')), @@ -338,7 +342,9 @@ describe(`testing hls.js playback in the browser on "${browserDescription}"`, fu console.log(source); throw e; } - console.log('Located the ID, page confirmed loaded'); + if (printDebugLogs) { + console.log('Test harness found, page confirmed loaded'); + } }); } catch (e) { throw new Error(`error getting test page loaded: ${e}`); @@ -346,10 +352,10 @@ describe(`testing hls.js playback in the browser on "${browserDescription}"`, fu }); afterEach(async function () { - // if (onTravis || (!onTravis && this.currentTest.isFailed())) { - const logString = await browser.executeScript('return logString'); - console.log(`${onTravis ? 'travis_fold:start:debug_logs' : ''}\n${logString}\n${onTravis ? 'travis_fold:end:debug_logs' : ''}`); - // } + if (printDebugLogs || this.currentTest.isFailed()) { + const logString = await browser.executeScript('return logString'); + console.log(`${onTravis ? 'travis_fold:start:debug_logs' : ''}\n${logString}\n${onTravis ? 'travis_fold:end:debug_logs' : ''}`); + } }); after(async function () { diff --git a/tests/test-streams.js b/tests/test-streams.js index c58c6b1babd..1351ccacb68 100644 --- a/tests/test-streams.js +++ b/tests/test-streams.js @@ -35,115 +35,89 @@ function createTestStreamWithConfig (target, config) { } module.exports = { - bbb: createTestStreamWithConfig({ + bbb: { url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', - description: 'Big Buck Bunny - adaptive qualities' + description: 'Big Buck Bunny - adaptive qualities', + abr: true }, - { - // try to workaround test failing because of slow seek on Chrome/Win10 - nudgeMaxRetry: 5 - } - ), fdr: { url: 'https://cdn.jwplayer.com/manifests/pZxWPRg4.m3u8', description: 'FDR - CDN packaged, 4s segments, 180p - 1080p', - live: false, abr: true }, bigBuckBunny480p: { url: 'https://test-streams.mux.dev/x36xhzz/url_6/193039199_mp4_h264_aac_hq_7.m3u8', description: 'Big Buck Bunny - 480p only', - live: false, abr: false, blacklist_ua: ['internet explorer'] }, arte: { url: 'https://test-streams.mux.dev/test_001/stream.m3u8', description: 'ARTE China,ABR', - live: false, abr: true }, deltatreDAI: { url: 'https://test-streams.mux.dev/dai-discontinuity-deltatre/manifest.m3u8', description: 'Ad-insertion in event stream', - live: false, abr: false, blacklist_ua: ['internet explorer'] }, issue666: { url: 'https://playertest.longtailvideo.com/adaptive/issue666/playlists/cisq0gim60007xzvi505emlxx.m3u8', description: 'Surveillance footage - https://github.com/video-dev/hls.js/issues/666', - live: false, abr: false, blacklist_ua: ['internet explorer'] }, - /* // went offline for us :( would be good to replace this for regression test with something mimicking the issue - issue649: { - 'url': 'https://cdn3.screen9.com/media/c/W/cW87csHkxsgu5TV1qs78aA_auto_hls.m3u8?auth=qlUjeCtbVdtkDfZYrtveTIVUXX1yuSqgF8wfWabzKpX72r-d5upW88-FHuyRRdnZA_1PKRTGAtTt_6Z-aj22kw', - 'description': 'hls.js/issues/649', - 'live': false, - 'abr': false - }, - */ closedCaptions: { url: 'https://playertest.longtailvideo.com/adaptive/captions/playlist.m3u8', description: 'CNN special report, with CC', - live: false, abr: false }, customIvBadDts: { url: 'https://playertest.longtailvideo.com/adaptive/customIV/prog_index.m3u8', description: 'Custom IV with bad PTS DTS', - live: false, abr: false }, oceansAES: { url: 'https://playertest.longtailvideo.com/adaptive/oceans_aes/oceans_aes.m3u8', description: 'AES encrypted,ABR', - live: false, abr: true }, /* bbbAES: { - 'url': 'https://test-streams.mux.dev/bbbAES/playlists/sample_aes/index.m3u8', - 'description': 'SAMPLE-AES encrypted', - 'live': false, - 'abr': false + url: 'https://test-streams.mux.dev/bbbAES/playlists/sample_aes/index.m3u8', + description: 'SAMPLE-AES encrypted', + live: false, + abr: false }, */ mp3Audio: { url: 'https://playertest.longtailvideo.com/adaptive/vod-with-mp3/manifest.m3u8', description: 'MP3 VOD demo', - live: false, - abr: false, - blacklist_ua: ['safari'] + abr: false }, mpegAudioOnly: { url: 'https://pl.streamingvideoprovider.com/mp3-playlist/playlist.m3u8', description: 'MPEG Audio Only demo', - live: false, abr: false, - blacklist_ua: ['internet explorer', 'MicrosoftEdge', 'safari', 'firefox'] + blacklist_ua: ['internet explorer', 'MicrosoftEdge', 'firefox'] }, fmp4: { url: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8', description: 'HLS fMP4 Angel-One multiple audio-tracks', - live: false, - abr: false, - blacklist_ua: ['safari', 'internet explorer'] + abr: true, + blacklist_ua: ['internet explorer'] }, fmp4Bitmovin: { url: 'https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s-fmp4/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8', description: 'HLS fMP4 by Bitmovin', - live: false, abr: true, - blacklist_ua: ['safari', 'internet explorer'] + blacklist_ua: ['internet explorer'] }, offset_pts: { url: 'https://test-streams.mux.dev/pts_shift/master.m3u8', description: 'DK Turntable, PTS shifted by 2.3s', - live: false, - abr: false + abr: true }, /* uspHLSAteam: createTestStream( @@ -154,64 +128,73 @@ module.exports = { angelOneShakaWidevine: createTestStreamWithConfig({ url: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine-hls/hls.m3u8', description: 'Shaka-packager Widevine DRM (EME) HLS-fMP4 - Angel One Demo', + abr: true, blacklist_ua: ['firefox', 'safari', 'internet explorer'] - }, - { + }, { widevineLicenseUrl: 'http://cwip-shaka-proxy.appspot.com/no_auth', emeEnabled: true - } - ), + }), audioOnlyMultipleLevels: { url: 'https://s3.amazonaws.com/qa.jwplayer.com/~alex/121628/new_master.m3u8', description: 'Multiple non-alternate audio levels', - live: false, - abr: false + abr: true }, pdtDuplicate: { url: 'https://playertest.longtailvideo.com/adaptive/artbeats/manifest.m3u8', - description: 'Stream with duplicate sequential PDT values' + description: 'Stream with duplicate sequential PDT values', + abr: false }, pdtLargeGap: { url: 'https://playertest.longtailvideo.com/adaptive/boxee/playlist.m3u8', - description: 'PDTs with large gaps following discontinuities' + description: 'PDTs with large gaps following discontinuities', + abr: false }, pdtBadValues: { url: 'https://playertest.longtailvideo.com/adaptive/progdatime/playlist2.m3u8', - description: 'PDTs with bad values' + description: 'PDTs with bad values', + abr: false }, pdtOneValue: { url: 'https://playertest.longtailvideo.com/adaptive/aviion/manifest.m3u8', - description: 'One PDT, no discontinuities' + description: 'One PDT, no discontinuities', + abr: false }, - noTrackIntersection: { + noTrackIntersection: createTestStreamWithConfig({ url: 'https://s3.amazonaws.com/qa.jwplayer.com/~alex/123633/new_master.m3u8', description: 'Audio/video track PTS values do not intersect; 10 second start gap', + abr: false + }, { avBufferOffset: 10.5 - }, + }), // altAudioNoVideoCodecSignaled: { // url: 'https://d35u71x3nb8v2y.cloudfront.net/4b711b97-513c-4d36-ad29-298ab23a2e5e/3cbf1114-b2f4-4320-afb3-f0f7eeeb8630/playlist.m3u8', // description: 'Alternate audio track, but no video codec is signaled in the master manifest' // }, altAudioAndTracks: { url: 'https://wowzaec2demo.streamlock.net/vod-multitrack/_definst_/smil:ElephantsDream/elephantsdream2.smil/playlist.m3u', - description: 'Alternate audio tracks, and multiple VTT tracks' + description: 'Alternate audio tracks, and multiple VTT tracks', + abr: true }, altAudioAudioOnly: { url: 'https://playertest.longtailvideo.com/adaptive/alt-audio-no-video/sintel/playlist.m3u8', - description: 'Audio only with alternate audio track (Sintel)' + description: 'Audio only with alternate audio track (Sintel)', + abr: false }, altAudioMultiAudioOnly: { url: 'https://playertest.longtailvideo.com/adaptive/alt-audio-no-video/angel-one.m3u8', - description: 'Audio only with multiple alternate audio tracks (Angel One)' + description: 'Audio only with multiple alternate audio tracks (Angel One)', + abr: false }, muxedFmp4: { url: 'https://s3.amazonaws.com/qa.jwplayer.com/hlsjs/muxed-fmp4/hls.m3u8', - description: 'Muxed av fmp4 - appended to "audiovideo" SourceBuffer' + description: 'Muxed av fmp4 - appended to "audiovideo" SourceBuffer', + abr: false }, altAudioWithPdtAndStartGap: { url: 'https://playertest.longtailvideo.com/adaptive/hls-test-streams/test-audio-pdt/playlist.m3u8', description: 'PDT before each segment, 1.59s start gap', - abr: true, + // Disable smooth switch on this stream. Test is flakey because of what looks like (auto)play issue. To be expected with this large a gap (for now). + // abr: true, startSeek: true } };