1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

update components

This commit is contained in:
Luke Pulverenti 2016-01-13 15:58:12 -05:00
parent c6b706f48a
commit 5de2e610ce
35 changed files with 1420 additions and 1130 deletions

View file

@ -15,11 +15,12 @@ const State = {
IDLE : 0,
KEY_LOADING : 1,
FRAG_LOADING : 2,
WAITING_LEVEL : 3,
PARSING : 4,
PARSED : 5,
APPENDING : 6,
BUFFER_FLUSHING : 7
FRAG_LOADING_WAITING_RETRY : 3,
WAITING_LEVEL : 4,
PARSING : 5,
PARSED : 6,
APPENDING : 7,
BUFFER_FLUSHING : 8
};
class MSEMediaController {
@ -28,6 +29,7 @@ class MSEMediaController {
this.config = hls.config;
this.audioCodecSwap = false;
this.hls = hls;
this.ticks = 0;
// Source Buffer listeners
this.onsbue = this.onSBUpdateEnd.bind(this);
this.onsbe = this.onSBUpdateError.bind(this);
@ -84,6 +86,7 @@ class MSEMediaController {
this.demuxer = new Demuxer(hls);
this.timer = setInterval(this.ontick, 100);
this.level = -1;
this.fragLoadError = 0;
hls.on(Event.FRAG_LOADED, this.onfl);
hls.on(Event.FRAG_PARSING_INIT_SEGMENT, this.onis);
hls.on(Event.FRAG_PARSING_DATA, this.onfpg);
@ -136,6 +139,17 @@ class MSEMediaController {
}
tick() {
this.ticks++;
if (this.ticks === 1) {
this.doTick();
if (this.ticks > 1) {
setTimeout(this.tick, 1);
}
this.ticks = 0;
}
}
doTick() {
var pos, level, levelDetails, hls = this.hls;
switch(this.state) {
case State.ERROR:
@ -367,6 +381,17 @@ class MSEMediaController {
}
}
break;
case State.FRAG_LOADING_WAITING_RETRY:
var now = performance.now();
var retryDate = this.retryDate;
var media = this.media;
var isSeeking = media && media.seeking;
// if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading
if(!retryDate || (now >= retryDate) || isSeeking) {
logger.log(`mediaController: retryDate reached, switch back to IDLE state`);
this.state = State.IDLE;
}
break;
case State.PARSING:
// nothing to do, wait for fragment being parsed
break;
@ -454,10 +479,10 @@ class MSEMediaController {
default:
break;
}
// check/update current fragment
this._checkFragmentChanged();
// check buffer
this._checkBuffer();
// check/update current fragment
this._checkFragmentChanged();
}
@ -876,8 +901,12 @@ class MSEMediaController {
}
onMediaMetadata() {
if (this.media.currentTime !== this.startPosition) {
this.media.currentTime = this.startPosition;
var media = this.media,
currentTime = media.currentTime;
// only adjust currentTime if not equal to 0
if (!currentTime && currentTime !== this.startPosition) {
logger.log('onMediaMetadata: adjust currentTime to startPosition');
media.currentTime = this.startPosition;
}
this.loadedmetadata = true;
this.tick();
@ -992,8 +1021,11 @@ class MSEMediaController {
level = fragCurrent.level,
sn = fragCurrent.sn,
audioCodec = currentLevel.audioCodec;
if(audioCodec && this.audioCodecSwap) {
if(this.audioCodecSwap) {
logger.log('swapping playlist audio codec');
if(audioCodec === undefined) {
audioCodec = this.lastAudioCodec;
}
if(audioCodec.indexOf('mp4a.40.5') !==-1) {
audioCodec = 'mp4a.40.2';
} else {
@ -1012,6 +1044,7 @@ class MSEMediaController {
// check if codecs have been explicitely defined in the master playlist for this level;
// if yes use these ones instead of the ones parsed from the demux
var audioCodec = this.levels[this.level].audioCodec, videoCodec = this.levels[this.level].videoCodec, sb;
this.lastAudioCodec = data.audioCodec;
if(audioCodec && this.audioCodecSwap) {
logger.log('swapping playlist audio codec');
if(audioCodec.indexOf('mp4a.40.5') !==-1) {
@ -1070,7 +1103,7 @@ class MSEMediaController {
this.tparse2 = Date.now();
var level = this.levels[this.level],
frag = this.fragCurrent;
logger.log(`parsed data, type/startPTS/endPTS/startDTS/endDTS/nb:${data.type}/${data.startPTS.toFixed(3)}/${data.endPTS.toFixed(3)}/${data.startDTS.toFixed(3)}/${data.endDTS.toFixed(3)}/${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}`);
var drift = LevelHelper.updateFragPTS(level.details,frag.sn,data.startPTS,data.endPTS);
this.hls.trigger(Event.LEVEL_PTS_UPDATED, {details: level.details, level: this.level, drift: drift});
@ -1097,25 +1130,32 @@ class MSEMediaController {
onError(event, data) {
switch(data.details) {
// abort fragment loading on errors
case ErrorDetails.FRAG_LOAD_ERROR:
case ErrorDetails.FRAG_LOAD_TIMEOUT:
var loadError = this.fragLoadError;
if(loadError) {
loadError++;
} else {
loadError=1;
}
if (loadError <= this.config.fragLoadingMaxRetry) {
this.fragLoadError = loadError;
// retry loading
this.state = State.IDLE;
} else {
logger.error(`mediaController: ${data.details} reaches max retry, redispatch as fatal ...`);
// redispatch same error but with fatal set to true
data.fatal = true;
this.hls.trigger(event, data);
this.state = State.ERROR;
if(!data.fatal) {
var loadError = this.fragLoadError;
if(loadError) {
loadError++;
} else {
loadError=1;
}
if (loadError <= this.config.fragLoadingMaxRetry) {
this.fragLoadError = loadError;
// reset load counter to avoid frag loop loading error
data.frag.loadCounter = 0;
// exponential backoff capped to 64s
var delay = Math.min(Math.pow(2,loadError-1)*this.config.fragLoadingRetryDelay,64000);
logger.warn(`mediaController: 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(`mediaController: ${data.details} reaches max retry, redispatch as fatal ...`);
// redispatch same error but with fatal set to true
data.fatal = true;
this.hls.trigger(event, data);
this.state = State.ERROR;
}
}
break;
case ErrorDetails.FRAG_LOOP_LOADING_ERROR:
@ -1162,24 +1202,35 @@ _checkBuffer() {
media.currentTime = seekAfterBuffered;
this.seekAfterBuffered = undefined;
}
} else if(readyState < 3 ) {
// readyState = 1 or 2
// HAVE_METADATA (numeric value 1) Enough of the resource has been obtained that the duration of the resource is available.
// The API will no longer throw an exception when seeking.
// HAVE_CURRENT_DATA (numeric value 2) Data for the immediate current playback position is available,
// but either not enough data is available that the user agent could
// successfully advance the current playback position
var currentTime = media.currentTime;
var bufferInfo = this.bufferInfo(currentTime,0);
// check if current time is buffered or not
if(bufferInfo.len === 0) {
// no buffer available @ currentTime, check if next buffer is close (in a 300 ms range)
var nextBufferStart = bufferInfo.nextStart;
if(nextBufferStart && (nextBufferStart - currentTime < 0.3)) {
// next buffer is close ! adjust currentTime to nextBufferStart
// this will ensure effective video decoding
logger.log(`adjust currentTime from ${currentTime} to ${nextBufferStart}`);
media.currentTime = nextBufferStart;
} else {
var currentTime = media.currentTime,
bufferInfo = this.bufferInfo(currentTime,0),
isPlaying = !(media.paused || media.ended || media.seeking || readyState < 3),
jumpThreshold = 0.2;
// check buffer upfront
// if less than 200ms is buffered, and media is playing but playhead is not moving,
// and we have a new buffer range available upfront, let's seek to that one
if(bufferInfo.len <= jumpThreshold) {
if(currentTime > media.playbackRate*this.lastCurrentTime || !isPlaying) {
// playhead moving or media not playing
jumpThreshold = 0;
} else {
logger.trace('playback seems stuck');
}
// if we are below threshold, try to jump if next buffer range is close
if(bufferInfo.len <= jumpThreshold) {
// no buffer available @ currentTime, check if next buffer is close (more than 5ms diff but within a 300 ms range)
var nextBufferStart = bufferInfo.nextStart, delta = nextBufferStart-currentTime;
if(nextBufferStart &&
(delta < 0.3) &&
(delta > 0.005) &&
!media.seeking) {
// next buffer is close ! adjust currentTime to nextBufferStart
// this will ensure effective video decoding
logger.log(`adjust currentTime from ${currentTime} to ${nextBufferStart}`);
media.currentTime = nextBufferStart;
}
}
}
}