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

update hls playback

This commit is contained in:
Luke Pulverenti 2016-04-20 14:51:47 -04:00
parent 7919706532
commit 396b125d66
27 changed files with 438 additions and 728 deletions

View file

@ -67,7 +67,7 @@ class BufferController extends EventHandler {
this.mediaSource = null;
this.media = null;
this.pendingTracks = null;
this.sourceBuffer = {};
this.sourceBuffer = null;
}
this.onmso = this.onmse = this.onmsc = null;
this.hls.trigger(Event.MEDIA_DETACHED);
@ -122,16 +122,18 @@ class BufferController extends EventHandler {
onBufferReset() {
var sourceBuffer = this.sourceBuffer;
for(var type in sourceBuffer) {
var sb = sourceBuffer[type];
try {
this.mediaSource.removeSourceBuffer(sb);
sb.removeEventListener('updateend', this.onsbue);
sb.removeEventListener('error', this.onsbe);
} catch(err) {
if (sourceBuffer) {
for(var type in sourceBuffer) {
var sb = sourceBuffer[type];
try {
this.mediaSource.removeSourceBuffer(sb);
sb.removeEventListener('updateend', this.onsbue);
sb.removeEventListener('error', this.onsbe);
} catch(err) {
}
}
this.sourceBuffer = null;
}
this.sourceBuffer = {};
this.flushRange = [];
this.appended = 0;
}
@ -144,24 +146,19 @@ class BufferController extends EventHandler {
return;
}
var sourceBuffer = this.sourceBuffer,mediaSource = this.mediaSource;
for (trackName in tracks) {
if(!sourceBuffer[trackName]) {
if (!this.sourceBuffer) {
var sourceBuffer = {}, mediaSource = this.mediaSource;
for (trackName in tracks) {
track = tracks[trackName];
// use levelCodec as first priority
codec = track.levelCodec || track.codec;
mimeType = `${track.container};codecs=${codec}`;
logger.log(`creating sourceBuffer with mimeType:${mimeType}`);
try {
sb = sourceBuffer[trackName] = mediaSource.addSourceBuffer(mimeType);
sb.addEventListener('updateend', this.onsbue);
sb.addEventListener('error', this.onsbe);
} catch(err) {
logger.error(`error while trying to add sourceBuffer:${err.message}`);
this.hls.trigger(Event.ERROR, {type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_ADD_CODEC_ERROR, fatal: false, err: err, mimeType : mimeType});
}
sb = sourceBuffer[trackName] = mediaSource.addSourceBuffer(mimeType);
sb.addEventListener('updateend', this.onsbue);
sb.addEventListener('error', this.onsbe);
}
this.sourceBuffer = sourceBuffer;
}
}
@ -226,8 +223,10 @@ class BufferController extends EventHandler {
// let's recompute this.appended, which is used to avoid flush looping
var appended = 0;
var sourceBuffer = this.sourceBuffer;
for (var type in sourceBuffer) {
appended += sourceBuffer[type].buffered.length;
if (sourceBuffer) {
for (var type in sourceBuffer) {
appended += sourceBuffer[type].buffered.length;
}
}
this.appended = appended;
this.hls.trigger(Event.BUFFER_FLUSHED);
@ -252,16 +251,9 @@ class BufferController extends EventHandler {
var segment = segments.shift();
try {
//logger.log(`appending ${segment.type} SB, size:${segment.data.length});
if(sourceBuffer[segment.type]) {
sourceBuffer[segment.type].appendBuffer(segment.data);
this.appendError = 0;
this.appended++;
} else {
// in case we don't have any source buffer matching with this segment type,
// it means that Mediasource fails to create sourcebuffer
// discard this segment, and trigger update end
this.onSBUpdateEnd();
}
sourceBuffer[segment.type].appendBuffer(segment.data);
this.appendError = 0;
this.appended++;
} catch(err) {
// in case any error occured while appending, put back segment in segments table
logger.error(`error while trying to append buffer:${err.message}`);

View file

@ -8,30 +8,20 @@ import EventHandler from '../event-handler';
class CapLevelController extends EventHandler {
constructor(hls) {
super(hls,
Event.FPS_DROP_LEVEL_CAPPING,
Event.MEDIA_ATTACHING,
Event.MANIFEST_PARSED);
}
destroy() {
if (this.hls.config.capLevelToPlayerSize) {
this.media = this.restrictedLevels = null;
this.media = null;
this.autoLevelCapping = Number.POSITIVE_INFINITY;
if (this.timer) {
this.timer = clearInterval(this.timer);
}
}
}
onFpsDropLevelCapping(data) {
if (!this.restrictedLevels) {
this.restrictedLevels = [];
}
if (!this.isLevelRestricted(data.droppedLevel)) {
this.restrictedLevels.push(data.droppedLevel);
}
}
onMediaAttaching(data) {
this.media = data.media instanceof HTMLVideoElement ? data.media : null;
}
@ -66,7 +56,7 @@ class CapLevelController extends EventHandler {
* returns level should be the one with the dimensions equal or greater than the media (player) dimensions (so the video will be downscaled)
*/
getMaxLevel(capLevelIndex) {
let result = 0,
let result,
i,
level,
mWidth = this.mediaWidth,
@ -76,9 +66,6 @@ class CapLevelController extends EventHandler {
for (i = 0; i <= capLevelIndex; i++) {
level = this.levels[i];
if (this.isLevelRestricted(i)) {
break;
}
result = i;
lWidth = level.width;
lHeight = level.height;
@ -89,10 +76,6 @@ class CapLevelController extends EventHandler {
return result;
}
isLevelRestricted(level) {
return (this.restrictedLevels && this.restrictedLevels.indexOf(level) !== -1) ? true : false;
}
get contentScaleFactor() {
let pixelRatio = 1;
try {

View file

@ -3,72 +3,46 @@
*/
import Event from '../events';
import EventHandler from '../event-handler';
import {logger} from '../utils/logger';
class FPSController extends EventHandler{
class FPSController {
constructor(hls) {
super(hls, Event.MEDIA_ATTACHING);
this.hls = hls;
this.timer = setInterval(this.checkFPS, hls.config.fpsDroppedMonitoringPeriod);
}
destroy() {
if (this.timer) {
clearInterval(this.timer);
clearInterval(this.timer);
}
this.isVideoPlaybackQualityAvailable = false;
}
onMediaAttaching(data) {
if (this.hls.config.capLevelOnFPSDrop) {
this.video = data.media instanceof HTMLVideoElement ? data.media : null;
if (typeof this.video.getVideoPlaybackQuality === 'function') {
this.isVideoPlaybackQualityAvailable = true;
}
clearInterval(this.timer);
this.timer = setInterval(this.checkFPSInterval.bind(this), this.hls.config.fpsDroppedMonitoringPeriod);
}
}
checkFPS(video, decodedFrames, droppedFrames) {
let currentTime = performance.now();
if (decodedFrames) {
if (this.lastTime) {
let currentPeriod = currentTime - this.lastTime,
currentDropped = droppedFrames - this.lastDroppedFrames,
currentDecoded = decodedFrames - this.lastDecodedFrames,
droppedFPS = 1000 * currentDropped / currentPeriod;
this.hls.trigger(Event.FPS_DROP, {currentDropped: currentDropped, currentDecoded: currentDecoded, totalDroppedFrames: droppedFrames});
if (droppedFPS > 0) {
//logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
if (currentDropped > this.hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
let currentLevel = this.hls.currentLevel;
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
if (currentLevel > 0 && (this.hls.autoLevelCapping === -1 || this.hls.autoLevelCapping >= currentLevel)) {
currentLevel = currentLevel - 1;
this.hls.trigger(Event.FPS_DROP_LEVEL_CAPPING, {level: currentLevel, droppedLevel: this.hls.currentLevel});
this.hls.autoLevelCapping = currentLevel;
this.hls.streamController.nextLevelSwitch();
checkFPS() {
var v = this.hls.video;
if (v) {
var decodedFrames = v.webkitDecodedFrameCount, droppedFrames = v.webkitDroppedFrameCount, currentTime = new Date();
if (decodedFrames) {
if (this.lastTime) {
var currentPeriod = currentTime - this.lastTime;
var currentDropped = droppedFrames - this.lastDroppedFrames;
var currentDecoded = decodedFrames - this.lastDecodedFrames;
var decodedFPS = 1000 * currentDecoded / currentPeriod;
var droppedFPS = 1000 * currentDropped / currentPeriod;
if (droppedFPS > 0) {
logger.log(`checkFPS : droppedFPS/decodedFPS:${droppedFPS.toFixed(1)}/${decodedFPS.toFixed(1)}`);
if (currentDropped > this.hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
logger.warn('drop FPS ratio greater than max allowed value');
this.hls.trigger(Event.FPS_DROP, {currentDropped: currentDropped, currentDecoded: currentDecoded, totalDroppedFrames: droppedFrames});
}
}
}
this.lastTime = currentTime;
this.lastDroppedFrames = droppedFrames;
this.lastDecodedFrames = decodedFrames;
}
this.lastTime = currentTime;
this.lastDroppedFrames = droppedFrames;
this.lastDecodedFrames = decodedFrames;
}
}
checkFPSInterval() {
if (this.video) {
if (this.isVideoPlaybackQualityAvailable) {
let videoPlaybackQuality = this.video.getVideoPlaybackQuality();
this.checkFPS(this.video, videoPlaybackQuality.totalVideoFrames, videoPlaybackQuality.droppedVideoFrames);
} else {
this.checkFPS(this.video, this.video.webkitDecodedFrameCount, this.video.webkitDroppedFrameCount);
}
}
}
}
export default FPSController;

View file

@ -234,15 +234,10 @@ class LevelController extends EventHandler {
onLevelLoaded(data) {
// check if current playlist is a live playlist
if (data.details.live) {
if (data.details.live && !this.timer) {
// if live playlist we will have to reload it periodically
// set reload period to average of the frag duration, if average not set then use playlist target duration
let timerInterval = data.details.averagetargetduration ? data.details.averagetargetduration : data.details.targetduration;
if (!this.timer || timerInterval !== this.timerInterval) {
clearInterval(this.timer);
this.timer = setInterval(this.ontick, 1000 * timerInterval);
this.timerInterval = timerInterval;
}
// set reload period to playlist target duration
this.timer = setInterval(this.ontick, 1000 * data.details.targetduration);
}
if (!data.details.live && this.timer) {
// playlist is not live and timer is armed : stopping it

View file

@ -218,7 +218,7 @@ class StreamController extends EventHandler {
// level 1 loaded [182580162,182580168] <============= here we should have bufferEnd > end. in that case break to avoid reloading 182580168
// level 1 loaded [182580164,182580171]
//
if (bufferEnd > end) {
if (levelDetails.PTSKnown && bufferEnd > end) {
break;
}
@ -486,13 +486,11 @@ class StreamController extends EventHandler {
fragCurrent.loader.abort();
}
this.fragCurrent = null;
// flush everything
this.hls.trigger(Event.BUFFER_FLUSHING, {startOffset: 0, endOffset: Number.POSITIVE_INFINITY});
this.state = State.PAUSED;
// increase fragment load Index to avoid frag loop loading error after buffer flush
this.fragLoadIdx += 2 * this.config.fragLoadingLoopThreshold;
// speed up switching, trigger timer function
this.tick();
this.state = State.PAUSED;
// flush everything
this.hls.trigger(Event.BUFFER_FLUSHING, {startOffset: 0, endOffset: Number.POSITIVE_INFINITY});
}
/*
@ -515,12 +513,14 @@ class StreamController extends EventHandler {
we should take into account new segment fetch time
*/
var fetchdelay, currentRange, nextRange;
// increase fragment load Index to avoid frag loop loading error after buffer flush
this.fragLoadIdx += 2 * this.config.fragLoadingLoopThreshold;
currentRange = this.getBufferRange(this.media.currentTime);
if (currentRange && currentRange.start > 1) {
// flush buffer preceding current fragment (flush until current fragment start offset)
// minus 1s to avoid video freezing, that could happen if we flush keyframe of current video ...
this.hls.trigger(Event.BUFFER_FLUSHING, {startOffset: 0, endOffset: currentRange.start - 1});
this.state = State.PAUSED;
this.hls.trigger(Event.BUFFER_FLUSHING, {startOffset: 0, endOffset: currentRange.start - 1});
}
if (!this.media.paused) {
// add a safety delay of 1s
@ -540,17 +540,15 @@ class StreamController extends EventHandler {
// we can flush buffer range following this one without stalling playback
nextRange = this.followingBufferRange(nextRange);
if (nextRange) {
// flush position is the start position of this new buffer
this.hls.trigger(Event.BUFFER_FLUSHING, {startOffset: nextRange.start, endOffset: Number.POSITIVE_INFINITY});
this.state = State.PAUSED;
// if we are here, we can also cancel any loading/demuxing in progress, as they are useless
var fragCurrent = this.fragCurrent;
if (fragCurrent && fragCurrent.loader) {
fragCurrent.loader.abort();
}
this.fragCurrent = null;
// increase fragment load Index to avoid frag loop loading error after buffer flush
this.fragLoadIdx += 2 * this.config.fragLoadingLoopThreshold;
// flush position is the start position of this new buffer
this.state = State.PAUSED;
this.hls.trigger(Event.BUFFER_FLUSHING, {startOffset: nextRange.start, endOffset: Number.POSITIVE_INFINITY});
}
}
}
@ -1047,8 +1045,9 @@ _checkBuffer() {
// next buffer is close ! adjust currentTime to nextBufferStart
// this will ensure effective video decoding
logger.log(`adjust currentTime from ${media.currentTime} to next buffered @ ${nextBufferStart} + nudge ${this.seekHoleNudgeDuration}`);
let hole = nextBufferStart + this.seekHoleNudgeDuration - media.currentTime;
media.currentTime = nextBufferStart + this.seekHoleNudgeDuration;
this.hls.trigger(Event.ERROR, {type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_SEEK_OVER_HOLE, fatal: false});
this.hls.trigger(Event.ERROR, {type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_SEEK_OVER_HOLE, fatal: false, hole : hole});
}
}
} else {