From 7b2029f82050fc607ac51f6cbe1e681515873cf2 Mon Sep 17 00:00:00 2001 From: mangui Date: Thu, 9 Jul 2015 15:49:26 +0200 Subject: [PATCH] add new error FRAG_LOOP_LOADING_ERROR, raised upon detection of same fragment being requested in loop. --- API.md | 3 +++ demo/index.html | 3 +++ design.md | 7 +++++-- src/controller/buffer-controller.js | 30 ++++++++++++++++++++++++----- src/errors.js | 2 ++ src/hls.js | 1 + 6 files changed, 39 insertions(+), 7 deletions(-) diff --git a/API.md b/API.md index c216a24d40d..36831150f70 100644 --- a/API.md +++ b/API.md @@ -93,6 +93,7 @@ each error is categorized by : - ```Hls.ErrorDetails.LEVEL_LOAD_TIMEOUT```raised when level loading fails because of a timeout - ```Hls.ErrorDetails.LEVEL_SWITCH_ERROR```raised when level switching fails - ```Hls.ErrorDetails.FRAG_LOAD_ERROR```raised when fragment loading fails because of a network error + - ```Hls.ErrorDetails.FRAG_LOOP_LOADING_ERROR```raised upon detection of same fragment being requested in loop - ```Hls.ErrorDetails.FRAG_LOAD_TIMEOUT```raised when fragment loading fails because of a timeout - ```Hls.ErrorDetails.FRAG_PARSING_ERROR```raised when fragment parsing fails - ```Hls.ErrorDetails.FRAG_APPENDING_ERROR```raised when mp4 boxes appending in SourceBuffer fails @@ -406,6 +407,8 @@ full list of Errors is described below: - data: { type : ```OTHER_ERROR```, details : ```Hls.ErrorDetails.LEVEL_SWITCH_ERROR```, fatal : ```false```,level : failed level index, reason : failure reason} - ```Hls.ErrorDetails.FRAG_LOAD_ERROR```raised when fragment loading fails because of a network error - data: { type : ```NETWORK_ERROR```, details : ```Hls.ErrorDetails.FRAG_LOAD_ERROR```, fatal : ```true/false```,frag : fragment object, response : xhr response} + - ```Hls.ErrorDetails.FRAG_LOOP_LOADING_ERROR```raised upon detection of same fragment being requested in loop + - data: { type : ```NETWORK_ERROR```, details : ```Hls.ErrorDetails.FRAG_LOOP_LOADING_ERROR```, fatal : ```true/false```,frag : fragment object} - ```Hls.ErrorDetails.FRAG_LOAD_TIMEOUT```raised when fragment loading fails because of a timeout - data: { type : ```NETWORK_ERROR```, details : ```Hls.ErrorDetails.FRAG_LOAD_TIMEOUT```, fatal : ```true/false```,frag : fragment object} - ```Hls.ErrorDetails.FRAG_PARSING_ERROR```raised when fragment parsing fails diff --git a/demo/index.html b/demo/index.html index 55627ac3a96..5b038826f1e 100644 --- a/demo/index.html +++ b/demo/index.html @@ -335,6 +335,9 @@

Stats Display

case Hls.ErrorDetails.FRAG_APPENDING_ERROR: $("#HlsStatus").text("Frag Appending Error"); break; + case Hls.ErrorDetails.FRAG_LOOP_LOADING_ERROR: + $("#HlsStatus").text("Frag Loop Loading Error"); + break; default: break; } diff --git a/design.md b/design.md index e6df7ae9d69..304242c0a95 100644 --- a/design.md +++ b/design.md @@ -21,9 +21,9 @@ design idea is pretty simple : - [src/controller/buffer-controller.js][] - in charge of: - ensuring that buffer is filled as per defined quality selection logic. - - monitoring current playback quality level + - monitoring current playback quality level (buffer controller maintains a map between media position and quality level) - if buffer is not filled up appropriately (i.e. as per defined maximum buffer size, or as per defined quality level), buffer controller will trigger the following actions: - - retrieve "not buffered" media position greater then current playback position + - retrieve "not buffered" media position greater then current playback position. this is performed by comparing video.buffered and video.currentTime. - retrieve URL of fragment matching with this media position, and appropriate quality level - trigger fragment loading - monitor fragment loading speed: @@ -110,6 +110,9 @@ design idea is pretty simple : - ```FRAG_LOAD_ERROR``` is raised by [src/loader/fragment-loader.js][] upon xhr failure detected by [src/utils/xhr-loader.js][]. - if auto level switch is enabled and loaded frag level is greater than 0, this error is not fatal: in that case [src/controller/level-controller.js][] will trigger an emergency switch down to level 0. - if frag level is 0 or auto level switch is disabled, this error is marked as fatal and a call to ```hls.recoverNetworkError()``` could help recover it. + - ```FRAG_LOOP_LOADING_ERROR``` is raised by [src/controller/buffer-controller.js][] upon detection of same fragment being requested in loop. this could happen with badly formatted fragments. + - if auto level switch is enabled and loaded frag level is greater than 0, this error is not fatal: in that case [src/controller/level-controller.js][] will trigger an emergency switch down to level 0. + - if frag level is 0 or auto level switch is disabled, this error is marked as fatal and a call to ```hls.recoverNetworkError()``` could help recover it. - ```FRAG_LOAD_TIMEOUT``` is raised by [src/loader/fragment-loader.js][] upon xhr timeout detected by [src/utils/xhr-loader.js][]. - if auto level switch is enabled and loaded frag level is greater than 0, this error is not fatal: in that case [src/controller/level-controller.js][] will trigger an emergency switch down to level 0. - if frag level is 0 or auto level switch is disabled, this error is marked as fatal and a call to ```hls.recoverNetworkError()``` could help recover it. diff --git a/src/controller/buffer-controller.js b/src/controller/buffer-controller.js index 38c9e651204..5d8f3f5fce9 100644 --- a/src/controller/buffer-controller.js +++ b/src/controller/buffer-controller.js @@ -70,7 +70,6 @@ this.stop(); this.demuxer = new Demuxer(this.config); this.timer = setInterval(this.ontick, 100); - this.appendError=0; this.level = -1; observer.on(Event.FRAG_LOADED, this.onfl); observer.on(Event.FRAG_PARSING_INIT_SEGMENT, this.onis); @@ -91,7 +90,6 @@ } this.frag = null; } - this.flushBufferCounter = 0; if(this.sourceBuffer) { for(var type in this.sourceBuffer) { var sb = this.sourceBuffer[type]; @@ -252,6 +250,26 @@ frag.expectedLen = Math.round(frag.duration*this.levels[level].bitrate/8); frag.trequest = new Date(); } + + // ensure that we are not reloading the same fragments in loop ... + (this.fragLoadIdx !== undefined) ? this.fragLoadIdx++ : this.fragLoadIdx = 0; + if(frag.loadCounter) { + frag.loadCounter++; + let maxThreshold = this.config.fragLoadingLoopThreshold; + // if this frag has already been loaded 3 times, and if it has been reloaded recently + if(frag.loadCounter > maxThreshold && (Math.abs(this.fragLoadIdx - frag.loadIdx) < maxThreshold)) { + // if auto level switch is enabled and loaded frag level is greater than 0, this error is not fatal + let fatal = !(this.hls.autoLevelEnabled && level); + observer.trigger(Event.ERROR, {type : ErrorTypes.MEDIA_ERROR, details : ErrorDetails.FRAG_LOOP_LOADING_ERROR, fatal:fatal, frag : this.frag}); + if(fatal) { + this.state = this.ERROR; + } + return; + } + } else { + frag.loadCounter=1; + } + frag.loadIdx = this.fragLoadIdx; this.frag = frag; this.startFragmentRequested = true; observer.trigger(Event.FRAG_LOADING, { frag: frag }); @@ -323,7 +341,7 @@ // in case any error occured while appending, put back segment in mp4segments table logger.log(`error while trying to append buffer:${err.message},try appending later`); this.mp4segments.unshift(segment); - this.appendError++; + this.appendError ? this.appendError++ : this.appendError=1; if(this.appendError > 3) { logger.log(`fail 3 times to append segment in sourceBuffer`); observer.trigger(Event.ERROR, {type : ErrorTypes.MEDIA_ERROR, details : ErrorDetails.FRAG_APPENDING_ERROR, fatal : true, frag : this.frag}); @@ -343,8 +361,6 @@ if(this.flushBuffer(range.start,range.end)) { // range flushed, remove from flush array this.flushRange.shift(); - // reset flush counter - this.flushBufferCounter = 0; } else { // flush in progress, come back later break; @@ -356,6 +372,8 @@ this.state = this.IDLE; // reset reference to frag this.frag = null; + // increase fragment load Index to avoid frag loop loading error after buffer flush + this.fragLoadIdx+=2*this.config.fragLoadingLoopThreshold; } /* if not everything flushed, stay in BUFFER_FLUSHING state. we will come back here each time sourceBuffer updateend() callback will be triggered @@ -572,6 +590,7 @@ this.frag.loader.abort(); } // flush everything + this.flushBufferCounter = 0; this.flushRange.push({ start : 0, end : Number.POSITIVE_INFINITY}); // trigger a sourceBuffer flush this.state = this.BUFFER_FLUSHING; @@ -625,6 +644,7 @@ } } if(this.flushRange.length) { + this.flushBufferCounter = 0; // trigger a sourceBuffer flush this.state = this.BUFFER_FLUSHING; // speed up switching, trigger timer function diff --git a/src/errors.js b/src/errors.js index 264d3aa21ec..069d62fc93d 100644 --- a/src/errors.js +++ b/src/errors.js @@ -23,6 +23,8 @@ export var ErrorDetails = { LEVEL_SWITCH_ERROR : 'levelSwitchError', // Identifier for fragment load error - data: { frag : fragment object, response : XHR response} FRAG_LOAD_ERROR : 'fragLoadError', + // Identifier for fragment loop loading error - data: { frag : fragment object} + FRAG_LOOP_LOADING_ERROR : 'fragLoopLoadingError', // Identifier for fragment load timeout error - data: { frag : fragment object} FRAG_LOAD_TIMEOUT : 'fragLoadTimeOut', // Identifier for a fragment parsing error event - data: parsing error description diff --git a/src/hls.js b/src/hls.js index 640ffebf784..bbed33ec255 100644 --- a/src/hls.js +++ b/src/hls.js @@ -42,6 +42,7 @@ class Hls { fragLoadingTimeOut : 20000, fragLoadingMaxRetry : 3, fragLoadingRetryDelay : 1000, + fragLoadingLoopThreshold : 3, manifestLoadingTimeOut : 10000, manifestLoadingMaxRetry : 3, manifestLoadingRetryDelay : 1000,