From 835c20f7887eca428b5a47862f0448962824812c Mon Sep 17 00:00:00 2001 From: Greg Kolb Date: Wed, 19 Jun 2019 10:48:51 -0600 Subject: [PATCH 1/2] Add support for multiple EXT-X-MAP tags --- src/controller/audio-stream-controller.js | 15 ++++---- src/controller/level-helper.js | 9 +++-- src/controller/stream-controller.js | 43 +++++++++++------------ src/loader/init-segment.ts | 10 ++++++ src/loader/level.js | 1 + src/loader/m3u8-parser.js | 8 +++++ 6 files changed, 53 insertions(+), 33 deletions(-) create mode 100644 src/loader/init-segment.ts diff --git a/src/controller/audio-stream-controller.js b/src/controller/audio-stream-controller.js index b12e53cc0ae..f0e4fe1837b 100644 --- a/src/controller/audio-stream-controller.js +++ b/src/controller/audio-stream-controller.js @@ -194,11 +194,7 @@ class AudioStreamController extends BaseStreamController { } } } - if (trackDetails.initSegment && !trackDetails.initSegment.data) { - frag = trackDetails.initSegment; - } // eslint-disable-line brace-style - // if bufferEnd before start of playlist, load first fragment - else if (bufferEnd <= start) { + if (bufferEnd <= start) { frag = fragments[0]; if (this.videoTrackCC !== null && frag.cc !== this.videoTrackCC) { // Ensure we find a fragment which matches the continuity of the video track @@ -281,6 +277,9 @@ class AudioStreamController extends BaseStreamController { logger.log(`Loading ${frag.sn}, cc: ${frag.cc} of [${trackDetails.startSN} ,${trackDetails.endSN}],track ${trackId}, currentTime:${pos},bufferEnd:${bufferEnd.toFixed(3)}`); // only load if fragment is not loaded or if in audio switch // we force a frag loading in audio switch as fragment tracker might not have evicted previous frags in case of quick audio switch + if (trackDetails.initSegments[frag.initSegment] && !trackDetails.initSegments[frag.initSegment].data) { + frag = trackDetails.initSegments[frag.initSegment].fragment; + } this.fragCurrent = frag; if (audioSwitch || this.fragmentTracker.getState(frag) === FragmentState.NOT_LOADED) { if (frag.sn !== 'initSegment') { @@ -504,7 +503,7 @@ class AudioStreamController extends BaseStreamController { this.state = State.IDLE; stats.tparsed = stats.tbuffered = performance.now(); - details.initSegment.data = data.payload; + details.initSegments[data.frag.relurl].data = data.payload; this.hls.trigger(Event.FRAG_BUFFERED, { stats: stats, frag: fragCurrent, id: 'audio' }); this.tick(); } else { @@ -518,8 +517,8 @@ class AudioStreamController extends BaseStreamController { // Check if we have video initPTS // If not we need to wait for it let initPTS = this.initPTS[cc]; - let initSegmentData = details.initSegment ? details.initSegment.data : []; - if (details.initSegment || initPTS !== undefined) { + const initSegmentData = details.initSegments[fragCurrent.initSegment] ? details.initSegments[fragCurrent.initSegment].data : []; + if (details.initSegments[fragCurrent.initSegment] || initPTS !== undefined) { this.pendingBuffering = true; logger.log(`Demuxing ${sn} of [${details.startSN} ,${details.endSN}],track ${trackId}`); // time Offset is accurate if level PTS is known, or if playlist is not sliding (not live) diff --git a/src/controller/level-helper.js b/src/controller/level-helper.js index 23f59c35257..b1bbd7d4f1e 100644 --- a/src/controller/level-helper.js +++ b/src/controller/level-helper.js @@ -110,9 +110,12 @@ export function updateFragPTSDTS (details, frag, startPTS, endPTS, startDTS, end } export function mergeDetails (oldDetails, newDetails) { - // potentially retrieve cached initsegment - if (newDetails.initSegment && oldDetails.initSegment) { - newDetails.initSegment = oldDetails.initSegment; + // potentially retrieve cached initsegments + if (newDetails.initSegments && oldDetails.initSegments) { + newDetails.initSegments = { + ...newDetails.initSegments, + ...oldDetails.initSegments + }; } // check if old/new playlists have fragments in common diff --git a/src/controller/stream-controller.js b/src/controller/stream-controller.js index 092a55bcc77..876d78f294f 100644 --- a/src/controller/stream-controller.js +++ b/src/controller/stream-controller.js @@ -230,27 +230,23 @@ class StreamController extends BaseStreamController { bufferEnd = bufferInfo.end, frag; - if (levelDetails.initSegment && !levelDetails.initSegment.data) { - frag = levelDetails.initSegment; - } else { - // in case of live playlist we need to ensure that requested position is not located before playlist start - if (levelDetails.live) { - let initialLiveManifestSize = this.config.initialLiveManifestSize; - if (fragLen < initialLiveManifestSize) { - logger.warn(`Can not start playback of a level, reason: not enough fragments ${fragLen} < ${initialLiveManifestSize}`); - return; - } + // in case of live playlist we need to ensure that requested position is not located before playlist start + if (levelDetails.live) { + let initialLiveManifestSize = this.config.initialLiveManifestSize; + if (fragLen < initialLiveManifestSize) { + logger.warn(`Can not start playback of a level, reason: not enough fragments ${fragLen} < ${initialLiveManifestSize}`); + return; + } - frag = this._ensureFragmentAtLivePoint(levelDetails, bufferEnd, start, end, fragPrevious, fragments, fragLen); - // if it explicitely returns null don't load any fragment and exit function now - if (frag === null) { - return; - } - } else { - // VoD playlist: if bufferEnd before start of playlist, load first fragment - if (bufferEnd < start) { - frag = fragments[0]; - } + frag = this._ensureFragmentAtLivePoint(levelDetails, bufferEnd, start, end, fragPrevious, fragments, fragLen); + // if it explicitely returns null don't load any fragment and exit function now + if (frag === null) { + return; + } + } else { + // VoD playlist: if bufferEnd before start of playlist, load first fragment + if (bufferEnd < start) { + frag = fragments[0]; } } if (!frag) { @@ -258,6 +254,9 @@ class StreamController extends BaseStreamController { } if (frag) { + if (levelDetails.initSegments[frag.initSegment] && !levelDetails.initSegments[frag.initSegment].data) { + frag = levelDetails.initSegments[frag.initSegment].fragment; + } if (frag.encrypted) { logger.log(`Loading key for ${frag.sn} of [${levelDetails.startSN} ,${levelDetails.endSN}],level ${level}`); this._loadKey(frag); @@ -848,7 +847,7 @@ class StreamController extends BaseStreamController { } else if (fragLoaded.sn === 'initSegment') { this.state = State.IDLE; stats.tparsed = stats.tbuffered = window.performance.now(); - details.initSegment.data = data.payload; + details.initSegments[data.frag.relurl].data = data.payload; hls.trigger(Event.FRAG_BUFFERED, { stats: stats, frag: fragCurrent, id: 'main' }); this.tick(); } else { @@ -868,7 +867,7 @@ class StreamController extends BaseStreamController { // time Offset is accurate if level PTS is known, or if playlist is not sliding (not live) and if media is not seeking (this is to overcome potential timestamp drifts between playlists and fragments) const accurateTimeOffset = !(media && media.seeking) && (details.PTSKnown || !details.live); - const initSegmentData = details.initSegment ? details.initSegment.data : []; + const initSegmentData = details.initSegments[fragCurrent.initSegment] ? details.initSegments[fragCurrent.initSegment].data : []; const audioCodec = this._getAudioCodec(currentLevel); // transmux the MPEG-TS data to ISO-BMFF segments diff --git a/src/loader/init-segment.ts b/src/loader/init-segment.ts new file mode 100644 index 00000000000..ea575cb4023 --- /dev/null +++ b/src/loader/init-segment.ts @@ -0,0 +1,10 @@ +import Fragment from './fragment'; + +export default class InitSegment { + public fragment: Fragment; + public data: ArrayBuffer | null = null; + + constructor (fragment: Fragment) { + this.fragment = fragment; + } +} diff --git a/src/loader/level.js b/src/loader/level.js index f0af8a9a1b3..84cc27fac4c 100644 --- a/src/loader/level.js +++ b/src/loader/level.js @@ -5,6 +5,7 @@ export default class Level { this.endSN = 0; this.fragments = []; this.initSegment = null; + this.initSegments = {}; this.live = true; this.needSidxRanges = false; this.startCC = 0; diff --git a/src/loader/m3u8-parser.js b/src/loader/m3u8-parser.js index efbbe59935d..9804eac69e2 100644 --- a/src/loader/m3u8-parser.js +++ b/src/loader/m3u8-parser.js @@ -1,6 +1,7 @@ import * as URLToolkit from 'url-toolkit'; import Fragment from './fragment'; +import InitSegment from './init-segment'; import Level from './level'; import LevelKey from './level-key'; @@ -156,6 +157,7 @@ export default class M3U8Parser { let frag = new Fragment(); let result; let i; + let initSegment = null; let firstPdtIndex = null; @@ -180,6 +182,9 @@ export default class M3U8Parser { frag.cc = cc; frag.urlId = levelUrlId; frag.baseurl = baseurl; + if (initSegment) { + frag.initSegment = initSegment.fragment.relurl; + } // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 frag.relurl = (' ' + result[3]).slice(1); assignProgramDateTime(frag, prevFrag); @@ -286,6 +291,9 @@ export default class M3U8Parser { frag.type = type; frag.sn = 'initSegment'; level.initSegment = frag; + frag.cc = cc; + initSegment = new InitSegment(frag); + level.initSegments[frag.relurl] = initSegment; frag = new Fragment(); frag.rawProgramDateTime = level.initSegment.rawProgramDateTime; break; From 1434574d4884b484c83489fe4c0f7da47f411b16 Mon Sep 17 00:00:00 2001 From: Greg Kolb Date: Wed, 19 Jun 2019 12:22:06 -0600 Subject: [PATCH 2/2] removed reference to old initsegment --- src/loader/fragment.ts | 4 ++++ src/loader/level.js | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/loader/fragment.ts b/src/loader/fragment.ts index f32dc2b09b5..87affd97cd9 100644 --- a/src/loader/fragment.ts +++ b/src/loader/fragment.ts @@ -42,6 +42,10 @@ export default class Fragment { // _decryptdata will set the IV for this segment based on the segment number in the fragment public levelkey?: LevelKey; + // initSegment is the relurl of the initsegment associated with the fragment which can be found + // on level details + public initSegment?: string; + // setByteRange converts a EXT-X-BYTERANGE attribute into a two element array setByteRange (value: string, previousFrag?: Fragment) { const params = value.split('@', 2); diff --git a/src/loader/level.js b/src/loader/level.js index 84cc27fac4c..3328a374f94 100644 --- a/src/loader/level.js +++ b/src/loader/level.js @@ -4,7 +4,6 @@ export default class Level { this.endCC = 0; this.endSN = 0; this.fragments = []; - this.initSegment = null; this.initSegments = {}; this.live = true; this.needSidxRanges = false;