Skip to content

Commit

Permalink
Resolve partial frag loop loading (video-dev#1692)
Browse files Browse the repository at this point in the history
- Load partial frags which have already been loaded once before
- Set currentFrag before arming abr check timer
- Implement `frag.encrypted` getter
- Add unit tests
  • Loading branch information
johnBartos authored May 7, 2018
1 parent a27ed40 commit 6ab1013
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 114 deletions.
8 changes: 6 additions & 2 deletions src/controller/abr-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class AbrController extends EventHandler {
let frag = data.frag;
if (frag.type === 'main') {
if (!this.timer) {
this.fragCurrent = frag;
this.timer = setInterval(this.onCheck, 100);
}

Expand All @@ -55,7 +56,6 @@ class AbrController extends EventHandler {
}
this._bwEstimator = new EwmaBandWidthEstimator(hls, ewmaSlow, ewmaFast, config.abrEwmaDefaultEstimate);
}
this.fragCurrent = frag;
}
}

Expand All @@ -65,7 +65,11 @@ class AbrController extends EventHandler {
we compute expected time of arrival of the complete fragment.
we compare it to expected time of buffer starvation
*/
let hls = this.hls, v = hls.media, frag = this.fragCurrent, loader = frag.loader, minAutoLevel = hls.minAutoLevel;
const hls = this.hls;
const v = hls.media;
const frag = this.fragCurrent;
const minAutoLevel = hls.minAutoLevel;
const loader = frag.loader;

// if loader has been destroyed or loading has been aborted, stop timer and return
if (!loader || (loader.stats && loader.stats.aborted)) {
Expand Down
2 changes: 1 addition & 1 deletion src/controller/audio-stream-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ class AudioStreamController extends TaskLoop {
}
if (frag) {
// logger.log(' loading frag ' + i +',pos/bufEnd:' + pos.toFixed(3) + '/' + bufferEnd.toFixed(3));
if (frag.decryptdata && (frag.decryptdata.uri != null) && (frag.decryptdata.key == null)) {
if (frag.encrypted) {
logger.log(`Loading key for ${frag.sn} of [${trackDetails.startSN} ,${trackDetails.endSN}],track ${trackId}`);
this.state = State.KEY_LOADING;
hls.trigger(Event.KEY_LOADING, { frag: frag });
Expand Down
75 changes: 39 additions & 36 deletions src/controller/stream-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,13 @@ class StreamController extends TaskLoop {
}

if (frag) {
this._loadFragmentOrKey(frag, level, levelDetails, pos, bufferEnd);
if (frag.encrypted) {
logger.log(`Loading key for ${frag.sn} of [${levelDetails.startSN} ,${levelDetails.endSN}],level ${level}`);
this._loadKey(frag);
} else {
logger.log(`Loading ${frag.sn} of [${levelDetails.startSN} ,${levelDetails.endSN}],level ${level}, currentTime:${pos.toFixed(3)},bufferEnd:${bufferEnd.toFixed(3)}`);
this._loadFragment(frag);
}
}
}

Expand Down Expand Up @@ -523,41 +529,38 @@ class StreamController extends TaskLoop {
return frag;
}

_loadFragmentOrKey (frag, level, levelDetails, pos, bufferEnd) {
// logger.log('loading frag ' + i +',pos/bufEnd:' + pos.toFixed(3) + '/' + bufferEnd.toFixed(3));
if ((frag.decryptdata && frag.decryptdata.uri != null) && (frag.decryptdata.key == null)) {
logger.log(`Loading key for ${frag.sn} of [${levelDetails.startSN} ,${levelDetails.endSN}],level ${level}`);
this.state = State.KEY_LOADING;
this.hls.trigger(Event.KEY_LOADING, { frag });
} else {
logger.log(`Loading ${frag.sn} of [${levelDetails.startSN} ,${levelDetails.endSN}],level ${level}, currentTime:${pos.toFixed(3)},bufferEnd:${bufferEnd.toFixed(3)}`);
// Check if fragment is not loaded
let fragState = this.fragmentTracker.getState(frag);

this.fragCurrent = frag;
this.startFragRequested = true;
// Don't update nextLoadPosition for fragments which are not buffered
if (!isNaN(frag.sn) && !frag.bitrateTest) {
this.nextLoadPosition = frag.start + frag.duration;
}
_loadKey (frag) {
this.state = State.KEY_LOADING;
this.hls.trigger(Event.KEY_LOADING, { frag });
}

// Allow backtracked fragments to load
if (frag.backtracked || fragState === FragmentState.NOT_LOADED) {
frag.autoLevel = this.hls.autoLevelEnabled;
frag.bitrateTest = this.bitrateTest;
_loadFragment (frag) {
// Check if fragment is not loaded
let fragState = this.fragmentTracker.getState(frag);

this.hls.trigger(Event.FRAG_LOADING, { frag });
// lazy demuxer init, as this could take some time ... do it during frag loading
if (!this.demuxer) {
this.demuxer = new Demuxer(this.hls, 'main');
}
this.fragCurrent = frag;
this.startFragRequested = true;
// Don't update nextLoadPosition for fragments which are not buffered
if (!isNaN(frag.sn) && !frag.bitrateTest) {
this.nextLoadPosition = frag.start + frag.duration;
}

this.state = State.FRAG_LOADING;
} else if (fragState === FragmentState.APPENDING) {
// Lower the buffer size and try again
if (this._reduceMaxBufferLength(frag.duration)) {
this.fragmentTracker.removeFragment(frag);
}
// Allow backtracked fragments to load
if (frag.backtracked || fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) {
frag.autoLevel = this.hls.autoLevelEnabled;
frag.bitrateTest = this.bitrateTest;

this.hls.trigger(Event.FRAG_LOADING, { frag });
// lazy demuxer init, as this could take some time ... do it during frag loading
if (!this.demuxer) {
this.demuxer = new Demuxer(this.hls, 'main');
}

this.state = State.FRAG_LOADING;
} else if (fragState === FragmentState.APPENDING) {
// Lower the buffer size and try again
if (this._reduceMaxBufferLength(frag.duration)) {
this.fragmentTracker.removeFragment(frag);
}
}
}
Expand Down Expand Up @@ -1167,9 +1170,9 @@ class StreamController extends TaskLoop {
if (!frag.backtracked) {
const levelDetails = level.details;
if (levelDetails && frag.sn === levelDetails.startSN) {
logger.warn('missing video frame(s) on first frag, appending with gap');
logger.warn('missing video frame(s) on first frag, appending with gap', frag.sn);
} else {
logger.warn('missing video frame(s), backtracking fragment');
logger.warn('missing video frame(s), backtracking fragment', frag.sn);
// Return back to the IDLE state without appending to buffer
// Causes findFragments to backtrack a segment and find the keyframe
// Audio fragments arriving before video sets the nextLoadPosition, causing _findFragments to skip the backtracked fragment
Expand All @@ -1182,7 +1185,7 @@ class StreamController extends TaskLoop {
return;
}
} else {
logger.warn('Already backtracked on this fragment, appending with the gap');
logger.warn('Already backtracked on this fragment, appending with the gap', frag.sn);
}
} else {
// Only reset the backtracked flag if we've loaded the frag without any dropped frames
Expand Down
2 changes: 1 addition & 1 deletion src/controller/subtitle-stream-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class SubtitleStreamController extends TaskLoop {
trackDetails.fragments.forEach(frag => {
if (!(alreadyProcessed(frag) || frag.sn === currentFragSN || alreadyInQueue(frag))) {
// Load key if subtitles are encrypted
if ((frag.decryptdata && frag.decryptdata.uri != null) && (frag.decryptdata.key == null)) {
if (frag.encrypted) {
logger.log(`Loading key for ${frag.sn}`);
this.state = State.KEY_LOADING;
this.hls.trigger(Event.KEY_LOADING, { frag: frag });
Expand Down
4 changes: 4 additions & 0 deletions src/loader/fragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ export default class Fragment {
return this._decryptdata;
}

get encrypted () {
return !!((this.decryptdata && this.decryptdata.uri !== null) && (this.decryptdata.key === null));
}

/**
* @param {ElementaryStreamType} type
*/
Expand Down
Loading

0 comments on commit 6ab1013

Please sign in to comment.