segment-loader.js 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403
  1. /**
  2. * @file segment-loader.js
  3. */
  4. 'use strict';
  5. Object.defineProperty(exports, '__esModule', {
  6. value: true
  7. });
  8. 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; }; })();
  9. var _get = function get(_x4, _x5, _x6) { var _again = true; _function: while (_again) { var object = _x4, property = _x5, receiver = _x6; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x4 = parent; _x5 = property; _x6 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
  10. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  11. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
  12. 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; }
  13. var _playlist = require('./playlist');
  14. var _playlist2 = _interopRequireDefault(_playlist);
  15. var _videoJs = require('video.js');
  16. var _videoJs2 = _interopRequireDefault(_videoJs);
  17. var _sourceUpdater = require('./source-updater');
  18. var _sourceUpdater2 = _interopRequireDefault(_sourceUpdater);
  19. var _config = require('./config');
  20. var _config2 = _interopRequireDefault(_config);
  21. var _globalWindow = require('global/window');
  22. var _globalWindow2 = _interopRequireDefault(_globalWindow);
  23. var _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs = require('videojs-contrib-media-sources/es5/remove-cues-from-track.js');
  24. var _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs2 = _interopRequireDefault(_videojsContribMediaSourcesEs5RemoveCuesFromTrackJs);
  25. var _binUtils = require('./bin-utils');
  26. var _mediaSegmentRequest = require('./media-segment-request');
  27. var _ranges = require('./ranges');
  28. var _playlistSelectors = require('./playlist-selectors');
  29. // in ms
  30. var CHECK_BUFFER_DELAY = 500;
  31. /**
  32. * Determines if we should call endOfStream on the media source based
  33. * on the state of the buffer or if appened segment was the final
  34. * segment in the playlist.
  35. *
  36. * @param {Object} playlist a media playlist object
  37. * @param {Object} mediaSource the MediaSource object
  38. * @param {Number} segmentIndex the index of segment we last appended
  39. * @returns {Boolean} do we need to call endOfStream on the MediaSource
  40. */
  41. var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) {
  42. if (!playlist || !mediaSource) {
  43. return false;
  44. }
  45. var segments = playlist.segments;
  46. // determine a few boolean values to help make the branch below easier
  47. // to read
  48. var appendedLastSegment = segmentIndex === segments.length;
  49. // if we've buffered to the end of the video, we need to call endOfStream
  50. // so that MediaSources can trigger the `ended` event when it runs out of
  51. // buffered data instead of waiting for me
  52. return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment;
  53. };
  54. var finite = function finite(num) {
  55. return typeof num === 'number' && isFinite(num);
  56. };
  57. var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, newSegmentMedia) {
  58. // Although these checks should most likely cover non 'main' types, for now it narrows
  59. // the scope of our checks.
  60. if (loaderType !== 'main' || !startingMedia || !newSegmentMedia) {
  61. return null;
  62. }
  63. if (!newSegmentMedia.containsAudio && !newSegmentMedia.containsVideo) {
  64. return 'Neither audio nor video found in segment.';
  65. }
  66. if (startingMedia.containsVideo && !newSegmentMedia.containsVideo) {
  67. return 'Only audio found in segment when we expected video.' + ' We can\'t switch to audio only from a stream that had video.' + ' To get rid of this message, please add codec information to the manifest.';
  68. }
  69. if (!startingMedia.containsVideo && newSegmentMedia.containsVideo) {
  70. return 'Video found in segment when we expected only audio.' + ' We can\'t switch to a stream with video from an audio only stream.' + ' To get rid of this message, please add codec information to the manifest.';
  71. }
  72. return null;
  73. };
  74. exports.illegalMediaSwitch = illegalMediaSwitch;
  75. /**
  76. * Calculates a time value that is safe to remove from the back buffer without interupting
  77. * playback.
  78. *
  79. * @param {TimeRange} seekable
  80. * The current seekable range
  81. * @param {Number} currentTime
  82. * The current time of the player
  83. * @param {Number} targetDuration
  84. * The target duration of the current playlist
  85. * @return {Number}
  86. * Time that is safe to remove from the back buffer without interupting playback
  87. */
  88. var safeBackBufferTrimTime = function safeBackBufferTrimTime(seekable, currentTime, targetDuration) {
  89. var removeToTime = undefined;
  90. if (seekable.length && seekable.start(0) > 0 && seekable.start(0) < currentTime) {
  91. // If we have a seekable range use that as the limit for what can be removed safely
  92. removeToTime = seekable.start(0);
  93. } else {
  94. // otherwise remove anything older than 30 seconds before the current play head
  95. removeToTime = currentTime - 30;
  96. }
  97. // Don't allow removing from the buffer within target duration of current time
  98. // to avoid the possibility of removing the GOP currently being played which could
  99. // cause playback stalls.
  100. return Math.min(removeToTime, currentTime - targetDuration);
  101. };
  102. exports.safeBackBufferTrimTime = safeBackBufferTrimTime;
  103. /**
  104. * An object that manages segment loading and appending.
  105. *
  106. * @class SegmentLoader
  107. * @param {Object} options required and optional options
  108. * @extends videojs.EventTarget
  109. */
  110. var SegmentLoader = (function (_videojs$EventTarget) {
  111. _inherits(SegmentLoader, _videojs$EventTarget);
  112. function SegmentLoader(settings) {
  113. var _this = this;
  114. var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
  115. _classCallCheck(this, SegmentLoader);
  116. _get(Object.getPrototypeOf(SegmentLoader.prototype), 'constructor', this).call(this);
  117. // check pre-conditions
  118. if (!settings) {
  119. throw new TypeError('Initialization settings are required');
  120. }
  121. if (typeof settings.currentTime !== 'function') {
  122. throw new TypeError('No currentTime getter specified');
  123. }
  124. if (!settings.mediaSource) {
  125. throw new TypeError('No MediaSource specified');
  126. }
  127. // public properties
  128. this.state = 'INIT';
  129. this.bandwidth = settings.bandwidth;
  130. this.throughput = { rate: 0, count: 0 };
  131. this.roundTrip = NaN;
  132. this.resetStats_();
  133. this.mediaIndex = null;
  134. // private settings
  135. this.hasPlayed_ = settings.hasPlayed;
  136. this.currentTime_ = settings.currentTime;
  137. this.seekable_ = settings.seekable;
  138. this.seeking_ = settings.seeking;
  139. this.duration_ = settings.duration;
  140. this.mediaSource_ = settings.mediaSource;
  141. this.hls_ = settings.hls;
  142. this.loaderType_ = settings.loaderType;
  143. this.startingMedia_ = void 0;
  144. this.segmentMetadataTrack_ = settings.segmentMetadataTrack;
  145. this.goalBufferLength_ = settings.goalBufferLength;
  146. // private instance variables
  147. this.checkBufferTimeout_ = null;
  148. this.error_ = void 0;
  149. this.currentTimeline_ = -1;
  150. this.pendingSegment_ = null;
  151. this.mimeType_ = null;
  152. this.sourceUpdater_ = null;
  153. this.xhrOptions_ = null;
  154. // Fragmented mp4 playback
  155. this.activeInitSegmentId_ = null;
  156. this.initSegments_ = {};
  157. this.decrypter_ = settings.decrypter;
  158. // Manages the tracking and generation of sync-points, mappings
  159. // between a time in the display time and a segment index within
  160. // a playlist
  161. this.syncController_ = settings.syncController;
  162. this.syncPoint_ = {
  163. segmentIndex: 0,
  164. time: 0
  165. };
  166. this.syncController_.on('syncinfoupdate', function () {
  167. return _this.trigger('syncinfoupdate');
  168. });
  169. this.mediaSource_.addEventListener('sourceopen', function () {
  170. return _this.ended_ = false;
  171. });
  172. // ...for determining the fetch location
  173. this.fetchAtBuffer_ = false;
  174. if (options.debug) {
  175. this.logger_ = _videoJs2['default'].log.bind(_videoJs2['default'], 'segment-loader', this.loaderType_, '->');
  176. }
  177. }
  178. /**
  179. * reset all of our media stats
  180. *
  181. * @private
  182. */
  183. _createClass(SegmentLoader, [{
  184. key: 'resetStats_',
  185. value: function resetStats_() {
  186. this.mediaBytesTransferred = 0;
  187. this.mediaRequests = 0;
  188. this.mediaRequestsAborted = 0;
  189. this.mediaRequestsTimedout = 0;
  190. this.mediaRequestsErrored = 0;
  191. this.mediaTransferDuration = 0;
  192. this.mediaSecondsLoaded = 0;
  193. }
  194. /**
  195. * dispose of the SegmentLoader and reset to the default state
  196. */
  197. }, {
  198. key: 'dispose',
  199. value: function dispose() {
  200. this.state = 'DISPOSED';
  201. this.pause();
  202. this.abort_();
  203. if (this.sourceUpdater_) {
  204. this.sourceUpdater_.dispose();
  205. }
  206. this.resetStats_();
  207. }
  208. /**
  209. * abort anything that is currently doing on with the SegmentLoader
  210. * and reset to a default state
  211. */
  212. }, {
  213. key: 'abort',
  214. value: function abort() {
  215. if (this.state !== 'WAITING') {
  216. if (this.pendingSegment_) {
  217. this.pendingSegment_ = null;
  218. }
  219. return;
  220. }
  221. this.abort_();
  222. // We aborted the requests we were waiting on, so reset the loader's state to READY
  223. // since we are no longer "waiting" on any requests. XHR callback is not always run
  224. // when the request is aborted. This will prevent the loader from being stuck in the
  225. // WAITING state indefinitely.
  226. this.state = 'READY';
  227. // don't wait for buffer check timeouts to begin fetching the
  228. // next segment
  229. if (!this.paused()) {
  230. this.monitorBuffer_();
  231. }
  232. }
  233. /**
  234. * abort all pending xhr requests and null any pending segements
  235. *
  236. * @private
  237. */
  238. }, {
  239. key: 'abort_',
  240. value: function abort_() {
  241. if (this.pendingSegment_) {
  242. this.pendingSegment_.abortRequests();
  243. }
  244. // clear out the segment being processed
  245. this.pendingSegment_ = null;
  246. }
  247. /**
  248. * set an error on the segment loader and null out any pending segements
  249. *
  250. * @param {Error} error the error to set on the SegmentLoader
  251. * @return {Error} the error that was set or that is currently set
  252. */
  253. }, {
  254. key: 'error',
  255. value: function error(_error) {
  256. if (typeof _error !== 'undefined') {
  257. this.error_ = _error;
  258. }
  259. this.pendingSegment_ = null;
  260. return this.error_;
  261. }
  262. }, {
  263. key: 'endOfStream',
  264. value: function endOfStream() {
  265. this.ended_ = true;
  266. this.pause();
  267. this.trigger('ended');
  268. }
  269. /**
  270. * Indicates which time ranges are buffered
  271. *
  272. * @return {TimeRange}
  273. * TimeRange object representing the current buffered ranges
  274. */
  275. }, {
  276. key: 'buffered_',
  277. value: function buffered_() {
  278. if (!this.sourceUpdater_) {
  279. return _videoJs2['default'].createTimeRanges();
  280. }
  281. return this.sourceUpdater_.buffered();
  282. }
  283. /**
  284. * Gets and sets init segment for the provided map
  285. *
  286. * @param {Object} map
  287. * The map object representing the init segment to get or set
  288. * @param {Boolean=} set
  289. * If true, the init segment for the provided map should be saved
  290. * @return {Object}
  291. * map object for desired init segment
  292. */
  293. }, {
  294. key: 'initSegment',
  295. value: function initSegment(map) {
  296. var set = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
  297. if (!map) {
  298. return null;
  299. }
  300. var id = (0, _binUtils.initSegmentId)(map);
  301. var storedMap = this.initSegments_[id];
  302. if (set && !storedMap && map.bytes) {
  303. this.initSegments_[id] = storedMap = {
  304. resolvedUri: map.resolvedUri,
  305. byterange: map.byterange,
  306. bytes: map.bytes
  307. };
  308. }
  309. return storedMap || map;
  310. }
  311. /**
  312. * Returns true if all configuration required for loading is present, otherwise false.
  313. *
  314. * @return {Boolean} True if the all configuration is ready for loading
  315. * @private
  316. */
  317. }, {
  318. key: 'couldBeginLoading_',
  319. value: function couldBeginLoading_() {
  320. return this.playlist_ && (
  321. // the source updater is created when init_ is called, so either having a
  322. // source updater or being in the INIT state with a mimeType is enough
  323. // to say we have all the needed configuration to start loading.
  324. this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused();
  325. }
  326. /**
  327. * load a playlist and start to fill the buffer
  328. */
  329. }, {
  330. key: 'load',
  331. value: function load() {
  332. // un-pause
  333. this.monitorBuffer_();
  334. // if we don't have a playlist yet, keep waiting for one to be
  335. // specified
  336. if (!this.playlist_) {
  337. return;
  338. }
  339. // not sure if this is the best place for this
  340. this.syncController_.setDateTimeMapping(this.playlist_);
  341. // if all the configuration is ready, initialize and begin loading
  342. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  343. return this.init_();
  344. }
  345. // if we're in the middle of processing a segment already, don't
  346. // kick off an additional segment request
  347. if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') {
  348. return;
  349. }
  350. this.state = 'READY';
  351. }
  352. /**
  353. * Once all the starting parameters have been specified, begin
  354. * operation. This method should only be invoked from the INIT
  355. * state.
  356. *
  357. * @private
  358. */
  359. }, {
  360. key: 'init_',
  361. value: function init_() {
  362. this.state = 'READY';
  363. this.sourceUpdater_ = new _sourceUpdater2['default'](this.mediaSource_, this.mimeType_);
  364. this.resetEverything();
  365. return this.monitorBuffer_();
  366. }
  367. /**
  368. * set a playlist on the segment loader
  369. *
  370. * @param {PlaylistLoader} media the playlist to set on the segment loader
  371. */
  372. }, {
  373. key: 'playlist',
  374. value: function playlist(newPlaylist) {
  375. var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
  376. if (!newPlaylist) {
  377. return;
  378. }
  379. var oldPlaylist = this.playlist_;
  380. var segmentInfo = this.pendingSegment_;
  381. this.playlist_ = newPlaylist;
  382. this.xhrOptions_ = options;
  383. // when we haven't started playing yet, the start of a live playlist
  384. // is always our zero-time so force a sync update each time the playlist
  385. // is refreshed from the server
  386. if (!this.hasPlayed_()) {
  387. newPlaylist.syncInfo = {
  388. mediaSequence: newPlaylist.mediaSequence,
  389. time: 0
  390. };
  391. }
  392. // in VOD, this is always a rendition switch (or we updated our syncInfo above)
  393. // in LIVE, we always want to update with new playlists (including refreshes)
  394. this.trigger('syncinfoupdate');
  395. // if we were unpaused but waiting for a playlist, start
  396. // buffering now
  397. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  398. return this.init_();
  399. }
  400. if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) {
  401. if (this.mediaIndex !== null) {
  402. // we must "resync" the segment loader when we switch renditions and
  403. // the segment loader is already synced to the previous rendition
  404. this.resyncLoader();
  405. }
  406. // the rest of this function depends on `oldPlaylist` being defined
  407. return;
  408. }
  409. // we reloaded the same playlist so we are in a live scenario
  410. // and we will likely need to adjust the mediaIndex
  411. var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence;
  412. this.logger_('mediaSequenceDiff', mediaSequenceDiff);
  413. // update the mediaIndex on the SegmentLoader
  414. // this is important because we can abort a request and this value must be
  415. // equal to the last appended mediaIndex
  416. if (this.mediaIndex !== null) {
  417. this.mediaIndex -= mediaSequenceDiff;
  418. }
  419. // update the mediaIndex on the SegmentInfo object
  420. // this is important because we will update this.mediaIndex with this value
  421. // in `handleUpdateEnd_` after the segment has been successfully appended
  422. if (segmentInfo) {
  423. segmentInfo.mediaIndex -= mediaSequenceDiff;
  424. // we need to update the referenced segment so that timing information is
  425. // saved for the new playlist's segment, however, if the segment fell off the
  426. // playlist, we can leave the old reference and just lose the timing info
  427. if (segmentInfo.mediaIndex >= 0) {
  428. segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex];
  429. }
  430. }
  431. this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist);
  432. }
  433. /**
  434. * Prevent the loader from fetching additional segments. If there
  435. * is a segment request outstanding, it will finish processing
  436. * before the loader halts. A segment loader can be unpaused by
  437. * calling load().
  438. */
  439. }, {
  440. key: 'pause',
  441. value: function pause() {
  442. if (this.checkBufferTimeout_) {
  443. _globalWindow2['default'].clearTimeout(this.checkBufferTimeout_);
  444. this.checkBufferTimeout_ = null;
  445. }
  446. }
  447. /**
  448. * Returns whether the segment loader is fetching additional
  449. * segments when given the opportunity. This property can be
  450. * modified through calls to pause() and load().
  451. */
  452. }, {
  453. key: 'paused',
  454. value: function paused() {
  455. return this.checkBufferTimeout_ === null;
  456. }
  457. /**
  458. * create/set the following mimetype on the SourceBuffer through a
  459. * SourceUpdater
  460. *
  461. * @param {String} mimeType the mime type string to use
  462. */
  463. }, {
  464. key: 'mimeType',
  465. value: function mimeType(_mimeType) {
  466. if (this.mimeType_) {
  467. return;
  468. }
  469. this.mimeType_ = _mimeType;
  470. // if we were unpaused but waiting for a sourceUpdater, start
  471. // buffering now
  472. if (this.state === 'INIT' && this.couldBeginLoading_()) {
  473. this.init_();
  474. }
  475. }
  476. /**
  477. * Delete all the buffered data and reset the SegmentLoader
  478. */
  479. }, {
  480. key: 'resetEverything',
  481. value: function resetEverything() {
  482. this.ended_ = false;
  483. this.resetLoader();
  484. this.remove(0, this.duration_());
  485. this.trigger('reseteverything');
  486. }
  487. /**
  488. * Force the SegmentLoader to resync and start loading around the currentTime instead
  489. * of starting at the end of the buffer
  490. *
  491. * Useful for fast quality changes
  492. */
  493. }, {
  494. key: 'resetLoader',
  495. value: function resetLoader() {
  496. this.fetchAtBuffer_ = false;
  497. this.resyncLoader();
  498. }
  499. /**
  500. * Force the SegmentLoader to restart synchronization and make a conservative guess
  501. * before returning to the simple walk-forward method
  502. */
  503. }, {
  504. key: 'resyncLoader',
  505. value: function resyncLoader() {
  506. this.mediaIndex = null;
  507. this.syncPoint_ = null;
  508. this.abort();
  509. }
  510. /**
  511. * Remove any data in the source buffer between start and end times
  512. * @param {Number} start - the start time of the region to remove from the buffer
  513. * @param {Number} end - the end time of the region to remove from the buffer
  514. */
  515. }, {
  516. key: 'remove',
  517. value: function remove(start, end) {
  518. if (this.sourceUpdater_) {
  519. this.sourceUpdater_.remove(start, end);
  520. }
  521. (0, _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs2['default'])(start, end, this.segmentMetadataTrack_);
  522. }
  523. /**
  524. * (re-)schedule monitorBufferTick_ to run as soon as possible
  525. *
  526. * @private
  527. */
  528. }, {
  529. key: 'monitorBuffer_',
  530. value: function monitorBuffer_() {
  531. if (this.checkBufferTimeout_) {
  532. _globalWindow2['default'].clearTimeout(this.checkBufferTimeout_);
  533. }
  534. this.checkBufferTimeout_ = _globalWindow2['default'].setTimeout(this.monitorBufferTick_.bind(this), 1);
  535. }
  536. /**
  537. * As long as the SegmentLoader is in the READY state, periodically
  538. * invoke fillBuffer_().
  539. *
  540. * @private
  541. */
  542. }, {
  543. key: 'monitorBufferTick_',
  544. value: function monitorBufferTick_() {
  545. if (this.state === 'READY') {
  546. this.fillBuffer_();
  547. }
  548. if (this.checkBufferTimeout_) {
  549. _globalWindow2['default'].clearTimeout(this.checkBufferTimeout_);
  550. }
  551. this.checkBufferTimeout_ = _globalWindow2['default'].setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY);
  552. }
  553. /**
  554. * fill the buffer with segements unless the sourceBuffers are
  555. * currently updating
  556. *
  557. * Note: this function should only ever be called by monitorBuffer_
  558. * and never directly
  559. *
  560. * @private
  561. */
  562. }, {
  563. key: 'fillBuffer_',
  564. value: function fillBuffer_() {
  565. if (this.sourceUpdater_.updating()) {
  566. return;
  567. }
  568. if (!this.syncPoint_) {
  569. this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_());
  570. }
  571. // see if we need to begin loading immediately
  572. var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_);
  573. if (!segmentInfo) {
  574. return;
  575. }
  576. var isEndOfStream = detectEndOfStream(this.playlist_, this.mediaSource_, segmentInfo.mediaIndex);
  577. if (isEndOfStream) {
  578. this.endOfStream();
  579. return;
  580. }
  581. if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) {
  582. return;
  583. }
  584. // We will need to change timestampOffset of the sourceBuffer if either of
  585. // the following conditions are true:
  586. // - The segment.timeline !== this.currentTimeline
  587. // (we are crossing a discontinuity somehow)
  588. // - The "timestampOffset" for the start of this segment is less than
  589. // the currently set timestampOffset
  590. if (segmentInfo.timeline !== this.currentTimeline_ || segmentInfo.startOfSegment !== null && segmentInfo.startOfSegment < this.sourceUpdater_.timestampOffset()) {
  591. this.syncController_.reset();
  592. segmentInfo.timestampOffset = segmentInfo.startOfSegment;
  593. }
  594. this.loadSegment_(segmentInfo);
  595. }
  596. /**
  597. * Determines what segment request should be made, given current playback
  598. * state.
  599. *
  600. * @param {TimeRanges} buffered - the state of the buffer
  601. * @param {Object} playlist - the playlist object to fetch segments from
  602. * @param {Number} mediaIndex - the previous mediaIndex fetched or null
  603. * @param {Boolean} hasPlayed - a flag indicating whether we have played or not
  604. * @param {Number} currentTime - the playback position in seconds
  605. * @param {Object} syncPoint - a segment info object that describes the
  606. * @returns {Object} a segment request object that describes the segment to load
  607. */
  608. }, {
  609. key: 'checkBuffer_',
  610. value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) {
  611. var lastBufferedEnd = 0;
  612. var startOfSegment = undefined;
  613. if (buffered.length) {
  614. lastBufferedEnd = buffered.end(buffered.length - 1);
  615. }
  616. var bufferedTime = Math.max(0, lastBufferedEnd - currentTime);
  617. if (!playlist.segments.length) {
  618. return null;
  619. }
  620. // if there is plenty of content buffered, and the video has
  621. // been played before relax for awhile
  622. if (bufferedTime >= this.goalBufferLength_()) {
  623. return null;
  624. }
  625. // if the video has not yet played once, and we already have
  626. // one segment downloaded do nothing
  627. if (!hasPlayed && bufferedTime >= 1) {
  628. return null;
  629. }
  630. this.logger_('checkBuffer_', 'mediaIndex:', mediaIndex, 'hasPlayed:', hasPlayed, 'currentTime:', currentTime, 'syncPoint:', syncPoint, 'fetchAtBuffer:', this.fetchAtBuffer_, 'bufferedTime:', bufferedTime);
  631. // When the syncPoint is null, there is no way of determining a good
  632. // conservative segment index to fetch from
  633. // The best thing to do here is to get the kind of sync-point data by
  634. // making a request
  635. if (syncPoint === null) {
  636. mediaIndex = this.getSyncSegmentCandidate_(playlist);
  637. this.logger_('getSync', 'mediaIndex:', mediaIndex);
  638. return this.generateSegmentInfo_(playlist, mediaIndex, null, true);
  639. }
  640. // Under normal playback conditions fetching is a simple walk forward
  641. if (mediaIndex !== null) {
  642. this.logger_('walkForward', 'mediaIndex:', mediaIndex + 1);
  643. var segment = playlist.segments[mediaIndex];
  644. if (segment && segment.end) {
  645. startOfSegment = segment.end;
  646. } else {
  647. startOfSegment = lastBufferedEnd;
  648. }
  649. return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false);
  650. }
  651. // There is a sync-point but the lack of a mediaIndex indicates that
  652. // we need to make a good conservative guess about which segment to
  653. // fetch
  654. if (this.fetchAtBuffer_) {
  655. // Find the segment containing the end of the buffer
  656. var mediaSourceInfo = _playlist2['default'].getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time);
  657. mediaIndex = mediaSourceInfo.mediaIndex;
  658. startOfSegment = mediaSourceInfo.startTime;
  659. } else {
  660. // Find the segment containing currentTime
  661. var mediaSourceInfo = _playlist2['default'].getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time);
  662. mediaIndex = mediaSourceInfo.mediaIndex;
  663. startOfSegment = mediaSourceInfo.startTime;
  664. }
  665. this.logger_('getMediaIndexForTime', 'mediaIndex:', mediaIndex, 'startOfSegment:', startOfSegment);
  666. return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false);
  667. }
  668. /**
  669. * The segment loader has no recourse except to fetch a segment in the
  670. * current playlist and use the internal timestamps in that segment to
  671. * generate a syncPoint. This function returns a good candidate index
  672. * for that process.
  673. *
  674. * @param {Object} playlist - the playlist object to look for a
  675. * @returns {Number} An index of a segment from the playlist to load
  676. */
  677. }, {
  678. key: 'getSyncSegmentCandidate_',
  679. value: function getSyncSegmentCandidate_(playlist) {
  680. var _this2 = this;
  681. if (this.currentTimeline_ === -1) {
  682. return 0;
  683. }
  684. var segmentIndexArray = playlist.segments.map(function (s, i) {
  685. return {
  686. timeline: s.timeline,
  687. segmentIndex: i
  688. };
  689. }).filter(function (s) {
  690. return s.timeline === _this2.currentTimeline_;
  691. });
  692. if (segmentIndexArray.length) {
  693. return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex;
  694. }
  695. return Math.max(playlist.segments.length - 1, 0);
  696. }
  697. }, {
  698. key: 'generateSegmentInfo_',
  699. value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) {
  700. if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) {
  701. return null;
  702. }
  703. var segment = playlist.segments[mediaIndex];
  704. return {
  705. requestId: 'segment-loader-' + Math.random(),
  706. // resolve the segment URL relative to the playlist
  707. uri: segment.resolvedUri,
  708. // the segment's mediaIndex at the time it was requested
  709. mediaIndex: mediaIndex,
  710. // whether or not to update the SegmentLoader's state with this
  711. // segment's mediaIndex
  712. isSyncRequest: isSyncRequest,
  713. startOfSegment: startOfSegment,
  714. // the segment's playlist
  715. playlist: playlist,
  716. // unencrypted bytes of the segment
  717. bytes: null,
  718. // when a key is defined for this segment, the encrypted bytes
  719. encryptedBytes: null,
  720. // The target timestampOffset for this segment when we append it
  721. // to the source buffer
  722. timestampOffset: null,
  723. // The timeline that the segment is in
  724. timeline: segment.timeline,
  725. // The expected duration of the segment in seconds
  726. duration: segment.duration,
  727. // retain the segment in case the playlist updates while doing an async process
  728. segment: segment
  729. };
  730. }
  731. /**
  732. * Determines if the network has enough bandwidth to complete the current segment
  733. * request in a timely manner. If not, the request will be aborted early and bandwidth
  734. * updated to trigger a playlist switch.
  735. *
  736. * @param {Object} stats
  737. * Object containing stats about the request timing and size
  738. * @return {Boolean} True if the request was aborted, false otherwise
  739. * @private
  740. */
  741. }, {
  742. key: 'abortRequestEarly_',
  743. value: function abortRequestEarly_(stats) {
  744. if (this.hls_.tech_.paused() ||
  745. // Don't abort if the current playlist is on the lowestEnabledRendition
  746. // TODO: Replace using timeout with a boolean indicating whether this playlist is
  747. // the lowestEnabledRendition.
  748. !this.xhrOptions_.timeout ||
  749. // Don't abort if we have no bandwidth information to estimate segment sizes
  750. !this.playlist_.attributes.BANDWIDTH) {
  751. return false;
  752. }
  753. // Wait at least 1 second since the first byte of data has been received before
  754. // using the calculated bandwidth from the progress event to allow the bitrate
  755. // to stabilize
  756. if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) {
  757. return false;
  758. }
  759. var currentTime = this.currentTime_();
  760. var measuredBandwidth = stats.bandwidth;
  761. var segmentDuration = this.pendingSegment_.duration;
  762. var requestTimeRemaining = _playlist2['default'].estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived);
  763. // Subtract 1 from the timeUntilRebuffer so we still consider an early abort
  764. // if we are only left with less than 1 second when the request completes.
  765. // A negative timeUntilRebuffering indicates we are already rebuffering
  766. var timeUntilRebuffer = (0, _ranges.timeUntilRebuffer)(this.buffered_(), currentTime, this.hls_.tech_.playbackRate()) - 1;
  767. // Only consider aborting early if the estimated time to finish the download
  768. // is larger than the estimated time until the player runs out of forward buffer
  769. if (requestTimeRemaining <= timeUntilRebuffer) {
  770. return false;
  771. }
  772. var switchCandidate = (0, _playlistSelectors.minRebufferMaxBandwidthSelector)({
  773. master: this.hls_.playlists.master,
  774. currentTime: currentTime,
  775. bandwidth: measuredBandwidth,
  776. duration: this.duration_(),
  777. segmentDuration: segmentDuration,
  778. timeUntilRebuffer: timeUntilRebuffer,
  779. currentTimeline: this.currentTimeline_,
  780. syncController: this.syncController_
  781. });
  782. if (!switchCandidate) {
  783. return;
  784. }
  785. var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer;
  786. var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact;
  787. var minimumTimeSaving = 0.5;
  788. // If we are already rebuffering, increase the amount of variance we add to the
  789. // potential round trip time of the new request so that we are not too aggressive
  790. // with switching to a playlist that might save us a fraction of a second.
  791. if (timeUntilRebuffer <= _ranges.TIME_FUDGE_FACTOR) {
  792. minimumTimeSaving = 1;
  793. }
  794. if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) {
  795. return false;
  796. }
  797. // set the bandwidth to that of the desired playlist being sure to scale by
  798. // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it
  799. // don't trigger a bandwidthupdate as the bandwidth is artifial
  800. this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * _config2['default'].BANDWIDTH_VARIANCE + 1;
  801. this.abort();
  802. this.trigger('earlyabort');
  803. return true;
  804. }
  805. /**
  806. * XHR `progress` event handler
  807. *
  808. * @param {Event}
  809. * The XHR `progress` event
  810. * @param {Object} simpleSegment
  811. * A simplified segment object copy
  812. * @private
  813. */
  814. }, {
  815. key: 'handleProgress_',
  816. value: function handleProgress_(event, simpleSegment) {
  817. if (!this.pendingSegment_ || simpleSegment.requestId !== this.pendingSegment_.requestId || this.abortRequestEarly_(simpleSegment.stats)) {
  818. return;
  819. }
  820. this.trigger('progress');
  821. }
  822. /**
  823. * load a specific segment from a request into the buffer
  824. *
  825. * @private
  826. */
  827. }, {
  828. key: 'loadSegment_',
  829. value: function loadSegment_(segmentInfo) {
  830. this.state = 'WAITING';
  831. this.pendingSegment_ = segmentInfo;
  832. this.trimBackBuffer_(segmentInfo);
  833. segmentInfo.abortRequests = (0, _mediaSegmentRequest.mediaSegmentRequest)(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.createSimplifiedSegmentObj_(segmentInfo),
  834. // progress callback
  835. this.handleProgress_.bind(this), this.segmentRequestFinished_.bind(this));
  836. }
  837. /**
  838. * trim the back buffer so that we don't have too much data
  839. * in the source buffer
  840. *
  841. * @private
  842. *
  843. * @param {Object} segmentInfo - the current segment
  844. */
  845. }, {
  846. key: 'trimBackBuffer_',
  847. value: function trimBackBuffer_(segmentInfo) {
  848. var removeToTime = safeBackBufferTrimTime(this.seekable_(), this.currentTime_(), this.playlist_.targetDuration || 10);
  849. // Chrome has a hard limit of 150MB of
  850. // buffer and a very conservative "garbage collector"
  851. // We manually clear out the old buffer to ensure
  852. // we don't trigger the QuotaExceeded error
  853. // on the source buffer during subsequent appends
  854. if (removeToTime > 0) {
  855. this.remove(0, removeToTime);
  856. }
  857. }
  858. /**
  859. * created a simplified copy of the segment object with just the
  860. * information necessary to perform the XHR and decryption
  861. *
  862. * @private
  863. *
  864. * @param {Object} segmentInfo - the current segment
  865. * @returns {Object} a simplified segment object copy
  866. */
  867. }, {
  868. key: 'createSimplifiedSegmentObj_',
  869. value: function createSimplifiedSegmentObj_(segmentInfo) {
  870. var segment = segmentInfo.segment;
  871. var simpleSegment = {
  872. resolvedUri: segment.resolvedUri,
  873. byterange: segment.byterange,
  874. requestId: segmentInfo.requestId
  875. };
  876. if (segment.key) {
  877. // if the media sequence is greater than 2^32, the IV will be incorrect
  878. // assuming 10s segments, that would be about 1300 years
  879. var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
  880. simpleSegment.key = {
  881. resolvedUri: segment.key.resolvedUri,
  882. iv: iv
  883. };
  884. }
  885. if (segment.map) {
  886. simpleSegment.map = this.initSegment(segment.map);
  887. }
  888. return simpleSegment;
  889. }
  890. /**
  891. * Handle the callback from the segmentRequest function and set the
  892. * associated SegmentLoader state and errors if necessary
  893. *
  894. * @private
  895. */
  896. }, {
  897. key: 'segmentRequestFinished_',
  898. value: function segmentRequestFinished_(error, simpleSegment) {
  899. // every request counts as a media request even if it has been aborted
  900. // or canceled due to a timeout
  901. this.mediaRequests += 1;
  902. if (simpleSegment.stats) {
  903. this.mediaBytesTransferred += simpleSegment.stats.bytesReceived;
  904. this.mediaTransferDuration += simpleSegment.stats.roundTripTime;
  905. }
  906. // The request was aborted and the SegmentLoader has already been reset
  907. if (!this.pendingSegment_) {
  908. this.mediaRequestsAborted += 1;
  909. return;
  910. }
  911. // the request was aborted and the SegmentLoader has already started
  912. // another request. this can happen when the timeout for an aborted
  913. // request triggers due to a limitation in the XHR library
  914. // do not count this as any sort of request or we risk double-counting
  915. if (simpleSegment.requestId !== this.pendingSegment_.requestId) {
  916. return;
  917. }
  918. // an error occurred from the active pendingSegment_ so reset everything
  919. if (error) {
  920. this.pendingSegment_ = null;
  921. this.state = 'READY';
  922. // the requests were aborted just record the aborted stat and exit
  923. // this is not a true error condition and nothing corrective needs
  924. // to be done
  925. if (error.code === _mediaSegmentRequest.REQUEST_ERRORS.ABORTED) {
  926. this.mediaRequestsAborted += 1;
  927. return;
  928. }
  929. this.pause();
  930. // the error is really just that at least one of the requests timed-out
  931. // set the bandwidth to a very low value and trigger an ABR switch to
  932. // take emergency action
  933. if (error.code === _mediaSegmentRequest.REQUEST_ERRORS.TIMEOUT) {
  934. this.mediaRequestsTimedout += 1;
  935. this.bandwidth = 1;
  936. this.roundTrip = NaN;
  937. this.trigger('bandwidthupdate');
  938. return;
  939. }
  940. // if control-flow has arrived here, then the error is real
  941. // emit an error event to blacklist the current playlist
  942. this.mediaRequestsErrored += 1;
  943. this.error(error);
  944. this.trigger('error');
  945. return;
  946. }
  947. // the response was a success so set any bandwidth stats the request
  948. // generated for ABR purposes
  949. this.bandwidth = simpleSegment.stats.bandwidth;
  950. this.roundTrip = simpleSegment.stats.roundTripTime;
  951. // if this request included an initialization segment, save that data
  952. // to the initSegment cache
  953. if (simpleSegment.map) {
  954. simpleSegment.map = this.initSegment(simpleSegment.map, true);
  955. }
  956. this.processSegmentResponse_(simpleSegment);
  957. }
  958. /**
  959. * Move any important data from the simplified segment object
  960. * back to the real segment object for future phases
  961. *
  962. * @private
  963. */
  964. }, {
  965. key: 'processSegmentResponse_',
  966. value: function processSegmentResponse_(simpleSegment) {
  967. var segmentInfo = this.pendingSegment_;
  968. segmentInfo.bytes = simpleSegment.bytes;
  969. if (simpleSegment.map) {
  970. segmentInfo.segment.map.bytes = simpleSegment.map.bytes;
  971. }
  972. segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests;
  973. this.handleSegment_();
  974. }
  975. /**
  976. * append a decrypted segement to the SourceBuffer through a SourceUpdater
  977. *
  978. * @private
  979. */
  980. }, {
  981. key: 'handleSegment_',
  982. value: function handleSegment_() {
  983. var _this3 = this;
  984. if (!this.pendingSegment_) {
  985. this.state = 'READY';
  986. return;
  987. }
  988. var segmentInfo = this.pendingSegment_;
  989. var segment = segmentInfo.segment;
  990. var timingInfo = this.syncController_.probeSegmentInfo(segmentInfo);
  991. // When we have our first timing info, determine what media types this loader is
  992. // dealing with. Although we're maintaining extra state, it helps to preserve the
  993. // separation of segment loader from the actual source buffers.
  994. if (typeof this.startingMedia_ === 'undefined' && timingInfo && (
  995. // Guard against cases where we're not getting timing info at all until we are
  996. // certain that all streams will provide it.
  997. timingInfo.containsAudio || timingInfo.containsVideo)) {
  998. this.startingMedia_ = {
  999. containsAudio: timingInfo.containsAudio,
  1000. containsVideo: timingInfo.containsVideo
  1001. };
  1002. }
  1003. var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.startingMedia_, timingInfo);
  1004. if (illegalMediaSwitchError) {
  1005. this.error({
  1006. message: illegalMediaSwitchError,
  1007. blacklistDuration: Infinity
  1008. });
  1009. this.trigger('error');
  1010. return;
  1011. }
  1012. if (segmentInfo.isSyncRequest) {
  1013. this.trigger('syncinfoupdate');
  1014. this.pendingSegment_ = null;
  1015. this.state = 'READY';
  1016. return;
  1017. }
  1018. if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) {
  1019. this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset);
  1020. // fired when a timestamp offset is set in HLS (can also identify discontinuities)
  1021. this.trigger('timestampoffset');
  1022. }
  1023. var timelineMapping = this.syncController_.mappingForTimeline(segmentInfo.timeline);
  1024. if (timelineMapping !== null) {
  1025. this.trigger({
  1026. type: 'segmenttimemapping',
  1027. mapping: timelineMapping
  1028. });
  1029. }
  1030. this.state = 'APPENDING';
  1031. // if the media initialization segment is changing, append it
  1032. // before the content segment
  1033. if (segment.map) {
  1034. (function () {
  1035. var initId = (0, _binUtils.initSegmentId)(segment.map);
  1036. if (!_this3.activeInitSegmentId_ || _this3.activeInitSegmentId_ !== initId) {
  1037. var initSegment = _this3.initSegment(segment.map);
  1038. _this3.sourceUpdater_.appendBuffer(initSegment.bytes, function () {
  1039. _this3.activeInitSegmentId_ = initId;
  1040. });
  1041. }
  1042. })();
  1043. }
  1044. segmentInfo.byteLength = segmentInfo.bytes.byteLength;
  1045. if (typeof segment.start === 'number' && typeof segment.end === 'number') {
  1046. this.mediaSecondsLoaded += segment.end - segment.start;
  1047. } else {
  1048. this.mediaSecondsLoaded += segment.duration;
  1049. }
  1050. this.sourceUpdater_.appendBuffer(segmentInfo.bytes, this.handleUpdateEnd_.bind(this));
  1051. }
  1052. /**
  1053. * callback to run when appendBuffer is finished. detects if we are
  1054. * in a good state to do things with the data we got, or if we need
  1055. * to wait for more
  1056. *
  1057. * @private
  1058. */
  1059. }, {
  1060. key: 'handleUpdateEnd_',
  1061. value: function handleUpdateEnd_() {
  1062. this.logger_('handleUpdateEnd_', 'segmentInfo:', this.pendingSegment_);
  1063. if (!this.pendingSegment_) {
  1064. this.state = 'READY';
  1065. if (!this.paused()) {
  1066. this.monitorBuffer_();
  1067. }
  1068. return;
  1069. }
  1070. var segmentInfo = this.pendingSegment_;
  1071. var segment = segmentInfo.segment;
  1072. var isWalkingForward = this.mediaIndex !== null;
  1073. this.pendingSegment_ = null;
  1074. this.recordThroughput_(segmentInfo);
  1075. this.addSegmentMetadataCue_(segmentInfo);
  1076. this.state = 'READY';
  1077. this.mediaIndex = segmentInfo.mediaIndex;
  1078. this.fetchAtBuffer_ = true;
  1079. this.currentTimeline_ = segmentInfo.timeline;
  1080. // We must update the syncinfo to recalculate the seekable range before
  1081. // the following conditional otherwise it may consider this a bad "guess"
  1082. // and attempt to resync when the post-update seekable window and live
  1083. // point would mean that this was the perfect segment to fetch
  1084. this.trigger('syncinfoupdate');
  1085. // If we previously appended a segment that ends more than 3 targetDurations before
  1086. // the currentTime_ that means that our conservative guess was too conservative.
  1087. // In that case, reset the loader state so that we try to use any information gained
  1088. // from the previous request to create a new, more accurate, sync-point.
  1089. if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) {
  1090. this.resetEverything();
  1091. return;
  1092. }
  1093. // Don't do a rendition switch unless we have enough time to get a sync segment
  1094. // and conservatively guess
  1095. if (isWalkingForward) {
  1096. this.trigger('bandwidthupdate');
  1097. }
  1098. this.trigger('progress');
  1099. // any time an update finishes and the last segment is in the
  1100. // buffer, end the stream. this ensures the "ended" event will
  1101. // fire if playback reaches that point.
  1102. var isEndOfStream = detectEndOfStream(segmentInfo.playlist, this.mediaSource_, segmentInfo.mediaIndex + 1);
  1103. if (isEndOfStream) {
  1104. this.endOfStream();
  1105. }
  1106. if (!this.paused()) {
  1107. this.monitorBuffer_();
  1108. }
  1109. }
  1110. /**
  1111. * Records the current throughput of the decrypt, transmux, and append
  1112. * portion of the semgment pipeline. `throughput.rate` is a the cumulative
  1113. * moving average of the throughput. `throughput.count` is the number of
  1114. * data points in the average.
  1115. *
  1116. * @private
  1117. * @param {Object} segmentInfo the object returned by loadSegment
  1118. */
  1119. }, {
  1120. key: 'recordThroughput_',
  1121. value: function recordThroughput_(segmentInfo) {
  1122. var rate = this.throughput.rate;
  1123. // Add one to the time to ensure that we don't accidentally attempt to divide
  1124. // by zero in the case where the throughput is ridiculously high
  1125. var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1;
  1126. // Multiply by 8000 to convert from bytes/millisecond to bits/second
  1127. var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000);
  1128. // This is just a cumulative moving average calculation:
  1129. // newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1)
  1130. this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count;
  1131. }
  1132. /**
  1133. * A debugging logger noop that is set to console.log only if debugging
  1134. * is enabled globally
  1135. *
  1136. * @private
  1137. */
  1138. }, {
  1139. key: 'logger_',
  1140. value: function logger_() {}
  1141. /**
  1142. * Adds a cue to the segment-metadata track with some metadata information about the
  1143. * segment
  1144. *
  1145. * @private
  1146. * @param {Object} segmentInfo
  1147. * the object returned by loadSegment
  1148. * @method addSegmentMetadataCue_
  1149. */
  1150. }, {
  1151. key: 'addSegmentMetadataCue_',
  1152. value: function addSegmentMetadataCue_(segmentInfo) {
  1153. if (!this.segmentMetadataTrack_) {
  1154. return;
  1155. }
  1156. var segment = segmentInfo.segment;
  1157. var start = segment.start;
  1158. var end = segment.end;
  1159. // Do not try adding the cue if the start and end times are invalid.
  1160. if (!finite(start) || !finite(end)) {
  1161. return;
  1162. }
  1163. (0, _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs2['default'])(start, end, this.segmentMetadataTrack_);
  1164. var Cue = _globalWindow2['default'].WebKitDataCue || _globalWindow2['default'].VTTCue;
  1165. var value = {
  1166. bandwidth: segmentInfo.playlist.attributes.BANDWIDTH,
  1167. resolution: segmentInfo.playlist.attributes.RESOLUTION,
  1168. codecs: segmentInfo.playlist.attributes.CODECS,
  1169. byteLength: segmentInfo.byteLength,
  1170. uri: segmentInfo.uri,
  1171. timeline: segmentInfo.timeline,
  1172. playlist: segmentInfo.playlist.uri,
  1173. start: start,
  1174. end: end
  1175. };
  1176. var data = JSON.stringify(value);
  1177. var cue = new Cue(start, end, data);
  1178. // Attach the metadata to the value property of the cue to keep consistency between
  1179. // the differences of WebKitDataCue in safari and VTTCue in other browsers
  1180. cue.value = value;
  1181. this.segmentMetadataTrack_.addCue(cue);
  1182. }
  1183. }]);
  1184. return SegmentLoader;
  1185. })(_videoJs2['default'].EventTarget);
  1186. exports['default'] = SegmentLoader;