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-18 14:07:26 -05:00
parent 4572a9dbaf
commit 576ca0442c
22 changed files with 472 additions and 303 deletions

View file

@ -3,23 +3,22 @@
*/
import Event from '../events';
import EventHandler from '../event-handler';
class AbrController {
class AbrController extends EventHandler {
constructor(hls) {
this.hls = hls;
super(hls, Event.FRAG_LOAD_PROGRESS);
this.lastfetchlevel = 0;
this._autoLevelCapping = -1;
this._nextAutoLevel = -1;
this.onflp = this.onFragmentLoadProgress.bind(this);
hls.on(Event.FRAG_LOAD_PROGRESS, this.onflp);
}
destroy() {
this.hls.off(Event.FRAG_LOAD_PROGRESS, this.onflp);
EventHandler.prototype.destroy.call(this);
}
onFragmentLoadProgress(event, data) {
onFragLoadProgress(data) {
var stats = data.stats;
if (stats.aborted === undefined) {
this.lastfetchduration = (performance.now() - stats.trequest) / 1000;

View file

@ -3,35 +3,29 @@
*/
import Event from '../events';
import EventHandler from '../event-handler';
import {logger} from '../utils/logger';
import {ErrorTypes, ErrorDetails} from '../errors';
class LevelController {
class LevelController extends EventHandler {
constructor(hls) {
this.hls = hls;
this.onml = this.onManifestLoaded.bind(this);
this.onll = this.onLevelLoaded.bind(this);
this.onerr = this.onError.bind(this);
super(hls,
Event.MANIFEST_LOADED,
Event.LEVEL_LOADED,
Event.ERROR);
this.ontick = this.tick.bind(this);
hls.on(Event.MANIFEST_LOADED, this.onml);
hls.on(Event.LEVEL_LOADED, this.onll);
hls.on(Event.ERROR, this.onerr);
this._manualLevel = this._autoLevelCapping = -1;
}
destroy() {
var hls = this.hls;
hls.off(Event.MANIFEST_LOADED, this.onml);
hls.off(Event.LEVEL_LOADED, this.onll);
hls.off(Event.ERROR, this.onerr);
if (this.timer) {
clearInterval(this.timer);
}
this._manualLevel = -1;
}
onManifestLoaded(event, data) {
onManifestLoaded(data) {
var levels0 = [], levels = [], bitrateStart, i, bitrateSet = {}, videoCodecFound = false, audioCodecFound = false, hls = this.hls;
// regroup redundant level together
@ -166,7 +160,7 @@ class LevelController {
this._startLevel = newLevel;
}
onError(event, data) {
onError(data) {
if(data.fatal) {
return;
}
@ -224,7 +218,7 @@ class LevelController {
}
}
onLevelLoaded(event, data) {
onLevelLoaded(data) {
// check if current playlist is a live playlist
if (data.details.live && !this.timer) {
// if live playlist we will have to reload it periodically

View file

@ -4,6 +4,7 @@
import Demuxer from '../demux/demuxer';
import Event from '../events';
import EventHandler from '../event-handler';
import {logger} from '../utils/logger';
import BinarySearch from '../utils/binary-search';
import LevelHelper from '../helper/level-helper';
@ -23,39 +24,31 @@ const State = {
BUFFER_FLUSHING : 8
};
class MSEMediaController {
class MSEMediaController extends EventHandler {
constructor(hls) {
super(hls, Event.MEDIA_ATTACHING,
Event.MEDIA_DETACHING,
Event.MANIFEST_PARSED,
Event.LEVEL_LOADED,
Event.KEY_LOADED,
Event.FRAG_LOADED,
Event.FRAG_PARSING_INIT_SEGMENT,
Event.FRAG_PARSING_DATA,
Event.FRAG_PARSED,
Event.ERROR);
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);
// internal listeners
this.onmediaatt0 = this.onMediaAttaching.bind(this);
this.onmediadet0 = this.onMediaDetaching.bind(this);
this.onmp = this.onManifestParsed.bind(this);
this.onll = this.onLevelLoaded.bind(this);
this.onfl = this.onFragLoaded.bind(this);
this.onkl = this.onKeyLoaded.bind(this);
this.onis = this.onInitSegment.bind(this);
this.onfpg = this.onFragParsing.bind(this);
this.onfp = this.onFragParsed.bind(this);
this.onerr = this.onError.bind(this);
this.ontick = this.tick.bind(this);
hls.on(Event.MEDIA_ATTACHING, this.onmediaatt0);
hls.on(Event.MEDIA_DETACHING, this.onmediadet0);
hls.on(Event.MANIFEST_PARSED, this.onmp);
}
destroy() {
this.stop();
var hls = this.hls;
hls.off(Event.MEDIA_ATTACHING, this.onmediaatt0);
hls.off(Event.MEDIA_DETACHING, this.onmediadet0);
hls.off(Event.MANIFEST_PARSED, this.onmp);
EventHandler.prototype.destroy.call(this);
this.state = State.IDLE;
}
@ -87,13 +80,6 @@ class MSEMediaController {
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);
hls.on(Event.FRAG_PARSED, this.onfp);
hls.on(Event.ERROR, this.onerr);
hls.on(Event.LEVEL_LOADED, this.onll);
hls.on(Event.KEY_LOADED, this.onkl);
}
stop() {
@ -128,14 +114,6 @@ class MSEMediaController {
this.demuxer.destroy();
this.demuxer = null;
}
var hls = this.hls;
hls.off(Event.FRAG_LOADED, this.onfl);
hls.off(Event.FRAG_PARSED, this.onfp);
hls.off(Event.FRAG_PARSING_DATA, this.onfpg);
hls.off(Event.LEVEL_LOADED, this.onll);
hls.off(Event.KEY_LOADED, this.onkl);
hls.off(Event.FRAG_PARSING_INIT_SEGMENT, this.onis);
hls.off(Event.ERROR, this.onerr);
}
tick() {
@ -189,7 +167,7 @@ class MSEMediaController {
// we are not at playback start, get next load level from level Controller
level = hls.nextLoadLevel;
}
var bufferInfo = this.bufferInfo(pos,0.3),
var bufferInfo = this.bufferInfo(pos,this.config.maxBufferHole),
bufferLen = bufferInfo.len,
bufferEnd = bufferInfo.end,
fragPrevious = this.fragPrevious,
@ -208,7 +186,9 @@ class MSEMediaController {
this.level = level;
levelDetails = this.levels[level].details;
// if level info not retrieved yet, switch state and wait for level retrieval
if (typeof levelDetails === 'undefined') {
// if live playlist, ensure that new playlist has been refreshed to avoid loading/try to load
// a useless and outdated fragment (that might even introduce load error if it is already out of the live playlist)
if (typeof levelDetails === 'undefined' || levelDetails.live && this.levelLastLoaded !== level) {
this.state = State.WAITING_LEVEL;
break;
}
@ -364,7 +344,7 @@ class MSEMediaController {
}
pos = v.currentTime;
var fragLoadedDelay = (frag.expectedLen - frag.loaded) / loadRate;
var bufferStarvationDelay = this.bufferInfo(pos,0.3).end - pos;
var bufferStarvationDelay = this.bufferInfo(pos,this.config.maxBufferHole).end - pos;
var fragLevelNextLoadedDelay = frag.duration * this.levels[hls.nextLoadLevel].bitrate / (8 * loadRate); //bps/Bps
/* if we have less than 2 frag duration in buffer and if frag loaded delay is greater than buffer starvation delay
... and also bigger than duration needed to load fragment at next level ...*/
@ -545,6 +525,7 @@ class MSEMediaController {
bufferLen = bufferEnd - pos;
} else if ((pos + maxHoleDuration) < start) {
bufferStartNext = start;
break;
}
}
return {len: bufferLen, start: bufferStart, end: bufferEnd, nextStart : bufferStartNext};
@ -797,7 +778,7 @@ class MSEMediaController {
}
}
onMediaAttaching(event, data) {
onMediaAttaching(data) {
var media = this.media = data.media;
// setup the media source
var ms = this.mediaSource = new MediaSource();
@ -870,7 +851,7 @@ class MSEMediaController {
if (this.state === State.FRAG_LOADING) {
// check if currently loaded fragment is inside buffer.
//if outside, cancel fragment loading, otherwise do nothing
if (this.bufferInfo(this.media.currentTime,0.3).len === 0) {
if (this.bufferInfo(this.media.currentTime,this.config.maxBufferHole).len === 0) {
logger.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
var fragCurrent = this.fragCurrent;
if (fragCurrent) {
@ -919,7 +900,7 @@ class MSEMediaController {
}
onManifestParsed(event, data) {
onManifestParsed(data) {
var aac = false, heaac = false, codecs;
data.levels.forEach(level => {
// detect if we have different kind of audio codecs used amongst playlists
@ -945,13 +926,14 @@ class MSEMediaController {
}
}
onLevelLoaded(event,data) {
onLevelLoaded(data) {
var newDetails = data.details,
newLevelId = data.level,
curLevel = this.levels[newLevelId],
duration = newDetails.totalduration;
logger.log(`level ${newLevelId} loaded [${newDetails.startSN},${newDetails.endSN}],duration:${duration}`);
this.levelLastLoaded = newLevelId;
if (newDetails.live) {
var curDetails = curLevel.details;
@ -998,7 +980,7 @@ class MSEMediaController {
}
}
onFragLoaded(event, data) {
onFragLoaded(data) {
var fragCurrent = this.fragCurrent;
if (this.state === State.FRAG_LOADING &&
fragCurrent &&
@ -1039,7 +1021,7 @@ class MSEMediaController {
this.fragLoadError = 0;
}
onInitSegment(event, data) {
onFragParsingInitSegment(data) {
if (this.state === State.PARSING) {
// 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
@ -1098,7 +1080,7 @@ class MSEMediaController {
}
}
onFragParsing(event, data) {
onFragParsingData(data) {
if (this.state === State.PARSING) {
this.tparse2 = Date.now();
var level = this.levels[this.level],
@ -1115,7 +1097,7 @@ class MSEMediaController {
//trigger handler right now
this.tick();
} else {
logger.warn(`not in PARSING state, discarding ${event}`);
logger.warn(`not in PARSING state, ignoring FRAG_PARSING_DATA event`);
}
}
@ -1128,7 +1110,7 @@ class MSEMediaController {
}
}
onError(event, data) {
onError(data) {
switch(data.details) {
case ErrorDetails.FRAG_LOAD_ERROR:
case ErrorDetails.FRAG_LOAD_TIMEOUT:
@ -1153,7 +1135,7 @@ class MSEMediaController {
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.hls.trigger(Event.ERROR, data);
this.state = State.ERROR;
}
}
@ -1216,14 +1198,14 @@ _checkBuffer() {
// playhead moving or media not playing
jumpThreshold = 0;
} else {
logger.trace('playback seems stuck');
logger.log('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)
// no buffer available @ currentTime, check if next buffer is close (more than 5ms diff but within a config.maxSeekHole second range)
var nextBufferStart = bufferInfo.nextStart, delta = nextBufferStart-currentTime;
if(nextBufferStart &&
(delta < 0.3) &&
(delta < this.config.maxSeekHole) &&
(delta > 0.005) &&
!media.seeking) {
// next buffer is close ! adjust currentTime to nextBufferStart

View file

@ -0,0 +1,66 @@
/*
*
* All objects in the event handling chain should inherit from this class
*
*/
//import {logger} from './utils/logger';
class EventHandler {
constructor(hls, ...events) {
this.hls = hls;
this.onEvent = this.onEvent.bind(this);
this.handledEvents = events;
this.useGenericHandler = true;
this.registerListeners();
}
destroy() {
this.unregisterListeners();
}
isEventHandler() {
return typeof this.handledEvents === 'object' && this.handledEvents.length && typeof this.onEvent === 'function';
}
registerListeners() {
if (this.isEventHandler()) {
this.handledEvents.forEach(function(event) {
if (event === 'hlsEventGeneric') {
throw new Error('Forbidden event name: ' + event);
}
this.hls.on(event, this.onEvent);
}.bind(this));
}
}
unregisterListeners() {
if (this.isEventHandler()) {
this.handledEvents.forEach(function(event) {
this.hls.off(event, this.onEvent);
}.bind(this));
}
}
/*
* arguments: event (string), data (any)
*/
onEvent(event, data) {
this.onEventGeneric(event, data);
}
onEventGeneric(event, data) {
var eventToFunction = function(event, data) {
var funcName = 'on' + event.replace('hls', '');
if (typeof this[funcName] !== 'function') {
throw new Error(`Event ${event} has no generic handler in this ${this.constructor.name} class (tried ${funcName})`);
}
return this[funcName].bind(this, data);
};
eventToFunction.call(this, event, data).call();
}
}
export default EventHandler;

View file

@ -1,4 +1,4 @@
export default {
module.exports = {
// fired before MediaSource is attaching to media element - data: { media }
MEDIA_ATTACHING: 'hlsMediaAttaching',
// fired when MediaSource has been succesfully attached to media element - data: { }
@ -20,7 +20,7 @@ export default {
// fired when a level's details have been updated based on previous details, after it has been loaded. - data: { details : levelDetails object, level : id of updated level }
LEVEL_UPDATED: 'hlsLevelUpdated',
// fired when a level's PTS information has been updated after parsing a fragment - data: { details : levelDetails object, level : id of updated level, drift: PTS drift observed when parsing last fragment }
LEVEL_PTS_UPDATED: 'hlsPTSUpdated',
LEVEL_PTS_UPDATED: 'hlsLevelPtsUpdated',
// fired when a level switch is requested - data: { level : id of new level }
LEVEL_SWITCH: 'hlsLevelSwitch',
// fired when a fragment loading starts - data: { frag : fragment object}
@ -34,7 +34,7 @@ export default {
// fired when Init Segment has been extracted from fragment - data: { moov : moov MP4 box, codecs : codecs found while parsing fragment}
FRAG_PARSING_INIT_SEGMENT: 'hlsFragParsingInitSegment',
// fired when parsing id3 is completed - data: { samples : [ id3 samples pes ] }
FRAG_PARSING_METADATA: 'hlsFraParsingMetadata',
FRAG_PARSING_METADATA: 'hlsFragParsingMetadata',
// fired when moof/mdat have been extracted from fragment - data: { moof : moof MP4 box, mdat : mdat MP4 box}
FRAG_PARSING_DATA: 'hlsFragParsingData',
// fired when fragment parsing is completed - data: undefined
@ -44,7 +44,7 @@ export default {
// fired when fragment matching with current media position is changing - data : { frag : fragment object }
FRAG_CHANGED: 'hlsFragChanged',
// Identifier for a FPS drop event - data: {curentDropped, currentDecoded, totalDroppedFrames}
FPS_DROP: 'hlsFPSDrop',
FPS_DROP: 'hlsFpsDrop',
// Identifier for an error event - data: { type : error type, details : error details, fatal : if true, hls.js cannot/will not try to recover, if false, hls.js will try to recover,other error specific data}
ERROR: 'hlsError',
// fired when hls.js instance starts destroying. Different from MEDIA_DETACHED as one could want to detach and reattach a media to the instance of hls.js to handle mid-rolls for example

View file

@ -41,6 +41,8 @@ class Hls {
debug: false,
maxBufferLength: 30,
maxBufferSize: 60 * 1000 * 1000,
maxBufferHole: 0.3,
maxSeekHole: 2,
liveSyncDurationCount:3,
liveMaxLatencyDurationCount: Infinity,
maxMaxBufferLength: 600,

View file

@ -3,14 +3,13 @@
*/
import Event from '../events';
import EventHandler from '../event-handler';
import {ErrorTypes, ErrorDetails} from '../errors';
class FragmentLoader {
class FragmentLoader extends EventHandler {
constructor(hls) {
this.hls = hls;
this.onfl = this.onFragLoading.bind(this);
hls.on(Event.FRAG_LOADING, this.onfl);
super(hls, Event.FRAG_LOADING);
}
destroy() {
@ -18,10 +17,10 @@ class FragmentLoader {
this.loader.destroy();
this.loader = null;
}
this.hls.off(Event.FRAG_LOADING, this.onfl);
EventHandler.prototype.destroy.call(this);
}
onFragLoading(event, data) {
onFragLoading(data) {
var frag = data.frag;
this.frag = frag;
this.frag.loaded = 0;

View file

@ -3,16 +3,15 @@
*/
import Event from '../events';
import EventHandler from '../event-handler';
import {ErrorTypes, ErrorDetails} from '../errors';
class KeyLoader {
class KeyLoader extends EventHandler {
constructor(hls) {
this.hls = hls;
super(hls, Event.KEY_LOADING);
this.decryptkey = null;
this.decrypturl = null;
this.ondkl = this.onDecryptKeyLoading.bind(this);
hls.on(Event.KEY_LOADING, this.ondkl);
}
destroy() {
@ -20,10 +19,10 @@ class KeyLoader {
this.loader.destroy();
this.loader = null;
}
this.hls.off(Event.KEY_LOADING, this.ondkl);
EventHandler.prototype.destroy.call(this);
}
onDecryptKeyLoading(event, data) {
onKeyLoading(data) {
var frag = this.frag = data.frag,
decryptdata = frag.decryptdata,
uri = decryptdata.uri;

View file

@ -3,19 +3,18 @@
*/
import Event from '../events';
import EventHandler from '../event-handler';
import {ErrorTypes, ErrorDetails} from '../errors';
import URLHelper from '../utils/url';
import AttrList from '../utils/attr-list';
//import {logger} from '../utils/logger';
class PlaylistLoader {
class PlaylistLoader extends EventHandler {
constructor(hls) {
this.hls = hls;
this.onml = this.onManifestLoading.bind(this);
this.onll = this.onLevelLoading.bind(this);
hls.on(Event.MANIFEST_LOADING, this.onml);
hls.on(Event.LEVEL_LOADING, this.onll);
super(hls,
Event.MANIFEST_LOADING,
Event.LEVEL_LOADING);
}
destroy() {
@ -24,15 +23,14 @@ class PlaylistLoader {
this.loader = null;
}
this.url = this.id = null;
this.hls.off(Event.MANIFEST_LOADING, this.onml);
this.hls.off(Event.LEVEL_LOADING, this.onll);
EventHandler.prototype.destroy.call(this);
}
onManifestLoading(event, data) {
onManifestLoading(data) {
this.load(data.url, null);
}
onLevelLoading(event, data) {
onLevelLoading(data) {
this.load(data.url, data.level, data.id);
}

View file

@ -129,6 +129,7 @@ class MP4Remuxer {
mdat, moof,
firstPTS, firstDTS, lastDTS,
pts, dts, ptsnorm, dtsnorm,
flags,
samples = [];
/* concatenate the video data and construct the mdat in place
(need 8 more bytes to fill length and mpdat type) */
@ -152,7 +153,7 @@ class MP4Remuxer {
dts = avcSample.dts - this._initDTS;
// ensure DTS is not bigger than PTS
dts = Math.min(pts,dts);
//logger.log(`Video/PTS/DTS:${pts}/${dts}`);
//logger.log(`Video/PTS/DTS:${Math.round(pts/90)}/${Math.round(dts/90)}`);
// if not first AVC sample of video track, normalize PTS/DTS with previous sample value
// and ensure that sample duration is positive
if (lastDTS !== undefined) {
@ -201,13 +202,14 @@ class MP4Remuxer {
degradPrio: 0
}
};
flags = mp4Sample.flags;
if (avcSample.key === true) {
// the current sample is a key frame
mp4Sample.flags.dependsOn = 2;
mp4Sample.flags.isNonSync = 0;
flags.dependsOn = 2;
flags.isNonSync = 0;
} else {
mp4Sample.flags.dependsOn = 1;
mp4Sample.flags.isNonSync = 1;
flags.dependsOn = 1;
flags.isNonSync = 1;
}
samples.push(mp4Sample);
lastDTS = dtsnorm;
@ -222,7 +224,7 @@ class MP4Remuxer {
track.len = 0;
track.nbNalu = 0;
if(samples.length && navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
var flags = samples[0].flags;
flags = samples[0].flags;
// chrome workaround, mark first sample as being a Random Access Point to avoid sourcebuffer append issue
// https://code.google.com/p/chromium/issues/detail?id=229412
flags.dependsOn = 2;
@ -270,7 +272,7 @@ class MP4Remuxer {
unit = aacSample.unit;
pts = aacSample.pts - this._initDTS;
dts = aacSample.dts - this._initDTS;
//logger.log(`Audio/PTS:${aacSample.pts.toFixed(0)}`);
//logger.log(`Audio/PTS:${Math.round(pts/90)}`);
// if not first sample
if (lastDTS !== undefined) {
ptsnorm = this._PTSNormalize(pts, lastDTS);

View file

@ -49,7 +49,7 @@ class XhrLoader {
loadInternal() {
var xhr = this.loader = new XMLHttpRequest();
xhr.onreadystatechange = this.statechange.bind(this);
xhr.onloadend = this.loadend.bind(this);
xhr.onprogress = this.loadprogress.bind(this);
xhr.open('GET', this.url, true);
@ -65,13 +65,12 @@ class XhrLoader {
xhr.send();
}
statechange(event) {
loadend(event) {
var xhr = event.currentTarget,
status = xhr.status,
stats = this.stats;
// don't proceed if xhr has been aborted
// 4 = Response from server has been completely loaded.
if (!stats.aborted && xhr.readyState === 4) {
if (!stats.aborted) {
// http status between 200 to 299 are all successful
if (status >= 200 && status < 300) {
window.clearTimeout(this.timeoutHandle);