master-playlist-controller.js 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363
  1. /**
  2. * @file master-playlist-controller.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(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _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 { _x = parent; _x2 = property; _x3 = 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 _playlistLoader = require('./playlist-loader');
  14. var _playlistLoader2 = _interopRequireDefault(_playlistLoader);
  15. var _playlistJs = require('./playlist.js');
  16. var _segmentLoader = require('./segment-loader');
  17. var _segmentLoader2 = _interopRequireDefault(_segmentLoader);
  18. var _vttSegmentLoader = require('./vtt-segment-loader');
  19. var _vttSegmentLoader2 = _interopRequireDefault(_vttSegmentLoader);
  20. var _ranges = require('./ranges');
  21. var _ranges2 = _interopRequireDefault(_ranges);
  22. var _videoJs = require('video.js');
  23. var _videoJs2 = _interopRequireDefault(_videoJs);
  24. var _adCueTags = require('./ad-cue-tags');
  25. var _adCueTags2 = _interopRequireDefault(_adCueTags);
  26. var _syncController = require('./sync-controller');
  27. var _syncController2 = _interopRequireDefault(_syncController);
  28. var _videojsContribMediaSourcesEs5CodecUtils = require('videojs-contrib-media-sources/es5/codec-utils');
  29. var _webwackify = require('webwackify');
  30. var _webwackify2 = _interopRequireDefault(_webwackify);
  31. var _decrypterWorker = require('./decrypter-worker');
  32. var _decrypterWorker2 = _interopRequireDefault(_decrypterWorker);
  33. var _config = require('./config');
  34. var _config2 = _interopRequireDefault(_config);
  35. var _utilCodecsJs = require('./util/codecs.js');
  36. var _mediaGroups = require('./media-groups');
  37. var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2;
  38. var Hls = undefined;
  39. // Default codec parameters if none were provided for video and/or audio
  40. var defaultCodecs = {
  41. videoCodec: 'avc1',
  42. videoObjectTypeIndicator: '.4d400d',
  43. // AAC-LC
  44. audioProfile: '2'
  45. };
  46. // SegmentLoader stats that need to have each loader's
  47. // values summed to calculate the final value
  48. var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred'];
  49. var sumLoaderStat = function sumLoaderStat(stat) {
  50. return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat];
  51. };
  52. var resolveDecrypterWorker = function resolveDecrypterWorker() {
  53. var result = undefined;
  54. try {
  55. result = require.resolve('./decrypter-worker');
  56. } catch (e) {
  57. // no result
  58. }
  59. return result;
  60. };
  61. /**
  62. * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the
  63. * standard `avc1.<hhhhhh>`.
  64. *
  65. * @param codecString {String} the codec string
  66. * @return {String} the codec string with old apple-style codecs replaced
  67. *
  68. * @private
  69. */
  70. var mapLegacyAvcCodecs_ = function mapLegacyAvcCodecs_(codecString) {
  71. return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) {
  72. return (0, _videojsContribMediaSourcesEs5CodecUtils.translateLegacyCodecs)([match])[0];
  73. });
  74. };
  75. exports.mapLegacyAvcCodecs_ = mapLegacyAvcCodecs_;
  76. /**
  77. * Build a media mime-type string from a set of parameters
  78. * @param {String} type either 'audio' or 'video'
  79. * @param {String} container either 'mp2t' or 'mp4'
  80. * @param {Array} codecs an array of codec strings to add
  81. * @return {String} a valid media mime-type
  82. */
  83. var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) {
  84. // The codecs array is filtered so that falsey values are
  85. // dropped and don't cause Array#join to create spurious
  86. // commas
  87. return type + '/' + container + '; codecs="' + codecs.filter(function (c) {
  88. return !!c;
  89. }).join(', ') + '"';
  90. };
  91. /**
  92. * Returns the type container based on information in the playlist
  93. * @param {Playlist} media the current media playlist
  94. * @return {String} a valid media container type
  95. */
  96. var getContainerType = function getContainerType(media) {
  97. // An initialization segment means the media playlist is an iframe
  98. // playlist or is using the mp4 container. We don't currently
  99. // support iframe playlists, so assume this is signalling mp4
  100. // fragments.
  101. if (media.segments && media.segments.length && media.segments[0].map) {
  102. return 'mp4';
  103. }
  104. return 'mp2t';
  105. };
  106. /**
  107. * Returns a set of codec strings parsed from the playlist or the default
  108. * codec strings if no codecs were specified in the playlist
  109. * @param {Playlist} media the current media playlist
  110. * @return {Object} an object with the video and audio codecs
  111. */
  112. var getCodecs = function getCodecs(media) {
  113. // if the codecs were explicitly specified, use them instead of the
  114. // defaults
  115. var mediaAttributes = media.attributes || {};
  116. if (mediaAttributes.CODECS) {
  117. return (0, _utilCodecsJs.parseCodecs)(mediaAttributes.CODECS);
  118. }
  119. return defaultCodecs;
  120. };
  121. /**
  122. * Calculates the MIME type strings for a working configuration of
  123. * SourceBuffers to play variant streams in a master playlist. If
  124. * there is no possible working configuration, an empty array will be
  125. * returned.
  126. *
  127. * @param master {Object} the m3u8 object for the master playlist
  128. * @param media {Object} the m3u8 object for the variant playlist
  129. * @return {Array} the MIME type strings. If the array has more than
  130. * one entry, the first element should be applied to the video
  131. * SourceBuffer and the second to the audio SourceBuffer.
  132. *
  133. * @private
  134. */
  135. var mimeTypesForPlaylist_ = function mimeTypesForPlaylist_(master, media) {
  136. var containerType = getContainerType(media);
  137. var codecInfo = getCodecs(media);
  138. var mediaAttributes = media.attributes || {};
  139. // Default condition for a traditional HLS (no demuxed audio/video)
  140. var isMuxed = true;
  141. var isMaat = false;
  142. if (!media) {
  143. // Not enough information
  144. return [];
  145. }
  146. if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) {
  147. var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO];
  148. // Handle the case where we are in a multiple-audio track scenario
  149. if (audioGroup) {
  150. isMaat = true;
  151. // Start with the everything demuxed then...
  152. isMuxed = false;
  153. // ...check to see if any audio group tracks are muxed (ie. lacking a uri)
  154. for (var groupId in audioGroup) {
  155. if (!audioGroup[groupId].uri) {
  156. isMuxed = true;
  157. break;
  158. }
  159. }
  160. }
  161. }
  162. // HLS with multiple-audio tracks must always get an audio codec.
  163. // Put another way, there is no way to have a video-only multiple-audio HLS!
  164. if (isMaat && !codecInfo.audioProfile) {
  165. _videoJs2['default'].log.warn('Multiple audio tracks present but no audio codec string is specified. ' + 'Attempting to use the default audio codec (mp4a.40.2)');
  166. codecInfo.audioProfile = defaultCodecs.audioProfile;
  167. }
  168. // Generate the final codec strings from the codec object generated above
  169. var codecStrings = {};
  170. if (codecInfo.videoCodec) {
  171. codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator;
  172. }
  173. if (codecInfo.audioProfile) {
  174. codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile;
  175. }
  176. // Finally, make and return an array with proper mime-types depending on
  177. // the configuration
  178. var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]);
  179. var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]);
  180. var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]);
  181. if (isMaat) {
  182. if (!isMuxed && codecStrings.video) {
  183. return [justVideo, justAudio];
  184. }
  185. // There exists the possiblity that this will return a `video/container`
  186. // mime-type for the first entry in the array even when there is only audio.
  187. // This doesn't appear to be a problem and simplifies the code.
  188. return [bothVideoAudio, justAudio];
  189. }
  190. // If there is ano video codec at all, always just return a single
  191. // audio/<container> mime-type
  192. if (!codecStrings.video) {
  193. return [justAudio];
  194. }
  195. // When not using separate audio media groups, audio and video is
  196. // *always* muxed
  197. return [bothVideoAudio];
  198. };
  199. exports.mimeTypesForPlaylist_ = mimeTypesForPlaylist_;
  200. /**
  201. * the master playlist controller controls all interactons
  202. * between playlists and segmentloaders. At this time this mainly
  203. * involves a master playlist and a series of audio playlists
  204. * if they are available
  205. *
  206. * @class MasterPlaylistController
  207. * @extends videojs.EventTarget
  208. */
  209. var MasterPlaylistController = (function (_videojs$EventTarget) {
  210. _inherits(MasterPlaylistController, _videojs$EventTarget);
  211. function MasterPlaylistController(options) {
  212. var _this = this;
  213. _classCallCheck(this, MasterPlaylistController);
  214. _get(Object.getPrototypeOf(MasterPlaylistController.prototype), 'constructor', this).call(this);
  215. var url = options.url;
  216. var handleManifestRedirects = options.handleManifestRedirects;
  217. var withCredentials = options.withCredentials;
  218. var mode = options.mode;
  219. var tech = options.tech;
  220. var bandwidth = options.bandwidth;
  221. var externHls = options.externHls;
  222. var useCueTags = options.useCueTags;
  223. var blacklistDuration = options.blacklistDuration;
  224. var enableLowInitialPlaylist = options.enableLowInitialPlaylist;
  225. if (!url) {
  226. throw new Error('A non-empty playlist URL is required');
  227. }
  228. Hls = externHls;
  229. this.tech_ = tech;
  230. this.hls_ = tech.hls;
  231. this.mode_ = mode;
  232. this.useCueTags_ = useCueTags;
  233. this.blacklistDuration = blacklistDuration;
  234. this.enableLowInitialPlaylist = enableLowInitialPlaylist;
  235. if (this.useCueTags_) {
  236. this.cueTagsTrack_ = this.tech_.addTextTrack('metadata', 'ad-cues');
  237. this.cueTagsTrack_.inBandMetadataTrackDispatchType = '';
  238. }
  239. this.requestOptions_ = {
  240. withCredentials: withCredentials,
  241. handleManifestRedirects: handleManifestRedirects,
  242. timeout: null
  243. };
  244. this.mediaTypes_ = (0, _mediaGroups.createMediaTypes)();
  245. this.mediaSource = new _videoJs2['default'].MediaSource({ mode: mode });
  246. // load the media source into the player
  247. this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen_.bind(this));
  248. this.seekable_ = _videoJs2['default'].createTimeRanges();
  249. this.hasPlayed_ = function () {
  250. return false;
  251. };
  252. this.syncController_ = new _syncController2['default'](options);
  253. this.segmentMetadataTrack_ = tech.addRemoteTextTrack({
  254. kind: 'metadata',
  255. label: 'segment-metadata'
  256. }, false).track;
  257. this.decrypter_ = (0, _webwackify2['default'])(_decrypterWorker2['default'], resolveDecrypterWorker());
  258. var segmentLoaderSettings = {
  259. hls: this.hls_,
  260. mediaSource: this.mediaSource,
  261. currentTime: this.tech_.currentTime.bind(this.tech_),
  262. seekable: function seekable() {
  263. return _this.seekable();
  264. },
  265. seeking: function seeking() {
  266. return _this.tech_.seeking();
  267. },
  268. duration: function duration() {
  269. return _this.mediaSource.duration;
  270. },
  271. hasPlayed: function hasPlayed() {
  272. return _this.hasPlayed_();
  273. },
  274. goalBufferLength: function goalBufferLength() {
  275. return _this.goalBufferLength();
  276. },
  277. bandwidth: bandwidth,
  278. syncController: this.syncController_,
  279. decrypter: this.decrypter_
  280. };
  281. // setup playlist loaders
  282. this.masterPlaylistLoader_ = new _playlistLoader2['default'](url, this.hls_, this.requestOptions_);
  283. this.setupMasterPlaylistLoaderListeners_();
  284. // setup segment loaders
  285. // combined audio/video or just video when alternate audio track is selected
  286. this.mainSegmentLoader_ = new _segmentLoader2['default'](_videoJs2['default'].mergeOptions(segmentLoaderSettings, {
  287. segmentMetadataTrack: this.segmentMetadataTrack_,
  288. loaderType: 'main'
  289. }), options);
  290. // alternate audio track
  291. this.audioSegmentLoader_ = new _segmentLoader2['default'](_videoJs2['default'].mergeOptions(segmentLoaderSettings, {
  292. loaderType: 'audio'
  293. }), options);
  294. this.subtitleSegmentLoader_ = new _vttSegmentLoader2['default'](_videoJs2['default'].mergeOptions(segmentLoaderSettings, {
  295. loaderType: 'vtt'
  296. }), options);
  297. this.setupSegmentLoaderListeners_();
  298. // Create SegmentLoader stat-getters
  299. loaderStats.forEach(function (stat) {
  300. _this[stat + '_'] = sumLoaderStat.bind(_this, stat);
  301. });
  302. this.masterPlaylistLoader_.load();
  303. }
  304. /**
  305. * Register event handlers on the master playlist loader. A helper
  306. * function for construction time.
  307. *
  308. * @private
  309. */
  310. _createClass(MasterPlaylistController, [{
  311. key: 'setupMasterPlaylistLoaderListeners_',
  312. value: function setupMasterPlaylistLoaderListeners_() {
  313. var _this2 = this;
  314. this.masterPlaylistLoader_.on('loadedmetadata', function () {
  315. var media = _this2.masterPlaylistLoader_.media();
  316. var requestTimeout = _this2.masterPlaylistLoader_.targetDuration * 1.5 * 1000;
  317. // If we don't have any more available playlists, we don't want to
  318. // timeout the request.
  319. if ((0, _playlistJs.isLowestEnabledRendition)(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
  320. _this2.requestOptions_.timeout = 0;
  321. } else {
  322. _this2.requestOptions_.timeout = requestTimeout;
  323. }
  324. // if this isn't a live video and preload permits, start
  325. // downloading segments
  326. if (media.endList && _this2.tech_.preload() !== 'none') {
  327. _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
  328. _this2.mainSegmentLoader_.load();
  329. }
  330. (0, _mediaGroups.setupMediaGroups)({
  331. segmentLoaders: {
  332. AUDIO: _this2.audioSegmentLoader_,
  333. SUBTITLES: _this2.subtitleSegmentLoader_,
  334. main: _this2.mainSegmentLoader_
  335. },
  336. tech: _this2.tech_,
  337. requestOptions: _this2.requestOptions_,
  338. masterPlaylistLoader: _this2.masterPlaylistLoader_,
  339. mode: _this2.mode_,
  340. hls: _this2.hls_,
  341. master: _this2.master(),
  342. mediaTypes: _this2.mediaTypes_,
  343. blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2)
  344. });
  345. _this2.triggerPresenceUsage_(_this2.master(), media);
  346. try {
  347. _this2.setupSourceBuffers_();
  348. } catch (e) {
  349. _videoJs2['default'].log.warn('Failed to create SourceBuffers', e);
  350. return _this2.mediaSource.endOfStream('decode');
  351. }
  352. _this2.setupFirstPlay();
  353. _this2.trigger('selectedinitialmedia');
  354. });
  355. this.masterPlaylistLoader_.on('loadedplaylist', function () {
  356. var updatedPlaylist = _this2.masterPlaylistLoader_.media();
  357. if (!updatedPlaylist) {
  358. var selectedMedia = undefined;
  359. if (_this2.enableLowInitialPlaylist) {
  360. selectedMedia = _this2.selectInitialPlaylist();
  361. }
  362. if (!selectedMedia) {
  363. selectedMedia = _this2.selectPlaylist();
  364. }
  365. _this2.initialMedia_ = selectedMedia;
  366. _this2.masterPlaylistLoader_.media(_this2.initialMedia_);
  367. return;
  368. }
  369. if (_this2.useCueTags_) {
  370. _this2.updateAdCues_(updatedPlaylist);
  371. }
  372. // TODO: Create a new event on the PlaylistLoader that signals
  373. // that the segments have changed in some way and use that to
  374. // update the SegmentLoader instead of doing it twice here and
  375. // on `mediachange`
  376. _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_);
  377. _this2.updateDuration();
  378. // If the player isn't paused, ensure that the segment loader is running,
  379. // as it is possible that it was temporarily stopped while waiting for
  380. // a playlist (e.g., in case the playlist errored and we re-requested it).
  381. if (!_this2.tech_.paused()) {
  382. _this2.mainSegmentLoader_.load();
  383. }
  384. if (!updatedPlaylist.endList) {
  385. (function () {
  386. var addSeekableRange = function addSeekableRange() {
  387. var seekable = _this2.seekable();
  388. if (seekable.length !== 0) {
  389. _this2.mediaSource.addSeekableRange_(seekable.start(0), seekable.end(0));
  390. }
  391. };
  392. if (_this2.duration() !== Infinity) {
  393. (function () {
  394. var onDurationchange = function onDurationchange() {
  395. if (_this2.duration() === Infinity) {
  396. addSeekableRange();
  397. } else {
  398. _this2.tech_.one('durationchange', onDurationchange);
  399. }
  400. };
  401. _this2.tech_.one('durationchange', onDurationchange);
  402. })();
  403. } else {
  404. addSeekableRange();
  405. }
  406. })();
  407. }
  408. });
  409. this.masterPlaylistLoader_.on('error', function () {
  410. _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error);
  411. });
  412. this.masterPlaylistLoader_.on('mediachanging', function () {
  413. _this2.mainSegmentLoader_.abort();
  414. _this2.mainSegmentLoader_.pause();
  415. });
  416. this.masterPlaylistLoader_.on('mediachange', function () {
  417. var media = _this2.masterPlaylistLoader_.media();
  418. var requestTimeout = _this2.masterPlaylistLoader_.targetDuration * 1.5 * 1000;
  419. // If we don't have any more available playlists, we don't want to
  420. // timeout the request.
  421. if ((0, _playlistJs.isLowestEnabledRendition)(_this2.masterPlaylistLoader_.master, _this2.masterPlaylistLoader_.media())) {
  422. _this2.requestOptions_.timeout = 0;
  423. } else {
  424. _this2.requestOptions_.timeout = requestTimeout;
  425. }
  426. // TODO: Create a new event on the PlaylistLoader that signals
  427. // that the segments have changed in some way and use that to
  428. // update the SegmentLoader instead of doing it twice here and
  429. // on `loadedplaylist`
  430. _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_);
  431. _this2.mainSegmentLoader_.load();
  432. _this2.tech_.trigger({
  433. type: 'mediachange',
  434. bubbles: true
  435. });
  436. });
  437. this.masterPlaylistLoader_.on('playlistunchanged', function () {
  438. var updatedPlaylist = _this2.masterPlaylistLoader_.media();
  439. var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist);
  440. if (playlistOutdated) {
  441. // Playlist has stopped updating and we're stuck at its end. Try to
  442. // blacklist it and switch to another playlist in the hope that that
  443. // one is updating (and give the player a chance to re-adjust to the
  444. // safe live point).
  445. _this2.blacklistCurrentPlaylist({
  446. message: 'Playlist no longer updating.'
  447. });
  448. // useful for monitoring QoS
  449. _this2.tech_.trigger('playliststuck');
  450. }
  451. });
  452. this.masterPlaylistLoader_.on('renditiondisabled', function () {
  453. _this2.tech_.trigger({ type: 'usage', name: 'hls-rendition-disabled' });
  454. });
  455. this.masterPlaylistLoader_.on('renditionenabled', function () {
  456. _this2.tech_.trigger({ type: 'usage', name: 'hls-rendition-enabled' });
  457. });
  458. }
  459. /**
  460. * A helper function for triggerring presence usage events once per source
  461. *
  462. * @private
  463. */
  464. }, {
  465. key: 'triggerPresenceUsage_',
  466. value: function triggerPresenceUsage_(master, media) {
  467. var mediaGroups = master.mediaGroups || {};
  468. var defaultDemuxed = true;
  469. var audioGroupKeys = Object.keys(mediaGroups.AUDIO);
  470. for (var mediaGroup in mediaGroups.AUDIO) {
  471. for (var label in mediaGroups.AUDIO[mediaGroup]) {
  472. var properties = mediaGroups.AUDIO[mediaGroup][label];
  473. if (!properties.uri) {
  474. defaultDemuxed = false;
  475. }
  476. }
  477. }
  478. if (defaultDemuxed) {
  479. this.tech_.trigger({ type: 'usage', name: 'hls-demuxed' });
  480. }
  481. if (Object.keys(mediaGroups.SUBTITLES).length) {
  482. this.tech_.trigger({ type: 'usage', name: 'hls-webvtt' });
  483. }
  484. if (Hls.Playlist.isAes(media)) {
  485. this.tech_.trigger({ type: 'usage', name: 'hls-aes' });
  486. }
  487. if (Hls.Playlist.isFmp4(media)) {
  488. this.tech_.trigger({ type: 'usage', name: 'hls-fmp4' });
  489. }
  490. if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) {
  491. this.tech_.trigger({ type: 'usage', name: 'hls-alternate-audio' });
  492. }
  493. if (this.useCueTags_) {
  494. this.tech_.trigger({ type: 'usage', name: 'hls-playlist-cue-tags' });
  495. }
  496. }
  497. /**
  498. * Register event handlers on the segment loaders. A helper function
  499. * for construction time.
  500. *
  501. * @private
  502. */
  503. }, {
  504. key: 'setupSegmentLoaderListeners_',
  505. value: function setupSegmentLoaderListeners_() {
  506. var _this3 = this;
  507. this.mainSegmentLoader_.on('bandwidthupdate', function () {
  508. var nextPlaylist = _this3.selectPlaylist();
  509. var currentPlaylist = _this3.masterPlaylistLoader_.media();
  510. var buffered = _this3.tech_.buffered();
  511. var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0;
  512. var bufferLowWaterLine = _this3.bufferLowWaterLine();
  513. // If the playlist is live, then we want to not take low water line into account.
  514. // This is because in LIVE, the player plays 3 segments from the end of the
  515. // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble
  516. // in those segments, a viewer will never experience a rendition upswitch.
  517. if (!currentPlaylist.endList ||
  518. // For the same reason as LIVE, we ignore the low water line when the VOD
  519. // duration is below the max potential low water line
  520. _this3.duration() < _config2['default'].MAX_BUFFER_LOW_WATER_LINE ||
  521. // we want to switch down to lower resolutions quickly to continue playback, but
  522. nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH ||
  523. // ensure we have some buffer before we switch up to prevent us running out of
  524. // buffer while loading a higher rendition.
  525. forwardBuffer >= bufferLowWaterLine) {
  526. _this3.masterPlaylistLoader_.media(nextPlaylist);
  527. }
  528. _this3.tech_.trigger('bandwidthupdate');
  529. });
  530. this.mainSegmentLoader_.on('progress', function () {
  531. _this3.trigger('progress');
  532. });
  533. this.mainSegmentLoader_.on('error', function () {
  534. _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error());
  535. });
  536. this.mainSegmentLoader_.on('syncinfoupdate', function () {
  537. _this3.onSyncInfoUpdate_();
  538. });
  539. this.mainSegmentLoader_.on('timestampoffset', function () {
  540. _this3.tech_.trigger({ type: 'usage', name: 'hls-timestamp-offset' });
  541. });
  542. this.audioSegmentLoader_.on('syncinfoupdate', function () {
  543. _this3.onSyncInfoUpdate_();
  544. });
  545. this.mainSegmentLoader_.on('ended', function () {
  546. _this3.onEndOfStream();
  547. });
  548. this.mainSegmentLoader_.on('earlyabort', function () {
  549. _this3.blacklistCurrentPlaylist({
  550. message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.'
  551. }, ABORT_EARLY_BLACKLIST_SECONDS);
  552. });
  553. this.mainSegmentLoader_.on('reseteverything', function () {
  554. // If playing an MTS stream, a videojs.MediaSource is listening for
  555. // hls-reset to reset caption parsing state in the transmuxer
  556. _this3.tech_.trigger('hls-reset');
  557. });
  558. this.mainSegmentLoader_.on('segmenttimemapping', function (event) {
  559. // If playing an MTS stream in html, a videojs.MediaSource is listening for
  560. // hls-segment-time-mapping update its internal mapping of stream to display time
  561. _this3.tech_.trigger({
  562. type: 'hls-segment-time-mapping',
  563. mapping: event.mapping
  564. });
  565. });
  566. this.audioSegmentLoader_.on('ended', function () {
  567. _this3.onEndOfStream();
  568. });
  569. }
  570. }, {
  571. key: 'mediaSecondsLoaded_',
  572. value: function mediaSecondsLoaded_() {
  573. return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded);
  574. }
  575. /**
  576. * Call load on our SegmentLoaders
  577. */
  578. }, {
  579. key: 'load',
  580. value: function load() {
  581. this.mainSegmentLoader_.load();
  582. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  583. this.audioSegmentLoader_.load();
  584. }
  585. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  586. this.subtitleSegmentLoader_.load();
  587. }
  588. }
  589. /**
  590. * Re-tune playback quality level for the current player
  591. * conditions. This method may perform destructive actions, like
  592. * removing already buffered content, to readjust the currently
  593. * active playlist quickly.
  594. *
  595. * @private
  596. */
  597. }, {
  598. key: 'fastQualityChange_',
  599. value: function fastQualityChange_() {
  600. var media = this.selectPlaylist();
  601. if (media !== this.masterPlaylistLoader_.media()) {
  602. this.masterPlaylistLoader_.media(media);
  603. this.mainSegmentLoader_.resetLoader();
  604. // don't need to reset audio as it is reset when media changes
  605. }
  606. }
  607. /**
  608. * Begin playback.
  609. */
  610. }, {
  611. key: 'play',
  612. value: function play() {
  613. if (this.setupFirstPlay()) {
  614. return;
  615. }
  616. if (this.tech_.ended()) {
  617. this.tech_.setCurrentTime(0);
  618. }
  619. if (this.hasPlayed_()) {
  620. this.load();
  621. }
  622. var seekable = this.tech_.seekable();
  623. // if the viewer has paused and we fell out of the live window,
  624. // seek forward to the live point
  625. if (this.tech_.duration() === Infinity) {
  626. if (this.tech_.currentTime() < seekable.start(0)) {
  627. return this.tech_.setCurrentTime(seekable.end(seekable.length - 1));
  628. }
  629. }
  630. }
  631. /**
  632. * Seek to the latest media position if this is a live video and the
  633. * player and video are loaded and initialized.
  634. */
  635. }, {
  636. key: 'setupFirstPlay',
  637. value: function setupFirstPlay() {
  638. var _this4 = this;
  639. var media = this.masterPlaylistLoader_.media();
  640. // Check that everything is ready to begin buffering for the first call to play
  641. // If 1) there is no active media
  642. // 2) the player is paused
  643. // 3) the first play has already been setup
  644. // then exit early
  645. if (!media || this.tech_.paused() || this.hasPlayed_()) {
  646. return false;
  647. }
  648. // when the video is a live stream
  649. if (!media.endList) {
  650. var _ret3 = (function () {
  651. var seekable = _this4.seekable();
  652. if (!seekable.length) {
  653. // without a seekable range, the player cannot seek to begin buffering at the live
  654. // point
  655. return {
  656. v: false
  657. };
  658. }
  659. if (_videoJs2['default'].browser.IE_VERSION && _this4.mode_ === 'html5' && _this4.tech_.readyState() === 0) {
  660. // IE11 throws an InvalidStateError if you try to set currentTime while the
  661. // readyState is 0, so it must be delayed until the tech fires loadedmetadata.
  662. _this4.tech_.one('loadedmetadata', function () {
  663. _this4.trigger('firstplay');
  664. _this4.tech_.setCurrentTime(seekable.end(0));
  665. _this4.hasPlayed_ = function () {
  666. return true;
  667. };
  668. });
  669. return {
  670. v: false
  671. };
  672. }
  673. // trigger firstplay to inform the source handler to ignore the next seek event
  674. _this4.trigger('firstplay');
  675. // seek to the live point
  676. _this4.tech_.setCurrentTime(seekable.end(0));
  677. })();
  678. if (typeof _ret3 === 'object') return _ret3.v;
  679. }
  680. this.hasPlayed_ = function () {
  681. return true;
  682. };
  683. // we can begin loading now that everything is ready
  684. this.load();
  685. return true;
  686. }
  687. /**
  688. * handle the sourceopen event on the MediaSource
  689. *
  690. * @private
  691. */
  692. }, {
  693. key: 'handleSourceOpen_',
  694. value: function handleSourceOpen_() {
  695. // Only attempt to create the source buffer if none already exist.
  696. // handleSourceOpen is also called when we are "re-opening" a source buffer
  697. // after `endOfStream` has been called (in response to a seek for instance)
  698. try {
  699. this.setupSourceBuffers_();
  700. } catch (e) {
  701. _videoJs2['default'].log.warn('Failed to create Source Buffers', e);
  702. return this.mediaSource.endOfStream('decode');
  703. }
  704. // if autoplay is enabled, begin playback. This is duplicative of
  705. // code in video.js but is required because play() must be invoked
  706. // *after* the media source has opened.
  707. if (this.tech_.autoplay()) {
  708. var playPromise = this.tech_.play();
  709. // Catch/silence error when a pause interrupts a play request
  710. // on browsers which return a promise
  711. if (typeof playPromise !== 'undefined' && typeof playPromise.then === 'function') {
  712. playPromise.then(null, function (e) {});
  713. }
  714. }
  715. this.trigger('sourceopen');
  716. }
  717. /**
  718. * Calls endOfStream on the media source when all active stream types have called
  719. * endOfStream
  720. *
  721. * @param {string} streamType
  722. * Stream type of the segment loader that called endOfStream
  723. * @private
  724. */
  725. }, {
  726. key: 'onEndOfStream',
  727. value: function onEndOfStream() {
  728. var isEndOfStream = this.mainSegmentLoader_.ended_;
  729. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  730. // if the audio playlist loader exists, then alternate audio is active, so we need
  731. // to wait for both the main and audio segment loaders to call endOfStream
  732. isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_;
  733. }
  734. if (isEndOfStream) {
  735. this.mediaSource.endOfStream();
  736. }
  737. }
  738. /**
  739. * Check if a playlist has stopped being updated
  740. * @param {Object} playlist the media playlist object
  741. * @return {boolean} whether the playlist has stopped being updated or not
  742. */
  743. }, {
  744. key: 'stuckAtPlaylistEnd_',
  745. value: function stuckAtPlaylistEnd_(playlist) {
  746. var seekable = this.seekable();
  747. if (!seekable.length) {
  748. // playlist doesn't have enough information to determine whether we are stuck
  749. return false;
  750. }
  751. var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration);
  752. if (expired === null) {
  753. return false;
  754. }
  755. // does not use the safe live end to calculate playlist end, since we
  756. // don't want to say we are stuck while there is still content
  757. var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired);
  758. var currentTime = this.tech_.currentTime();
  759. var buffered = this.tech_.buffered();
  760. if (!buffered.length) {
  761. // return true if the playhead reached the absolute end of the playlist
  762. return absolutePlaylistEnd - currentTime <= _ranges2['default'].SAFE_TIME_DELTA;
  763. }
  764. var bufferedEnd = buffered.end(buffered.length - 1);
  765. // return true if there is too little buffer left and buffer has reached absolute
  766. // end of playlist
  767. return bufferedEnd - currentTime <= _ranges2['default'].SAFE_TIME_DELTA && absolutePlaylistEnd - bufferedEnd <= _ranges2['default'].SAFE_TIME_DELTA;
  768. }
  769. /**
  770. * Blacklists a playlist when an error occurs for a set amount of time
  771. * making it unavailable for selection by the rendition selection algorithm
  772. * and then forces a new playlist (rendition) selection.
  773. *
  774. * @param {Object=} error an optional error that may include the playlist
  775. * to blacklist
  776. * @param {Number=} blacklistDuration an optional number of seconds to blacklist the
  777. * playlist
  778. */
  779. }, {
  780. key: 'blacklistCurrentPlaylist',
  781. value: function blacklistCurrentPlaylist(error, blacklistDuration) {
  782. if (error === undefined) error = {};
  783. var currentPlaylist = undefined;
  784. var nextPlaylist = undefined;
  785. // If the `error` was generated by the playlist loader, it will contain
  786. // the playlist we were trying to load (but failed) and that should be
  787. // blacklisted instead of the currently selected playlist which is likely
  788. // out-of-date in this scenario
  789. currentPlaylist = error.playlist || this.masterPlaylistLoader_.media();
  790. blacklistDuration = blacklistDuration || error.blacklistDuration || this.blacklistDuration;
  791. // If there is no current playlist, then an error occurred while we were
  792. // trying to load the master OR while we were disposing of the tech
  793. if (!currentPlaylist) {
  794. this.error = error;
  795. try {
  796. return this.mediaSource.endOfStream('network');
  797. } catch (e) {
  798. return this.trigger('error');
  799. }
  800. }
  801. var isFinalRendition = this.masterPlaylistLoader_.master.playlists.filter(_playlistJs.isEnabled).length === 1;
  802. if (isFinalRendition) {
  803. // Never blacklisting this playlist because it's final rendition
  804. _videoJs2['default'].log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the final playlist.');
  805. this.tech_.trigger('retryplaylist');
  806. return this.masterPlaylistLoader_.load(isFinalRendition);
  807. }
  808. // Blacklist this playlist
  809. currentPlaylist.excludeUntil = Date.now() + blacklistDuration * 1000;
  810. this.tech_.trigger('blacklistplaylist');
  811. this.tech_.trigger({ type: 'usage', name: 'hls-rendition-blacklisted' });
  812. // Select a new playlist
  813. nextPlaylist = this.selectPlaylist();
  814. _videoJs2['default'].log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.');
  815. return this.masterPlaylistLoader_.media(nextPlaylist);
  816. }
  817. /**
  818. * Pause all segment loaders
  819. */
  820. }, {
  821. key: 'pauseLoading',
  822. value: function pauseLoading() {
  823. this.mainSegmentLoader_.pause();
  824. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  825. this.audioSegmentLoader_.pause();
  826. }
  827. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  828. this.subtitleSegmentLoader_.pause();
  829. }
  830. }
  831. /**
  832. * set the current time on all segment loaders
  833. *
  834. * @param {TimeRange} currentTime the current time to set
  835. * @return {TimeRange} the current time
  836. */
  837. }, {
  838. key: 'setCurrentTime',
  839. value: function setCurrentTime(currentTime) {
  840. var buffered = _ranges2['default'].findRange(this.tech_.buffered(), currentTime);
  841. if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) {
  842. // return immediately if the metadata is not ready yet
  843. return 0;
  844. }
  845. // it's clearly an edge-case but don't thrown an error if asked to
  846. // seek within an empty playlist
  847. if (!this.masterPlaylistLoader_.media().segments) {
  848. return 0;
  849. }
  850. // In flash playback, the segment loaders should be reset on every seek, even
  851. // in buffer seeks. If the seek location is already buffered, continue buffering as
  852. // usual
  853. if (buffered && buffered.length && this.mode_ !== 'flash') {
  854. return currentTime;
  855. }
  856. // cancel outstanding requests so we begin buffering at the new
  857. // location
  858. this.mainSegmentLoader_.resetEverything();
  859. this.mainSegmentLoader_.abort();
  860. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  861. this.audioSegmentLoader_.resetEverything();
  862. this.audioSegmentLoader_.abort();
  863. }
  864. if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
  865. this.subtitleSegmentLoader_.resetEverything();
  866. this.subtitleSegmentLoader_.abort();
  867. }
  868. // start segment loader loading in case they are paused
  869. this.load();
  870. }
  871. /**
  872. * get the current duration
  873. *
  874. * @return {TimeRange} the duration
  875. */
  876. }, {
  877. key: 'duration',
  878. value: function duration() {
  879. if (!this.masterPlaylistLoader_) {
  880. return 0;
  881. }
  882. if (this.mediaSource) {
  883. return this.mediaSource.duration;
  884. }
  885. return Hls.Playlist.duration(this.masterPlaylistLoader_.media());
  886. }
  887. /**
  888. * check the seekable range
  889. *
  890. * @return {TimeRange} the seekable range
  891. */
  892. }, {
  893. key: 'seekable',
  894. value: function seekable() {
  895. return this.seekable_;
  896. }
  897. }, {
  898. key: 'onSyncInfoUpdate_',
  899. value: function onSyncInfoUpdate_() {
  900. var mainSeekable = undefined;
  901. var audioSeekable = undefined;
  902. if (!this.masterPlaylistLoader_) {
  903. return;
  904. }
  905. var media = this.masterPlaylistLoader_.media();
  906. if (!media) {
  907. return;
  908. }
  909. var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
  910. if (expired === null) {
  911. // not enough information to update seekable
  912. return;
  913. }
  914. mainSeekable = Hls.Playlist.seekable(media, expired);
  915. if (mainSeekable.length === 0) {
  916. return;
  917. }
  918. if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
  919. media = this.mediaTypes_.AUDIO.activePlaylistLoader.media();
  920. expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration);
  921. if (expired === null) {
  922. return;
  923. }
  924. audioSeekable = Hls.Playlist.seekable(media, expired);
  925. if (audioSeekable.length === 0) {
  926. return;
  927. }
  928. }
  929. if (!audioSeekable) {
  930. // seekable has been calculated based on buffering video data so it
  931. // can be returned directly
  932. this.seekable_ = mainSeekable;
  933. } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) {
  934. // seekables are pretty far off, rely on main
  935. this.seekable_ = mainSeekable;
  936. } else {
  937. this.seekable_ = _videoJs2['default'].createTimeRanges([[audioSeekable.start(0) > mainSeekable.start(0) ? audioSeekable.start(0) : mainSeekable.start(0), audioSeekable.end(0) < mainSeekable.end(0) ? audioSeekable.end(0) : mainSeekable.end(0)]]);
  938. }
  939. this.tech_.trigger('seekablechanged');
  940. }
  941. /**
  942. * Update the player duration
  943. */
  944. }, {
  945. key: 'updateDuration',
  946. value: function updateDuration() {
  947. var _this5 = this;
  948. var oldDuration = this.mediaSource.duration;
  949. var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media());
  950. var buffered = this.tech_.buffered();
  951. var setDuration = function setDuration() {
  952. _this5.mediaSource.duration = newDuration;
  953. _this5.tech_.trigger('durationchange');
  954. _this5.mediaSource.removeEventListener('sourceopen', setDuration);
  955. };
  956. if (buffered.length > 0) {
  957. newDuration = Math.max(newDuration, buffered.end(buffered.length - 1));
  958. }
  959. // if the duration has changed, invalidate the cached value
  960. if (oldDuration !== newDuration) {
  961. // update the duration
  962. if (this.mediaSource.readyState !== 'open') {
  963. this.mediaSource.addEventListener('sourceopen', setDuration);
  964. } else {
  965. setDuration();
  966. }
  967. }
  968. }
  969. /**
  970. * dispose of the MasterPlaylistController and everything
  971. * that it controls
  972. */
  973. }, {
  974. key: 'dispose',
  975. value: function dispose() {
  976. var _this6 = this;
  977. this.decrypter_.terminate();
  978. this.masterPlaylistLoader_.dispose();
  979. this.mainSegmentLoader_.dispose();
  980. ['AUDIO', 'SUBTITLES'].forEach(function (type) {
  981. var groups = _this6.mediaTypes_[type].groups;
  982. for (var id in groups) {
  983. groups[id].forEach(function (group) {
  984. if (group.playlistLoader) {
  985. group.playlistLoader.dispose();
  986. }
  987. });
  988. }
  989. });
  990. this.audioSegmentLoader_.dispose();
  991. this.subtitleSegmentLoader_.dispose();
  992. }
  993. /**
  994. * return the master playlist object if we have one
  995. *
  996. * @return {Object} the master playlist object that we parsed
  997. */
  998. }, {
  999. key: 'master',
  1000. value: function master() {
  1001. return this.masterPlaylistLoader_.master;
  1002. }
  1003. /**
  1004. * return the currently selected playlist
  1005. *
  1006. * @return {Object} the currently selected playlist object that we parsed
  1007. */
  1008. }, {
  1009. key: 'media',
  1010. value: function media() {
  1011. // playlist loader will not return media if it has not been fully loaded
  1012. return this.masterPlaylistLoader_.media() || this.initialMedia_;
  1013. }
  1014. /**
  1015. * setup our internal source buffers on our segment Loaders
  1016. *
  1017. * @private
  1018. */
  1019. }, {
  1020. key: 'setupSourceBuffers_',
  1021. value: function setupSourceBuffers_() {
  1022. var media = this.masterPlaylistLoader_.media();
  1023. var mimeTypes = undefined;
  1024. // wait until a media playlist is available and the Media Source is
  1025. // attached
  1026. if (!media || this.mediaSource.readyState !== 'open') {
  1027. return;
  1028. }
  1029. mimeTypes = mimeTypesForPlaylist_(this.masterPlaylistLoader_.master, media);
  1030. if (mimeTypes.length < 1) {
  1031. this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri;
  1032. return this.mediaSource.endOfStream('decode');
  1033. }
  1034. this.mainSegmentLoader_.mimeType(mimeTypes[0]);
  1035. if (mimeTypes[1]) {
  1036. this.audioSegmentLoader_.mimeType(mimeTypes[1]);
  1037. }
  1038. // exclude any incompatible variant streams from future playlist
  1039. // selection
  1040. this.excludeIncompatibleVariants_(media);
  1041. }
  1042. /**
  1043. * Blacklist playlists that are known to be codec or
  1044. * stream-incompatible with the SourceBuffer configuration. For
  1045. * instance, Media Source Extensions would cause the video element to
  1046. * stall waiting for video data if you switched from a variant with
  1047. * video and audio to an audio-only one.
  1048. *
  1049. * @param {Object} media a media playlist compatible with the current
  1050. * set of SourceBuffers. Variants in the current master playlist that
  1051. * do not appear to have compatible codec or stream configurations
  1052. * will be excluded from the default playlist selection algorithm
  1053. * indefinitely.
  1054. * @private
  1055. */
  1056. }, {
  1057. key: 'excludeIncompatibleVariants_',
  1058. value: function excludeIncompatibleVariants_(media) {
  1059. var master = this.masterPlaylistLoader_.master;
  1060. var codecCount = 2;
  1061. var videoCodec = null;
  1062. var codecs = undefined;
  1063. if (media.attributes.CODECS) {
  1064. codecs = (0, _utilCodecsJs.parseCodecs)(media.attributes.CODECS);
  1065. videoCodec = codecs.videoCodec;
  1066. codecCount = codecs.codecCount;
  1067. }
  1068. master.playlists.forEach(function (variant) {
  1069. var variantCodecs = {
  1070. codecCount: 2,
  1071. videoCodec: null
  1072. };
  1073. if (variant.attributes.CODECS) {
  1074. var codecString = variant.attributes.CODECS;
  1075. variantCodecs = (0, _utilCodecsJs.parseCodecs)(codecString);
  1076. if (window.MediaSource && window.MediaSource.isTypeSupported && !window.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs_(codecString) + '"')) {
  1077. variant.excludeUntil = Infinity;
  1078. }
  1079. }
  1080. // if the streams differ in the presence or absence of audio or
  1081. // video, they are incompatible
  1082. if (variantCodecs.codecCount !== codecCount) {
  1083. variant.excludeUntil = Infinity;
  1084. }
  1085. // if h.264 is specified on the current playlist, some flavor of
  1086. // it must be specified on all compatible variants
  1087. if (variantCodecs.videoCodec !== videoCodec) {
  1088. variant.excludeUntil = Infinity;
  1089. }
  1090. });
  1091. }
  1092. }, {
  1093. key: 'updateAdCues_',
  1094. value: function updateAdCues_(media) {
  1095. var offset = 0;
  1096. var seekable = this.seekable();
  1097. if (seekable.length) {
  1098. offset = seekable.start(0);
  1099. }
  1100. _adCueTags2['default'].updateAdCues(media, this.cueTagsTrack_, offset);
  1101. }
  1102. /**
  1103. * Calculates the desired forward buffer length based on current time
  1104. *
  1105. * @return {Number} Desired forward buffer length in seconds
  1106. */
  1107. }, {
  1108. key: 'goalBufferLength',
  1109. value: function goalBufferLength() {
  1110. var currentTime = this.tech_.currentTime();
  1111. var initial = _config2['default'].GOAL_BUFFER_LENGTH;
  1112. var rate = _config2['default'].GOAL_BUFFER_LENGTH_RATE;
  1113. var max = Math.max(initial, _config2['default'].MAX_GOAL_BUFFER_LENGTH);
  1114. return Math.min(initial + currentTime * rate, max);
  1115. }
  1116. /**
  1117. * Calculates the desired buffer low water line based on current time
  1118. *
  1119. * @return {Number} Desired buffer low water line in seconds
  1120. */
  1121. }, {
  1122. key: 'bufferLowWaterLine',
  1123. value: function bufferLowWaterLine() {
  1124. var currentTime = this.tech_.currentTime();
  1125. var initial = _config2['default'].BUFFER_LOW_WATER_LINE;
  1126. var rate = _config2['default'].BUFFER_LOW_WATER_LINE_RATE;
  1127. var max = Math.max(initial, _config2['default'].MAX_BUFFER_LOW_WATER_LINE);
  1128. return Math.min(initial + currentTime * rate, max);
  1129. }
  1130. }]);
  1131. return MasterPlaylistController;
  1132. })(_videoJs2['default'].EventTarget);
  1133. exports.MasterPlaylistController = MasterPlaylistController;