945 lines
41 KiB
JavaScript
945 lines
41 KiB
JavaScript
|
|
'use strict';
|
||
|
|
|
||
|
|
Object.defineProperty(exports, "__esModule", {
|
||
|
|
value: true
|
||
|
|
});
|
||
|
|
|
||
|
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||
|
|
|
||
|
|
var _binarySearch = require('../utils/binary-search');
|
||
|
|
|
||
|
|
var _binarySearch2 = _interopRequireDefault(_binarySearch);
|
||
|
|
|
||
|
|
var _bufferHelper = require('../helper/buffer-helper');
|
||
|
|
|
||
|
|
var _bufferHelper2 = _interopRequireDefault(_bufferHelper);
|
||
|
|
|
||
|
|
var _demuxer = require('../demux/demuxer');
|
||
|
|
|
||
|
|
var _demuxer2 = _interopRequireDefault(_demuxer);
|
||
|
|
|
||
|
|
var _events = require('../events');
|
||
|
|
|
||
|
|
var _events2 = _interopRequireDefault(_events);
|
||
|
|
|
||
|
|
var _eventHandler = require('../event-handler');
|
||
|
|
|
||
|
|
var _eventHandler2 = _interopRequireDefault(_eventHandler);
|
||
|
|
|
||
|
|
var _levelHelper = require('../helper/level-helper');
|
||
|
|
|
||
|
|
var _levelHelper2 = _interopRequireDefault(_levelHelper);
|
||
|
|
|
||
|
|
var _timeRanges = require('../utils/timeRanges');
|
||
|
|
|
||
|
|
var _timeRanges2 = _interopRequireDefault(_timeRanges);
|
||
|
|
|
||
|
|
var _errors = require('../errors');
|
||
|
|
|
||
|
|
var _logger = require('../utils/logger');
|
||
|
|
|
||
|
|
var _discontinuities = require('../utils/discontinuities');
|
||
|
|
|
||
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||
|
|
|
||
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||
|
|
|
||
|
|
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
|
||
|
|
|
||
|
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /*
|
||
|
|
* Audio Stream Controller
|
||
|
|
*/
|
||
|
|
|
||
|
|
var State = {
|
||
|
|
STOPPED: 'STOPPED',
|
||
|
|
STARTING: 'STARTING',
|
||
|
|
IDLE: 'IDLE',
|
||
|
|
PAUSED: 'PAUSED',
|
||
|
|
KEY_LOADING: 'KEY_LOADING',
|
||
|
|
FRAG_LOADING: 'FRAG_LOADING',
|
||
|
|
FRAG_LOADING_WAITING_RETRY: 'FRAG_LOADING_WAITING_RETRY',
|
||
|
|
WAITING_TRACK: 'WAITING_TRACK',
|
||
|
|
PARSING: 'PARSING',
|
||
|
|
PARSED: 'PARSED',
|
||
|
|
BUFFER_FLUSHING: 'BUFFER_FLUSHING',
|
||
|
|
ENDED: 'ENDED',
|
||
|
|
ERROR: 'ERROR',
|
||
|
|
WAITING_INIT_PTS: 'WAITING_INIT_PTS'
|
||
|
|
};
|
||
|
|
|
||
|
|
var AudioStreamController = function (_EventHandler) {
|
||
|
|
_inherits(AudioStreamController, _EventHandler);
|
||
|
|
|
||
|
|
function AudioStreamController(hls) {
|
||
|
|
_classCallCheck(this, AudioStreamController);
|
||
|
|
|
||
|
|
var _this = _possibleConstructorReturn(this, (AudioStreamController.__proto__ || Object.getPrototypeOf(AudioStreamController)).call(this, hls, _events2.default.MEDIA_ATTACHED, _events2.default.MEDIA_DETACHING, _events2.default.AUDIO_TRACKS_UPDATED, _events2.default.AUDIO_TRACK_SWITCHING, _events2.default.AUDIO_TRACK_LOADED, _events2.default.KEY_LOADED, _events2.default.FRAG_LOADED, _events2.default.FRAG_PARSING_INIT_SEGMENT, _events2.default.FRAG_PARSING_DATA, _events2.default.FRAG_PARSED, _events2.default.ERROR, _events2.default.BUFFER_CREATED, _events2.default.BUFFER_APPENDED, _events2.default.BUFFER_FLUSHED, _events2.default.INIT_PTS_FOUND));
|
||
|
|
|
||
|
|
_this.config = hls.config;
|
||
|
|
_this.audioCodecSwap = false;
|
||
|
|
_this.ticks = 0;
|
||
|
|
_this._state = State.STOPPED;
|
||
|
|
_this.ontick = _this.tick.bind(_this);
|
||
|
|
_this.initPTS = [];
|
||
|
|
_this.waitingFragment = null;
|
||
|
|
_this.videoTrackCC = null;
|
||
|
|
return _this;
|
||
|
|
}
|
||
|
|
|
||
|
|
_createClass(AudioStreamController, [{
|
||
|
|
key: 'destroy',
|
||
|
|
value: function destroy() {
|
||
|
|
this.stopLoad();
|
||
|
|
if (this.timer) {
|
||
|
|
clearInterval(this.timer);
|
||
|
|
this.timer = null;
|
||
|
|
}
|
||
|
|
_eventHandler2.default.prototype.destroy.call(this);
|
||
|
|
this.state = State.STOPPED;
|
||
|
|
}
|
||
|
|
|
||
|
|
//Signal that video PTS was found
|
||
|
|
|
||
|
|
}, {
|
||
|
|
key: 'onInitPtsFound',
|
||
|
|
value: function onInitPtsFound(data) {
|
||
|
|
var demuxerId = data.id,
|
||
|
|
cc = data.frag.cc,
|
||
|
|
initPTS = data.initPTS;
|
||
|
|
if (demuxerId === 'main') {
|
||
|
|
//Always update the new INIT PTS
|
||
|
|
//Can change due level switch
|
||
|
|
this.initPTS[cc] = initPTS;
|
||
|
|
this.videoTrackCC = cc;
|
||
|
|
_logger.logger.log('InitPTS for cc:' + cc + ' found from video track:' + initPTS);
|
||
|
|
|
||
|
|
//If we are waiting we need to demux/remux the waiting frag
|
||
|
|
//With the new initPTS
|
||
|
|
if (this.state === State.WAITING_INIT_PTS) {
|
||
|
|
this.tick();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'startLoad',
|
||
|
|
value: function startLoad(startPosition) {
|
||
|
|
if (this.tracks) {
|
||
|
|
var lastCurrentTime = this.lastCurrentTime;
|
||
|
|
this.stopLoad();
|
||
|
|
if (!this.timer) {
|
||
|
|
this.timer = setInterval(this.ontick, 100);
|
||
|
|
}
|
||
|
|
this.fragLoadError = 0;
|
||
|
|
if (lastCurrentTime > 0 && startPosition === -1) {
|
||
|
|
_logger.logger.log('audio:override startPosition with lastCurrentTime @' + lastCurrentTime.toFixed(3));
|
||
|
|
this.state = State.IDLE;
|
||
|
|
} else {
|
||
|
|
this.lastCurrentTime = this.startPosition ? this.startPosition : startPosition;
|
||
|
|
this.state = State.STARTING;
|
||
|
|
}
|
||
|
|
this.nextLoadPosition = this.startPosition = this.lastCurrentTime;
|
||
|
|
this.tick();
|
||
|
|
} else {
|
||
|
|
this.startPosition = startPosition;
|
||
|
|
this.state = State.STOPPED;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'stopLoad',
|
||
|
|
value: function stopLoad() {
|
||
|
|
var frag = this.fragCurrent;
|
||
|
|
if (frag) {
|
||
|
|
if (frag.loader) {
|
||
|
|
frag.loader.abort();
|
||
|
|
}
|
||
|
|
this.fragCurrent = null;
|
||
|
|
}
|
||
|
|
this.fragPrevious = null;
|
||
|
|
if (this.demuxer) {
|
||
|
|
this.demuxer.destroy();
|
||
|
|
this.demuxer = null;
|
||
|
|
}
|
||
|
|
this.state = State.STOPPED;
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'tick',
|
||
|
|
value: function tick() {
|
||
|
|
this.ticks++;
|
||
|
|
if (this.ticks === 1) {
|
||
|
|
this.doTick();
|
||
|
|
if (this.ticks > 1) {
|
||
|
|
setTimeout(this.tick, 1);
|
||
|
|
}
|
||
|
|
this.ticks = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'doTick',
|
||
|
|
value: function doTick() {
|
||
|
|
var pos,
|
||
|
|
track,
|
||
|
|
trackDetails,
|
||
|
|
hls = this.hls,
|
||
|
|
config = hls.config;
|
||
|
|
//logger.log('audioStream:' + this.state);
|
||
|
|
switch (this.state) {
|
||
|
|
case State.ERROR:
|
||
|
|
//don't do anything in error state to avoid breaking further ...
|
||
|
|
case State.PAUSED:
|
||
|
|
//don't do anything in paused state either ...
|
||
|
|
case State.BUFFER_FLUSHING:
|
||
|
|
break;
|
||
|
|
case State.STARTING:
|
||
|
|
this.state = State.WAITING_TRACK;
|
||
|
|
this.loadedmetadata = false;
|
||
|
|
break;
|
||
|
|
case State.IDLE:
|
||
|
|
var tracks = this.tracks;
|
||
|
|
// audio tracks not received => exit loop
|
||
|
|
if (!tracks) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
// if video not attached AND
|
||
|
|
// start fragment already requested OR start frag prefetch disable
|
||
|
|
// exit loop
|
||
|
|
// => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
|
||
|
|
if (!this.media && (this.startFragRequested || !config.startFragPrefetch)) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
// determine next candidate fragment to be loaded, based on current position and
|
||
|
|
// end of buffer position
|
||
|
|
// if we have not yet loaded any fragment, start loading from start position
|
||
|
|
if (this.loadedmetadata) {
|
||
|
|
pos = this.media.currentTime;
|
||
|
|
} else {
|
||
|
|
pos = this.nextLoadPosition;
|
||
|
|
if (pos === undefined) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
var media = this.mediaBuffer ? this.mediaBuffer : this.media,
|
||
|
|
bufferInfo = _bufferHelper2.default.bufferInfo(media, pos, config.maxBufferHole),
|
||
|
|
bufferLen = bufferInfo.len,
|
||
|
|
bufferEnd = bufferInfo.end,
|
||
|
|
fragPrevious = this.fragPrevious,
|
||
|
|
maxBufLen = config.maxMaxBufferLength,
|
||
|
|
audioSwitch = this.audioSwitch,
|
||
|
|
trackId = this.trackId;
|
||
|
|
|
||
|
|
// if buffer length is less than maxBufLen try to load a new fragment
|
||
|
|
if ((bufferLen < maxBufLen || audioSwitch) && trackId < tracks.length) {
|
||
|
|
trackDetails = tracks[trackId].details;
|
||
|
|
// if track info not retrieved yet, switch state and wait for track retrieval
|
||
|
|
if (typeof trackDetails === 'undefined') {
|
||
|
|
this.state = State.WAITING_TRACK;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
// we just got done loading the final fragment, check if we need to finalize media stream
|
||
|
|
if (!audioSwitch && !trackDetails.live && fragPrevious && fragPrevious.sn === trackDetails.endSN) {
|
||
|
|
// if we are not seeking or if we are seeking but everything (almost) til the end is buffered, let's signal eos
|
||
|
|
// we don't compare exactly media.duration === bufferInfo.end as there could be some subtle media duration difference when switching
|
||
|
|
// between different renditions. using half frag duration should help cope with these cases.
|
||
|
|
if (!this.media.seeking || this.media.duration - bufferEnd < fragPrevious.duration / 2) {
|
||
|
|
// Finalize the media stream
|
||
|
|
this.hls.trigger(_events2.default.BUFFER_EOS, { type: 'audio' });
|
||
|
|
this.state = State.ENDED;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// find fragment index, contiguous with end of buffer position
|
||
|
|
var fragments = trackDetails.fragments,
|
||
|
|
fragLen = fragments.length,
|
||
|
|
start = fragments[0].start,
|
||
|
|
end = fragments[fragLen - 1].start + fragments[fragLen - 1].duration,
|
||
|
|
frag = void 0;
|
||
|
|
|
||
|
|
// When switching audio track, reload audio as close as possible to currentTime
|
||
|
|
if (audioSwitch) {
|
||
|
|
if (trackDetails.live && !trackDetails.PTSKnown) {
|
||
|
|
_logger.logger.log('switching audiotrack, live stream, unknown PTS,load first fragment');
|
||
|
|
bufferEnd = 0;
|
||
|
|
} else {
|
||
|
|
bufferEnd = pos;
|
||
|
|
// if currentTime (pos) is less than alt audio playlist start time, it means that alt audio is ahead of currentTime
|
||
|
|
if (trackDetails.PTSKnown && pos < start) {
|
||
|
|
// if everything is buffered from pos to start or if audio buffer upfront, let's seek to start
|
||
|
|
if (bufferInfo.end > start || bufferInfo.nextStart) {
|
||
|
|
_logger.logger.log('alt audio track ahead of main track, seek to start of alt audio track');
|
||
|
|
this.media.currentTime = start + 0.05;
|
||
|
|
} else {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (trackDetails.initSegment && !trackDetails.initSegment.data) {
|
||
|
|
frag = trackDetails.initSegment;
|
||
|
|
}
|
||
|
|
// if bufferEnd before start of playlist, load first fragment
|
||
|
|
else if (bufferEnd <= start) {
|
||
|
|
frag = fragments[0];
|
||
|
|
if (this.videoTrackCC !== null && frag.cc !== this.videoTrackCC) {
|
||
|
|
// Ensure we find a fragment which matches the continuity of the video track
|
||
|
|
frag = (0, _discontinuities.findFragWithCC)(fragments, this.videoTrackCC);
|
||
|
|
}
|
||
|
|
if (trackDetails.live && frag.loadIdx && frag.loadIdx === this.fragLoadIdx) {
|
||
|
|
// we just loaded this first fragment, and we are still lagging behind the start of the live playlist
|
||
|
|
// let's force seek to start
|
||
|
|
var nextBuffered = bufferInfo.nextStart ? bufferInfo.nextStart : start;
|
||
|
|
_logger.logger.log('no alt audio available @currentTime:' + this.media.currentTime + ', seeking @' + (nextBuffered + 0.05));
|
||
|
|
this.media.currentTime = nextBuffered + 0.05;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
var foundFrag = void 0;
|
||
|
|
var maxFragLookUpTolerance = config.maxFragLookUpTolerance;
|
||
|
|
var fragNext = fragPrevious ? fragments[fragPrevious.sn - fragments[0].sn + 1] : undefined;
|
||
|
|
var fragmentWithinToleranceTest = function fragmentWithinToleranceTest(candidate) {
|
||
|
|
// offset should be within fragment boundary - config.maxFragLookUpTolerance
|
||
|
|
// this is to cope with situations like
|
||
|
|
// bufferEnd = 9.991
|
||
|
|
// frag[Ø] : [0,10]
|
||
|
|
// frag[1] : [10,20]
|
||
|
|
// bufferEnd is within frag[0] range ... although what we are expecting is to return frag[1] here
|
||
|
|
// frag start frag start+duration
|
||
|
|
// |-----------------------------|
|
||
|
|
// <---> <--->
|
||
|
|
// ...--------><-----------------------------><---------....
|
||
|
|
// previous frag matching fragment next frag
|
||
|
|
// return -1 return 0 return 1
|
||
|
|
//logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`);
|
||
|
|
// Set the lookup tolerance to be small enough to detect the current segment - ensures we don't skip over very small segments
|
||
|
|
var candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration);
|
||
|
|
if (candidate.start + candidate.duration - candidateLookupTolerance <= bufferEnd) {
|
||
|
|
return 1;
|
||
|
|
} // if maxFragLookUpTolerance will have negative value then don't return -1 for first element
|
||
|
|
else if (candidate.start - candidateLookupTolerance > bufferEnd && candidate.start) {
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
};
|
||
|
|
|
||
|
|
if (bufferEnd < end) {
|
||
|
|
if (bufferEnd > end - maxFragLookUpTolerance) {
|
||
|
|
maxFragLookUpTolerance = 0;
|
||
|
|
}
|
||
|
|
// Prefer the next fragment if it's within tolerance
|
||
|
|
if (fragNext && !fragmentWithinToleranceTest(fragNext)) {
|
||
|
|
foundFrag = fragNext;
|
||
|
|
} else {
|
||
|
|
foundFrag = _binarySearch2.default.search(fragments, fragmentWithinToleranceTest);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// reach end of playlist
|
||
|
|
foundFrag = fragments[fragLen - 1];
|
||
|
|
}
|
||
|
|
if (foundFrag) {
|
||
|
|
frag = foundFrag;
|
||
|
|
start = foundFrag.start;
|
||
|
|
//logger.log('find SN matching with pos:' + bufferEnd + ':' + frag.sn);
|
||
|
|
if (fragPrevious && frag.level === fragPrevious.level && frag.sn === fragPrevious.sn) {
|
||
|
|
if (frag.sn < trackDetails.endSN) {
|
||
|
|
frag = fragments[frag.sn + 1 - trackDetails.startSN];
|
||
|
|
_logger.logger.log('SN just loaded, load next one: ' + frag.sn);
|
||
|
|
} else {
|
||
|
|
frag = null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (frag) {
|
||
|
|
//logger.log(' loading frag ' + i +',pos/bufEnd:' + pos.toFixed(3) + '/' + bufferEnd.toFixed(3));
|
||
|
|
if (frag.decryptdata && frag.decryptdata.uri != null && frag.decryptdata.key == null) {
|
||
|
|
_logger.logger.log('Loading key for ' + frag.sn + ' of [' + trackDetails.startSN + ' ,' + trackDetails.endSN + '],track ' + trackId);
|
||
|
|
this.state = State.KEY_LOADING;
|
||
|
|
hls.trigger(_events2.default.KEY_LOADING, { frag: frag });
|
||
|
|
} else {
|
||
|
|
_logger.logger.log('Loading ' + frag.sn + ', cc: ' + frag.cc + ' of [' + trackDetails.startSN + ' ,' + trackDetails.endSN + '],track ' + trackId + ', currentTime:' + pos + ',bufferEnd:' + bufferEnd.toFixed(3));
|
||
|
|
// ensure that we are not reloading the same fragments in loop ...
|
||
|
|
if (this.fragLoadIdx !== undefined) {
|
||
|
|
this.fragLoadIdx++;
|
||
|
|
} else {
|
||
|
|
this.fragLoadIdx = 0;
|
||
|
|
}
|
||
|
|
if (frag.loadCounter) {
|
||
|
|
frag.loadCounter++;
|
||
|
|
var maxThreshold = 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) {
|
||
|
|
hls.trigger(_events2.default.ERROR, { type: _errors.ErrorTypes.MEDIA_ERROR, details: _errors.ErrorDetails.FRAG_LOOP_LOADING_ERROR, fatal: false, frag: frag });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
frag.loadCounter = 1;
|
||
|
|
}
|
||
|
|
frag.loadIdx = this.fragLoadIdx;
|
||
|
|
this.fragCurrent = frag;
|
||
|
|
this.startFragRequested = true;
|
||
|
|
if (!isNaN(frag.sn)) {
|
||
|
|
this.nextLoadPosition = frag.start + frag.duration;
|
||
|
|
}
|
||
|
|
hls.trigger(_events2.default.FRAG_LOADING, { frag: frag });
|
||
|
|
this.state = State.FRAG_LOADING;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case State.WAITING_TRACK:
|
||
|
|
track = this.tracks[this.trackId];
|
||
|
|
// check if playlist is already loaded
|
||
|
|
if (track && track.details) {
|
||
|
|
this.state = State.IDLE;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case State.FRAG_LOADING_WAITING_RETRY:
|
||
|
|
var now = performance.now();
|
||
|
|
var retryDate = this.retryDate;
|
||
|
|
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.logger.log('audioStreamController: retryDate reached, switch back to IDLE state');
|
||
|
|
this.state = State.IDLE;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case State.WAITING_INIT_PTS:
|
||
|
|
var videoTrackCC = this.videoTrackCC;
|
||
|
|
if (this.initPTS[videoTrackCC] === undefined) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ensure we don't get stuck in the WAITING_INIT_PTS state if the waiting frag CC doesn't match any initPTS
|
||
|
|
var waitingFrag = this.waitingFragment;
|
||
|
|
if (waitingFrag) {
|
||
|
|
var waitingFragCC = waitingFrag.frag.cc;
|
||
|
|
if (videoTrackCC !== waitingFragCC) {
|
||
|
|
track = this.tracks[this.trackId];
|
||
|
|
if (track.details && track.details.live) {
|
||
|
|
_logger.logger.warn('Waiting fragment CC (' + waitingFragCC + ') does not match video track CC (' + videoTrackCC + ')');
|
||
|
|
this.waitingFragment = null;
|
||
|
|
this.state = State.IDLE;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
this.state = State.FRAG_LOADING;
|
||
|
|
this.onFragLoaded(this.waitingFragment);
|
||
|
|
this.waitingFragment = null;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
this.state = State.IDLE;
|
||
|
|
}
|
||
|
|
|
||
|
|
break;
|
||
|
|
case State.STOPPED:
|
||
|
|
case State.FRAG_LOADING:
|
||
|
|
case State.PARSING:
|
||
|
|
case State.PARSED:
|
||
|
|
case State.ENDED:
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onMediaAttached',
|
||
|
|
value: function onMediaAttached(data) {
|
||
|
|
var media = this.media = this.mediaBuffer = data.media;
|
||
|
|
this.onvseeking = this.onMediaSeeking.bind(this);
|
||
|
|
this.onvended = this.onMediaEnded.bind(this);
|
||
|
|
media.addEventListener('seeking', this.onvseeking);
|
||
|
|
media.addEventListener('ended', this.onvended);
|
||
|
|
var config = this.config;
|
||
|
|
if (this.tracks && config.autoStartLoad) {
|
||
|
|
this.startLoad(config.startPosition);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onMediaDetaching',
|
||
|
|
value: function onMediaDetaching() {
|
||
|
|
var media = this.media;
|
||
|
|
if (media && media.ended) {
|
||
|
|
_logger.logger.log('MSE detaching and video ended, reset startPosition');
|
||
|
|
this.startPosition = this.lastCurrentTime = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// reset fragment loading counter on MSE detaching to avoid reporting FRAG_LOOP_LOADING_ERROR after error recovery
|
||
|
|
var tracks = this.tracks;
|
||
|
|
if (tracks) {
|
||
|
|
// reset fragment load counter
|
||
|
|
tracks.forEach(function (track) {
|
||
|
|
if (track.details) {
|
||
|
|
track.details.fragments.forEach(function (fragment) {
|
||
|
|
fragment.loadCounter = undefined;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
// remove video listeners
|
||
|
|
if (media) {
|
||
|
|
media.removeEventListener('seeking', this.onvseeking);
|
||
|
|
media.removeEventListener('ended', this.onvended);
|
||
|
|
this.onvseeking = this.onvseeked = this.onvended = null;
|
||
|
|
}
|
||
|
|
this.media = this.mediaBuffer = null;
|
||
|
|
this.loadedmetadata = false;
|
||
|
|
this.stopLoad();
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onMediaSeeking',
|
||
|
|
value: function onMediaSeeking() {
|
||
|
|
if (this.state === State.ENDED) {
|
||
|
|
// switch to IDLE state to check for potential new fragment
|
||
|
|
this.state = State.IDLE;
|
||
|
|
}
|
||
|
|
if (this.media) {
|
||
|
|
this.lastCurrentTime = this.media.currentTime;
|
||
|
|
}
|
||
|
|
// avoid reporting fragment loop loading error in case user is seeking several times on same position
|
||
|
|
if (this.fragLoadIdx !== undefined) {
|
||
|
|
this.fragLoadIdx += 2 * this.config.fragLoadingLoopThreshold;
|
||
|
|
}
|
||
|
|
// tick to speed up processing
|
||
|
|
this.tick();
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onMediaEnded',
|
||
|
|
value: function onMediaEnded() {
|
||
|
|
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
||
|
|
this.startPosition = this.lastCurrentTime = 0;
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onAudioTracksUpdated',
|
||
|
|
value: function onAudioTracksUpdated(data) {
|
||
|
|
_logger.logger.log('audio tracks updated');
|
||
|
|
this.tracks = data.audioTracks;
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onAudioTrackSwitching',
|
||
|
|
value: function onAudioTrackSwitching(data) {
|
||
|
|
// if any URL found on new audio track, it is an alternate audio track
|
||
|
|
var altAudio = !!data.url;
|
||
|
|
this.trackId = data.id;
|
||
|
|
|
||
|
|
this.fragCurrent = null;
|
||
|
|
this.state = State.PAUSED;
|
||
|
|
this.waitingFragment = null;
|
||
|
|
// destroy useless demuxer when switching audio to main
|
||
|
|
if (!altAudio) {
|
||
|
|
if (this.demuxer) {
|
||
|
|
this.demuxer.destroy();
|
||
|
|
this.demuxer = null;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// switching to audio track, start timer if not already started
|
||
|
|
if (!this.timer) {
|
||
|
|
this.timer = setInterval(this.ontick, 100);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//should we switch tracks ?
|
||
|
|
if (altAudio) {
|
||
|
|
this.audioSwitch = true;
|
||
|
|
//main audio track are handled by stream-controller, just do something if switching to alt audio track
|
||
|
|
this.state = State.IDLE;
|
||
|
|
// increase fragment load Index to avoid frag loop loading error after buffer flush
|
||
|
|
if (this.fragLoadIdx !== undefined) {
|
||
|
|
this.fragLoadIdx += 2 * this.config.fragLoadingLoopThreshold;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
this.tick();
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onAudioTrackLoaded',
|
||
|
|
value: function onAudioTrackLoaded(data) {
|
||
|
|
var newDetails = data.details,
|
||
|
|
trackId = data.id,
|
||
|
|
track = this.tracks[trackId],
|
||
|
|
duration = newDetails.totalduration,
|
||
|
|
sliding = 0;
|
||
|
|
|
||
|
|
_logger.logger.log('track ' + trackId + ' loaded [' + newDetails.startSN + ',' + newDetails.endSN + '],duration:' + duration);
|
||
|
|
|
||
|
|
if (newDetails.live) {
|
||
|
|
var curDetails = track.details;
|
||
|
|
if (curDetails && newDetails.fragments.length > 0) {
|
||
|
|
// we already have details for that level, merge them
|
||
|
|
_levelHelper2.default.mergeDetails(curDetails, newDetails);
|
||
|
|
sliding = newDetails.fragments[0].start;
|
||
|
|
// TODO
|
||
|
|
//this.liveSyncPosition = this.computeLivePosition(sliding, curDetails);
|
||
|
|
if (newDetails.PTSKnown) {
|
||
|
|
_logger.logger.log('live audio playlist sliding:' + sliding.toFixed(3));
|
||
|
|
} else {
|
||
|
|
_logger.logger.log('live audio playlist - outdated PTS, unknown sliding');
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
newDetails.PTSKnown = false;
|
||
|
|
_logger.logger.log('live audio playlist - first load, unknown sliding');
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
newDetails.PTSKnown = false;
|
||
|
|
}
|
||
|
|
track.details = newDetails;
|
||
|
|
|
||
|
|
// compute start position
|
||
|
|
if (!this.startFragRequested) {
|
||
|
|
// compute start position if set to -1. use it straight away if value is defined
|
||
|
|
if (this.startPosition === -1) {
|
||
|
|
// first, check if start time offset has been set in playlist, if yes, use this value
|
||
|
|
var startTimeOffset = newDetails.startTimeOffset;
|
||
|
|
if (!isNaN(startTimeOffset)) {
|
||
|
|
_logger.logger.log('start time offset found in playlist, adjust startPosition to ' + startTimeOffset);
|
||
|
|
this.startPosition = startTimeOffset;
|
||
|
|
} else {
|
||
|
|
this.startPosition = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
this.nextLoadPosition = this.startPosition;
|
||
|
|
}
|
||
|
|
// only switch batck to IDLE state if we were waiting for track to start downloading a new fragment
|
||
|
|
if (this.state === State.WAITING_TRACK) {
|
||
|
|
this.state = State.IDLE;
|
||
|
|
}
|
||
|
|
//trigger handler right now
|
||
|
|
this.tick();
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onKeyLoaded',
|
||
|
|
value: function onKeyLoaded() {
|
||
|
|
if (this.state === State.KEY_LOADING) {
|
||
|
|
this.state = State.IDLE;
|
||
|
|
this.tick();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onFragLoaded',
|
||
|
|
value: function onFragLoaded(data) {
|
||
|
|
var fragCurrent = this.fragCurrent,
|
||
|
|
fragLoaded = data.frag;
|
||
|
|
if (this.state === State.FRAG_LOADING && fragCurrent && fragLoaded.type === 'audio' && fragLoaded.level === fragCurrent.level && fragLoaded.sn === fragCurrent.sn) {
|
||
|
|
var track = this.tracks[this.trackId],
|
||
|
|
details = track.details,
|
||
|
|
duration = details.totalduration,
|
||
|
|
trackId = fragCurrent.level,
|
||
|
|
sn = fragCurrent.sn,
|
||
|
|
cc = fragCurrent.cc,
|
||
|
|
audioCodec = this.config.defaultAudioCodec || track.audioCodec || 'mp4a.40.2',
|
||
|
|
stats = this.stats = data.stats;
|
||
|
|
if (sn === 'initSegment') {
|
||
|
|
this.state = State.IDLE;
|
||
|
|
|
||
|
|
stats.tparsed = stats.tbuffered = performance.now();
|
||
|
|
details.initSegment.data = data.payload;
|
||
|
|
this.hls.trigger(_events2.default.FRAG_BUFFERED, { stats: stats, frag: fragCurrent, id: 'audio' });
|
||
|
|
this.tick();
|
||
|
|
} else {
|
||
|
|
this.state = State.PARSING;
|
||
|
|
// transmux the MPEG-TS data to ISO-BMFF segments
|
||
|
|
this.appended = false;
|
||
|
|
if (!this.demuxer) {
|
||
|
|
this.demuxer = new _demuxer2.default(this.hls, 'audio');
|
||
|
|
}
|
||
|
|
//Check if we have video initPTS
|
||
|
|
// If not we need to wait for it
|
||
|
|
var initPTS = this.initPTS[cc];
|
||
|
|
var initSegmentData = details.initSegment ? details.initSegment.data : [];
|
||
|
|
if (details.initSegment || initPTS !== undefined) {
|
||
|
|
this.pendingBuffering = true;
|
||
|
|
_logger.logger.log('Demuxing ' + sn + ' of [' + details.startSN + ' ,' + details.endSN + '],track ' + trackId);
|
||
|
|
// time Offset is accurate if level PTS is known, or if playlist is not sliding (not live)
|
||
|
|
var accurateTimeOffset = false; //details.PTSKnown || !details.live;
|
||
|
|
this.demuxer.push(data.payload, initSegmentData, audioCodec, null, fragCurrent, duration, accurateTimeOffset, initPTS);
|
||
|
|
} else {
|
||
|
|
_logger.logger.log('unknown video PTS for continuity counter ' + cc + ', waiting for video PTS before demuxing audio frag ' + sn + ' of [' + details.startSN + ' ,' + details.endSN + '],track ' + trackId);
|
||
|
|
this.waitingFragment = data;
|
||
|
|
this.state = State.WAITING_INIT_PTS;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
this.fragLoadError = 0;
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onFragParsingInitSegment',
|
||
|
|
value: function onFragParsingInitSegment(data) {
|
||
|
|
var fragCurrent = this.fragCurrent;
|
||
|
|
var fragNew = data.frag;
|
||
|
|
if (fragCurrent && data.id === 'audio' && fragNew.sn === fragCurrent.sn && fragNew.level === fragCurrent.level && this.state === State.PARSING) {
|
||
|
|
var tracks = data.tracks,
|
||
|
|
track = void 0;
|
||
|
|
|
||
|
|
// delete any video track found on audio demuxer
|
||
|
|
if (tracks.video) {
|
||
|
|
delete tracks.video;
|
||
|
|
}
|
||
|
|
|
||
|
|
// include levelCodec in audio and video tracks
|
||
|
|
track = tracks.audio;
|
||
|
|
if (track) {
|
||
|
|
track.levelCodec = track.codec;
|
||
|
|
track.id = data.id;
|
||
|
|
this.hls.trigger(_events2.default.BUFFER_CODECS, tracks);
|
||
|
|
_logger.logger.log('audio track:audio,container:' + track.container + ',codecs[level/parsed]=[' + track.levelCodec + '/' + track.codec + ']');
|
||
|
|
var initSegment = track.initSegment;
|
||
|
|
if (initSegment) {
|
||
|
|
var appendObj = { type: 'audio', data: initSegment, parent: 'audio', content: 'initSegment' };
|
||
|
|
if (this.audioSwitch) {
|
||
|
|
this.pendingData = [appendObj];
|
||
|
|
} else {
|
||
|
|
this.appended = true;
|
||
|
|
// arm pending Buffering flag before appending a segment
|
||
|
|
this.pendingBuffering = true;
|
||
|
|
this.hls.trigger(_events2.default.BUFFER_APPENDING, appendObj);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
//trigger handler right now
|
||
|
|
this.tick();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onFragParsingData',
|
||
|
|
value: function onFragParsingData(data) {
|
||
|
|
var _this2 = this;
|
||
|
|
|
||
|
|
var fragCurrent = this.fragCurrent;
|
||
|
|
var fragNew = data.frag;
|
||
|
|
if (fragCurrent && data.id === 'audio' && data.type === 'audio' && fragNew.sn === fragCurrent.sn && fragNew.level === fragCurrent.level && this.state === State.PARSING) {
|
||
|
|
var trackId = this.trackId,
|
||
|
|
track = this.tracks[trackId],
|
||
|
|
hls = this.hls;
|
||
|
|
|
||
|
|
if (isNaN(data.endPTS)) {
|
||
|
|
data.endPTS = data.startPTS + fragCurrent.duration;
|
||
|
|
data.endDTS = data.startDTS + fragCurrent.duration;
|
||
|
|
}
|
||
|
|
|
||
|
|
_logger.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);
|
||
|
|
_levelHelper2.default.updateFragPTSDTS(track.details, fragCurrent, data.startPTS, data.endPTS);
|
||
|
|
|
||
|
|
var audioSwitch = this.audioSwitch,
|
||
|
|
media = this.media,
|
||
|
|
appendOnBufferFlush = false;
|
||
|
|
//Only flush audio from old audio tracks when PTS is known on new audio track
|
||
|
|
if (audioSwitch && media) {
|
||
|
|
if (media.readyState) {
|
||
|
|
var currentTime = media.currentTime;
|
||
|
|
_logger.logger.log('switching audio track : currentTime:' + currentTime);
|
||
|
|
if (currentTime >= data.startPTS) {
|
||
|
|
_logger.logger.log('switching audio track : flushing all audio');
|
||
|
|
this.state = State.BUFFER_FLUSHING;
|
||
|
|
hls.trigger(_events2.default.BUFFER_FLUSHING, { startOffset: 0, endOffset: Number.POSITIVE_INFINITY, type: 'audio' });
|
||
|
|
appendOnBufferFlush = true;
|
||
|
|
//Lets announce that the initial audio track switch flush occur
|
||
|
|
this.audioSwitch = false;
|
||
|
|
hls.trigger(_events2.default.AUDIO_TRACK_SWITCHED, { id: trackId });
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
//Lets announce that the initial audio track switch flush occur
|
||
|
|
this.audioSwitch = false;
|
||
|
|
hls.trigger(_events2.default.AUDIO_TRACK_SWITCHED, { id: trackId });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var pendingData = this.pendingData;
|
||
|
|
if (!this.audioSwitch) {
|
||
|
|
[data.data1, data.data2].forEach(function (buffer) {
|
||
|
|
if (buffer && buffer.length) {
|
||
|
|
pendingData.push({ type: data.type, data: buffer, parent: 'audio', content: 'data' });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
if (!appendOnBufferFlush && pendingData.length) {
|
||
|
|
pendingData.forEach(function (appendObj) {
|
||
|
|
// only append in PARSING state (rationale is that an appending error could happen synchronously on first segment appending)
|
||
|
|
// in that case it is useless to append following segments
|
||
|
|
if (_this2.state === State.PARSING) {
|
||
|
|
// arm pending Buffering flag before appending a segment
|
||
|
|
_this2.pendingBuffering = true;
|
||
|
|
_this2.hls.trigger(_events2.default.BUFFER_APPENDING, appendObj);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
this.pendingData = [];
|
||
|
|
this.appended = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
//trigger handler right now
|
||
|
|
this.tick();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onFragParsed',
|
||
|
|
value: function onFragParsed(data) {
|
||
|
|
var fragCurrent = this.fragCurrent;
|
||
|
|
var fragNew = data.frag;
|
||
|
|
if (fragCurrent && data.id === 'audio' && fragNew.sn === fragCurrent.sn && fragNew.level === fragCurrent.level && this.state === State.PARSING) {
|
||
|
|
this.stats.tparsed = performance.now();
|
||
|
|
this.state = State.PARSED;
|
||
|
|
this._checkAppendedParsed();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onBufferCreated',
|
||
|
|
value: function onBufferCreated(data) {
|
||
|
|
var audioTrack = data.tracks.audio;
|
||
|
|
if (audioTrack) {
|
||
|
|
this.mediaBuffer = audioTrack.buffer;
|
||
|
|
this.loadedmetadata = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onBufferAppended',
|
||
|
|
value: function onBufferAppended(data) {
|
||
|
|
if (data.parent === 'audio') {
|
||
|
|
var state = this.state;
|
||
|
|
if (state === State.PARSING || state === State.PARSED) {
|
||
|
|
// check if all buffers have been appended
|
||
|
|
this.pendingBuffering = data.pending > 0;
|
||
|
|
this._checkAppendedParsed();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: '_checkAppendedParsed',
|
||
|
|
value: function _checkAppendedParsed() {
|
||
|
|
//trigger handler right now
|
||
|
|
if (this.state === State.PARSED && (!this.appended || !this.pendingBuffering)) {
|
||
|
|
var frag = this.fragCurrent,
|
||
|
|
stats = this.stats,
|
||
|
|
hls = this.hls;
|
||
|
|
if (frag) {
|
||
|
|
this.fragPrevious = frag;
|
||
|
|
stats.tbuffered = performance.now();
|
||
|
|
hls.trigger(_events2.default.FRAG_BUFFERED, { stats: stats, frag: frag, id: 'audio' });
|
||
|
|
var media = this.mediaBuffer ? this.mediaBuffer : this.media;
|
||
|
|
_logger.logger.log('audio buffered : ' + _timeRanges2.default.toString(media.buffered));
|
||
|
|
if (this.audioSwitch && this.appended) {
|
||
|
|
this.audioSwitch = false;
|
||
|
|
hls.trigger(_events2.default.AUDIO_TRACK_SWITCHED, { id: this.trackId });
|
||
|
|
}
|
||
|
|
this.state = State.IDLE;
|
||
|
|
}
|
||
|
|
this.tick();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onError',
|
||
|
|
value: function onError(data) {
|
||
|
|
var frag = data.frag;
|
||
|
|
// don't handle frag error not related to audio fragment
|
||
|
|
if (frag && frag.type !== 'audio') {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
switch (data.details) {
|
||
|
|
case _errors.ErrorDetails.FRAG_LOAD_ERROR:
|
||
|
|
case _errors.ErrorDetails.FRAG_LOAD_TIMEOUT:
|
||
|
|
if (!data.fatal) {
|
||
|
|
var loadError = this.fragLoadError;
|
||
|
|
if (loadError) {
|
||
|
|
loadError++;
|
||
|
|
} else {
|
||
|
|
loadError = 1;
|
||
|
|
}
|
||
|
|
var config = this.config;
|
||
|
|
if (loadError <= config.fragLoadingMaxRetry) {
|
||
|
|
this.fragLoadError = loadError;
|
||
|
|
// reset load counter to avoid frag loop loading error
|
||
|
|
frag.loadCounter = 0;
|
||
|
|
// exponential backoff capped to config.fragLoadingMaxRetryTimeout
|
||
|
|
var delay = Math.min(Math.pow(2, loadError - 1) * config.fragLoadingRetryDelay, config.fragLoadingMaxRetryTimeout);
|
||
|
|
_logger.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.logger.error('audioStreamController: ' + data.details + ' reaches max retry, redispatch as fatal ...');
|
||
|
|
// switch error to fatal
|
||
|
|
data.fatal = true;
|
||
|
|
this.state = State.ERROR;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case _errors.ErrorDetails.FRAG_LOOP_LOADING_ERROR:
|
||
|
|
case _errors.ErrorDetails.AUDIO_TRACK_LOAD_ERROR:
|
||
|
|
case _errors.ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT:
|
||
|
|
case _errors.ErrorDetails.KEY_LOAD_ERROR:
|
||
|
|
case _errors.ErrorDetails.KEY_LOAD_TIMEOUT:
|
||
|
|
// when in ERROR state, don't switch back to IDLE state in case a non-fatal error is received
|
||
|
|
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.logger.warn('audioStreamController: ' + data.details + ' while loading frag,switch to ' + this.state + ' state ...');
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case _errors.ErrorDetails.BUFFER_FULL_ERROR:
|
||
|
|
// if in appending state
|
||
|
|
if (data.parent === 'audio' && (this.state === State.PARSING || this.state === State.PARSED)) {
|
||
|
|
var media = this.mediaBuffer,
|
||
|
|
currentTime = this.media.currentTime,
|
||
|
|
mediaBuffered = media && _bufferHelper2.default.isBuffered(media, currentTime) && _bufferHelper2.default.isBuffered(media, currentTime + 0.5);
|
||
|
|
// reduce max buf len if current position is buffered
|
||
|
|
if (mediaBuffered) {
|
||
|
|
var _config = this.config;
|
||
|
|
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.logger.warn('audio:reduce max buffer length to ' + _config.maxMaxBufferLength + 's');
|
||
|
|
// increase fragment load Index to avoid frag loop loading error after buffer flush
|
||
|
|
this.fragLoadIdx += 2 * _config.fragLoadingLoopThreshold;
|
||
|
|
}
|
||
|
|
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.logger.warn('buffer full error also media.currentTime is not buffered, flush audio buffer');
|
||
|
|
this.fragCurrent = null;
|
||
|
|
// flush everything
|
||
|
|
this.state = State.BUFFER_FLUSHING;
|
||
|
|
this.hls.trigger(_events2.default.BUFFER_FLUSHING, { startOffset: 0, endOffset: Number.POSITIVE_INFINITY, type: 'audio' });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'onBufferFlushed',
|
||
|
|
value: function onBufferFlushed() {
|
||
|
|
var _this3 = this;
|
||
|
|
|
||
|
|
var pendingData = this.pendingData;
|
||
|
|
if (pendingData && pendingData.length) {
|
||
|
|
_logger.logger.log('appending pending audio data on Buffer Flushed');
|
||
|
|
pendingData.forEach(function (appendObj) {
|
||
|
|
_this3.hls.trigger(_events2.default.BUFFER_APPENDING, appendObj);
|
||
|
|
});
|
||
|
|
this.appended = true;
|
||
|
|
this.pendingData = [];
|
||
|
|
this.state = State.PARSED;
|
||
|
|
} else {
|
||
|
|
// move to IDLE once flush complete. this should trigger new fragment loading
|
||
|
|
this.state = State.IDLE;
|
||
|
|
// reset reference to frag
|
||
|
|
this.fragPrevious = null;
|
||
|
|
this.tick();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, {
|
||
|
|
key: 'state',
|
||
|
|
set: function set(nextState) {
|
||
|
|
if (this.state !== nextState) {
|
||
|
|
var previousState = this.state;
|
||
|
|
this._state = nextState;
|
||
|
|
_logger.logger.log('audio stream:' + previousState + '->' + nextState);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
get: function get() {
|
||
|
|
return this._state;
|
||
|
|
}
|
||
|
|
}]);
|
||
|
|
|
||
|
|
return AudioStreamController;
|
||
|
|
}(_eventHandler2.default);
|
||
|
|
|
||
|
|
exports.default = AudioStreamController;
|