update collapsible

This commit is contained in:
Luke Pulverenti 2016-07-02 14:05:40 -04:00
parent f351643d29
commit 891a865464
42 changed files with 1185 additions and 536 deletions

View file

@ -50,7 +50,7 @@ class AbrController extends EventHandler {
ewmaFast = config.abrEwmaFastVoD;
ewmaSlow = config.abrEwmaSlowVoD;
}
this.bwEstimator = new EwmaBandWidthEstimator(hls,ewmaSlow,ewmaFast);
this.bwEstimator = new EwmaBandWidthEstimator(hls,ewmaSlow,ewmaFast,config.abrEwmaDefaultEstimate);
}
let frag = data.frag;
@ -184,7 +184,8 @@ class AbrController extends EventHandler {
return Math.min(this._nextAutoLevel,maxAutoLevel);
}
let avgbw = this.bwEstimator.getEstimate(),adjustedbw;
let avgbw = this.bwEstimator ? this.bwEstimator.getEstimate() : config.abrEwmaDefaultEstimate,
adjustedbw;
// follow algorithm captured from stagefright :
// https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/httplive/LiveSession.cpp
// Pick the highest bandwidth stream below or equal to estimated bandwidth.

View file

@ -11,9 +11,9 @@ import EWMA from '../utils/ewma';
class EwmaBandWidthEstimator {
constructor(hls,slow,fast) {
constructor(hls,slow,fast,defaultEstimate) {
this.hls = hls;
this.defaultEstimate_ = 5e5; // 500kbps
this.defaultEstimate_ = defaultEstimate;
this.minWeight_ = 0.001;
this.minDelayMs_ = 50;
this.slow_ = new EWMA(slow);
@ -32,7 +32,7 @@ class EwmaBandWidthEstimator {
getEstimate() {
if (!this.fast_ || this.fast_.getTotalWeight() < this.minWeight_) {
if (!this.fast_ || !this.slow_ || this.fast_.getTotalWeight() < this.minWeight_) {
return this.defaultEstimate_;
}
//console.log('slow estimate:'+ Math.round(this.slow_.getEstimate()));

View file

@ -61,7 +61,7 @@ class StreamController extends EventHandler {
this.state = State.STOPPED;
}
startLoad(startPosition=0) {
startLoad(startPosition) {
if (this.levels) {
var media = this.media, lastCurrentTime = this.lastCurrentTime;
this.stopLoad();
@ -71,7 +71,7 @@ class StreamController extends EventHandler {
}
this.level = -1;
this.fragLoadError = 0;
if (media && lastCurrentTime) {
if (media && lastCurrentTime > 0) {
logger.log(`configure startPosition @${lastCurrentTime}`);
if (!this.lastPaused) {
logger.log('resuming video');
@ -288,10 +288,10 @@ class StreamController extends EventHandler {
let deltaPTS = fragPrevious.deltaPTS,
curSNIdx = frag.sn - levelDetails.startSN;
// if there is a significant delta between audio and video, larger than max allowed hole,
// it might be because video fragment does not start with a keyframe.
// and if previous remuxed fragment did not start with a keyframe. (fragPrevious.dropped)
// let's try to load previous fragment again to get last keyframe
// then we will reload again current fragment (that way we should be able to fill the buffer hole ...)
if (deltaPTS && deltaPTS > config.maxBufferHole) {
if (deltaPTS && deltaPTS > config.maxBufferHole && fragPrevious.dropped) {
frag = fragments[curSNIdx-1];
logger.warn(`SN just loaded, with large PTS gap between audio and video, maybe frag is not starting with a keyframe ? load previous one to try to overcome this`);
// decrement previous frag load counter to avoid frag loop loading error when next fragment will get reloaded
@ -300,6 +300,10 @@ class StreamController extends EventHandler {
frag = fragments[curSNIdx+1];
logger.log(`SN just loaded, load next one: ${frag.sn}`);
}
// ensure frag is not undefined
if(!frag) {
break;
}
} else {
// have we reached end of VOD playlist ?
if (!levelDetails.live) {
@ -489,8 +493,15 @@ class StreamController extends EventHandler {
logger.log('immediateLevelSwitch');
if (!this.immediateSwitch) {
this.immediateSwitch = true;
this.previouslyPaused = this.media.paused;
this.media.pause();
let media = this.media, previouslyPaused;
if (media) {
previouslyPaused = media.paused;
media.pause();
} else {
// don't restart playback after instant level switch in case media not attached
previouslyPaused = true;
}
this.previouslyPaused = previouslyPaused;
}
var fragCurrent = this.fragCurrent;
if (fragCurrent && fragCurrent.loader) {
@ -579,8 +590,9 @@ class StreamController extends EventHandler {
media.addEventListener('seeking', this.onvseeking);
media.addEventListener('seeked', this.onvseeked);
media.addEventListener('ended', this.onvended);
if(this.levels && this.config.autoStartLoad) {
this.hls.startLoad();
let config = this.config;
if(this.levels && config.autoStartLoad) {
this.hls.startLoad(config.startPosition);
}
}
@ -688,8 +700,9 @@ class StreamController extends EventHandler {
this.levels = data.levels;
this.startLevelLoaded = false;
this.startFragRequested = false;
if (this.config.autoStartLoad) {
this.hls.startLoad();
let config = this.config;
if (config.autoStartLoad) {
this.hls.startLoad(config.startPosition);
}
}
@ -725,12 +738,23 @@ class StreamController extends EventHandler {
curLevel.details = newDetails;
this.hls.trigger(Event.LEVEL_UPDATED, { details: newDetails, level: newLevelId });
// compute start position
if (this.startFragRequested === false) {
// if live playlist, set start position to be fragment N-this.config.liveSyncDurationCount (usually 3)
if (newDetails.live) {
let targetLatency = this.config.liveSyncDuration !== undefined ? this.config.liveSyncDuration : this.config.liveSyncDurationCount * newDetails.targetduration;
this.startPosition = Math.max(0, sliding + duration - targetLatency);
// compute start position if set to -1. use it straight away if value is defined
if (this.startPosition === -1) {
// first, check if start time offset has been set in playlist, if yes, use this value
let startTimeOffset = newDetails.startTimeOffset;
if(!isNaN(startTimeOffset)) {
logger.log(`start time offset found in playlist, adjust startPosition to ${startTimeOffset}`);
this.startPosition = startTimeOffset;
} else {
// if live playlist, set start position to be fragment N-this.config.liveSyncDurationCount (usually 3)
if (newDetails.live) {
let targetLatency = this.config.liveSyncDuration !== undefined ? this.config.liveSyncDuration : this.config.liveSyncDurationCount * newDetails.targetduration;
this.startPosition = Math.max(0, sliding + duration - targetLatency);
} else {
this.startPosition = 0;
}
}
}
this.nextLoadPosition = this.startPosition;
}
@ -789,7 +813,7 @@ class StreamController extends EventHandler {
}
}
this.pendingAppending = 0;
logger.log(`Demuxing ${sn} of [${details.startSN} ,${details.endSN}],level ${level}`);
logger.log(`Demuxing ${sn} of [${details.startSN} ,${details.endSN}],level ${level}, cc ${fragCurrent.cc}`);
let demuxer = this.demuxer;
if (demuxer) {
demuxer.push(data.payload, audioCodec, currentLevel.videoCodec, start, fragCurrent.cc, level, sn, duration, fragCurrent.decryptdata);
@ -885,12 +909,17 @@ class StreamController extends EventHandler {
var level = this.levels[this.level],
frag = this.fragCurrent;
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}`);
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},dropped:${data.dropped || 0}`);
var drift = LevelHelper.updateFragPTSDTS(level.details,frag.sn,data.startPTS,data.endPTS,data.startDTS,data.endDTS),
hls = this.hls;
hls.trigger(Event.LEVEL_PTS_UPDATED, {details: level.details, level: this.level, drift: drift});
// has remuxer dropped video frames located before first keyframe ?
if(data.type === 'video') {
frag.dropped = data.dropped;
}
[data.data1, data.data2].forEach(buffer => {
if (buffer) {
this.pendingAppending++;

View file

@ -38,7 +38,7 @@ var DemuxerWorker = function (self) {
});
observer.on(Event.FRAG_PARSING_DATA, function(ev, data) {
var objData = {event: ev, type: data.type, startPTS: data.startPTS, endPTS: data.endPTS, startDTS: data.startDTS, endDTS: data.endDTS, data1: data.data1.buffer, data2: data.data2.buffer, nb: data.nb};
var objData = {event: ev, type: data.type, startPTS: data.startPTS, endPTS: data.endPTS, startDTS: data.startDTS, endDTS: data.endDTS, data1: data.data1.buffer, data2: data.data2.buffer, nb: data.nb, dropped : data.dropped};
// pass data1/data2 as transferable object (no copy)
self.postMessage(objData, [objData.data1, objData.data2]);
});

View file

@ -88,7 +88,8 @@ class Demuxer {
startDTS: data.startDTS,
endDTS: data.endDTS,
type: data.type,
nb: data.nb
nb: data.nb,
dropped: data.dropped,
});
break;
case Event.FRAG_PARSING_METADATA:

View file

@ -37,7 +37,7 @@
switchLevel() {
this.pmtParsed = false;
this._pmtId = -1;
this._avcTrack = {container : 'video/mp2t', type: 'video', id :-1, sequenceNumber: 0, samples : [], len : 0, nbNalu : 0};
this._avcTrack = {container : 'video/mp2t', type: 'video', id :-1, sequenceNumber: 0, samples : [], len : 0, nbNalu : 0, dropped : 0};
this._aacTrack = {container : 'video/mp2t', type: 'audio', id :-1, sequenceNumber: 0, samples : [], len : 0};
this._id3Track = {type: 'id3', id :-1, sequenceNumber: 0, samples : [], len : 0};
this._txtTrack = {type: 'text', id: -1, sequenceNumber: 0, samples: [], len: 0};
@ -355,6 +355,9 @@
samples.push(avcSample);
track.len += length;
track.nbNalu += units2.length;
} else {
// dropped samples, track it
track.dropped++;
}
units2 = [];
length = 0;

View file

@ -43,6 +43,7 @@ class Hls {
if(!Hls.defaultConfig) {
Hls.defaultConfig = {
autoStartLoad: true,
startPosition: -1,
debug: false,
capLevelToPlayerSize: false,
maxBufferLength: 30,
@ -87,6 +88,7 @@ class Hls {
abrEwmaSlowLive: 9,
abrEwmaFastVoD: 4,
abrEwmaSlowVoD: 15,
abrEwmaDefaultEstimate: 5e5, // 500 kbps
abrBandWidthFactor : 0.8,
abrBandWidthUpFactor : 0.7
};
@ -181,7 +183,7 @@ class Hls {
this.trigger(Event.MANIFEST_LOADING, {url: url});
}
startLoad(startPosition=0) {
startLoad(startPosition=-1) {
logger.log('startLoad');
this.levelController.startLoad();
this.streamController.startLoad(startPosition);

View file

@ -137,7 +137,7 @@ class PlaylistLoader extends EventHandler {
byteRangeEndOffset,
byteRangeStartOffset;
regexp = /(?:#EXT-X-(MEDIA-SEQUENCE):(\d+))|(?:#EXT-X-(TARGETDURATION):(\d+))|(?:#EXT-X-(KEY):(.*))|(?:#EXT(INF):([\d\.]+)[^\r\n]*([\r\n]+[^#|\r\n]+)?)|(?:#EXT-X-(BYTERANGE):([\d]+[@[\d]*)]*[\r\n]+([^#|\r\n]+)?|(?:#EXT-X-(ENDLIST))|(?:#EXT-X-(DIS)CONTINUITY))|(?:#EXT-X-(PROGRAM-DATE-TIME):(.*)[\r\n]+([^#|\r\n]+)?)/g;
regexp = /(?:#EXT-X-(MEDIA-SEQUENCE):(\d+))|(?:#EXT-X-(TARGETDURATION):(\d+))|(?:#EXT-X-(KEY):(.*))|(?:#EXT-X-(START):(.*))|(?:#EXT(INF):([\d\.]+)[^\r\n]*([\r\n]+[^#|\r\n]+)?)|(?:#EXT-X-(BYTERANGE):([\d]+[@[\d]*)]*[\r\n]+([^#|\r\n]+)?|(?:#EXT-X-(ENDLIST))|(?:#EXT-X-(DIS)CONTINUITY))|(?:#EXT-X-(PROGRAM-DATE-TIME):(.*)[\r\n]+([^#|\r\n]+)?)/g;
while ((result = regexp.exec(string)) !== null) {
result.shift();
result = result.filter(function(n) { return (n !== undefined); });
@ -210,6 +210,14 @@ class PlaylistLoader extends EventHandler {
}
}
break;
case 'START':
let startParams = result[1];
let startAttrs = new AttrList(startParams);
let startTimeOffset = startAttrs.decimalFloatingPoint('TIME-OFFSET');
if (startTimeOffset) {
level.startTimeOffset = startTimeOffset;
}
break;
case 'PROGRAM-DATE-TIME':
programDateTime = new Date(Date.parse(result[1]));
if (frag && !frag.url && result.length >= 3) {

View file

@ -247,8 +247,10 @@ class MP4Remuxer {
}
// next AVC sample DTS should be equal to last sample DTS + last sample duration
this.nextAvcDts = dtsnorm + lastSampleDuration * pes2mp4ScaleFactor;
let dropped = track.dropped;
track.len = 0;
track.nbNalu = 0;
track.dropped = 0;
if(samples.length && navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
flags = samples[0].flags;
// chrome workaround, mark first sample as being a Random Access Point to avoid sourcebuffer append issue
@ -267,7 +269,8 @@ class MP4Remuxer {
startDTS: firstDTS / pesTimeScale,
endDTS: this.nextAvcDts / pesTimeScale,
type: 'video',
nb: samples.length
nb: samples.length,
dropped : dropped
});
}

View file

@ -62,7 +62,8 @@ class PassThroughRemuxer {
startPTS: timeOffset,
startDTS: timeOffset,
type: 'audiovideo',
nb: 1
nb: 1,
dropped : 0
});
}
}