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 8f126607f5d..0e469d666b8 100644 --- a/demo/main.js +++ b/demo/main.js @@ -58,10 +58,13 @@ $(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 () { diff --git a/src/controller/audio-stream-controller.js b/src/controller/audio-stream-controller.js index 177e3aed4f0..524137783d6 100644 --- a/src/controller/audio-stream-controller.js +++ b/src/controller/audio-stream-controller.js @@ -58,7 +58,7 @@ class AudioStreamController extends BaseStreamController { // Can change due level switch this.initPTS[cc] = initPTS; this.videoTrackCC = cc; - logger.log(`InitPTS for cc: ${cc} found from video track: ${initPTS}`); + logger.log(`InitPTS for cc: ${cc} found from main: ${initPTS}`); // If we are waiting we need to demux/remux the waiting frag // With the new initPTS @@ -264,7 +264,9 @@ class AudioStreamController extends BaseStreamController { // we force a frag loading in audio switch as fragment tracker might not have evicted previous frags in case of quick audio switch this.fragCurrent = frag; if (audioSwitch || this.fragmentTracker.getState(frag) === FragmentState.NOT_LOADED) { - logger.log(`Loading ${frag.sn}, cc: ${frag.cc} of [${trackDetails.startSN} ,${trackDetails.endSN}],track ${trackId}, currentTime:${pos},bufferEnd:${bufferEnd.toFixed(3)}`); + logger.log(`Loading ${frag.sn}, cc: ${frag.cc} of [${trackDetails.startSN} ,${trackDetails.endSN}],track ${trackId}, ${ + this.loadedmetadata ? 'currentTime' : 'nextLoadPosition' + }: ${pos}, bufferEnd: ${bufferEnd.toFixed(3)}`); if (frag.sn !== 'initSegment') { this.startFragRequested = true; @@ -593,9 +595,10 @@ class AudioStreamController extends BaseStreamController { logger.log(`parsed ${data.type},PTS:[${data.startPTS.toFixed(3)},${data.endPTS.toFixed(3)}],DTS:[${data.startDTS.toFixed(3)}/${data.endDTS.toFixed(3)}],nb:${data.nb}`); LevelHelper.updateFragPTSDTS(track.details, fragCurrent, data.startPTS, data.endPTS); - let audioSwitch = this.audioSwitch, media = this.media, appendOnBufferFlush = false; + const media = this.media; + let appendOnBufferFlush = false; // Only flush audio from old audio tracks when PTS is known on new audio track - if (audioSwitch) { + if (this.audioSwitch) { if (media && media.readyState) { let currentTime = media.currentTime; logger.log('switching audio track : currentTime:' + currentTime); diff --git a/src/controller/stream-controller.js b/src/controller/stream-controller.js index 82f621d42f2..7755122a266 100644 --- a/src/controller/stream-controller.js +++ b/src/controller/stream-controller.js @@ -426,7 +426,7 @@ class StreamController extends BaseStreamController { } _loadKey (frag, levelDetails) { - logger.log(`Loading key for ${frag.sn} of [${levelDetails.startSN} ,${levelDetails.endSN}],level ${this.level}`); + logger.log(`Loading key for ${frag.sn} of [${levelDetails.startSN}-${levelDetails.endSN}], level ${this.level}`); this.state = State.KEY_LOADING; this.hls.trigger(Event.KEY_LOADING, { frag }); } @@ -449,7 +449,9 @@ class StreamController extends BaseStreamController { frag.autoLevel = this.hls.autoLevelEnabled; frag.bitrateTest = this.bitrateTest; - logger.log(`Loading ${frag.sn} of [${levelDetails.startSN} ,${levelDetails.endSN}],level ${this.level}, currentTime:${pos.toFixed(3)},bufferEnd:${bufferEnd.toFixed(3)}`); + logger.log(`Loading ${frag.sn} of [${levelDetails.startSN}-${levelDetails.endSN}], level ${this.level}, ${ + this.loadedmetadata ? 'currentTime' : 'nextLoadPosition' + }: ${parseFloat(pos.toFixed(3))}, bufferEnd: ${parseFloat(bufferEnd.toFixed(3))}`); this.hls.trigger(Event.FRAG_LOADING, { frag }); // lazy demuxer init, as this could take some time ... do it during frag loading @@ -1085,8 +1087,9 @@ class StreamController extends BaseStreamController { onAudioTrackSwitching (data) { // if any URL found on new audio track, it is an alternate audio track - let altAudio = !!data.url, - trackId = data.id; + 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 // don't do anything if we switch to alt audio: audio stream controller is handling it. // we will just have to change buffer scheduling on audioTrackSwitched @@ -1111,10 +1114,17 @@ class StreamController extends BaseStreamController { this.state = State.IDLE; } let hls = this.hls; - // switching to main audio, flush all audio and trigger track switched - hls.trigger(Event.BUFFER_FLUSHING, { startOffset: 0, endOffset: Number.POSITIVE_INFINITY, type: 'audio' }); - hls.trigger(Event.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(Event.BUFFER_FLUSHING, { + startOffset: 0, + endOffset: Number.POSITIVE_INFINITY, + type: 'audio' + }); + } + hls.trigger(Event.AUDIO_TRACK_SWITCHED, { + id: trackId + }); } } diff --git a/tests/functional/auto/setup.js b/tests/functional/auto/setup.js index c8454be3c26..e173e67bf73 100644 --- a/tests/functional/auto/setup.js +++ b/tests/functional/auto/setup.js @@ -123,10 +123,10 @@ async function testSmoothSwitch (url, config) { const callback = arguments[arguments.length - 1]; window.startStream(url, config, callback); const video = window.video; - window.hls.once(window.Hls.Events.FRAG_CHANGED, (event, data) => { + window.hls.once(window.Hls.Events.FRAG_CHANGED, function (event, data) { window.switchToHighestLevel('next'); }); - window.hls.on(window.Hls.Events.LEVEL_SWITCHED, (event, data) => { + window.hls.on(window.Hls.Events.LEVEL_SWITCHED, function (event, data) { console.log('[test] > level switched: ' + data.level); let currentTime = video.currentTime; if (data.level === window.hls.levels.length - 1) { diff --git a/tests/test-streams.js b/tests/test-streams.js index 455f27af723..ef989f8b579 100644 --- a/tests/test-streams.js +++ b/tests/test-streams.js @@ -35,116 +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'] + url: 'https://test-streams.mux.dev/x36xhzz/url_6/193039199_mp4_h264_aac_hq_7.m3u8', + description: 'Big Buck Bunny - 480p only', + 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 + url: 'https://test-streams.mux.dev/test_001/stream.m3u8', + description: 'ARTE China,ABR', + 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'] + url: 'https://test-streams.mux.dev/dai-discontinuity-deltatre/manifest.m3u8', + description: 'Ad-insertion in event stream', + 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 + url: 'https://playertest.longtailvideo.com/adaptive/issue666/playlists/cisq0gim60007xzvi505emlxx.m3u8', + description: 'Surveillance footage - https://github.com/video-dev/hls.js/issues/666', + abr: false, + blacklist_ua: ['internet explorer'] }, - */ closedCaptions: { - 'url': 'https://playertest.longtailvideo.com/adaptive/captions/playlist.m3u8', - 'description': 'CNN special report, with CC', - 'live': false, - 'abr': false + url: 'https://playertest.longtailvideo.com/adaptive/captions/playlist.m3u8', + description: 'CNN special report, with CC', + abr: false }, customIvBadDts: { - 'url': 'https://playertest.longtailvideo.com/adaptive/customIV/prog_index.m3u8', - 'description': 'Custom IV with bad PTS DTS', - 'live': false, - 'abr': false, - 'blacklist_ua': ['safari'] + url: 'https://playertest.longtailvideo.com/adaptive/customIV/prog_index.m3u8', + description: 'Custom IV with bad PTS DTS', + abr: false }, oceansAES: { - 'url': 'https://playertest.longtailvideo.com/adaptive/oceans_aes/oceans_aes.m3u8', - 'description': 'AES encrypted,ABR', - 'live': false, - 'abr': true + url: 'https://playertest.longtailvideo.com/adaptive/oceans_aes/oceans_aes.m3u8', + description: 'AES encrypted,ABR', + 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'] + url: 'https://playertest.longtailvideo.com/adaptive/vod-with-mp3/manifest.m3u8', + description: 'MP3 VOD demo', + 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'] + url: 'https://pl.streamingvideoprovider.com/mp3-playlist/playlist.m3u8', + description: 'MPEG Audio Only demo', + abr: false, + 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'] + url: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8', + description: 'HLS fMP4 Angel-One multiple audio-tracks', + 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'] + url: 'https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s-fmp4/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8', + description: 'HLS fMP4 by Bitmovin', + abr: true, + 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 + url: 'https://test-streams.mux.dev/pts_shift/master.m3u8', + description: 'DK Turntable, PTS shifted by 2.3s', + abr: true }, /* uspHLSAteam: createTestStream( @@ -155,59 +128,67 @@ 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 + url: 'https://s3.amazonaws.com/qa.jwplayer.com/~alex/121628/new_master.m3u8', + description: 'Multiple non-alternate audio levels', + 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',