| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628 | 
							- /**
 
-  * @module playlist-loader
 
-  *
 
-  * @file A state machine that manages the loading, caching, and updating of
 
-  * M3U8 playlists.
 
-  */
 
- 'use strict';
 
- Object.defineProperty(exports, '__esModule', {
 
-   value: true
 
- });
 
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
 
- var _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); } } };
 
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
 
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
 
- function _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; }
 
- var _resolveUrl = require('./resolve-url');
 
- var _resolveUrl2 = _interopRequireDefault(_resolveUrl);
 
- var _videoJs = require('video.js');
 
- var _m3u8Parser = require('m3u8-parser');
 
- var _m3u8Parser2 = _interopRequireDefault(_m3u8Parser);
 
- var _globalWindow = require('global/window');
 
- var _globalWindow2 = _interopRequireDefault(_globalWindow);
 
- /**
 
-  * Returns a new array of segments that is the result of merging
 
-  * properties from an older list of segments onto an updated
 
-  * list. No properties on the updated playlist will be overridden.
 
-  *
 
-  * @param {Array} original the outdated list of segments
 
-  * @param {Array} update the updated list of segments
 
-  * @param {Number=} offset the index of the first update
 
-  * segment in the original segment list. For non-live playlists,
 
-  * this should always be zero and does not need to be
 
-  * specified. For live playlists, it should be the difference
 
-  * between the media sequence numbers in the original and updated
 
-  * playlists.
 
-  * @return a list of merged segment objects
 
-  */
 
- var updateSegments = function updateSegments(original, update, offset) {
 
-   var result = update.slice();
 
-   offset = offset || 0;
 
-   var length = Math.min(original.length, update.length + offset);
 
-   for (var i = offset; i < length; i++) {
 
-     result[i - offset] = (0, _videoJs.mergeOptions)(original[i], result[i - offset]);
 
-   }
 
-   return result;
 
- };
 
- exports.updateSegments = updateSegments;
 
- var resolveSegmentUris = function resolveSegmentUris(segment, baseUri) {
 
-   if (!segment.resolvedUri) {
 
-     segment.resolvedUri = (0, _resolveUrl2['default'])(baseUri, segment.uri);
 
-   }
 
-   if (segment.key && !segment.key.resolvedUri) {
 
-     segment.key.resolvedUri = (0, _resolveUrl2['default'])(baseUri, segment.key.uri);
 
-   }
 
-   if (segment.map && !segment.map.resolvedUri) {
 
-     segment.map.resolvedUri = (0, _resolveUrl2['default'])(baseUri, segment.map.uri);
 
-   }
 
- };
 
- exports.resolveSegmentUris = resolveSegmentUris;
 
- /**
 
-   * Returns a new master playlist that is the result of merging an
 
-   * updated media playlist into the original version. If the
 
-   * updated media playlist does not match any of the playlist
 
-   * entries in the original master playlist, null is returned.
 
-   *
 
-   * @param {Object} master a parsed master M3U8 object
 
-   * @param {Object} media a parsed media M3U8 object
 
-   * @return {Object} a new object that represents the original
 
-   * master playlist with the updated media playlist merged in, or
 
-   * null if the merge produced no change.
 
-   */
 
- var updateMaster = function updateMaster(master, media) {
 
-   var result = (0, _videoJs.mergeOptions)(master, {});
 
-   var playlist = result.playlists.filter(function (p) {
 
-     return p.uri === media.uri;
 
-   })[0];
 
-   if (!playlist) {
 
-     return null;
 
-   }
 
-   // consider the playlist unchanged if the number of segments is equal and the media
 
-   // sequence number is unchanged
 
-   if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.mediaSequence === media.mediaSequence) {
 
-     return null;
 
-   }
 
-   var mergedPlaylist = (0, _videoJs.mergeOptions)(playlist, media);
 
-   // if the update could overlap existing segment information, merge the two segment lists
 
-   if (playlist.segments) {
 
-     mergedPlaylist.segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence);
 
-   }
 
-   // resolve any segment URIs to prevent us from having to do it later
 
-   mergedPlaylist.segments.forEach(function (segment) {
 
-     resolveSegmentUris(segment, mergedPlaylist.resolvedUri);
 
-   });
 
-   // TODO Right now in the playlists array there are two references to each playlist, one
 
-   // that is referenced by index, and one by URI. The index reference may no longer be
 
-   // necessary.
 
-   for (var i = 0; i < result.playlists.length; i++) {
 
-     if (result.playlists[i].uri === media.uri) {
 
-       result.playlists[i] = mergedPlaylist;
 
-     }
 
-   }
 
-   result.playlists[media.uri] = mergedPlaylist;
 
-   return result;
 
- };
 
- exports.updateMaster = updateMaster;
 
- var setupMediaPlaylists = function setupMediaPlaylists(master) {
 
-   // setup by-URI lookups and resolve media playlist URIs
 
-   var i = master.playlists.length;
 
-   while (i--) {
 
-     var playlist = master.playlists[i];
 
-     master.playlists[playlist.uri] = playlist;
 
-     playlist.resolvedUri = (0, _resolveUrl2['default'])(master.uri, playlist.uri);
 
-     if (!playlist.attributes) {
 
-       // Although the spec states an #EXT-X-STREAM-INF tag MUST have a
 
-       // BANDWIDTH attribute, we can play the stream without it. This means a poorly
 
-       // formatted master playlist may not have an attribute list. An attributes
 
-       // property is added here to prevent undefined references when we encounter
 
-       // this scenario.
 
-       playlist.attributes = {};
 
-       _videoJs.log.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.');
 
-     }
 
-   }
 
- };
 
- exports.setupMediaPlaylists = setupMediaPlaylists;
 
- var resolveMediaGroupUris = function resolveMediaGroupUris(master) {
 
-   ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) {
 
-     for (var groupKey in master.mediaGroups[mediaType]) {
 
-       for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
 
-         var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
 
-         if (mediaProperties.uri) {
 
-           mediaProperties.resolvedUri = (0, _resolveUrl2['default'])(master.uri, mediaProperties.uri);
 
-         }
 
-       }
 
-     }
 
-   });
 
- };
 
- exports.resolveMediaGroupUris = resolveMediaGroupUris;
 
- /**
 
-  * Calculates the time to wait before refreshing a live playlist
 
-  *
 
-  * @param {Object} media
 
-  *        The current media
 
-  * @param {Boolean} update
 
-  *        True if there were any updates from the last refresh, false otherwise
 
-  * @return {Number}
 
-  *         The time in ms to wait before refreshing the live playlist
 
-  */
 
- var refreshDelay = function refreshDelay(media, update) {
 
-   var lastSegment = media.segments[media.segments.length - 1];
 
-   var delay = undefined;
 
-   if (update && lastSegment && lastSegment.duration) {
 
-     delay = lastSegment.duration * 1000;
 
-   } else {
 
-     // if the playlist is unchanged since the last reload or last segment duration
 
-     // cannot be determined, try again after half the target duration
 
-     delay = (media.targetDuration || 10) * 500;
 
-   }
 
-   return delay;
 
- };
 
- exports.refreshDelay = refreshDelay;
 
- /**
 
-  * Load a playlist from a remote location
 
-  *
 
-  * @class PlaylistLoader
 
-  * @extends videojs.EventTarget
 
-  * @param {String} srcUrl the url to start with
 
-  * @param {Object} hls
 
-  * @param {Object} [options]
 
-  * @param {Boolean} [options.withCredentials=false] the withCredentials xhr option
 
-  * @param {Boolean} [options.handleManifestRedirects=false] whether to follow redirects, when any
 
-  *        playlist request was redirected
 
-  */
 
- var PlaylistLoader = (function (_EventTarget) {
 
-   _inherits(PlaylistLoader, _EventTarget);
 
-   function PlaylistLoader(srcUrl, hls, options) {
 
-     var _this = this;
 
-     _classCallCheck(this, PlaylistLoader);
 
-     _get(Object.getPrototypeOf(PlaylistLoader.prototype), 'constructor', this).call(this);
 
-     options = options || {};
 
-     this.srcUrl = srcUrl;
 
-     this.hls_ = hls;
 
-     this.withCredentials = !!options.withCredentials;
 
-     this.handleManifestRedirects = !!options.handleManifestRedirects;
 
-     if (!this.srcUrl) {
 
-       throw new Error('A non-empty playlist URL is required');
 
-     }
 
-     // initialize the loader state
 
-     this.state = 'HAVE_NOTHING';
 
-     // live playlist staleness timeout
 
-     this.on('mediaupdatetimeout', function () {
 
-       if (_this.state !== 'HAVE_METADATA') {
 
-         // only refresh the media playlist if no other activity is going on
 
-         return;
 
-       }
 
-       _this.state = 'HAVE_CURRENT_METADATA';
 
-       _this.request = _this.hls_.xhr({
 
-         uri: (0, _resolveUrl2['default'])(_this.master.uri, _this.media().uri),
 
-         withCredentials: _this.withCredentials
 
-       }, function (error, req) {
 
-         // disposed
 
-         if (!_this.request) {
 
-           return;
 
-         }
 
-         if (error) {
 
-           return _this.playlistRequestError(_this.request, _this.media().uri, 'HAVE_METADATA');
 
-         }
 
-         _this.haveMetadata(_this.request, _this.media().uri);
 
-       });
 
-     });
 
-   }
 
-   _createClass(PlaylistLoader, [{
 
-     key: 'playlistRequestError',
 
-     value: function playlistRequestError(xhr, url, startingState) {
 
-       // any in-flight request is now finished
 
-       this.request = null;
 
-       if (startingState) {
 
-         this.state = startingState;
 
-       }
 
-       this.error = {
 
-         playlist: this.master.playlists[url],
 
-         status: xhr.status,
 
-         message: 'HLS playlist request error at URL: ' + url,
 
-         responseText: xhr.responseText,
 
-         code: xhr.status >= 500 ? 4 : 2
 
-       };
 
-       this.trigger('error');
 
-     }
 
-     // update the playlist loader's state in response to a new or
 
-     // updated playlist.
 
-   }, {
 
-     key: 'haveMetadata',
 
-     value: function haveMetadata(xhr, url) {
 
-       var _this2 = this;
 
-       // any in-flight request is now finished
 
-       this.request = null;
 
-       this.state = 'HAVE_METADATA';
 
-       var parser = new _m3u8Parser2['default'].Parser();
 
-       parser.push(xhr.responseText);
 
-       parser.end();
 
-       parser.manifest.uri = url;
 
-       // m3u8-parser does not attach an attributes property to media playlists so make
 
-       // sure that the property is attached to avoid undefined reference errors
 
-       parser.manifest.attributes = parser.manifest.attributes || {};
 
-       // merge this playlist into the master
 
-       var update = updateMaster(this.master, parser.manifest);
 
-       this.targetDuration = parser.manifest.targetDuration;
 
-       if (update) {
 
-         this.master = update;
 
-         this.media_ = this.master.playlists[parser.manifest.uri];
 
-       } else {
 
-         this.trigger('playlistunchanged');
 
-       }
 
-       // refresh live playlists after a target duration passes
 
-       if (!this.media().endList) {
 
-         _globalWindow2['default'].clearTimeout(this.mediaUpdateTimeout);
 
-         this.mediaUpdateTimeout = _globalWindow2['default'].setTimeout(function () {
 
-           _this2.trigger('mediaupdatetimeout');
 
-         }, refreshDelay(this.media(), !!update));
 
-       }
 
-       this.trigger('loadedplaylist');
 
-     }
 
-     /**
 
-      * Abort any outstanding work and clean up.
 
-      */
 
-   }, {
 
-     key: 'dispose',
 
-     value: function dispose() {
 
-       this.stopRequest();
 
-       _globalWindow2['default'].clearTimeout(this.mediaUpdateTimeout);
 
-     }
 
-   }, {
 
-     key: 'stopRequest',
 
-     value: function stopRequest() {
 
-       if (this.request) {
 
-         var oldRequest = this.request;
 
-         this.request = null;
 
-         oldRequest.onreadystatechange = null;
 
-         oldRequest.abort();
 
-       }
 
-     }
 
-     /**
 
-      * When called without any arguments, returns the currently
 
-      * active media playlist. When called with a single argument,
 
-      * triggers the playlist loader to asynchronously switch to the
 
-      * specified media playlist. Calling this method while the
 
-      * loader is in the HAVE_NOTHING causes an error to be emitted
 
-      * but otherwise has no effect.
 
-      *
 
-      * @param {Object=} playlist the parsed media playlist
 
-      * object to switch to
 
-      * @return {Playlist} the current loaded media
 
-      */
 
-   }, {
 
-     key: 'media',
 
-     value: function media(playlist) {
 
-       var _this3 = this;
 
-       // getter
 
-       if (!playlist) {
 
-         return this.media_;
 
-       }
 
-       // setter
 
-       if (this.state === 'HAVE_NOTHING') {
 
-         throw new Error('Cannot switch media playlist from ' + this.state);
 
-       }
 
-       var startingState = this.state;
 
-       // find the playlist object if the target playlist has been
 
-       // specified by URI
 
-       if (typeof playlist === 'string') {
 
-         if (!this.master.playlists[playlist]) {
 
-           throw new Error('Unknown playlist URI: ' + playlist);
 
-         }
 
-         playlist = this.master.playlists[playlist];
 
-       }
 
-       var mediaChange = !this.media_ || playlist.uri !== this.media_.uri;
 
-       // switch to fully loaded playlists immediately
 
-       if (this.master.playlists[playlist.uri].endList) {
 
-         // abort outstanding playlist requests
 
-         if (this.request) {
 
-           this.request.onreadystatechange = null;
 
-           this.request.abort();
 
-           this.request = null;
 
-         }
 
-         this.state = 'HAVE_METADATA';
 
-         this.media_ = playlist;
 
-         // trigger media change if the active media has been updated
 
-         if (mediaChange) {
 
-           this.trigger('mediachanging');
 
-           this.trigger('mediachange');
 
-         }
 
-         return;
 
-       }
 
-       // switching to the active playlist is a no-op
 
-       if (!mediaChange) {
 
-         return;
 
-       }
 
-       this.state = 'SWITCHING_MEDIA';
 
-       // there is already an outstanding playlist request
 
-       if (this.request) {
 
-         if (playlist.resolvedUri === this.request.url) {
 
-           // requesting to switch to the same playlist multiple times
 
-           // has no effect after the first
 
-           return;
 
-         }
 
-         this.request.onreadystatechange = null;
 
-         this.request.abort();
 
-         this.request = null;
 
-       }
 
-       // request the new playlist
 
-       if (this.media_) {
 
-         this.trigger('mediachanging');
 
-       }
 
-       this.request = this.hls_.xhr({
 
-         uri: playlist.resolvedUri,
 
-         withCredentials: this.withCredentials
 
-       }, function (error, req) {
 
-         // disposed
 
-         if (!_this3.request) {
 
-           return;
 
-         }
 
-         playlist.resolvedUri = _this3.resolveManifestRedirect(playlist.resolvedUri, req);
 
-         if (error) {
 
-           return _this3.playlistRequestError(_this3.request, playlist.uri, startingState);
 
-         }
 
-         _this3.haveMetadata(req, playlist.uri);
 
-         // fire loadedmetadata the first time a media playlist is loaded
 
-         if (startingState === 'HAVE_MASTER') {
 
-           _this3.trigger('loadedmetadata');
 
-         } else {
 
-           _this3.trigger('mediachange');
 
-         }
 
-       });
 
-     }
 
-     /**
 
-      * Checks whether xhr request was redirected and returns correct url depending
 
-      * on `handleManifestRedirects` option
 
-      *
 
-      * @api private
 
-      *
 
-      * @param  {String} url - an url being requested
 
-      * @param  {XMLHttpRequest} req - xhr request result
 
-      *
 
-      * @return {String}
 
-      */
 
-   }, {
 
-     key: 'resolveManifestRedirect',
 
-     value: function resolveManifestRedirect(url, req) {
 
-       if (this.handleManifestRedirects && req.responseURL && url !== req.responseURL) {
 
-         return req.responseURL;
 
-       }
 
-       return url;
 
-     }
 
-     /**
 
-      * pause loading of the playlist
 
-      */
 
-   }, {
 
-     key: 'pause',
 
-     value: function pause() {
 
-       this.stopRequest();
 
-       _globalWindow2['default'].clearTimeout(this.mediaUpdateTimeout);
 
-       if (this.state === 'HAVE_NOTHING') {
 
-         // If we pause the loader before any data has been retrieved, its as if we never
 
-         // started, so reset to an unstarted state.
 
-         this.started = false;
 
-       }
 
-       // Need to restore state now that no activity is happening
 
-       if (this.state === 'SWITCHING_MEDIA') {
 
-         // if the loader was in the process of switching media, it should either return to
 
-         // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media
 
-         // playlist yet. This is determined by the existence of loader.media_
 
-         if (this.media_) {
 
-           this.state = 'HAVE_METADATA';
 
-         } else {
 
-           this.state = 'HAVE_MASTER';
 
-         }
 
-       } else if (this.state === 'HAVE_CURRENT_METADATA') {
 
-         this.state = 'HAVE_METADATA';
 
-       }
 
-     }
 
-     /**
 
-      * start loading of the playlist
 
-      */
 
-   }, {
 
-     key: 'load',
 
-     value: function load(isFinalRendition) {
 
-       var _this4 = this;
 
-       _globalWindow2['default'].clearTimeout(this.mediaUpdateTimeout);
 
-       var media = this.media();
 
-       if (isFinalRendition) {
 
-         var delay = media ? media.targetDuration / 2 * 1000 : 5 * 1000;
 
-         this.mediaUpdateTimeout = _globalWindow2['default'].setTimeout(function () {
 
-           return _this4.load();
 
-         }, delay);
 
-         return;
 
-       }
 
-       if (!this.started) {
 
-         this.start();
 
-         return;
 
-       }
 
-       if (media && !media.endList) {
 
-         this.trigger('mediaupdatetimeout');
 
-       } else {
 
-         this.trigger('loadedplaylist');
 
-       }
 
-     }
 
-     /**
 
-      * start loading of the playlist
 
-      */
 
-   }, {
 
-     key: 'start',
 
-     value: function start() {
 
-       var _this5 = this;
 
-       this.started = true;
 
-       // request the specified URL
 
-       this.request = this.hls_.xhr({
 
-         uri: this.srcUrl,
 
-         withCredentials: this.withCredentials
 
-       }, function (error, req) {
 
-         // disposed
 
-         if (!_this5.request) {
 
-           return;
 
-         }
 
-         // clear the loader's request reference
 
-         _this5.request = null;
 
-         if (error) {
 
-           _this5.error = {
 
-             status: req.status,
 
-             message: 'HLS playlist request error at URL: ' + _this5.srcUrl,
 
-             responseText: req.responseText,
 
-             // MEDIA_ERR_NETWORK
 
-             code: 2
 
-           };
 
-           if (_this5.state === 'HAVE_NOTHING') {
 
-             _this5.started = false;
 
-           }
 
-           return _this5.trigger('error');
 
-         }
 
-         var parser = new _m3u8Parser2['default'].Parser();
 
-         parser.push(req.responseText);
 
-         parser.end();
 
-         _this5.state = 'HAVE_MASTER';
 
-         _this5.srcUrl = _this5.resolveManifestRedirect(_this5.srcUrl, req);
 
-         parser.manifest.uri = _this5.srcUrl;
 
-         // loaded a master playlist
 
-         if (parser.manifest.playlists) {
 
-           _this5.master = parser.manifest;
 
-           setupMediaPlaylists(_this5.master);
 
-           resolveMediaGroupUris(_this5.master);
 
-           _this5.trigger('loadedplaylist');
 
-           if (!_this5.request) {
 
-             // no media playlist was specifically selected so start
 
-             // from the first listed one
 
-             _this5.media(parser.manifest.playlists[0]);
 
-           }
 
-           return;
 
-         }
 
-         // loaded a media playlist
 
-         // infer a master playlist if none was previously requested
 
-         _this5.master = {
 
-           mediaGroups: {
 
-             'AUDIO': {},
 
-             'VIDEO': {},
 
-             'CLOSED-CAPTIONS': {},
 
-             'SUBTITLES': {}
 
-           },
 
-           uri: _globalWindow2['default'].location.href,
 
-           playlists: [{
 
-             uri: _this5.srcUrl,
 
-             resolvedUri: _this5.srcUrl,
 
-             // m3u8-parser does not attach an attributes property to media playlists so make
 
-             // sure that the property is attached to avoid undefined reference errors
 
-             attributes: {}
 
-           }]
 
-         };
 
-         _this5.master.playlists[_this5.srcUrl] = _this5.master.playlists[0];
 
-         _this5.haveMetadata(req, _this5.srcUrl);
 
-         return _this5.trigger('loadedmetadata');
 
-       });
 
-     }
 
-   }]);
 
-   return PlaylistLoader;
 
- })(_videoJs.EventTarget);
 
- exports['default'] = PlaylistLoader;
 
 
  |