Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multiple EXT-X-MAP tags #2279

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 7 additions & 8 deletions src/controller/audio-stream-controller.js
Expand Up @@ -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
Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down
9 changes: 6 additions & 3 deletions src/controller/level-helper.js
Expand Up @@ -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
Expand Down
43 changes: 21 additions & 22 deletions src/controller/stream-controller.js
Expand Up @@ -230,34 +230,33 @@ 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) {
frag = this._findFragment(start, fragPrevious, fragLen, fragments, bufferEnd, end, levelDetails);
}

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);
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/loader/fragment.ts
Expand Up @@ -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);
Expand Down
10 changes: 10 additions & 0 deletions 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;
}
}
2 changes: 1 addition & 1 deletion src/loader/level.js
Expand Up @@ -4,7 +4,7 @@ export default class Level {
this.endCC = 0;
this.endSN = 0;
this.fragments = [];
this.initSegment = null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little concerned about this being a breaking API change. I don't know who would use hlsjs.levels[n].initSegment as an integral part of there app, but it's possible.

We could keep this property and just have it reflect the active initSegment used to buffer media (details.initSegments[data.frag.relurl]).

this.initSegments = {};
this.live = true;
this.needSidxRanges = false;
this.startCC = 0;
Expand Down
8 changes: 8 additions & 0 deletions 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';

Expand Down Expand Up @@ -156,6 +157,7 @@ export default class M3U8Parser {
let frag = new Fragment();
let result;
let i;
let initSegment = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to use the following to resolve a TypeScript error (I branched off v0.14.8):

let initSegment: InitSegment | null = null;


let firstPdtIndex = null;

Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down