Skip to content

Commit

Permalink
Merge pull request #1630 from video-dev/feature/audio-track-fallback
Browse files Browse the repository at this point in the history
Redundant streams fallback behavior for alternate renditions with multiple media groups
  • Loading branch information
tchakabam committed Jun 11, 2018
2 parents 3b3a001 + fce0fb7 commit 741d531
Show file tree
Hide file tree
Showing 15 changed files with 795 additions and 226 deletions.
24 changes: 12 additions & 12 deletions demo/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ function loadSelectedStream() {

if (selectedTestStream && selectedTestStream.config) {
Object.assign(hlsConfig, selectedTestStream.config)
console.log('Using Hls.js config:', hlsConfig);
}

if (hlsConfig.widevineLicenseUrl) {
Expand All @@ -229,6 +228,7 @@ function loadSelectedStream() {
}

onDemoConfigChanged();
console.log('Using Hls.js config:', hlsConfig);

window.hls = hls = new Hls(hlsConfig);

Expand Down Expand Up @@ -365,7 +365,9 @@ function loadSelectedStream() {

stats.levelParsed++;
stats.levelParsingUs = Math.round(1000*this.sumLevelParsingMs / stats.levelParsed);
console.log('parsing level duration :' + stats.levelParsingUs + 'us,count:' + stats.levelParsed);

//console.log('parsing level duration :' + stats.levelParsingUs + 'us,count:' + stats.levelParsed);

events.load.push(event);
trimEventHistory();
refreshCanvas();
Expand Down Expand Up @@ -549,7 +551,7 @@ function loadSelectedStream() {
});

hls.on(Hls.Events.ERROR, function(event, data) {
console.warn(data);
console.warn('Error event:', data);
switch(data.details) {
case Hls.ErrorDetails.MANIFEST_LOAD_ERROR:
try {
Expand Down Expand Up @@ -613,7 +615,7 @@ function loadSelectedStream() {
break;
}
if(data.fatal) {
console.log('Fatal error :' + data.details);
console.error('Fatal error :' + data.details);
switch(data.type) {
case Hls.ErrorTypes.MEDIA_ERROR:
handleMediaError();
Expand Down Expand Up @@ -930,7 +932,7 @@ function hideCanvas() {
function getMetrics() {
let json = JSON.stringify(events);
let jsonpacked = jsonpack.pack(json);
console.log('packing JSON from ' + json.length + ' to ' + jsonpacked.length + ' bytes');
// console.log('packing JSON from ' + json.length + ' to ' + jsonpacked.length + ' bytes');
return btoa(jsonpacked);
}

Expand All @@ -941,15 +943,15 @@ function copyMetricsToClipBoard() {
function goToMetrics() {
let url = document.URL;
url = url.substr(0, url.lastIndexOf('/')+1) + 'metrics.html';
console.log(url);
// console.log(url);
window.open(url, '_blank');
}

function goToMetricsPermaLink() {
let url = document.URL;
let b64 = getMetrics();
url = url.substr(0, url.lastIndexOf('/')+1) + 'metrics.html#data=' + b64;
console.log(url);
// console.log(url);
window.open(url, '_blank');
}

Expand Down Expand Up @@ -1020,7 +1022,7 @@ function updateLevelInfo() {
html1 += button_disabled;
}

let levelName = i;
let levelName = i;
let label = level2label(i);
if(label) {
levelName += ' (' + level2label(i) + 'p)';
Expand Down Expand Up @@ -1119,7 +1121,7 @@ function level2label(index) {
}

function getDemoConfigPropOrDefault(propName, defaultVal) {
return typeof demoConfig[propName] !== 'undefined' ? demoConfig[propName] : defaultVal;
return typeof demoConfig[propName] !== 'undefined' ? demoConfig[propName] : defaultVal;
}

function getURLParam(sParam, defaultValue) {
Expand Down Expand Up @@ -1148,8 +1150,6 @@ function onDemoConfigChanged() {
widevineLicenseUrl: escape(widevineLicenseUrl)
}

console.log(demoConfig)

const serializedDemoConfig = btoa(JSON.stringify(demoConfig))

const baseURL = document.URL.split('?')[0]
Expand Down Expand Up @@ -1213,4 +1213,4 @@ function logStatus(message) {

function logError(message) {
appendLog('errorOut', message)
}
}
46 changes: 26 additions & 20 deletions src/controller/abr-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,22 @@ class AbrController extends EventHandler {
}

onFragLoading (data) {
let frag = data.frag;
const frag = data.frag;
if (frag.type === 'main') {
if (!this.timer) {
this.fragCurrent = frag;
this.timer = setInterval(this.onCheck, 100);
}

// lazy init of bw Estimator, rationale is that we use different params for Live/VoD
// lazy init of BwEstimator, rationale is that we use different params for Live/VoD
// so we need to wait for stream manifest / playlist type to instantiate it.
if (!this._bwEstimator) {
let hls = this.hls,
level = data.frag.level,
isLive = hls.levels[level].details.live,
config = hls.config,
ewmaFast, ewmaSlow;
const hls = this.hls;
const config = hls.config;
const level = frag.level;
const isLive = hls.levels[level].details.live;

let ewmaFast, ewmaSlow;
if (isLive) {
ewmaFast = config.abrEwmaFastLive;
ewmaSlow = config.abrEwmaSlowLive;
Expand All @@ -68,10 +68,15 @@ class AbrController extends EventHandler {
we compare it to expected time of buffer starvation
*/
const hls = this.hls;
const v = hls.media;
const video = hls.media;
const frag = this.fragCurrent;
const minAutoLevel = hls.minAutoLevel;

if (!frag) {
return;
}

const loader = frag.loader;
const minAutoLevel = hls.minAutoLevel;

// if loader has been destroyed or loading has been aborted, stop timer and return
if (!loader || (loader.stats && loader.stats.aborted)) {
Expand All @@ -84,9 +89,9 @@ class AbrController extends EventHandler {
let stats = loader.stats;
/* only monitor frag retrieval time if
(video not paused OR first fragment being loaded(ready state === HAVE_NOTHING = 0)) AND autoswitching enabled AND not lowest level (=> means that we have several levels) */
if (v && stats && ((!v.paused && (v.playbackRate !== 0)) || !v.readyState) && frag.autoLevel && frag.level) {
if (video && stats && ((!video.paused && (video.playbackRate !== 0)) || !video.readyState) && frag.autoLevel && frag.level) {
let requestDelay = performance.now() - stats.trequest,
playbackRate = Math.abs(v.playbackRate);
playbackRate = Math.abs(video.playbackRate);
// monitor fragment load progress after half of expected fragment duration,to stabilize bitrate
if (requestDelay > (500 * frag.duration / playbackRate)) {
let levels = hls.levels,
Expand All @@ -95,9 +100,9 @@ class AbrController extends EventHandler {
level = levels[frag.level],
levelBitrate = level.realBitrate ? Math.max(level.realBitrate, level.bitrate) : level.bitrate,
expectedLen = stats.total ? stats.total : Math.max(stats.loaded, Math.round(frag.duration * levelBitrate / 8)),
pos = v.currentTime,
pos = video.currentTime,
fragLoadedDelay = (expectedLen - stats.loaded) / loadRate,
bufferStarvationDelay = (BufferHelper.bufferInfo(v, pos, hls.config.maxBufferHole).end - pos) / playbackRate;
bufferStarvationDelay = (BufferHelper.bufferInfo(video, pos, hls.config.maxBufferHole).end - pos) / playbackRate;
// consider emergency switch down only if we have less than 2 frag buffered AND
// time to finish loading current fragment is bigger than buffer starvation delay
// ie if we risk buffer starvation if bw does not increase quickly
Expand Down Expand Up @@ -136,7 +141,7 @@ class AbrController extends EventHandler {
}

onFragLoaded (data) {
let frag = data.frag;
const frag = data.frag;
if (frag.type === 'main' && !isNaN(frag.sn)) {
// stop monitoring bw once frag loaded
this.clearTimer();
Expand All @@ -163,7 +168,8 @@ class AbrController extends EventHandler {
}

onFragBuffered (data) {
let stats = data.stats, frag = data.frag;
const stats = data.stats;
const frag = data.frag;
// only update stats on first frag buffering
// if same frag is loaded multiple times, it might be in browser cache, and loaded quickly
// and leading to wrong bw estimation
Expand Down Expand Up @@ -222,16 +228,16 @@ class AbrController extends EventHandler {
}
get _nextABRAutoLevel () {
let hls = this.hls, maxAutoLevel = hls.maxAutoLevel, levels = hls.levels, config = hls.config, minAutoLevel = hls.minAutoLevel;
const v = hls.media,
const video = hls.media,
currentLevel = this.lastLoadedFragLevel,
currentFragDuration = this.fragCurrent ? this.fragCurrent.duration : 0,
pos = (v ? v.currentTime : 0),
// playbackRate is the absolute value of the playback rate; if v.playbackRate is 0, we use 1 to load as
pos = (video ? video.currentTime : 0),
// playbackRate is the absolute value of the playback rate; if video.playbackRate is 0, we use 1 to load as
// if we're playing back at the normal rate.
playbackRate = ((v && (v.playbackRate !== 0)) ? Math.abs(v.playbackRate) : 1.0),
playbackRate = ((video && (video.playbackRate !== 0)) ? Math.abs(video.playbackRate) : 1.0),
avgbw = this._bwEstimator ? this._bwEstimator.getEstimate() : config.abrEwmaDefaultEstimate,
// bufferStarvationDelay is the wall-clock time left until the playback buffer is exhausted.
bufferStarvationDelay = (BufferHelper.bufferInfo(v, pos, config.maxBufferHole).end - pos) / playbackRate;
bufferStarvationDelay = (BufferHelper.bufferInfo(video, pos, config.maxBufferHole).end - pos) / playbackRate;

// First, look to see if we can find a level matching with our avg bandwidth AND that could also guarantee no rebuffering at all
let bestLevel = this._findBestLevel(currentLevel, currentFragDuration, avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, config.abrBandWidthFactor, config.abrBandWidthUpFactor, levels);
Expand Down
24 changes: 15 additions & 9 deletions src/controller/audio-stream-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ class AudioStreamController extends TaskLoop {
let pendingData = this.pendingData;

if (!pendingData) {
console.warn('Apparently attempt to enqueue media payload without codec initialization data upfront');
logger.warn('Apparently attempt to enqueue media payload without codec initialization data upfront');
hls.trigger(Event.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: null, fatal: true });
return;
}
Expand Down Expand Up @@ -791,6 +791,12 @@ class AudioStreamController extends TaskLoop {
switch (data.details) {
case ErrorDetails.FRAG_LOAD_ERROR:
case ErrorDetails.FRAG_LOAD_TIMEOUT:
const frag = data.frag;
// don't handle frag error not related to audio fragment
if (frag && frag.type !== 'audio') {
break;
}

if (!data.fatal) {
let loadError = this.fragLoadError;
if (loadError) {
Expand All @@ -799,17 +805,17 @@ class AudioStreamController extends TaskLoop {
loadError = 1;
}

let config = this.config;
const config = this.config;
if (loadError <= config.fragLoadingMaxRetry) {
this.fragLoadError = loadError;
// exponential backoff capped to config.fragLoadingMaxRetryTimeout
let delay = Math.min(Math.pow(2, loadError - 1) * config.fragLoadingRetryDelay, config.fragLoadingMaxRetryTimeout);
logger.warn(`audioStreamController: frag loading failed, retry in ${delay} ms`);
const delay = Math.min(Math.pow(2, loadError - 1) * config.fragLoadingRetryDelay, config.fragLoadingMaxRetryTimeout);
logger.warn(`AudioStreamController: frag loading failed, retry in ${delay} ms`);
this.retryDate = performance.now() + delay;
// retry loading state
this.state = State.FRAG_LOADING_WAITING_RETRY;
} else {
logger.error(`audioStreamController: ${data.details} reaches max retry, redispatch as fatal ...`);
logger.error(`AudioStreamController: ${data.details} reaches max retry, redispatch as fatal ...`);
// switch error to fatal
data.fatal = true;
this.state = State.ERROR;
Expand All @@ -824,7 +830,7 @@ class AudioStreamController extends TaskLoop {
if (this.state !== State.ERROR) {
// if fatal error, stop processing, otherwise move to IDLE to retry loading
this.state = data.fatal ? State.ERROR : State.IDLE;
logger.warn(`audioStreamController: ${data.details} while loading frag,switch to ${this.state} state ...`);
logger.warn(`AudioStreamController: ${data.details} while loading frag, now switching to ${this.state} state ...`);
}
break;
case ErrorDetails.BUFFER_FULL_ERROR:
Expand All @@ -839,14 +845,14 @@ class AudioStreamController extends TaskLoop {
if (config.maxMaxBufferLength >= config.maxBufferLength) {
// reduce max buffer length as it might be too high. we do this to avoid loop flushing ...
config.maxMaxBufferLength /= 2;
logger.warn(`audio:reduce max buffer length to ${config.maxMaxBufferLength}s`);
logger.warn(`AudioStreamController: reduce max buffer length to ${config.maxMaxBufferLength}s`);
}
this.state = State.IDLE;
} else {
// current position is not buffered, but browser is still complaining about buffer full error
// this happens on IE/Edge, refer to https://github.com/video-dev/hls.js/pull/708
// in that case flush the whole audio buffer to recover
logger.warn('buffer full error also media.currentTime is not buffered, flush audio buffer');
logger.warn('AudioStreamController: buffer full error also media.currentTime is not buffered, flush audio buffer');
this.fragCurrent = null;
// flush everything
this.state = State.BUFFER_FLUSHING;
Expand All @@ -862,7 +868,7 @@ class AudioStreamController extends TaskLoop {
onBufferFlushed () {
let pendingData = this.pendingData;
if (pendingData && pendingData.length) {
logger.log('appending pending audio data on Buffer Flushed');
logger.log('AudioStreamController: appending pending audio data after buffer flushed');
pendingData.forEach(appendObj => {
this.hls.trigger(Event.BUFFER_APPENDING, appendObj);
});
Expand Down
Loading

0 comments on commit 741d531

Please sign in to comment.