1/** 2 * videojs-contrib-hls 3 * @version 5.11.0 4 * @copyright 2017 Brightcove, Inc 5 * @license Apache-2.0 6 */ 7(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.videojsContribHls = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ 8/** 9 * @file ad-cue-tags.js 10 */ 11'use strict'; 12 13Object.defineProperty(exports, '__esModule', { 14 value: true 15}); 16 17var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })(); 18 19function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 20 21var _globalWindow = require('global/window'); 22 23var _globalWindow2 = _interopRequireDefault(_globalWindow); 24 25/** 26 * Searches for an ad cue that overlaps with the given mediaTime 27 */ 28var findAdCue = function findAdCue(track, mediaTime) { 29 var cues = track.cues; 30 31 for (var i = 0; i < cues.length; i++) { 32 var cue = cues[i]; 33 34 if (mediaTime >= cue.adStartTime && mediaTime <= cue.adEndTime) { 35 return cue; 36 } 37 } 38 return null; 39}; 40 41var updateAdCues = function updateAdCues(media, track) { 42 var offset = arguments.length <= 2 || arguments[2] === undefined ? 0 : arguments[2]; 43 44 if (!media.segments) { 45 return; 46 } 47 48 var mediaTime = offset; 49 var cue = undefined; 50 51 for (var i = 0; i < media.segments.length; i++) { 52 var segment = media.segments[i]; 53 54 if (!cue) { 55 // Since the cues will span for at least the segment duration, adding a fudge 56 // factor of half segment duration will prevent duplicate cues from being 57 // created when timing info is not exact (e.g. cue start time initialized 58 // at 10.006677, but next call mediaTime is 10.003332 ) 59 cue = findAdCue(track, mediaTime + segment.duration / 2); 60 } 61 62 if (cue) { 63 if ('cueIn' in segment) { 64 // Found a CUE-IN so end the cue 65 cue.endTime = mediaTime; 66 cue.adEndTime = mediaTime; 67 mediaTime += segment.duration; 68 cue = null; 69 continue; 70 } 71 72 if (mediaTime < cue.endTime) { 73 // Already processed this mediaTime for this cue 74 mediaTime += segment.duration; 75 continue; 76 } 77 78 // otherwise extend cue until a CUE-IN is found 79 cue.endTime += segment.duration; 80 } else { 81 if ('cueOut' in segment) { 82 cue = new _globalWindow2['default'].VTTCue(mediaTime, mediaTime + segment.duration, segment.cueOut); 83 cue.adStartTime = mediaTime; 84 // Assumes tag format to be 85 // #EXT-X-CUE-OUT:30 86 cue.adEndTime = mediaTime + parseFloat(segment.cueOut); 87 track.addCue(cue); 88 } 89 90 if ('cueOutCont' in segment) { 91 // Entered into the middle of an ad cue 92 var adOffset = undefined; 93 var adTotal = undefined; 94 95 // Assumes tag formate to be 96 // #EXT-X-CUE-OUT-CONT:10/30 97 98 var _segment$cueOutCont$split$map = segment.cueOutCont.split('/').map(parseFloat); 99 100 var _segment$cueOutCont$split$map2 = _slicedToArray(_segment$cueOutCont$split$map, 2); 101 102 adOffset = _segment$cueOutCont$split$map2[0]; 103 adTotal = _segment$cueOutCont$split$map2[1]; 104 105 cue = new _globalWindow2['default'].VTTCue(mediaTime, mediaTime + segment.duration, ''); 106 cue.adStartTime = mediaTime - adOffset; 107 cue.adEndTime = cue.adStartTime + adTotal; 108 track.addCue(cue); 109 } 110 } 111 mediaTime += segment.duration; 112 } 113}; 114 115exports['default'] = { 116 updateAdCues: updateAdCues, 117 findAdCue: findAdCue 118}; 119module.exports = exports['default']; 120},{"global/window":32}],2:[function(require,module,exports){ 121/** 122 * @file bin-utils.js 123 */ 124 125/** 126 * convert a TimeRange to text 127 * 128 * @param {TimeRange} range the timerange to use for conversion 129 * @param {Number} i the iterator on the range to convert 130 */ 131'use strict'; 132 133Object.defineProperty(exports, '__esModule', { 134 value: true 135}); 136var textRange = function textRange(range, i) { 137 return range.start(i) + '-' + range.end(i); 138}; 139 140/** 141 * format a number as hex string 142 * 143 * @param {Number} e The number 144 * @param {Number} i the iterator 145 */ 146var formatHexString = function formatHexString(e, i) { 147 var value = e.toString(16); 148 149 return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : ''); 150}; 151var formatAsciiString = function formatAsciiString(e) { 152 if (e >= 0x20 && e < 0x7e) { 153 return String.fromCharCode(e); 154 } 155 return '.'; 156}; 157 158/** 159 * Creates an object for sending to a web worker modifying properties that are TypedArrays 160 * into a new object with seperated properties for the buffer, byteOffset, and byteLength. 161 * 162 * @param {Object} message 163 * Object of properties and values to send to the web worker 164 * @return {Object} 165 * Modified message with TypedArray values expanded 166 * @function createTransferableMessage 167 */ 168var createTransferableMessage = function createTransferableMessage(message) { 169 var transferable = {}; 170 171 Object.keys(message).forEach(function (key) { 172 var value = message[key]; 173 174 if (ArrayBuffer.isView(value)) { 175 transferable[key] = { 176 bytes: value.buffer, 177 byteOffset: value.byteOffset, 178 byteLength: value.byteLength 179 }; 180 } else { 181 transferable[key] = value; 182 } 183 }); 184 185 return transferable; 186}; 187 188/** 189 * Returns a unique string identifier for a media initialization 190 * segment. 191 */ 192var initSegmentId = function initSegmentId(initSegment) { 193 var byterange = initSegment.byterange || { 194 length: Infinity, 195 offset: 0 196 }; 197 198 return [byterange.length, byterange.offset, initSegment.resolvedUri].join(','); 199}; 200 201/** 202 * utils to help dump binary data to the console 203 */ 204var utils = { 205 hexDump: function hexDump(data) { 206 var bytes = Array.prototype.slice.call(data); 207 var step = 16; 208 var result = ''; 209 var hex = undefined; 210 var ascii = undefined; 211 212 for (var j = 0;
212 j < bytes.length / step; j++) { 213 hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(''); 214 ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join(''); 215 result += hex + ' ' + ascii + '\n'; 216 } 217 return result; 218 }, 219 tagDump: function tagDump(tag) { 220 return utils.hexDump(tag.bytes); 221 }, 222 textRanges: function textRanges(ranges) { 223 var result = ''; 224 var i = undefined; 225 226 for (i = 0; i < ranges.length; i++) { 227 result += textRange(ranges, i) + ' '; 228 } 229 return result; 230 }, 231 createTransferableMessage: createTransferableMessage, 232 initSegmentId: initSegmentId 233}; 234 235exports['default'] = utils; 236module.exports = exports['default']; 237},{}],3:[function(require,module,exports){ 238"use strict"; 239 240Object.defineProperty(exports, "__esModule", { 241 value: true 242}); 243exports["default"] = { 244 GOAL_BUFFER_LENGTH: 30, 245 MAX_GOAL_BUFFER_LENGTH: 60, 246 GOAL_BUFFER_LENGTH_RATE: 1, 247 // A fudge factor to apply to advertised playlist bitrates to account for 248 // temporary flucations in client bandwidth 249 BANDWIDTH_VARIANCE: 1.2, 250 // How much of the buffer must be filled before we consider upswitching 251 BUFFER_LOW_WATER_LINE: 0, 252 MAX_BUFFER_LOW_WATER_LINE: 30, 253 BUFFER_LOW_WATER_LINE_RATE: 1 254}; 255module.exports = exports["default"]; 256},{}],4:[function(require,module,exports){ 257'use strict'; 258 259Object.defineProperty(exports, '__esModule', { 260 value: true 261}); 262 263function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 264 265var _globalWindow = require('global/window'); 266 267var _globalWindow2 = _interopRequireDefault(_globalWindow); 268 269var _aesDecrypter = require('aes-decrypter'); 270 271var _binUtils = require('./bin-utils'); 272 273/** 274 * Our web worker interface so that things can talk to aes-decrypter 275 * that will be running in a web worker. the scope is passed to this by 276 * webworkify. 277 * 278 * @param {Object} self 279 * the scope for the web worker 280 */ 281var DecrypterWorker = function DecrypterWorker(self) { 282 self.onmessage = function (event) { 283 var data = event.data; 284 var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength); 285 var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4); 286 var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4); 287 288 /* eslint-disable no-new, handle-callback-err */ 289 new _aesDecrypter.Decrypter(encrypted, key, iv, function (err, bytes) { 290 _globalWindow2['default'].postMessage((0, _binUtils.createTransferableMessage)({ 291 source: data.source, 292 decrypted: bytes 293 }), [bytes.buffer]); 294 }); 295 /* eslint-enable */ 296 }; 297}; 298 299exports['default'] = function (self) { 300 return new DecrypterWorker(self); 301}; 302 303module.exports = exports['default']; 304},{"./bin-utils":2,"aes-decrypter":25,"global/window":32}],5:[function(require,module,exports){ 305(function (global){ 306/** 307 * @file master-playlist-controller.js 308 */ 309'use strict'; 310 311Object.defineProperty(exports, '__esModule', { 312 value: true 313}); 314 315var _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; }; })(); 316 317var _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); } } }; 318 319function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 320 321function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 322 323function _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, { c
323onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 324 325var _playlistLoader = require('./playlist-loader'); 326 327var _playlistLoader2 = _interopRequireDefault(_playlistLoader); 328 329var _segmentLoader = require('./segment-loader'); 330 331var _segmentLoader2 = _interopRequireDefault(_segmentLoader); 332 333var _vttSegmentLoader = require('./vtt-segment-loader'); 334 335var _vttSegmentLoader2 = _interopRequireDefault(_vttSegmentLoader); 336 337var _ranges = require('./ranges'); 338 339var _ranges2 = _interopRequireDefault(_ranges); 340 341var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 342 343var _videoJs2 = _interopRequireDefault(_videoJs); 344 345var _adCueTags = require('./ad-cue-tags'); 346 347var _adCueTags2 = _interopRequireDefault(_adCueTags); 348 349var _syncController = require('./sync-controller'); 350 351var _syncController2 = _interopRequireDefault(_syncController); 352 353var _videojsContribMediaSourcesEs5CodecUtils = require('videojs-contrib-media-sources/es5/codec-utils'); 354 355var _webworkify = require('webworkify'); 356 357var _webworkify2 = _interopRequireDefault(_webworkify); 358 359var _decrypterWorker = require('./decrypter-worker'); 360 361var _decrypterWorker2 = _interopRequireDefault(_decrypterWorker); 362 363var _config = require('./config'); 364 365var _config2 = _interopRequireDefault(_config); 366 367var _utilCodecsJs = require('./util/codecs.js'); 368 369var _mediaGroups = require('./media-groups'); 370 371var ABORT_EARLY_BLACKLIST_SECONDS = 60 * 2; 372 373var Hls = undefined; 374 375// Default codec parameters if none were provided for video and/or audio 376var defaultCodecs = { 377 videoCodec: 'avc1', 378 videoObjectTypeIndicator: '.4d400d', 379 // AAC-LC 380 audioProfile: '2' 381}; 382 383// SegmentLoader stats that need to have each loader's 384// values summed to calculate the final value 385var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred']; 386var sumLoaderStat = function sumLoaderStat(stat) { 387 return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat]; 388}; 389 390/** 391 * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the 392 * standard `avc1.<hhhhhh>`. 393 * 394 * @param codecString {String} the codec string 395 * @return {String} the codec string with old apple-style codecs replaced 396 * 397 * @private 398 */ 399var mapLegacyAvcCodecs_ = function mapLegacyAvcCodecs_(codecString) { 400 return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) { 401 return (0, _videojsContribMediaSourcesEs5CodecUtils.translateLegacyCodecs)([match])[0]; 402 }); 403}; 404 405exports.mapLegacyAvcCodecs_ = mapLegacyAvcCodecs_; 406/** 407 * Build a media mime-type string from a set of parameters 408 * @param {String} type either 'audio' or 'video' 409 * @param {String} container either 'mp2t' or 'mp4' 410 * @param {Array} codecs an array of codec strings to add 411 * @return {String} a valid media mime-type 412 */ 413var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) { 414 // The codecs array is filtered so that falsey values are 415 // dropped and don't cause Array#join to create spurious 416 // commas 417 return type + '/' + container + '; codecs="' + codecs.filter(function (c) { 418 return !!c; 419 }).join(', ') + '"'; 420}; 421 422/** 423 * Returns the type container based on information in the playlist 424 * @param {Playlist} media the current media playlist 425 * @return {String} a valid media container type 426 */ 427var getContainerType = function getContainerType(media) { 428 // An initialization segment means the media playlist is an iframe 429 // playlist or is using the mp4 container. We don't currently 430 // support iframe playlists, so assume this is signalling mp4 431 // fragments. 432 if (media.segments && media.segments.length && media.segments[0].map) { 433 return 'mp4'; 434 } 435 return 'mp2t'; 436}; 437 438/** 439 * Returns a set of codec strings parsed from the playlist or the default 440 * codec strings if no codecs were specified in the playlist 441 * @param {Playlist} media the current media playlist 442 * @return {Object} an object with the video and audio codecs 443 */ 444var getCodecs = function getCodecs(media) { 445 // if the codecs were explicitly specified, use them instead of the 446 // defaults 447 var mediaAttributes = media.attributes || {}; 448 449 if (mediaAttributes.CODECS) { 450 return (0, _utilCodecsJs.parseCodecs)(mediaAttributes.CODECS); 451 } 452 return defaultCodecs; 453}; 454 455/**
456 * Calculates the MIME type strings for a working configuration of 457 * SourceBuffers to play variant streams in a master playlist. If 458 * there is no possible working configuration, an empty array will be 459 * returned. 460 * 461 * @param master {Object} the m3u8 object for the master playlist 462 * @param media {Object} the m3u8 object for the variant playlist 463 * @return {Array} the MIME type strings. If the array has more than 464 * one entry, the first element should be applied to the video 465 * SourceBuffer and the second to the audio SourceBuffer. 466 * 467 * @private 468 */ 469var mimeTypesForPlaylist_ = function mimeTypesForPlaylist_(master, media) { 470 var containerType = getContainerType(media); 471 var codecInfo = getCodecs(media); 472 var mediaAttributes = media.attributes || {}; 473 // Default condition for a traditional HLS (no demuxed audio/video) 474 var isMuxed = true; 475 var isMaat = false; 476 477 if (!media) { 478 // Not enough information 479 return []; 480 } 481 482 if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) { 483 var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; 484 485 // Handle the case where we are in a multiple-audio track scenario 486 if (audioGroup) { 487 isMaat = true; 488 // Start with the everything demuxed then... 489 isMuxed = false; 490 // ...check to see if any audio group tracks are muxed (ie. lacking a uri) 491 for (var groupId in audioGroup) { 492 if (!audioGroup[groupId].uri) { 493 isMuxed = true; 494 break; 495 } 496 } 497 } 498 } 499 500 // HLS with multiple-audio tracks must always get an audio codec. 501 // Put another way, there is no way to have a video-only multiple-audio HLS! 502 if (isMaat && !codecInfo.audioProfile) { 503 _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)'); 504 codecInfo.audioProfile = defaultCodecs.audioProfile; 505 } 506 507 // Generate the final codec strings from the codec object generated above 508 var codecStrings = {}; 509 510 if (codecInfo.videoCodec) { 511 codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator; 512 } 513 514 if (codecInfo.audioProfile) { 515 codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile; 516 } 517 518 // Finally, make and return an array with proper mime-types depending on 519 // the configuration 520 var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]); 521 var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]); 522 var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]); 523 524 if (isMaat) { 525 if (!isMuxed && codecStrings.video) { 526 return [justVideo, justAudio]; 527 } 528 // There exists the possiblity that this will return a `video/container` 529 // mime-type for the first entry in the array even when there is only audio. 530 // This doesn't appear to be a problem and simplifies the code. 531 return [bothVideoAudio, justAudio]; 532 } 533 534 // If there is ano video codec at all, always just return a single 535 // audio/<container> mime-type 536 if (!codecStrings.video) { 537 return [justAudio]; 538 } 539 540 // When not using separate audio media groups, audio and video is 541 // *always* muxed 542 return [bothVideoAudio]; 543}; 544 545exports.mimeTypesForPlaylist_ = mimeTypesForPlaylist_; 546/** 547 * the master playlist controller controller all interactons 548 * between playlists and segmentloaders. At this time this mainly 549 * involves a master playlist and a series of audio playlists 550 * if they are available 551 * 552 * @class MasterPlaylistController 553 * @extends videojs.EventTarget 554 */ 555 556var MasterPlaylistController = (function (_videojs$EventTarget) { 557 _inherits(MasterPlaylistController, _videojs$EventTarget); 558 559 function MasterPlaylistController(options) { 560 var _this = this; 561 562 _classCallCheck(this, MasterPlaylistController); 563 564 _get(Object.getPrototypeOf(MasterPlaylistController.prototype), 'constructor', this).call(this); 565 566 var url = options.url; 567 var withCredentials = options.withCredentials; 568 var mode = options.mode; 569 var tech = options.tech; 570 var bandwidth = options.bandwidth; 571 var externHls = options.externHls; 572 var useCueTags = options.useCueTags;
573 var blacklistDuration = options.blacklistDuration; 574 var enableLowInitialPlaylist = options.enableLowInitialPlaylist; 575 576 if (!url) { 577 throw new Error('A non-empty playlist URL is required'); 578 } 579 580 Hls = externHls; 581 582 this.withCredentials = withCredentials; 583 this.tech_ = tech; 584 this.hls_ = tech.hls; 585 this.mode_ = mode; 586 this.useCueTags_ = useCueTags; 587 this.blacklistDuration = blacklistDuration; 588 this.enableLowInitialPlaylist = enableLowInitialPlaylist; 589 if (this.useCueTags_) { 590 this.cueTagsTrack_ = this.tech_.addTextTrack('metadata', 'ad-cues'); 591 this.cueTagsTrack_.inBandMetadataTrackDispatchType = ''; 592 } 593 594 this.requestOptions_ = { 595 withCredentials: this.withCredentials, 596 timeout: null 597 }; 598 599 this.mediaTypes_ = (0, _mediaGroups.createMediaTypes)(); 600 601 this.mediaSource = new _videoJs2['default'].MediaSource({ mode: mode }); 602 603 // load the media source into the player 604 this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen_.bind(this)); 605 606 this.seekable_ = _videoJs2['default'].createTimeRanges(); 607 this.hasPlayed_ = function () { 608 return false; 609 }; 610 611 this.syncController_ = new _syncController2['default'](options); 612 this.segmentMetadataTrack_ = tech.addRemoteTextTrack({ 613 kind: 'metadata', 614 label: 'segment-metadata' 615 }, false).track; 616 617 this.decrypter_ = (0, _webworkify2['default'])(_decrypterWorker2['default']); 618 619 var segmentLoaderSettings = { 620 hls: this.hls_, 621 mediaSource: this.mediaSource, 622 currentTime: this.tech_.currentTime.bind(this.tech_), 623 seekable: function seekable() { 624 return _this.seekable(); 625 }, 626 seeking: function seeking() { 627 return _this.tech_.seeking(); 628 }, 629 duration: function duration() { 630 return _this.mediaSource.duration; 631 }, 632 hasPlayed: function hasPlayed() { 633 return _this.hasPlayed_(); 634 }, 635 goalBufferLength: function goalBufferLength() { 636 return _this.goalBufferLength(); 637 }, 638 bandwidth: bandwidth, 639 syncController: this.syncController_, 640 decrypter: this.decrypter_ 641 }; 642 643 // setup playlist loaders 644 this.masterPlaylistLoader_ = new _playlistLoader2['default'](url, this.hls_, this.withCredentials); 645 this.setupMasterPlaylistLoaderListeners_(); 646 647 // setup segment loaders 648 // combined audio/video or just video when alternate audio track is selected 649 this.mainSegmentLoader_ = new _segmentLoader2['default'](_videoJs2['default'].mergeOptions(segmentLoaderSettings, { 650 segmentMetadataTrack: this.segmentMetadataTrack_, 651 loaderType: 'main' 652 }), options); 653 654 // alternate audio track 655 this.audioSegmentLoader_ = new _segmentLoader2['default'](_videoJs2['default'].mergeOptions(segmentLoaderSettings, { 656 loaderType: 'audio' 657 }), options); 658 659 this.subtitleSegmentLoader_ = new _vttSegmentLoader2['default'](_videoJs2['default'].mergeOptions(segmentLoaderSettings, { 660 loaderType: 'vtt' 661 }), options); 662 663 this.setupSegmentLoaderListeners_(); 664 665 // Create SegmentLoader stat-getters
vendor: 7,535 bytes, lines 666-875
666 loaderStats.forEach(function (stat) { 667 _this[stat + '_'] = sumLoaderStat.bind(_this, stat); 668 }); 669 670 this.masterPlaylistLoader_.load(); 671 } 672 673 /** 674 * Register event handlers on the master playlist loader. A helper 675 * function for construction time. 676 * 677 * @private 678 */ 679 680 _createClass(MasterPlaylistController, [{ 681 key: 'setupMasterPlaylistLoaderListeners_', 682 value: function setupMasterPlaylistLoaderListeners_() { 683 var _this2 = this; 684 685 this.masterPlaylistLoader_.on('loadedmetadata', function () { 686 var media = _this2.masterPlaylistLoader_.media(); 687 var requestTimeout = _this2.masterPlaylistLoader_.targetDuration * 1.5 * 1000; 688 689 // If we don't have any more available playlists, we don't want to 690 // timeout the request. 691 if (_this2.masterPlaylistLoader_.isLowestEnabledRendition_()) { 692 _this2.requestOptions_.timeout = 0; 693 } else { 694 _this2.requestOptions_.timeout = requestTimeout; 695 } 696 697 // if this isn't a live video and preload permits, start 698 // downloading segments 699 if (media.endList && _this2.tech_.preload() !== 'none') { 700 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 701 _this2.mainSegmentLoader_.load(); 702 } 703 704 (0, _mediaGroups.setupMediaGroups)({ 705 segmentLoaders: { 706 AUDIO: _this2.audioSegmentLoader_, 707 SUBTITLES: _this2.subtitleSegmentLoader_, 708 main: _this2.mainSegmentLoader_ 709 }, 710 tech: _this2.tech_, 711 requestOptions: _this2.requestOptions_, 712 masterPlaylistLoader: _this2.masterPlaylistLoader_, 713 mode: _this2.mode_, 714 hls: _this2.hls_, 715 master: _this2.master(), 716 mediaTypes: _this2.mediaTypes_, 717 blacklistCurrentPlaylist: _this2.blacklistCurrentPlaylist.bind(_this2) 718 }); 719 720 _this2.triggerPresenceUsage_(_this2.master(), media); 721 722 try { 723 _this2.setupSourceBuffers_(); 724 } catch (e) { 725 _videoJs2['default'].log.warn('Failed to create SourceBuffers', e); 726 return _this2.mediaSource.endOfStream('decode'); 727 } 728 _this2.setupFirstPlay(); 729 730 _this2.trigger('selectedinitialmedia'); 731 }); 732 733 this.masterPlaylistLoader_.on('loadedplaylist', function () { 734 var updatedPlaylist = _this2.masterPlaylistLoader_.media(); 735 736 if (!updatedPlaylist) { 737 var selectedMedia = undefined; 738 739 if (_this2.enableLowInitialPlaylist) { 740 selectedMedia = _this2.selectInitialPlaylist(); 741 } 742 743 if (!selectedMedia) { 744 selectedMedia = _this2.selectPlaylist(); 745 } 746 747 _this2.initialMedia_ = selectedMedia; 748 _this2.masterPlaylistLoader_.media(_this2.initialMedia_); 749 return; 750 } 751 752 if (_this2.useCueTags_) { 753 _this2.updateAdCues_(updatedPlaylist); 754 } 755 756 // TODO: Create a new event on the PlaylistLoader that signals 757 // that the segments have changed in some way and use that to 758 // update the SegmentLoader instead of doing it twice here and 759 // on `mediachange` 760 _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_); 761 _this2.updateDuration(); 762 763 // If the player isn't paused, ensure that the segment loader is running, 764 // as it is possible that it was temporarily stopped while waiting for 765 // a playlist (e.g., in case the playlist errored and we re-requested it). 766 if (!_this2.tech_.paused()) { 767 _this2.mainSegmentLoader_.load(); 768 } 769 770 if (!updatedPlaylist.endList) { 771 (function () { 772 var addSeekableRange = function addSeekableRange() { 773 var seekable = _this2.seekable(); 774 775 if (seekable.length !== 0) { 776 _this2.mediaSource.addSeekableRange_(seekable.start(0), seekable.end(0)); 777 } 778 }; 779 780 if (_this2.duration() !== Infinity) { 781 (function () { 782 var onDurationchange = function onDurationchange() { 783 if (_this2.duration() === Infinity) { 784 addSeekableRange(); 785 } else { 786 _this2.tech_.one('durationchange', onDurationchange); 787 } 788 }; 789 790 _this2.tech_.one('durationchange', onDurationchange); 791 })(); 792 } else { 793 addSeekableRange(); 794 } 795 })(); 796 } 797 }); 798 799 this.masterPlaylistLoader_.on('error', function () { 800 _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error); 801 }); 802 803 this.masterPlaylistLoader_.on('mediachanging', function () { 804 _this2.mainSegmentLoader_.abort(); 805 _this2.mainSegmentLoader_.pause(); 806 }); 807 808 this.masterPlaylistLoader_.on('mediachange', function () { 809 var media = _this2.masterPlaylistLoader_.media(); 810 var requestTimeout = _this2.masterPlaylistLoader_.targetDuration * 1.5 * 1000; 811 812 // If we don't have any more available playlists, we don't want to 813 // timeout the request. 814 if (_this2.masterPlaylistLoader_.isLowestEnabledRendition_()) { 815 _this2.requestOptions_.timeout = 0; 816 } else { 817 _this2.requestOptions_.timeout = requestTimeout; 818 } 819 820 // TODO: Create a new event on the PlaylistLoader that signals 821 // that the segments have changed in some way and use that to 822 // update the SegmentLoader instead of doing it twice here and 823 // on `loadedplaylist` 824 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 825 _this2.mainSegmentLoader_.load(); 826 827 _this2.tech_.trigger({ 828 type: 'mediachange', 829 bubbles: true 830 }); 831 }); 832 833 this.masterPlaylistLoader_.on('playlistunchanged', function () { 834 var updatedPlaylist = _this2.masterPlaylistLoader_.media(); 835 var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist); 836 837 if (playlistOutdated) { 838 // Playlist has stopped updating and we're stuck at its end. Try to 839 // blacklist it and switch to another playlist in the hope that that 840 // one is updating (and give the player a chance to re-adjust to the 841 // safe live point). 842 _this2.blacklistCurrentPlaylist({ 843 message: 'Playlist no longer updating.' 844 }); 845 // useful for monitoring QoS 846 _this2.tech_.trigger('playliststuck'); 847 } 848 }); 849 850 this.masterPlaylistLoader_.on('renditiondisabled', function () { 851 _this2.tech_.trigger({ type: 'usage', name: 'hls-rendition-disabled' }); 852 }); 853 this.masterPlaylistLoader_.on('renditionenabled', function () { 854 _this2.tech_.trigger({ type: 'usage', name: 'hls-rendition-enabled' }); 855 }); 856 } 857 858 /** 859 * A helper function for triggerring presence usage events once per source 860 * 861 * @private 862 */ 863 }, { 864 key: 'triggerPresenceUsage_', 865 value: function triggerPresenceUsage_(master, media) { 866 var mediaGroups = master.mediaGroups || {}; 867 var defaultDemuxed = true; 868 var audioGroupKeys = Object.keys(mediaGroups.AUDIO); 869 870 for (var mediaGroup in mediaGroups.AUDIO) { 871 for (var label in mediaGroups.AUDIO[mediaGroup]) { 872 var properties = mediaGroups.AUDIO[mediaGroup][label]; 873 874 if (!properties.uri) { 875 defaultDemuxed = false;
876 } 877 } 878 } 879 880 if (defaultDemuxed) { 881 this.tech_.trigger({ type: 'usage', name: 'hls-demuxed' }); 882 } 883 884 if (Object.keys(mediaGroups.SUBTITLES).length) { 885 this.tech_.trigger({ type: 'usage', name: 'hls-webvtt' }); 886 } 887 888 if (Hls.Playlist.isAes(media)) { 889 this.tech_.trigger({ type: 'usage', name: 'hls-aes' }); 890 } 891 892 if (Hls.Playlist.isFmp4(media)) { 893 this.tech_.trigger({ type: 'usage', name: 'hls-fmp4' }); 894 } 895 896 if (audioGroupKeys.length && Object.keys(mediaGroups.AUDIO[audioGroupKeys[0]]).length > 1) { 897 this.tech_.trigger({ type: 'usage', name: 'hls-alternate-audio' }); 898 } 899 900 if (this.useCueTags_) { 901 this.tech_.trigger({ type: 'usage', name: 'hls-playlist-cue-tags' }); 902 } 903 } 904 905 /** 906 * Register event handlers on the segment loaders. A helper function 907 * for construction time. 908 * 909 * @private 910 */ 911 }, { 912 key: 'setupSegmentLoaderListeners_', 913 value: function setupSegmentLoaderListeners_() { 914 var _this3 = this; 915 916 this.mainSegmentLoader_.on('bandwidthupdate', function () { 917 var nextPlaylist = _this3.selectPlaylist(); 918 var currentPlaylist = _this3.masterPlaylistLoader_.media(); 919 var buffered = _this3.tech_.buffered(); 920 var forwardBuffer = buffered.length ? buffered.end(buffered.length - 1) - _this3.tech_.currentTime() : 0; 921 922 var bufferLowWaterLine = _this3.bufferLowWaterLine(); 923 924 // If the playlist is live, then we want to not take low water line into account. 925 // This is because in LIVE, the player plays 3 segments from the end of the 926 // playlist, and if `BUFFER_LOW_WATER_LINE` is greater than the duration availble 927 // in those segments, a viewer will never experience a rendition upswitch. 928 if (!currentPlaylist.endList || 929 // For the same reason as LIVE, we ignore the low water line when the VOD 930 // duration is below the max potential low water line 931 _this3.duration() < _config2['default'].MAX_BUFFER_LOW_WATER_LINE || 932 // we want to switch down to lower resolutions quickly to continue playback, but 933 nextPlaylist.attributes.BANDWIDTH < currentPlaylist.attributes.BANDWIDTH || 934 // ensure we have some buffer before we switch up to prevent us running out of 935 // buffer while loading a higher rendition. 936 forwardBuffer >= bufferLowWaterLine) { 937 _this3.masterPlaylistLoader_.media(nextPlaylist); 938 } 939 940 _this3.tech_.trigger('bandwidthupdate'); 941 }); 942 this.mainSegmentLoader_.on('progress', function () { 943 _this3.trigger('progress'); 944 }); 945 946 this.mainSegmentLoader_.on('error', function () { 947 _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error()); 948 }); 949 950 this.mainSegmentLoader_.on('syncinfoupdate', function () { 951 _this3.onSyncInfoUpdate_(); 952 }); 953 954 this.mainSegmentLoader_.on('timestampoffset', function () { 955 _this3.tech_.trigger({ type: 'usage', name: 'hls-timestamp-offset' }); 956 }); 957 this.audioSegmentLoader_.on('syncinfoupdate', function () { 958 _this3.onSyncInfoUpdate_(); 959 }); 960 961 this.mainSegmentLoader_.on('ended', function () { 962 _this3.onEndOfStream(); 963 }); 964 965 this.mainSegmentLoader_.on('earlyabort', function () { 966 _this3.blacklistCurrentPlaylist({ 967 message: 'Aborted early because there isn\'t enough bandwidth to complete the ' + 'request without rebuffering.' 968 }, ABORT_EARLY_BLACKLIST_SECONDS); 969 }); 970 971 this.mainSegmentLoader_.on('reseteverything', function () { 972 _this3.tech_.trigger('hls-reset'); 973 }); 974 975 this.audioSegmentLoader_.on('ended', function () { 976 _this3.onEndOfStream(); 977 }); 978 } 979 }, { 980 key: 'mediaSecondsLoaded_', 981 value: function mediaSecondsLoaded_() { 982 return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded); 983 } 984 985 /** 986 * Call load on our SegmentLoaders 987 */ 988 }, { 989 key: 'load', 990 value: function load() { 991 this.mainSegmentLoader_.load(); 992 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 993 this.audioSegmentLoader_.load(); 994 } 995 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 996 this.subtitleSegmentLoader_.load(); 997 } 998 } 999 1000 /** 1001 * Re-tune playback quality level for the current player 1002 * conditions. This method may perform destructive actions, like 1003 * removing already buffered content, to readjust the currently 1004 * active playlist quickly. 1005 * 1006 * @private 1007 */ 1008 }, { 1009 key: 'fastQualityChange_', 1010 value: function fastQualityChange_() { 1011 var media = this.selectPlaylist(); 1012 1013 if (media !== this.masterPlaylistLoader_.media()) { 1014 this.masterPlaylistLoader_.media(media); 1015 1016 this.mainSegmentLoader_.resetLoader(); 1017 // don't need to reset audio as it is reset when media changes 1018 } 1019 } 1020 1021 /** 1022 * Begin playback. 1023 */ 1024 }, { 1025 key: 'play', 1026 value: function play() { 1027 if (this.setupFirstPlay()) { 1028 return; 1029 } 1030 1031 if (this.tech_.ended()) { 1032 this.tech_.setCurrentTime(0); 1033 } 1034 1035 if (this.hasPlayed_()) { 1036 this.load(); 1037 } 1038 1039 var seekable = this.tech_.seekable(); 1040 1041 // if the viewer has paused and we fell out of the live window, 1042 // seek forward to the live point 1043 if (this.tech_.duration() === Infinity) { 1044 if (this.tech_.currentTime() < seekable.start(0)) { 1045 return this.tech_.setCurrentTime(seekable.end(seekable.length - 1)); 1046 } 1047 } 1048 } 1049 1050 /** 1051 * Seek to the latest media position if this is a live video and the 1052 * player and video are loaded and initialized. 1053 */ 1054 }, { 1055 key: 'setupFirstPlay', 1056 value: function setupFirstPlay() { 1057 var seekable = undefined; 1058 var media = this.masterPlaylistLoader_.media(); 1059 1060 // check that everything is ready to begin buffering in the live 1061 // scenario 1062 // 1) the active media playlist is available 1063 if (media && 1064 // 2) the player is not paused 1065 !this.tech_.paused() && 1066 // 3) the player has not started playing 1067 !this.hasPlayed_()) { 1068 1069 // when the video is a live stream 1070 if (!media.endList) { 1071 this.trigger('firstplay'); 1072 1073 // seek to the latest media position for live videos 1074 seekable = this.seekable(); 1075 if (seekable.length) { 1076 this.tech_.setCurrentTime(seekable.end(0)); 1077 } 1078 } 1079 this.hasPlayed_ = function () { 1080 return true; 1081 }; 1082 // now that we are ready, load the segment 1083 this.load(); 1084 return true; 1085 } 1086 return false;
1087 } 1088 1089 /** 1090 * handle the sourceopen event on the MediaSource 1091 * 1092 * @private 1093 */ 1094 }, { 1095 key: 'handleSourceOpen_', 1096 value: function handleSourceOpen_() { 1097 // Only attempt to create the source buffer if none already exist. 1098 // handleSourceOpen is also called when we are "re-opening" a source buffer 1099 // after `endOfStream` has been called (in response to a seek for instance) 1100 try { 1101 this.setupSourceBuffers_(); 1102 } catch (e) { 1103 _videoJs2['default'].log.warn('Failed to create Source Buffers', e); 1104 return this.mediaSource.endOfStream('decode'); 1105 } 1106 1107 // if autoplay is enabled, begin playback. This is duplicative of 1108 // code in video.js but is required because play() must be invoked 1109 // *after* the media source has opened. 1110 if (this.tech_.autoplay()) { 1111 this.tech_.play(); 1112 } 1113 1114 this.trigger('sourceopen'); 1115 } 1116 1117 /** 1118 * Calls endOfStream on the media source when all active stream types have called 1119 * endOfStream 1120 * 1121 * @param {string} streamType 1122 * Stream type of the segment loader that called endOfStream 1123 * @private 1124 */ 1125 }, { 1126 key: 'onEndOfStream', 1127 value: function onEndOfStream() { 1128 var isEndOfStream = this.mainSegmentLoader_.ended_; 1129 1130 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 1131 // if the audio playlist loader exists, then alternate audio is active, so we need 1132 // to wait for both the main and audio segment loaders to call endOfStream 1133 isEndOfStream = isEndOfStream && this.audioSegmentLoader_.ended_; 1134 } 1135 1136 if (isEndOfStream) { 1137 this.mediaSource.endOfStream(); 1138 } 1139 } 1140 1141 /** 1142 * Check if a playlist has stopped being updated 1143 * @param {Object} playlist the media playlist object 1144 * @return {boolean} whether the playlist has stopped being updated or not 1145 */ 1146 }, { 1147 key: 'stuckAtPlaylistEnd_', 1148 value: function stuckAtPlaylistEnd_(playlist) { 1149 var seekable = this.seekable(); 1150 1151 if (!seekable.length) { 1152 // playlist doesn't have enough information to determine whether we are stuck 1153 return false; 1154 } 1155 1156 var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration); 1157 1158 if (expired === null) { 1159 return false; 1160 } 1161 1162 // does not use the safe live end to calculate playlist end, since we 1163 // don't want to say we are stuck while there is still content 1164 var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired); 1165 var currentTime = this.tech_.currentTime(); 1166 var buffered = this.tech_.buffered(); 1167 1168 if (!buffered.length) { 1169 // return true if the playhead reached the absolute end of the playlist 1170 return absolutePlaylistEnd - currentTime <= _ranges2['default'].TIME_FUDGE_FACTOR; 1171 } 1172 var bufferedEnd = buffered.end(buffered.length - 1); 1173 1174 // return true if there is too little buffer left and 1175 // buffer has reached absolute end of playlist 1176 return bufferedEnd - currentTime <= _ranges2['default'].TIME_FUDGE_FACTOR && absolutePlaylistEnd - bufferedEnd <= _ranges2['default'].TIME_FUDGE_FACTOR; 1177 } 1178 1179 /** 1180 * Blacklists a playlist when an error occurs for a set amount of time 1181 * making it unavailable for selection by the rendition selection algorithm 1182 * and then forces a new playlist (rendition) selection. 1183 * 1184 * @param {Object=} error an optional error that may include the playlist 1185 * to blacklist 1186 * @param {Number=} blacklistDuration an optional number of seconds to blacklist the 1187 * playlist 1188 */ 1189 }, { 1190 key: 'blacklistCurrentPlaylist', 1191 value: function blacklistCurrentPlaylist(error, blacklistDuration) { 1192 if (error === undefined) error = {}; 1193 1194 var currentPlaylist = undefined; 1195 var nextPlaylist = undefined; 1196 1197 // If the `error` was generated by the playlist loader, it will contain 1198 // the playlist we were trying to load (but failed) and that should be 1199 // blacklisted instead of the currently selected playlist which is likely 1200 // out-of-date in this scenario 1201 currentPlaylist = error.playlist || this.masterPlaylistLoader_.media(); 1202 1203 // If there is no current playlist, then an error occurred while we were 1204 // trying to load the master OR while we were disposing of the tech 1205 if (!currentPlaylist) { 1206 this.error = error; 1207 1208 try { 1209 return this.mediaSource.endOfStream('network'); 1210 } catch (e) { 1211 return this.trigger('error'); 1212 } 1213 } 1214 1215 var isFinalRendition = this.masterPlaylistLoader_.isFinalRendition_(); 1216 1217 if (isFinalRendition) {
vendor: 6,883 bytes, lines 1218-1436
1218 // Never blacklisting this playlist because it's final rendition 1219 _videoJs2['default'].log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the final playlist.'); 1220 1221 this.tech_.trigger('retryplaylist'); 1222 return this.masterPlaylistLoader_.load(isFinalRendition); 1223 } 1224 // Blacklist this playlist 1225 currentPlaylist.excludeUntil = Date.now() + (blacklistDuration ? blacklistDuration : this.blacklistDuration) * 1000; 1226 this.tech_.trigger('blacklistplaylist'); 1227 this.tech_.trigger({ type: 'usage', name: 'hls-rendition-blacklisted' }); 1228 1229 // Select a new playlist 1230 nextPlaylist = this.selectPlaylist(); 1231 _videoJs2['default'].log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.'); 1232 1233 return this.masterPlaylistLoader_.media(nextPlaylist); 1234 } 1235 1236 /** 1237 * Pause all segment loaders 1238 */ 1239 }, { 1240 key: 'pauseLoading', 1241 value: function pauseLoading() { 1242 this.mainSegmentLoader_.pause(); 1243 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 1244 this.audioSegmentLoader_.pause(); 1245 } 1246 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 1247 this.subtitleSegmentLoader_.pause(); 1248 } 1249 } 1250 1251 /** 1252 * set the current time on all segment loaders 1253 * 1254 * @param {TimeRange} currentTime the current time to set 1255 * @return {TimeRange} the current time 1256 */ 1257 }, { 1258 key: 'setCurrentTime', 1259 value: function setCurrentTime(currentTime) { 1260 var buffered = _ranges2['default'].findRange(this.tech_.buffered(), currentTime); 1261 1262 if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) { 1263 // return immediately if the metadata is not ready yet 1264 return 0; 1265 } 1266 1267 // it's clearly an edge-case but don't thrown an error if asked to 1268 // seek within an empty playlist 1269 if (!this.masterPlaylistLoader_.media().segments) { 1270 return 0; 1271 } 1272 1273 // In flash playback, the segment loaders should be reset on every seek, even 1274 // in buffer seeks 1275 var isFlash = this.mode_ === 'flash' || this.mode_ === 'auto' && !_videoJs2['default'].MediaSource.supportsNativeMediaSources(); 1276 1277 // if the seek location is already buffered, continue buffering as 1278 // usual 1279 if (buffered && buffered.length && !isFlash) { 1280 return currentTime; 1281 } 1282 1283 // cancel outstanding requests so we begin buffering at the new 1284 // location 1285 this.mainSegmentLoader_.resetEverything(); 1286 this.mainSegmentLoader_.abort(); 1287 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 1288 this.audioSegmentLoader_.resetEverything(); 1289 this.audioSegmentLoader_.abort(); 1290 } 1291 if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) { 1292 this.subtitleSegmentLoader_.resetEverything(); 1293 this.subtitleSegmentLoader_.abort(); 1294 } 1295 1296 // start segment loader loading in case they are paused 1297 this.load(); 1298 } 1299 1300 /** 1301 * get the current duration 1302 * 1303 * @return {TimeRange} the duration 1304 */ 1305 }, { 1306 key: 'duration', 1307 value: function duration() { 1308 if (!this.masterPlaylistLoader_) { 1309 return 0; 1310 } 1311 1312 if (this.mediaSource) { 1313 return this.mediaSource.duration; 1314 } 1315 1316 return Hls.Playlist.duration(this.masterPlaylistLoader_.media()); 1317 } 1318 1319 /** 1320 * check the seekable range 1321 * 1322 * @return {TimeRange} the seekable range 1323 */ 1324 }, { 1325 key: 'seekable', 1326 value: function seekable() { 1327 return this.seekable_; 1328 } 1329 }, { 1330 key: 'onSyncInfoUpdate_', 1331 value: function onSyncInfoUpdate_() { 1332 var mainSeekable = undefined; 1333 var audioSeekable = undefined; 1334 1335 if (!this.masterPlaylistLoader_) { 1336 return; 1337 } 1338 1339 var media = this.masterPlaylistLoader_.media(); 1340 1341 if (!media) { 1342 return; 1343 } 1344 1345 var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration); 1346 1347 if (expired === null) { 1348 // not enough information to update seekable 1349 return; 1350 } 1351 1352 mainSeekable = Hls.Playlist.seekable(media, expired); 1353 1354 if (mainSeekable.length === 0) { 1355 return; 1356 } 1357 1358 if (this.mediaTypes_.AUDIO.activePlaylistLoader) { 1359 media = this.mediaTypes_.AUDIO.activePlaylistLoader.media(); 1360 expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration); 1361 1362 if (expired === null) { 1363 return; 1364 } 1365 1366 audioSeekable = Hls.Playlist.seekable(media, expired); 1367 1368 if (audioSeekable.length === 0) { 1369 return; 1370 } 1371 } 1372 1373 if (!audioSeekable) { 1374 // seekable has been calculated based on buffering video data so it 1375 // can be returned directly 1376 this.seekable_ = mainSeekable; 1377 } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) { 1378 // seekables are pretty far off, rely on main 1379 this.seekable_ = mainSeekable; 1380 } else { 1381 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)]]); 1382 } 1383 1384 this.tech_.trigger('seekablechanged'); 1385 } 1386 1387 /** 1388 * Update the player duration 1389 */ 1390 }, { 1391 key: 'updateDuration', 1392 value: function updateDuration() { 1393 var _this4 = this; 1394 1395 var oldDuration = this.mediaSource.duration; 1396 var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media()); 1397 var buffered = this.tech_.buffered(); 1398 var setDuration = function setDuration() { 1399 _this4.mediaSource.duration = newDuration; 1400 _this4.tech_.trigger('durationchange'); 1401 1402 _this4.mediaSource.removeEventListener('sourceopen', setDuration); 1403 }; 1404 1405 if (buffered.length > 0) { 1406 newDuration = Math.max(newDuration, buffered.end(buffered.length - 1)); 1407 } 1408 1409 // if the duration has changed, invalidate the cached value 1410 if (oldDuration !== newDuration) { 1411 // update the duration 1412 if (this.mediaSource.readyState !== 'open') { 1413 this.mediaSource.addEventListener('sourceopen', setDuration); 1414 } else { 1415 setDuration(); 1416 } 1417 } 1418 } 1419 1420 /** 1421 * dispose of the MasterPlaylistController and everything 1422 * that it controls 1423 */ 1424 }, { 1425 key: 'dispose', 1426 value: function dispose() { 1427 var _this5 = this; 1428 1429 this.decrypter_.terminate(); 1430 this.masterPlaylistLoader_.dispose(); 1431 this.mainSegmentLoader_.dispose(); 1432 1433 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 1434 var groups = _this5.mediaTypes_[type].groups; 1435 1436 for (var id in groups) {
1437 groups[id].forEach(function (group) { 1438 if (group.playlistLoader) { 1439 group.playlistLoader.dispose(); 1440 } 1441 }); 1442 } 1443 }); 1444 1445 this.audioSegmentLoader_.dispose(); 1446 this.subtitleSegmentLoader_.dispose(); 1447 } 1448 1449 /** 1450 * return the master playlist object if we have one 1451 * 1452 * @return {Object} the master playlist object that we parsed 1453 */ 1454 }, { 1455 key: 'master', 1456 value: function master() { 1457 return this.masterPlaylistLoader_.master; 1458 } 1459 1460 /** 1461 * return the currently selected playlist 1462 * 1463 * @return {Object} the currently selected playlist object that we parsed 1464 */ 1465 }, { 1466 key: 'media', 1467 value: function media() { 1468 // playlist loader will not return media if it has not been fully loaded 1469 return this.masterPlaylistLoader_.media() || this.initialMedia_; 1470 } 1471 1472 /** 1473 * setup our internal source buffers on our segment Loaders 1474 * 1475 * @private 1476 */ 1477 }, { 1478 key: 'setupSourceBuffers_', 1479 value: function setupSourceBuffers_() { 1480 var media = this.masterPlaylistLoader_.media(); 1481 var mimeTypes = undefined; 1482 1483 // wait until a media playlist is available and the Media Source is 1484 // attached 1485 if (!media || this.mediaSource.readyState !== 'open') { 1486 return; 1487 } 1488 1489 mimeTypes = mimeTypesForPlaylist_(this.masterPlaylistLoader_.master, media); 1490 if (mimeTypes.length < 1) { 1491 this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri; 1492 return this.mediaSource.endOfStream('decode'); 1493 } 1494 this.mainSegmentLoader_.mimeType(mimeTypes[0]); 1495 if (mimeTypes[1]) { 1496 this.audioSegmentLoader_.mimeType(mimeTypes[1]); 1497 } 1498 1499 // exclude any incompatible variant streams from future playlist 1500 // selection 1501 this.excludeIncompatibleVariants_(media); 1502 } 1503 1504 /** 1505 * Blacklist playlists that are known to be codec or 1506 * stream-incompatible with the SourceBuffer configuration. For 1507 * instance, Media Source Extensions would cause the video element to 1508 * stall waiting for video data if you switched from a variant with 1509 * video and audio to an audio-only one. 1510 * 1511 * @param {Object} media a media playlist compatible with the current 1512 * set of SourceBuffers. Variants in the current master playlist that 1513 * do not appear to have compatible codec or stream configurations 1514 * will be excluded from the default playlist selection algorithm 1515 * indefinitely. 1516 * @private 1517 */ 1518 }, { 1519 key: 'excludeIncompatibleVariants_', 1520 value: function excludeIncompatibleVariants_(media) { 1521 var master = this.masterPlaylistLoader_.master; 1522 var codecCount = 2; 1523 var videoCodec = null; 1524 var codecs = undefined; 1525 1526 if (media.attributes.CODECS) { 1527 codecs = (0, _utilCodecsJs.parseCodecs)(media.attributes.CODECS); 1528 videoCodec = codecs.videoCodec; 1529 codecCount = codecs.codecCount; 1530 }
1531 master.playlists.forEach(function (variant) { 1532 var variantCodecs = { 1533 codecCount: 2, 1534 videoCodec: null 1535 }; 1536 1537 if (variant.attributes.CODECS) { 1538 var codecString = variant.attributes.CODECS; 1539 1540 variantCodecs = (0, _utilCodecsJs.parseCodecs)(codecString); 1541 1542 if (window.MediaSource && window.MediaSource.isTypeSupported && !window.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs_(codecString) + '"')) { 1543 variant.excludeUntil = Infinity; 1544 } 1545 } 1546 1547 // if the streams differ in the presence or absence of audio or 1548 // video, they are incompatible 1549 if (variantCodecs.codecCount !== codecCount) { 1550 variant.excludeUntil = Infinity; 1551 } 1552 1553 // if h.264 is specified on the current playlist, some flavor of 1554 // it must be specified on all compatible variants 1555 if (variantCodecs.videoCodec !== videoCodec) { 1556 variant.excludeUntil = Infinity; 1557 } 1558 }); 1559 } 1560 }, { 1561 key: 'updateAdCues_', 1562 value: function updateAdCues_(media) { 1563 var offset = 0; 1564 var seekable = this.seekable(); 1565 1566 if (seekable.length) { 1567 offset = seekable.start(0); 1568 } 1569 1570 _adCueTags2['default'].updateAdCues(media, this.cueTagsTrack_, offset); 1571 } 1572 1573 /** 1574 * Calculates the desired forward buffer length based on current time 1575 * 1576 * @return {Number} Desired forward buffer length in seconds 1577 */ 1578 }, { 1579 key: 'goalBufferLength', 1580 value: function goalBufferLength() { 1581 var currentTime = this.tech_.currentTime(); 1582 var initial = _config2['default'].GOAL_BUFFER_LENGTH; 1583 var rate = _config2['default'].GOAL_BUFFER_LENGTH_RATE; 1584 var max = Math.max(initial, _config2['default'].MAX_GOAL_BUFFER_LENGTH); 1585 1586 return Math.min(initial + currentTime * rate, max); 1587 } 1588 1589 /** 1590 * Calculates the desired buffer low water line based on current time 1591 * 1592 * @return {Number} Desired buffer low water line in seconds 1593 */ 1594 }, { 1595 key: 'bufferLowWaterLine', 1596 value: function bufferLowWaterLine() { 1597 var currentTime = this.tech_.currentTime(); 1598 var initial = _config2['default'].BUFFER_LOW_WATER_LINE; 1599 var rate = _config2['default'].BUFFER_LOW_WATER_LINE_RATE; 1600 var max = Math.max(initial, _config2['default'].MAX_BUFFER_LOW_WATER_LINE); 1601 1602 return Math.min(initial + currentTime * rate, max); 1603 } 1604 }]); 1605 1606 return MasterPlaylistController; 1607})(_videoJs2['default'].EventTarget); 1608 1609exports.MasterPlaylistController = MasterPlaylistController; 1610}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 1611},{"./ad-cue-tags":1,"./config":3,"./decrypter-worker":4,"./media-groups":6,"./playlist-loader":9,"./ranges":12,"./segment-loader":16,"./sync-controller":18,"./util/codecs.js":19,"./vtt-segment-loader":20,"videojs-contrib-media-sources/es5/codec-utils":65,"webworkify":76}],6:[function(require,module,exports){ 1612(function (global){ 1613'use strict'; 1614 1615Object.defineProperty(exports, '__esModule', { 1616 value: true 1617}); 1618 1619function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 1620 1621var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 1622 1623var _videoJs2 = _interopRequireDefault(_videoJs); 1624 1625var _playlistLoader = require('./playlist-loader'); 1626 1627var _playlistLoader2 = _interopRequireDefault(_playlistLoader); 1628 1629var noop = function noop() {}; 1630 1631/** 1632 * Convert the properties of an HLS track into an audioTrackKind. 1633 * 1634 * @private 1635 */ 1636var audioTrackKind_ = function audioTrackKind_(properties) { 1637 var kind = properties['default'] ? 'main' : 'alternative'; 1638 1639 if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) { 1640 kind = 'main-desc'; 1641 } 1642 1643 return kind; 1644}; 1645 1646/** 1647 * Pause provided segment loader and playlist loader if active 1648 * 1649 * @param {SegmentLoader} segmentLoader 1650 * SegmentLoader to pause 1651 * @param {Object} mediaType 1652 * Active media type 1653 * @function stopLoaders 1654 */ 1655var stopLoaders = function stopLoaders(segmentLoader, mediaType) { 1656 segmentLoader.abort(); 1657 segmentLoader.pause(); 1658 1659 if (mediaType && mediaType.activePlaylistLoader) { 1660 mediaType.activePlaylistLoader.pause(); 1661 mediaType.activePlaylistLoader = null; 1662 } 1663}; 1664 1665exports.stopLoaders = stopLoaders; 1666/** 1667 * Start loading provided segment loader and playlist loader 1668 * 1669 * @param {PlaylistLoader} playlistLoader 1670 * PlaylistLoader to start loading 1671 * @param {Object} mediaType 1672 * Active media type 1673 * @function startLoaders 1674 */ 1675var startLoaders = function startLoaders(playlistLoader, mediaType) { 1676 // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the 1677 // playlist loader 1678 mediaType.activePlaylistLoader = playlistLoader; 1679 playlistLoader.load(); 1680}; 1681 1682exports.startLoaders = startLoaders; 1683/** 1684 * Returns a function to be called when the media group changes. It performs a 1685 * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a 1686 * change of group is merely a rendition switch of the same content at another encoding, 1687 * rather than a change of content, such as switching audio from English to Spanish. 1688 * 1689 * @param {String} type 1690 * MediaGroup type 1691 * @param {Object} settings 1692 * Object containing required information for media groups 1693 * @return {Function} 1694 * Handler for a non-destructive resync of SegmentLoader when the active media 1695 * group changes. 1696 * @function onGroupChanged 1697 */ 1698var onGroupChanged = function onGroupChanged(type, settings) { 1699 return function () { 1700 var _settings$segmentLoaders = settings.segmentLoaders; 1701 var segmentLoader = _settings$segmentLoaders[type]; 1702 var mainSegmentLoader = _settings$segmentLoaders.main; 1703 var mediaType = settings.mediaTypes[type]; 1704 1705 var activeTrack = mediaType.activeTrack(); 1706 var activeGroup = mediaType.activeGroup(activeTrack); 1707 var previousActiveLoader = mediaType.activePlaylistLoader; 1708 1709 stopLoaders(segmentLoader, mediaType); 1710 1711 if (!activeGroup) { 1712 // there is no group active 1713 return; 1714 } 1715 1716 if (!activeGroup.playlistLoader) { 1717 if (previousActiveLoader) { 1718 // The previous group had a playlist loader but the new active group does not 1719 // this means we are switching from demuxed to muxed audio. In this case we want to 1720 // do a destructive reset of the main segment loader and n
1720ot restart the audio 1721 // loaders. 1722 mainSegmentLoader.resetEverything(); 1723 } 1724 return; 1725 } 1726 1727 // Non-destructive resync 1728 segmentLoader.resyncLoader(); 1729 1730 startLoaders(activeGroup.playlistLoader, mediaType); 1731 }; 1732}; 1733 1734exports.onGroupChanged = onGroupChanged; 1735/** 1736 * Returns a function to be called when the media track changes. It performs a 1737 * destructive reset of the SegmentLoader to ensure we start loading as close to 1738 * currentTime as possible. 1739 * 1740 * @param {String} type 1741 * MediaGroup type 1742 * @param {Object} settings 1743 * Object containing required information for media groups 1744 * @return {Function} 1745 * Handler for a destructive reset of SegmentLoader when the active media 1746 * track changes. 1747 * @function onTrackChanged 1748 */ 1749var onTrackChanged = function onTrackChanged(type, settings) { 1750 return function () { 1751 var _settings$segmentLoaders2 = settings.segmentLoaders; 1752 var segmentLoader = _settings$segmentLoaders2[type]; 1753 var mainSegmentLoader = _settings$segmentLoaders2.main; 1754 var mediaType = settings.mediaTypes[type]; 1755 1756 var activeTrack = mediaType.activeTrack(); 1757 var activeGroup = mediaType.activeGroup(activeTrack); 1758 var previousActiveLoader = mediaType.activePlaylistLoader; 1759 1760 stopLoaders(segmentLoader, mediaType); 1761 1762 if (!activeGroup) { 1763 // there is no group active so we do not want to restart loaders 1764 return; 1765 } 1766 1767 if (!activeGroup.playlistLoader) { 1768 // when switching from demuxed audio/video to muxed audio/video (noted by no playlist 1769 // loader for the audio group), we want to do a destructive reset of the main segment 1770 // loader and not restart the audio loaders 1771 mainSegmentLoader.resetEverything(); 1772 return; 1773 } 1774 1775 if (previousActiveLoader === activeGroup.playlistLoader) { 1776 // Nothing has actually changed. This can happen because track change events can fire 1777 // multiple times for a "single" change. One for enabling the new active track, and 1778 // one for disabling the track that was active 1779 startLoaders(activeGroup.playlistLoader, mediaType); 1780 return; 1781 } 1782 1783 if (segmentLoader.track) { 1784 // For WebVTT, set the new text track in the segmentloader 1785 segmentLoader.track(activeTrack); 1786 } 1787 1788 // destructive reset 1789 segmentLoader.resetEverything(); 1790 1791 startLoaders(activeGroup.playlistLoader, mediaType); 1792 }; 1793}; 1794 1795exports.onTrackChanged = onTrackChanged; 1796var onError = { 1797 /** 1798 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters 1799 * an error. 1800 * 1801 * @param {String} type 1802 * MediaGroup type 1803 * @param {Object} settings 1804 * Object containing required information for media groups 1805 * @return {Function} 1806 * Error handler. Logs warning (or error if the playlist is blacklisted) to 1807 * console and switches back to default audio track. 1808 * @function onError.AUDIO 1809 */ 1810 AUDIO: function AUDIO(type, settings) { 1811 return function () { 1812 var segmentLoader = settings.segmentLoaders[type]; 1813 var mediaType = settings.mediaTypes[type]; 1814 var blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist; 1815 1816 stopLoaders(segmentLoader, mediaType); 1817 1818 // switch back to default audio track 1819 var activeTrack = mediaType.activeTrack(); 1820 var activeGroup = mediaType.activeGroup(); 1821 var id = (activeGroup.filter(function (group) { 1822 return group['default']; 1823 })[0] || activeGroup[0]).id; 1824 var defaultTrack = mediaType.tracks[id]; 1825 1826 if (activeTrack === defaultTrack) { 1827 // Default track encountered an error. All we can do now is blacklist the current 1828 // rendition and hope another will switch audio groups 1829 blacklistCurrentPlaylist({ 1830 message: 'Problem encountered loading the default audio track.' 1831 }); 1832 return; 1833 } 1834 1835 _videoJs2['default'].log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.'); 1836 1837 for (var trackId in mediaType.tracks) { 1838 mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack; 1839 } 1840 1841 mediaType.onTrackChanged(); 1842 }; 1843 }, 1844 /** 1845 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters 1846 * an error. 1847 * 1848 * @param {String} type 1849 * MediaGroup type 1850 * @param {Object} settings 1851 * Object containing required information for media groups 1852 * @return {Function} 1853 * Error handler. Logs warning to console and disables the active subtitle track 1854 * @function onError.SUBTITLES 1855 */ 1856 SUBTITLES: function SUBTITLES(type, settings) { 1857 return function () { 1858 var segmentLoader = settings.segmentLoaders[type]; 1859 var mediaType = settings.mediaTypes[type]; 1860 1861 _videoJs2['default'].log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.'); 1862 1863 stopLoaders(segmentLoader, mediaType); 1864 1865 var track = mediaType.activeTrack(); 1866 1867 if (track) { 1868 track.mode = 'disabled'; 1869 } 1870 1871 mediaType.onTrackChanged(); 1872 }; 1873 } 1874}; 1875 1876exports.onError = onError; 1877var setupListeners = { 1878 /** 1879 * Setup event listeners for audio playlist loader 1880 * 1881 * @param {String} type 1882 * MediaGroup type 1883 * @param {PlaylistLoader|null} playlistLoader 1884 * PlaylistLoader to register listeners on 1885 * @param {Object} settings 1886 * Object containing required information for media groups 1887 * @function setupListeners.AUDIO 1888 */ 1889 AUDIO: function AUDIO(type, playlistLoader, settings) { 1890 if (!playlistLoader) { 1891 // no playlist loader means audio will be muxed with the video 1892 return; 1893 } 1894 1895 var tech = settings.tech; 1896 var requestOptions = settings.requestOptions; 1897 var segmentLoader = settings.segmentLoaders[type]; 1898 1899 playlistLoader.on('loadedmetadata', function () { 1900 var media = playlistLoader.media(); 1901 1902 segmentLoader.playlist(media, requestOptions); 1903 1904 // if the video is already playing, or if this isn't a live video and preload 1905 // permits, start downloading segments 1906 if (!tech.paused() || media.endList && tech.preload() !== 'none') { 1907 segmentLoader.load(); 1908 } 1909 }); 1910 1911 playlistLoader.on('loadedplaylist', function () { 1912 segmentLoader.playlist(playlistLoader.media(), requestOptions); 1913 1914 // If the player isn't paused, ensure that the segment loader is running 1915 if (!tech.paused()) { 1916 segmentLoader.load(); 1917 } 1918 }); 1919 1920 playlistLoader.on('error', onError[type](type, settings)); 1921 }, 1922 /** 1923 * Setup event listeners for subtitle playlist loader 1924 * 1925 * @param {String} type 1926 * MediaGroup type 1927 * @param {PlaylistLoader|null} playlistLoader 1928 * PlaylistLoader to register listeners on 1929 * @param {Object} settings 1930 * Object containing required information for media groups 1931 * @function setupListeners.SUBTITLES 1932 */ 1933 SUBTITLES: function SUBTITLES(type, playlistLoader, settings) { 1934 var tech = settings.tech; 1935 var requestOptions = settings.requestOptions; 1936 var segmentLoader = settings.segmentLoaders[type]; 1937 var mediaType = settings.mediaTypes[type]; 1938 1939 playlistLoader.on('loadedmetadata', function () { 1940 var media = playlistLoader.media(); 1941 1942 segmentLoader.playlist(media, requestOptions); 1943 segmentLoader.track(mediaType.activeTrack()); 1944 1945 // if the video is already playing, or if this isn't a live video and preload 1946 // permits, start downloading segments 1947 if (!tech.paused() || media.endList && tech.preload() !== 'none') { 1948 segmentLoader.load(); 1949 } 1950 }); 1951 1952 playlistLoader.on('loadedplaylist', function () { 1953 segmentLoader.playlist(playlistLoader.media(), requestOptions); 1954 1955 // If the player isn't paused, ensure that the segment loader is running 1956 if (!tech.paused()) { 1957 segmentLoader.load(); 1958 } 1959 }); 1960 1961 playlistLoader.on('error', onError[type](type, settings)); 1962 } 1963}; 1964 1965exports.setupListeners = setupListeners; 1966var initialize = { 1967 /** 1968 * Setup PlaylistLoaders and AudioTracks for the audio groups 1969 * 1970 * @param {String} type 1971 * MediaGroup type 1972 * @param {Object} settings 1973 * Object containing required information for media groups 1974 * @function initialize.AUDIO 1975 */ 1976 'AUDIO': function AUDIO(type, settings) { 1977 var mode = settings.mode; 1978 var hls = settings.hls; 1979 var segmentLoader = settings.segmentLoaders[type]; 1980 var withCredentials = settings.requestOptions.withCredentials; 1981 var mediaGroups = settings.master.mediaGroups; 1982 var _settings$mediaTypes$type = settings.mediaTypes[type]; 1983 var groups = _settings$mediaTypes$type.groups; 1984 var tracks = _settings$mediaTypes$type.tracks; 1985 1986 // force a default if we have none or we are not
1987 // in html5 mode (the only mode to support more than one 1988 // audio track) 1989 if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0 || mode !== 'html5') { 1990 mediaGroups[type] = { main: { 'default': { 'default': true } } }; 1991 } 1992 1993 for (var groupId in mediaGroups[type]) { 1994 if (!groups[groupId]) { 1995 groups[groupId] = []; 1996 } 1997 1998 for (var variantLabel in mediaGroups[type][groupId]) { 1999 var properties = mediaGroups[type][groupId][variantLabel]; 2000 var playlistLoader = undefined; 2001 2002 if (properties.resolvedUri) { 2003 playlistLoader = new _playlistLoader2['default'](properties.resolvedUri, hls, withCredentials); 2004 } else { 2005 // no resolvedUri means the audio is muxed with the video when using this 2006 // audio track 2007 playlistLoader = null; 2008 } 2009 2010 properties = _videoJs2['default'].mergeOptions({ id: variantLabel, playlistLoader: playlistLoader }, properties); 2011 2012 setupListeners[type](type, properties.playlistLoader, settings); 2013 2014 groups[groupId].push(properties); 2015 2016 if (typeof tracks[variantLabel] === 'undefined') { 2017 var track = new _videoJs2['default'].AudioTrack({ 2018 id: variantLabel, 2019 kind: audioTrackKind_(properties), 2020 enabled: false, 2021 language: properties.language, 2022 'default': properties['default'], 2023 label: variantLabel 2024 }); 2025 2026 tracks[variantLabel] = track; 2027 } 2028 } 2029 } 2030 2031 // setup single error event handler for the segment loader 2032 segmentLoader.on('error', onError[type](type, settings)); 2033 }, 2034 /** 2035 * Setup PlaylistLoaders and TextTracks for the subtitle groups 2036 * 2037 * @param {String} type 2038 * MediaGroup type 2039 * @param {Object} settings 2040 * Object containing required information for media groups 2041 * @function initialize.SUBTITLES 2042 */ 2043 'SUBTITLES': function SUBTITLES(type, settings) { 2044 var tech = settings.tech; 2045 var hls = settings.hls; 2046 var segmentLoader = settings.segmentLoaders[type]; 2047 var withCredentials = settings.requestOptions.withCredentials; 2048 var mediaGroups = settings.master.mediaGroups; 2049 var _settings$mediaTypes$type2 = settings.mediaTypes[type]; 2050 var groups = _settings$mediaTypes$type2.groups; 2051 var tracks = _settings$mediaTypes$type2.tracks; 2052 2053 for (var groupId in mediaGroups[type]) { 2054 if (!groups[groupId]) { 2055 groups[groupId] = []; 2056 } 2057 2058 for (var variantLabel in mediaGroups[type][groupId]) { 2059 if (mediaGroups[type][groupId][variantLabel].forced) { 2060 // Subtitle playlists with the forced attribute are not selectable in Safari. 2061 // According to Apple's HLS Authoring Specification: 2062 // If content has forced subtitles and regular subtitles in a given language, 2063 // the regular subtitles track in that language MUST contain both the forced 2064 // subtitles and the regular subtitles for that language. 2065 // Because of this requirement and that Safari does not add forced subtitles, 2066 // forced subtitles are skipped here to maintain consistent experience across 2067 // all platforms 2068 continue; 2069 } 2070 2071 var properties = mediaGroups[type][groupId][variantLabel]; 2072 2073 properties = _videoJs2['default'].mergeOptions({ 2074 id: variantLabel, 2075 playlistLoader: new _playlistLoader2['default'](properties.resolvedUri, hls, withCredentials) 2076 }, properties); 2077 2078 setupListeners[type](type, properties.playlistLoader, settings); 2079 2080 groups[groupId].push(properties); 2081 2082 if (typeof tracks[variantLabel] === 'undefined') { 2083 var track = tech.addRemoteTextTrack({ 2084 id: variantLabel, 2085 kind: 'subtitles', 2086 enabled: false, 2087 language: properties.language, 2088 label: variantLabel 2089 }, false).track; 2090 2091 tracks[variantLabel] = track; 2092 } 2093 } 2094 } 2095 2096 // setup single error event handler for the segment loader 2097 segmentLoader.on('error', onError[type](type, settings)); 2098 }, 2099 /** 2100 * Setup TextTracks for the closed-caption groups 2101 * 2102 * @param {String} type 2103 * MediaGroup type 2104 * @param {Object} settings 2105 * Object containing required information for media groups 2106 * @function initialize['CLOSED-CAPTIONS'] 2107 */ 2108 'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) { 2109 var tech = settings.tech; 2110 var mediaGroups = settings.master.mediaGroups; 2111 var _settings$mediaTypes$type3 = settings.mediaTypes[type]; 2112 var groups = _settings$mediaTypes$type3.groups; 2113 var tracks = _settings$mediaTypes$type3.tracks; 2114 2115 for (var groupId in mediaGroups[type]) { 2116 if (!groups[groupId]) { 2117 groups[groupId] = []; 2118 } 2119 2120 for (var variantLabel in mediaGroups[type][groupId]) { 2121 var properties = mediaGroups[type][groupId][variantLabel]; 2122 2123 // We only support CEA608 captions for now, so ignore anything that 2124 // doesn't use a CCx INSTREAM-ID 2125 if (!properties.instreamId.match(/CC\d/)) { 2126 continue; 2127 } 2128 2129 // No PlaylistLoader is required for Closed-Captions because the captions are 2130 // embedded within the video stream 2131 groups[groupId].push(_videoJs2['default'].mergeOptions({ id: variantLabel }, properties)); 2132 2133 if (typeof tracks[variantLabel] === 'undefined') { 2134 var track = tech.addRemoteTextTrack({ 2135 id: properties.instreamId, 2136 kind: 'captions', 2137 enabled: false, 2138 language: properties.language, 2139 label: variantLabel 2140 }, false).track; 2141 2142 tracks[variantLabel] = track; 2143 } 2144 } 2145 } 2146 } 2147}; 2148 2149exports.initialize = initialize; 2150/** 2151 * Returns a function used to get the active group of the provided type 2152 * 2153 * @param {String} type 2154 * MediaGroup type 2155 * @param {Object} settings 2156 * Object containing required information for media groups 2157 * @return {Function} 2158 * Function that returns the active media group for the provided type. Takes an 2159 * optional parameter {TextTrack} track. If no track is provided, a list of all 2160 * variants in the group, otherwise the variant corresponding to the provided 2161 * track is returned. 2162 * @function activeGroup 2163 */ 2164var activeGroup = function activeGroup(type, settings) { 2165 return function (track) { 2166 var masterPlaylistLoader = settings.masterPlaylistLoader; 2167 var groups = settings.mediaTypes[type].groups; 2168 2169 var media = masterPlaylistLoader.media(); 2170 2171 if (!media) { 2172 return null; 2173 } 2174 2175 var variants = null; 2176 2177 if (media.attributes[type]) { 2178 variants = groups[media.attributes[type]]; 2179 } 2180 2181 variants = variants || groups.main; 2182 2183 if (typeof track === 'undefined') { 2184 return variants; 2185 } 2186 2187 if (track === null) { 2188 // An active track was specified so a corresponding group is expected. track === null 2189 // means no track is currently active so there is no corresponding group 2190 return null; 2191 } 2192 2193 return variants.filter(function (props) { 2194 return props.id === track.id; 2195 })[0] || null; 2196 }; 2197}; 2198 2199exports.activeGroup = activeGroup; 2200var activeTrack = { 2201 /** 2202 * Returns a function used to get the active track of type provided 2203 * 2204 * @param {String} type 2205 * MediaGroup type 2206 * @param {Object} settings 2207 * Object containing required information for media groups 2208 * @return {Function} 2209 * Function that returns the active media track for the provided type. Returns 2210 * null if no track is active 2211 * @function activeTrack.AUDIO 2212 */ 2213 AUDIO: function AUDIO(type, settings) { 2214 return function () { 2215 var tracks = settings.mediaTypes[type].tracks; 2216 2217 for (var id in tracks) { 2218 if (tracks[id].enabled) { 2219 return tracks[id]; 2220 } 2221 } 2222 2223 return null; 2224 }; 2225 }, 2226 /** 2227 * Returns a function used to get the active track of type provided 2228 * 2229 * @param {String} type 2230 * MediaGroup type 2231 * @param {Object} settings 2232 * Object containing required information for media groups 2233 * @return {Function} 2234 * Function that returns the active media track for the provided type. Returns 2235 * null if no track is active 2236 * @function activeTrack.SUBTITLES 2237 */ 2238 SUBTITLES: function SUBTITLES(type, settings) { 2239 return function () { 2240 var tracks = settings.mediaTypes[type].tracks; 2241 2242 for (var id in tracks) { 2243 if (tracks[id].mode === 'showing') { 2244 return tracks[id]; 2245 } 2246 } 2247 2248 return null; 2249 }; 2250 } 2251}; 2252 2253exports.activeTrack = activeTrack; 2254/** 2255 * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles, 2256 * Closed-Captions) specified in the master manifest. 2257 * 2258 * @param {Object} settings 2259 * Object containing required information for setting up the media groups 2260 * @param {SegmentLoader} settings.segmentLoaders.AUDIO 2261 * Audio segment loader 2262 * @param {SegmentLoader} settings.segmentLoaders.SUBTITLES 2263 * Subtitle segment loader 2264 * @param {SegmentLoader} settings.segmentLoaders.main 2265 * Main segment loader 2266 * @param {Tech} settings.tech 2267 * The tech of the player 2268 * @param {Object} settings.requestOptions 2269 * XHR request options used by the segment loaders 2270 * @param {PlaylistLoader} settings.masterPlaylistLoader 2271 * PlaylistLoader for the master source 2272 * @param {String} mode 2273 * Mode of the hls source handler. Can be 'auto', 'html5', or 'flash' 2274 * @param {HlsHandler} settings.hls 2275 * HLS SourceHandler 2276 * @param {Object} settings.master 2277 * The parsed master manifest 2278 * @param {Object} settings.mediaTypes 2279 * Object to store the loaders, tracks, and utility methods for each media type 2280 * @param {Function} settings.blacklistCurrentPlaylist 2281 * Blacklists the current rendition and forces a rendition switch. 2282 * @function setupMediaGroups 2283 */ 2284var setupMediaGroups = function setupMediaGroups(settings) { 2285 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) { 2286 initialize[type](type, settings); 2287 }); 2288 2289 var mediaTypes = settings.mediaTypes; 2290 var masterPlaylistLoader = settings.masterPlaylistLoader; 2291 var tech = settings.tech; 2292 var hls = settings.hls; 2293 2294 // setup active group and track getters and change event handlers 2295 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 2296 mediaTypes[type].activeGroup = activeGroup(type, settings); 2297 mediaTypes[type].activeTrack = activeTrack[type](type, settings); 2298 mediaTypes[type].onGroupChanged = onGroupChanged(type, settings); 2299 mediaTypes[type].onTrackChanged = onTrackChanged(type, settings); 2300 }); 2301 2302 // DO NOT enable the default subtitle or caption track. 2303 // DO enable the default audio track 2304 var audioGroup = mediaTypes.AUDIO.activeGroup(); 2305 var groupId = (audioGroup.filter(function (group) { 2306 return group['default']; 2307 })[0] || audioGroup[0]).id; 2308 2309 mediaTypes.AUDIO.tracks[groupId].enabled = true; 2310 mediaTypes.AUDIO.onTrackChanged(); 2311 2312 masterPlaylistLoader.on('mediachange', function () { 2313 ['AUDIO', 'SUBTITLES'].forEach(function (type) { 2314 return mediaTypes[type].onGroupChanged(); 2315 }); 2316 }); 2317 2318 // custom audio track change event handler for usage event 2319 var onAudioTrackChanged = function onAudioTrackChanged() { 2320 mediaTypes.AUDIO.onTrackChanged(); 2321 tech.trigger({ type: 'usage', name: 'hls-audio-change' }); 2322 }; 2323 2324 tech.audioTracks().addEventListener('change', onAudioTrackChanged); 2325 tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged); 2326 2327 hls.on('dispose', function () { 2328 tech.audioTracks().removeEventListener('change', onAudioTrackChanged); 2329 tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged); 2330 }); 2331 2332 // clear existing audio tracks and add the ones we just created 2333 tech.clearTracks('audio'); 2334 2335 for (var id in mediaTypes.AUDIO.tracks) { 2336 tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]); 2337 } 2338}; 2339 2340exports.setupMediaGroups = setupMediaGroups; 2341/** 2342 * Creates skeleton object used to store the loaders, tracks, and utility methods for each 2343 * media type 2344 * 2345 * @return {Object} 2346 * Object to store the loaders, tracks, and utility methods for each media type 2347 * @function createMediaTypes 2348 */ 2349var createMediaTypes = function createMediaTypes() { 2350 var mediaTypes = {}; 2351 2352 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) { 2353 mediaTypes[type] = { 2354 groups: {}, 2355 tracks: {}, 2356 activePlaylistLoader: null, 2357 activeGroup: noop, 2358 activeTrack: noop, 2359 onGroupChanged: noop, 2360 onTrackChanged: noop 2361 }; 2362 }); 2363 2364 return mediaTypes; 2365}; 2366exports.createMediaTypes = createMediaTypes; 2367}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 2368},{"./playlist-loader":9}],7:[function(require,module,exports){ 2369(function (global){ 2370'use strict'; 2371 2372Object.defineProperty(exports, '__esModule', { 2373 value: true 2374}); 2375 2376function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 2377 2378var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 2379 2380var _videoJs2 = _interopRequireDefault(_videoJs); 2381 2382var _binUtils = require('./bin-utils'); 2383 2384var REQUEST_ERRORS = { 2385 FAILURE: 2, 2386 TIMEOUT: -101, 2387 ABORTED: -102 2388}; 2389 2390exports.REQUEST_ERRORS = REQUEST_ERRORS; 2391/** 2392 * Turns segment byterange into a string suitable for use in 2393 * HTTP Range requests 2394 * 2395 * @param {Object} byterange - an object with two values defining the start and end 2396 * of a byte-range 2397 */ 2398var byterangeStr = function byterangeStr(byterange) { 2399 var byterangeStart = undefined; 2400 var byterangeEnd = undefined; 2401 2402 // `byterangeEnd` is one less than `offset + length` because the HTTP range 2403 // header uses inclusive ranges 2404 byterangeEnd = byterange.offset + byterange.length - 1; 2405 byterangeStart = byterange.offset; 2406 return 'bytes=' + byterangeStart + '-' + byterangeEnd; 2407}; 2408 2409/** 2410 * Defines headers for use in the xhr request for a particular segment. 2411 * 2412 * @param {Object} segment - a simplified copy of the segmentInfo object 2413 * from SegmentLoader 2414 */ 2415var segmentXhrHeaders = function segmentXhrHeaders(segment) { 2416 var headers = {}; 2417 2418 if (segment.byterange) { 2419 headers.Range = byterangeStr(segment.byterange); 2420 } 2421 return headers; 2422}; 2423 2424/** 2425 * Abort all requests 2426 * 2427 * @param {Object} activeXhrs - an object that tracks all XHR requests 2428 */ 2429var abortAll = function abortAll(activeXhrs) {
vendor: 4,369 bytes, lines 2430-2577
2430 activeXhrs.forEach(function (xhr) { 2431 xhr.abort(); 2432 }); 2433}; 2434 2435/** 2436 * Gather important bandwidth stats once a request has completed 2437 * 2438 * @param {Object} request - the XHR request from which to gather stats 2439 */ 2440var getRequestStats = function getRequestStats(request) { 2441 return { 2442 bandwidth: request.bandwidth, 2443 bytesReceived: request.bytesReceived || 0, 2444 roundTripTime: request.roundTripTime || 0 2445 }; 2446}; 2447 2448/** 2449 * If possible gather bandwidth stats as a request is in 2450 * progress 2451 * 2452 * @param {Event} progressEvent - an event object from an XHR's progress event 2453 */ 2454var getProgressStats = function getProgressStats(progressEvent) { 2455 var request = progressEvent.target; 2456 var roundTripTime = Date.now() - request.requestTime; 2457 var stats = { 2458 bandwidth: Infinity, 2459 bytesReceived: 0, 2460 roundTripTime: roundTripTime || 0 2461 }; 2462 2463 stats.bytesReceived = progressEvent.loaded; 2464 // This can result in Infinity if stats.roundTripTime is 0 but that is ok 2465 // because we should only use bandwidth stats on progress to determine when 2466 // abort a request early due to insufficient bandwidth 2467 stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000); 2468 2469 return stats; 2470}; 2471 2472/** 2473 * Handle all error conditions in one place and return an object 2474 * with all the information 2475 * 2476 * @param {Error|null} error - if non-null signals an error occured with the XHR 2477 * @param {Object} request - the XHR request that possibly generated the error 2478 */ 2479var handleErrors = function handleErrors(error, request) { 2480 if (request.timedout) { 2481 return { 2482 status: request.status, 2483 message: 'HLS request timed-out at URL: ' + request.uri, 2484 code: REQUEST_ERRORS.TIMEOUT, 2485 xhr: request 2486 }; 2487 } 2488 2489 if (request.aborted) { 2490 return { 2491 status: request.status, 2492 message: 'HLS request aborted at URL: ' + request.uri, 2493 code: REQUEST_ERRORS.ABORTED, 2494 xhr: request 2495 }; 2496 } 2497 2498 if (error) { 2499 return { 2500 status: request.status, 2501 message: 'HLS request errored at URL: ' + request.uri, 2502 code: REQUEST_ERRORS.FAILURE, 2503 xhr: request 2504 }; 2505 } 2506 2507 return null; 2508}; 2509 2510/** 2511 * Handle responses for key data and convert the key data to the correct format 2512 * for the decryption step later 2513 * 2514 * @param {Object} segment - a simplified copy of the segmentInfo object 2515 * from SegmentLoader 2516 * @param {Function} finishProcessingFn - a callback to execute to continue processing 2517 * this request 2518 */ 2519var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) { 2520 return function (error, request) { 2521 var response = request.response; 2522 var errorObj = handleErrors(error, request); 2523 2524 if (errorObj) { 2525 return finishProcessingFn(errorObj, segment); 2526 } 2527 2528 if (response.byteLength !== 16) { 2529 return finishProcessingFn({ 2530 status: request.status, 2531 message: 'Invalid HLS key at URL: ' + request.uri, 2532 code: REQUEST_ERRORS.FAILURE, 2533 xhr: request 2534 }, segment); 2535 } 2536 2537 var view = new DataView(response); 2538 2539 segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]); 2540 return finishProcessingFn(null, segment); 2541 }; 2542}; 2543 2544/** 2545 * Handle init-segment responses 2546 * 2547 * @param {Object} segment - a simplified copy of the segmentInfo object 2548 * from SegmentLoader 2549 * @param {Function} finishProcessingFn - a callback to execute to continue processing 2550 * this request 2551 */ 2552var handleInitSegmentResponse = function handleInitSegmentResponse(segment, finishProcessingFn) { 2553 return function (error, request) { 2554 var response = request.response; 2555 var errorObj = handleErrors(error, request); 2556 2557 if (errorObj) { 2558 return finishProcessingFn(errorObj, segment); 2559 } 2560 2561 // stop processing if received empty content 2562 if (response.byteLength === 0) { 2563 return finishProcessingFn({ 2564 status: request.status, 2565 message: 'Empty HLS segment content at URL: ' + request.uri, 2566 code: REQUEST_ERRORS.FAILURE, 2567 xhr: request 2568 }, segment); 2569 } 2570 2571 segment.map.bytes = new Uint8Array(request.response); 2572 return finishProcessingFn(null, segment); 2573 }; 2574}; 2575 2576/** 2577 * Response handler for segment-requests being sure to set the correct
2578 * property depending on whether the segment is encryped or not 2579 * Also records and keeps track of stats that are used for ABR purposes 2580 * 2581 * @param {Object} segment - a simplified copy of the segmentInfo object 2582 * from SegmentLoader 2583 * @param {Function} finishProcessingFn - a callback to execute to continue processing 2584 * this request 2585 */ 2586var handleSegmentResponse = function handleSegmentResponse(segment, finishProcessingFn) { 2587 return function (error, request) { 2588 var response = request.response; 2589 var errorObj = handleErrors(error, request); 2590 2591 if (errorObj) { 2592 return finishProcessingFn(errorObj, segment); 2593 } 2594 2595 // stop processing if received empty content 2596 if (response.byteLength === 0) { 2597 return finishProcessingFn({ 2598 status: request.status, 2599 message: 'Empty HLS segment content at URL: ' + request.uri, 2600 code: REQUEST_ERRORS.FAILURE, 2601 xhr: request 2602 }, segment); 2603 } 2604 2605 segment.stats = getRequestStats(request); 2606 2607 if (segment.key) { 2608 segment.encryptedBytes = new Uint8Array(request.response); 2609 } else { 2610 segment.bytes = new Uint8Array(request.response); 2611 } 2612 2613 return finishProcessingFn(null, segment); 2614 }; 2615}; 2616 2617/** 2618 * Decrypt the segment via the decryption web worker 2619 * 2620 * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines 2621 * @param {Object} segment - a simplified copy of the segmentInfo object 2622 * from SegmentLoader 2623 * @param {Function} doneFn - a callback that is executed after decryption has completed 2624 */ 2625var decryptSegment = function decryptSegment(decrypter, segment, doneFn) { 2626 var decryptionHandler = function decryptionHandler(event) { 2627 if (event.data.source === segment.requestId) { 2628 decrypter.removeEventListener('message', decryptionHandler); 2629 var decrypted = event.data.decrypted; 2630 2631 segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength); 2632 return doneFn(null, segment); 2633 } 2634 }; 2635 2636 decrypter.addEventListener('message', decryptionHandler); 2637 2638 // this is an encrypted segment 2639 // incrementally decrypt the segment 2640 decrypter.postMessage((0, _binUtils.createTransferableMessage)({ 2641 source: segment.requestId, 2642 encrypted: segment.encryptedBytes, 2643 key: segment.key.bytes, 2644 iv: segment.key.iv 2645 }), [segment.encryptedBytes.buffer, segment.key.bytes.buffer]); 2646}; 2647 2648/** 2649 * The purpose of this function is to get the most pertinent error from the 2650 * array of errors. 2651 * For instance if a timeout and two aborts occur, then the aborts were 2652 * likely triggered by the timeout so return that error object. 2653 */ 2654var getMostImportantError = function getMostImportantError(errors) { 2655 return errors.reduce(function (prev, err) { 2656 return err.code > prev.code ? err : prev; 2657 }); 2658}; 2659 2660/** 2661 * This function waits for all XHRs to finish (with either success or failure) 2662 * before continueing processing via it's callback. The function gathers errors 2663 * from each request into a single errors array so that the error status for 2664 * each request can be examined later. 2665 * 2666 * @param {Object} activeXhrs - an object that tracks all XHR requests 2667 * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines 2668 * @param {Function} doneFn - a callback that is executed after all resources have been 2669 * downloaded and any decryption completed 2670 */ 2671var waitForCompletion = function waitForCompletion(activeXhrs, decrypter, doneFn) { 2672 var errors = []; 2673 var count = 0; 2674 2675 return function (error, segment) { 2676 if (error) { 2677 // If there are errors, we have to abort any outstanding requests 2678 abortAll(activeXhrs); 2679 errors.push(error); 2680 } 2681 count += 1; 2682 2683 if (count === activeXhrs.length) { 2684 // Keep track of when *all* of the requests have completed 2685 segment.endOfAllRequests = Date.now(); 2686 2687 if (errors.length > 0) { 2688 var worstError = getMostImportantError(errors); 2689 2690 return doneFn(worstError, segment); 2691 } 2692 if (segment.encryptedBytes) { 2693 return decryptSegment(decrypter, segment, doneFn); 2694 } 2695 // Otherwise, everything is ready just continue 2696 return doneFn(null, segment); 2697 } 2698 }; 2699}; 2700 2701/** 2702 * Simple progress event callback handler that gathers some stats before 2703 * executing a provided callback with the `segment` object 2704 * 2705 * @param {Object} segment - a simplified copy of the segmentInfo object 2706 * from SegmentLoader 2707 * @param {Function} progressFn - a callback that is executed each time a progress event 2708 * is received 2709 * @param {Event} event - the progress event object from XMLHttpRequest 2710 */ 2711var handleProgress = function handleProgress(segment, progressFn) { 2712 return function (event) { 2713 segment.stats = _videoJs2['default'].mergeOptions(segment.stats, getProgressStats(event)); 2714 2715 // record the time that we receive the first byte of data 2716 if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) { 2717 segment.stats.firstBytesReceivedAt = Date.now(); 2718 } 2719 2720 return progressFn(event, segment); 2721 }; 2722}; 2723 2724/** 2725 * Load all resources and does any processing necessary for a media-segment 2726 * 2727 * Features: 2728 * decrypts the media-segment if it has a key uri and an iv 2729 * aborts *all* requests if *any* one request fails 2730 * 2731 * The segment object, at minimum, has the following format: 2732 * { 2733 * resolvedUri: String, 2734 * [byterange]: { 2735 * offset: Number, 2736 * length: Number 2737 * }, 2738 * [key]: { 2739 * resolvedUri: String 2740 * [byterange]: { 2741 * offset: Number, 2742 * length: Number 2743 * }, 2744 * iv: { 2745 * bytes: Uint32Array 2746 * } 2747 * }, 2748 * [map]: { 2749 * resolvedUri: String, 2750 * [byterange]: { 2751 * offset: Number, 2752 * length: Number 2753 * }, 2754 * [bytes]: Uint8Array 2755 * } 2756 * } 2757 * ...where [name] denotes optional properties 2758 * 2759 * @param {Function} xhr - an instance of the xhr wrapper in xhr.js 2760 * @param {Object} xhrOptions - the base options to provide to all xhr requests 2761 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 2762 * decryption routines 2763 * @param {Object} segment - a simplified copy of the segmentInfo object 2764 * from SegmentLoader 2765 * @param {Function} progressFn - a callback that receives progress events from the main 2766 * segment's xhr request 2767 * @param {Function} doneFn - a callback that is executed only once all requests have 2768 * succeeded or failed 2769 * @returns {Function} a function that, when invoked, immediately aborts all 2770 * outstanding requests 2771 */ 2772var mediaSegmentRequest = function mediaSegmentRequest(xhr, xhrOptions, decryptionWorker, segment, progressFn, doneFn) { 2773 var activeXhrs = [];
2774 var finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn); 2775 2776 // optionally, request the decryption key 2777 if (segment.key) { 2778 var keyRequestOptions = _videoJs2['default'].mergeOptions(xhrOptions, { 2779 uri: segment.key.resolvedUri, 2780 responseType: 'arraybuffer' 2781 }); 2782 var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn); 2783 var keyXhr = xhr(keyRequestOptions, keyRequestCallback); 2784 2785 activeXhrs.push(keyXhr); 2786 } 2787 2788 // optionally, request the associated media init segment 2789 if (segment.map && !segment.map.bytes) { 2790 var initSegmentOptions = _videoJs2['default'].mergeOptions(xhrOptions, { 2791 uri: segment.map.resolvedUri, 2792 responseType: 'arraybuffer', 2793 headers: segmentXhrHeaders(segment.map) 2794 }); 2795 var initSegmentRequestCallback = handleInitSegmentResponse(segment, finishProcessingFn); 2796 var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback); 2797 2798 activeXhrs.push(initSegmentXhr); 2799 } 2800 2801 var segmentRequestOptions = _videoJs2['default'].mergeOptions(xhrOptions, { 2802 uri: segment.resolvedUri, 2803 responseType: 'arraybuffer', 2804 headers: segmentXhrHeaders(segment) 2805 }); 2806 var segmentRequestCallback = handleSegmentResponse(segment, finishProcessingFn); 2807 var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback); 2808 2809 segmentXhr.addEventListener('progress', handleProgress(segment, progressFn)); 2810 activeXhrs.push(segmentXhr); 2811 2812 return function () { 2813 return abortAll(activeXhrs); 2814 }; 2815}; 2816exports.mediaSegmentRequest = mediaSegmentRequest; 2817}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 2818},{"./bin-utils":2}],8:[function(require,module,exports){ 2819(function (global){ 2820/** 2821 * @file playback-watcher.js 2822 * 2823 * Playback starts, and now my watch begins. It shall not end until my death. I shall 2824 * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns 2825 * and win no glory. I shall live and die at my post. I am the corrector of the underflow. 2826 * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge 2827 * my life and honor to the Playback Watch, for this Player and all the Players to come. 2828 */ 2829 2830'use strict'; 2831 2832Object.defineProperty(exports, '__esModule', { 2833 value: true 2834}); 2835 2836var _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; }; })(); 2837 2838function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 2839 2840function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 2841 2842var _globalWindow = require('global/window'); 2843 2844var _globalWindow2 = _interopRequireDefault(_globalWindow); 2845 2846var _ranges = require('./ranges'); 2847 2848var _ranges2 = _interopRequireDefault(_ranges); 2849 2850var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 2851 2852var _videoJs2 = _interopRequireDefault(_videoJs); 2853 2854// Set of events that reset the playback-watcher time check logic and clear the timeout 2855var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error']; 2856 2857/** 2858 * @class PlaybackWatcher 2859 */ 2860 2861var PlaybackWatcher = (function () { 2862 /** 2863 * Represents an PlaybackWatcher object. 2864 * @constructor 2865 * @param {object} options an object that includes the tech and settings 2866 */ 2867 2868 function PlaybackWatcher(options) { 2869 var _this = this; 2870 2871 _classCallCheck(this, PlaybackWatcher); 2872 2873 this.tech_ = options.tech; 2874 this.seekable = options.seekable; 2875 2876 this.consecutiveUpdates = 0; 2877 this.lastRecordedTime = null; 2878 this.timer_ = null; 2879 this.checkCurrentTimeTimeout_ = null; 2880 2881 if (options.debug) { 2882 this.logger_ = _videoJs2['default'].log.bind(_videoJs2['default'], 'playback-watcher ->'); 2883 } 2884 this.logger_('initialize'); 2885 2886 var canPlayHandler = function canPlayHandler() { 2887 return _this.monitorCurrentTime_(); 2888 }; 2889 var waitingHandler = function waitingHandler() { 2890 return _this.techWaiting_(); 2891 }; 2892 var cancelTimerHandler = function cancelTimerHandler() { 2893 return _this.cancelTimer_(); 2894 }; 2895 var fixesBadSeeksHandler = function fixesBadSeeksHandler() { 2896 return _this.fixesBadSeeks_(); 2897 }; 2898 2899 this.tech_.on('seekablechanged', fixesBadSeeksHandler); 2900 this.tech_.on('waiting', waitingHandler); 2901 this.tech_.on(timerCancelEvents, cancelTimerHandler); 2902 this.tech_.on('canplay', canPlayHandler); 2903 2904 // Define the dispose function to clean up our events 2905 this.dispose = function () { 2906 _this.logger_('dispose'); 2907 _this.tech_.off('seekablechanged', fixesBadSeeksHandler); 2908 _this.tech_.off('waiting', waitingHandler); 2909 _this.tech_.off(timerCancelEvents, cancelTimerHandler); 2910 _this.tech_.off('canplay', canPlayHandler); 2911 if (_this.checkCurrentTimeTimeout_) { 2912 _globalWindow2['default'].clearTimeout(_this.checkCurrentTimeTimeout_); 2913 } 2914 _this.cancelTimer_(); 2915 }; 2916 } 2917 2918 /** 2919 * Periodically check current time to see if playback stopped 2920 * 2921 * @private 2922 */ 2923 2924 _createClass(PlaybackWatcher, [{ 2925 key: 'monitorCurrentTime_', 2926 value: function monitorCurrentTime_() { 2927 this.checkCurrentTime_(); 2928 2929 if (this.checkCurrentTimeTimeout_) { 2930 _globalWindow2['default'].clearTimeout(this.checkCurrentTimeTimeout_); 2931 } 2932 2933 // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 2934 this.checkCurrentTimeTimeout_ = _globalWindow2['default'].setTimeout(this.monitorCurrentTime_.bind(this), 250); 2935 } 2936 2937 /** 2938 * The purpose of this function is to emulate the "waiting" event on 2939 * browsers that do not emit it when they are waiting for more 2940 * data to continue playback 2941 * 2942 * @private 2943 */ 2944 }, { 2945 key: 'checkCurrentTime_', 2946 value: function checkCurrentTime_() { 2947 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 2948 this.consecutiveUpdates = 0; 2949 this.lastRecordedTime = this.tech_.currentTime(); 2950 return; 2951 } 2952 2953 if (this.tech_.paused() || this.tech_.seeking()) { 2954 return; 2955 } 2956 2957 var currentTime = this.tech_.currentTime(); 2958 var buffered = this.tech_.buffered(); 2959 2960 if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + 0.1 >= buffered.end(buffered.length - 1))) { 2961 // If current time is at the end of the final buffered region, then any playback 2962 // stall is most likely caused by buffering in a low bandwidth environment. The tech 2963 // should fire a `waiting` event in this scenario, but due to browser and tech
vendor: 6,639 bytes, lines 2964-3140
2964 // inconsistencies (e.g. The Flash tech does not fire a `waiting` event when the end 2965 // of the buffer is reached and has fallen off the live window). Calling 2966 // `techWaiting_` here allows us to simulate responding to a native `waiting` event 2967 // when the tech fails to emit one. 2968 return this.techWaiting_(); 2969 } 2970 2971 if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) { 2972 this.consecutiveUpdates++; 2973 this.waiting_(); 2974 } else if (currentTime === this.lastRecordedTime) { 2975 this.consecutiveUpdates++; 2976 } else { 2977 this.consecutiveUpdates = 0; 2978 this.lastRecordedTime = currentTime; 2979 } 2980 } 2981 2982 /** 2983 * Cancels any pending timers and resets the 'timeupdate' mechanism 2984 * designed to detect that we are stalled 2985 * 2986 * @private 2987 */ 2988 }, { 2989 key: 'cancelTimer_', 2990 value: function cancelTimer_() { 2991 this.consecutiveUpdates = 0; 2992 2993 if (this.timer_) { 2994 this.logger_('cancelTimer_'); 2995 clearTimeout(this.timer_); 2996 } 2997 2998 this.timer_ = null; 2999 } 3000 3001 /** 3002 * Fixes situations where there's a bad seek 3003 * 3004 * @return {Boolean} whether an action was taken to fix the seek 3005 * @private 3006 */ 3007 }, { 3008 key: 'fixesBadSeeks_', 3009 value: function fixesBadSeeks_() { 3010 var seeking = this.tech_.seeking(); 3011 var seekable = this.seekable(); 3012 var currentTime = this.tech_.currentTime(); 3013 var seekTo = undefined; 3014 3015 if (seeking && this.afterSeekableWindow_(seekable, currentTime)) { 3016 var seekableEnd = seekable.end(seekable.length - 1); 3017 3018 // sync to live point (if VOD, our seekable was updated and we're simply adjusting) 3019 seekTo = seekableEnd; 3020 } 3021 3022 if (seeking && this.beforeSeekableWindow_(seekable, currentTime)) { 3023 var seekableStart = seekable.start(0); 3024 3025 // sync to the beginning of the live window 3026 // provide a buffer of .1 seconds to handle rounding/imprecise numbers 3027 seekTo = seekableStart + 0.1; 3028 } 3029 3030 if (typeof seekTo !== 'undefined') { 3031 this.logger_('Trying to seek outside of seekable at time ' + currentTime + ' with ' + ('seekable range ' + _ranges2['default'].printableRange(seekable) + '. Seeking to ') + (seekTo + '.')); 3032 3033 this.tech_.setCurrentTime(seekTo); 3034 return true; 3035 } 3036 3037 return false; 3038 } 3039 3040 /** 3041 * Handler for situations when we determine the player is waiting. 3042 * 3043 * @private 3044 */ 3045 }, { 3046 key: 'waiting_', 3047 value: function waiting_() { 3048 if (this.techWaiting_()) { 3049 return; 3050 } 3051 3052 // All tech waiting checks failed. Use last resort correction 3053 var currentTime = this.tech_.currentTime(); 3054 var buffered = this.tech_.buffered(); 3055 var currentRange = _ranges2['default'].findRange(buffered, currentTime); 3056 3057 // Sometimes the player can stall for unknown reasons within a contiguous buffered 3058 // region with no indication that anything is amiss (seen in Firefox). Seeking to 3059 // currentTime is usually enough to kickstart the player. This checks that the player 3060 // is currently within a buffered region before attempting a corrective seek. 3061 // Chrome does not appear to continue `timeupdate` events after a `waiting` event 3062 // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also 3063 // make sure there is ~3 seconds of forward buffer before taking any corrective action 3064 // to avoid triggering an `unknownwaiting` event when the network is slow. 3065 if (currentRange.length && currentTime + 3 <= currentRange.end(0)) { 3066 this.cancelTimer_(); 3067 this.tech_.setCurrentTime(currentTime); 3068 3069 this.logger_('Stopped at ' + currentTime + ' while inside a buffered region ' + ('[' + currentRange.start(0) + ' -> ' + currentRange.end(0) + ']. Attempting to resume ') + 'playback by seeking to the current time.'); 3070 3071 // unknown waiting corrections may be useful for monitoring QoS 3072 this.tech_.trigger({ type: 'usage', name: 'hls-unknown-waiting' }); 3073 return; 3074 } 3075 } 3076 3077 /** 3078 * Handler for situations when the tech fires a `waiting` event 3079 * 3080 * @return {Boolean} 3081 * True if an action (or none) was needed to correct the waiting. False if no 3082 * checks passed 3083 * @private 3084 */ 3085 }, { 3086 key: 'techWaiting_', 3087 value: function techWaiting_() { 3088 var seekable = this.seekable(); 3089 var currentTime = this.tech_.currentTime(); 3090 3091 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 3092 // Tech is seeking or bad seek fixed, no action needed 3093 return true; 3094 } 3095 3096 if (this.tech_.seeking() || this.timer_ !== null) { 3097 // Tech is seeking or already waiting on another action, no action needed 3098 return true; 3099 } 3100 3101 if (this.beforeSeekableWindow_(seekable, currentTime)) { 3102 var livePoint = seekable.end(seekable.length - 1); 3103 3104 this.logger_('Fell out of live window at time ' + currentTime + '. Seeking to ' + ('live point (seekable end) ' + livePoint)); 3105 this.cancelTimer_(); 3106 this.tech_.setCurrentTime(livePoint); 3107 3108 // live window resyncs may be useful for monitoring QoS 3109 this.tech_.trigger({ type: 'usage', name: 'hls-live-resync' }); 3110 return true; 3111 } 3112 3113 var buffered = this.tech_.buffered(); 3114 var nextRange = _ranges2['default'].findNextRange(buffered, currentTime); 3115 3116 if (this.videoUnderflow_(nextRange, buffered, currentTime)) { 3117 // Even though the video underflowed and was stuck in a gap, the audio overplayed 3118 // the gap, leading currentTime into a buffered range. Seeking to currentTime 3119 // allows the video to catch up to the audio position without losing any audio 3120 // (only suffering ~3 seconds of frozen video and a pause in audio playback). 3121 this.cancelTimer_(); 3122 this.tech_.setCurrentTime(currentTime); 3123 3124 // video underflow may be useful for monitoring QoS 3125 this.tech_.trigger({ type: 'usage', name: 'hls-video-underflow' }); 3126 return true; 3127 } 3128 3129 // check for gap 3130 if (nextRange.length > 0) { 3131 var difference = nextRange.start(0) - currentTime; 3132 3133 this.logger_('Stopped at ' + currentTime + ', setting timer for ' + difference + ', seeking ' + ('to ' + nextRange.start(0))); 3134 3135 this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime); 3136 return true; 3137 } 3138 3139 // All checks failed. Returning false to indicate failure to correct waiting 3140 return false;
3141 } 3142 }, { 3143 key: 'afterSeekableWindow_', 3144 value: function afterSeekableWindow_(seekable, currentTime) { 3145 if (!seekable.length) { 3146 // we can't make a solid case if there's no seekable, default to false 3147 return false; 3148 } 3149 3150 // provide a buffer of .1 seconds to handle rounding/imprecise numbers 3151 if (currentTime > seekable.end(seekable.length - 1) + 0.1) { 3152 return true; 3153 } 3154 3155 return false; 3156 } 3157 }, { 3158 key: 'beforeSeekableWindow_', 3159 value: function beforeSeekableWindow_(seekable, currentTime) { 3160 if (seekable.length && 3161 // can't fall before 0 and 0 seekable start identifies VOD stream 3162 seekable.start(0) > 0 && 3163 // provide a buffer of .1 seconds to handle rounding/imprecise numbers 3164 currentTime < seekable.start(0) - 0.1) { 3165 return true; 3166 } 3167 3168 return false; 3169 } 3170 }, { 3171 key: 'videoUnderflow_', 3172 value: function videoUnderflow_(nextRange, buffered, currentTime) { 3173 if (nextRange.length === 0) { 3174 // Even if there is no available next range, there is still a possibility we are 3175 // stuck in a gap due to video underflow. 3176 var gap = this.gapFromVideoUnderflow_(buffered, currentTime); 3177 3178 if (gap) { 3179 this.logger_('Encountered a gap in video from ' + gap.start + ' to ' + gap.end + '. ' + ('Seeking to current time ' + currentTime)); 3180 3181 return true; 3182 } 3183 } 3184 3185 return false; 3186 } 3187 3188 /** 3189 * Timer callback. If playback still has not proceeded, then we seek 3190 * to the start of the next buffered region. 3191 * 3192 * @private 3193 */ 3194 }, { 3195 key: 'skipTheGap_', 3196 value: function skipTheGap_(scheduledCurrentTime) { 3197 var buffered = this.tech_.buffered(); 3198 var currentTime = this.tech_.currentTime(); 3199 var nextRange = _ranges2['default'].findNextRange(buffered, currentTime); 3200 3201 this.cancelTimer_(); 3202 3203 if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) { 3204 return; 3205 } 3206 3207 this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); 3208 3209 // only seek if we still have not played 3210 this.tech_.setCurrentTime(nextRange.start(0) + _ranges2['default'].TIME_FUDGE_FACTOR); 3211 3212 this.tech_.trigger({ type: 'usage', name: 'hls-gap-skip' }); 3213 } 3214 }, { 3215 key: 'gapFromVideoUnderflow_', 3216 value: function gapFromVideoUnderflow_(buffered, currentTime) { 3217 // At least in Chrome, if there is a gap in the video buffer, the audio will continue 3218 // playing for ~3 seconds after the video gap starts. This is done to account for 3219 // video buffer underflow/underrun (note that this is not done when there is audio 3220 // buffer underflow/underrun -- in that case the video will stop as soon as it 3221 // encounters the gap, as audio stalls are more noticeable/jarring to a user than 3222 // video stalls). The player's time will reflect the playthrough of audio, so the 3223 // time will appear as if we are in a buffered region, even if we are stuck in a 3224 // "gap." 3225 // 3226 // Example: 3227 // video buffer: 0 => 10.1, 10.2 => 20 3228 // audio buffer: 0 => 20 3229 // overall buffer: 0 => 10.1, 10.2 => 20 3230 // current time: 13 3231 // 3232 // Chrome's video froze at 10 seconds, where the video buffer encountered the gap, 3233 // however, the audio continued playing until it reached ~3 seconds past the gap 3234 // (13 seconds), at which point it stops as well. Since current time is past the 3235 // gap, findNextRange will return no ranges. 3236 // 3237 // To check for this issue, we see if there is a gap that starts somewhere within 3238 // a 3 second range (3 seconds +/- 1 second) back from our current time. 3239 var gaps = _ranges2['default'].findGaps(buffered); 3240 3241 for (var i = 0; i < gaps.length; i++) { 3242 var start = gaps.start(i); 3243 var end = gaps.end(i); 3244 3245 // gap is starts no more than 4 seconds back 3246 if (currentTime - start < 4 && currentTime - start > 2) { 3247 return { 3248 start: start, 3249 end: end 3250 }; 3251 } 3252 } 3253 3254 return null; 3255 } 3256 3257 /** 3258 * A debugging logger noop that is set to console.log only if debugging 3259 * is enabled globally 3260 * 3261 * @private 3262 */ 3263 }, { 3264 key: 'logger_', 3265 value: function logger_() {} 3266 }]); 3267 3268 return PlaybackWatcher; 3269})(); 3270 3271exports['default'] = PlaybackWatcher; 3272module.exports = exports['default']; 3273}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 3274},{"./ranges":12,"global/window":32}],9:[function(require,module,exports){ 3275(function (global){ 3276/** 3277 * @file playlist-loader.js 3278 * 3279 * A state machine that manages the loading, caching, and updating of 3280 * M3U8 playlists. 3281 * 3282 */ 3283'use strict'; 3284 3285Object.defineProperty(exports, '__esModule', { 3286 value: true 3287}); 3288 3289function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 3290 3291var _resolveUrl = require('./resolve-url'); 3292 3293var _resolveUrl2 = _interopRequireDefault(_resolveUrl); 3294 3295var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 3296 3297var _playlistJs = require('./playlist.js'); 3298 3299var _m3u8Parser = require('m3u8-parser'); 3300 3301var _m3u8Parser2 = _interopRequireDefault(_m3u8Parser); 3302 3303var _globalWindow = require('global/window'); 3304 3305var _globalWindow2 = _interopRequireDefault(_globalWindow); 3306 3307/** 3308 * Returns a new array of segments that is the result of merging 3309 * properties from an older list of segments onto an updated 3310 * list. No properties on the updated playlist will be overridden. 3311 * 3312 * @param {Array} original the outdated list of segments 3313 * @param {Array} update the updated list of segments 3314 * @param {Number=} offset the index of the first update 3315 * segment in the original segment list. For non-live playlists, 3316 * this should always be zero and does not need to be 3317 * specified. For live playlists, it should be the difference 3318 * between the media sequence numbers in the original and updated 3319 * playlists. 3320 * @return a list of merged segment objects 3321 */ 3322var updateSegments = function updateSegments(original, update, offset) { 3323 var result = update.slice(); 3324 var length = undefined; 3325 var i = undefined; 3326 3327 offset = offset || 0; 3328 length = Math.min(original.length, update.length + offset); 3329 3330 for (i = offset; i < length; i++) { 3331 result[i - offset] = (0, _videoJs.mergeOptions)(original[i], result[i - offset]); 3332 } 3333 return result; 3334}; 3335 3336/** 3337 * Returns a new master playlist that is the result of merging an 3338 * updated media playlist into the original version. If the 3339 * updated media playlist does not match any of the playlist 3340 * entries in the original master playlist, null is returned. 3341 * 3342 * @param {Object} master a parsed master M3U8 object 3343 * @param {Object} media a parsed media M3U8 object 3344 * @return {Object} a new object that represents the original 3345 * master playlist with the updated media playlist merged in, or 3346 * null if the merge produced no change. 3347 */ 3348var updateMaster = function updateMaster(master, media) { 3349 var changed = false;
3350 var result = (0, _videoJs.mergeOptions)(master, {}); 3351 var i = master.playlists.length; 3352 var playlist = undefined; 3353 var segment = undefined; 3354 var j = undefined; 3355 3356 while (i--) { 3357 playlist = result.playlists[i]; 3358 if (playlist.uri === media.uri) { 3359 // consider the playlist unchanged if the number of segments 3360 // are equal and the media sequence number is unchanged 3361 if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.mediaSequence === media.mediaSequence) { 3362 continue; 3363 } 3364 3365 result.playlists[i] = (0, _videoJs.mergeOptions)(playlist, media); 3366 result.playlists[media.uri] = result.playlists[i]; 3367 3368 // if the update could overlap existing segment information, 3369 // merge the two lists 3370 if (playlist.segments) { 3371 result.playlists[i].segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence); 3372 } 3373 // resolve any missing segment and key URIs 3374 j = 0; 3375 if (result.playlists[i].segments) { 3376 j = result.playlists[i].segments.length; 3377 } 3378 while (j--) { 3379 segment = result.playlists[i].segments[j]; 3380 if (!segment.resolvedUri) { 3381 segment.resolvedUri = (0, _resolveUrl2['default'])(playlist.resolvedUri, segment.uri); 3382 } 3383 if (segment.key && !segment.key.resolvedUri) { 3384 segment.key.resolvedUri = (0, _resolveUrl2['default'])(playlist.resolvedUri, segment.key.uri); 3385 } 3386 if (segment.map && !segment.map.resolvedUri) { 3387 segment.map.resolvedUri = (0, _resolveUrl2['default'])(playlist.resolvedUri, segment.map.uri); 3388 } 3389 } 3390 changed = true; 3391 } 3392 } 3393 return changed ? result : null; 3394}; 3395 3396/** 3397 * Load a playlist from a remote location 3398 * 3399 * @class PlaylistLoader 3400 * @extends Stream 3401 * @param {String} srcUrl the url to start with 3402 * @param {Boolean} withCredentials the withCredentials xhr option 3403 * @constructor 3404 */ 3405var PlaylistLoader = function PlaylistLoader(srcUrl, hls, withCredentials) { 3406 var _this = this; 3407 3408 /* eslint-disable consistent-this */ 3409 var loader = this; 3410 /* eslint-enable consistent-this */ 3411 var mediaUpdateTimeout = undefined; 3412 var request = undefined; 3413 var playlistRequestError = undefined; 3414 var haveMetadata = undefined; 3415 3416 PlaylistLoader.prototype.constructor.call(this); 3417 3418 this.hls_ = hls; 3419 3420 if (!srcUrl) { 3421 throw new Error('A non-empty playlist URL is required'); 3422 } 3423 3424 playlistRequestError = function (xhr, url, startingState) { 3425 loader.setBandwidth(request || xhr); 3426 3427 // any in-flight request is now finished 3428 request = null; 3429 3430 if (startingState) { 3431 loader.state = startingState; 3432 } 3433 3434 loader.error = { 3435 playlist: loader.master.playlists[url], 3436 status: xhr.status, 3437 message: 'HLS playlist request error at URL: ' + url, 3438 responseText: xhr.responseText, 3439 code: xhr.status >= 500 ? 4 : 2 3440 }; 3441 3442 loader.trigger('error'); 3443 }; 3444 3445 // update the playlist loader's state in response to a new or 3446 // updated playlist. 3447 haveMetadata = function (xhr, url) { 3448 var parser = undefined; 3449 var refreshDelay = undefined; 3450 var update = undefined; 3451 3452 loader.setBandwidth(request || xhr); 3453 3454 // any in-flight request is now finished 3455 request = null; 3456 3457 loader.state = 'HAVE_METADATA'; 3458 3459 parser = new _m3u8Parser2['default'].Parser(); 3460 parser.push(xhr.responseText); 3461 parser.end(); 3462 parser.manifest.uri = url; 3463 // m3u8-parser does not attach an attributes property to media playlists so make 3464 // sure that the property is attached to avoid undefined reference errors 3465 parser.manifest.attributes = parser.manifest.attributes || {}; 3466 3467 // merge this playlist into the master 3468 update = updateMaster(loader.master, parser.manifest); 3469 refreshDelay = (parser.manifest.targetDuration || 10) * 1000; 3470 loader.targetDuration = parser.manifest.targetDuration; 3471 if (update) { 3472 loader.master = update; 3473 loader.media_ = loader.master.playlists[parser.manifest.uri]; 3474 } else { 3475 // if the playlist is unchanged since the last reload, 3476 // try again after half the target duration 3477 refreshDelay /= 2; 3478 loader.trigger('playlistunchanged'); 3479 } 3480 3481 // refresh live playlists after a target duration passes 3482 if (!loader.media().endList) { 3483 _globalWindow2['default'].clearTimeout(mediaUpdateTimeout); 3484 mediaUpdateTimeout = _globalWindow2['default'].setTimeout(function () { 3485 loader.trigger('mediaupdatetimeout'); 3486 }, refreshDelay); 3487 } 3488 3489 loader.trigger('loadedplaylist'); 3490 }; 3491 3492 // initialize the loader state 3493 loader.state = 'HAVE_NOTHING'; 3494 3495 /** 3496 * Abort any outstanding work and clean up. 3497 */ 3498 loader.dispose = function () { 3499 loader.stopRequest(); 3500 _globalWindow2['default'].clearTimeout(mediaUpdateTimeout); 3501 loader.off(); 3502 }; 3503 3504 loader.stopRequest = function () { 3505 if (request) { 3506 var oldRequest = request; 3507 3508 request = null; 3509 oldRequest.onreadystatechange = null; 3510 oldRequest.abort(); 3511 } 3512 }; 3513 3514 /** 3515 * Returns the number of enabled playlists on the master playlist object 3516 * 3517 * @return {Number} number of eneabled playlists 3518 */ 3519 loader.enabledPlaylists_ = function () { 3520 return loader.master.playlists.filter(_playlistJs.isEnabled).length; 3521 }; 3522 3523 /** 3524 * Returns whether the current playlist is the lowest rendition 3525 * 3526 * @return {Boolean} true if on lowest rendition 3527 */ 3528 loader.isLowestEnabledRendition_ = function () { 3529 if (loader.master.playlists.length === 1) { 3530 return true; 3531 } 3532 3533 var media = loader.media(); 3534 3535 var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE; 3536 3537 return loader.master.playlists.filter(function (playlist) { 3538 var enabled = (0, _playlistJs.isEnabled)(playlist); 3539 3540 if (!enabled) { 3541 return false;
3542 } 3543 3544 return (playlist.attributes.BANDWIDTH || 0) < currentBandwidth; 3545 }).length === 0; 3546 }; 3547 3548 /** 3549 * Returns whether the current playlist is the final available rendition 3550 * 3551 * @return {Boolean} true if on final rendition 3552 */ 3553 loader.isFinalRendition_ = function () { 3554 return loader.master.playlists.filter(_playlistJs.isEnabled).length === 1; 3555 }; 3556 3557 /** 3558 * When called without any arguments, returns the currently 3559 * active media playlist. When called with a single argument, 3560 * triggers the playlist loader to asynchronously switch to the 3561 * specified media playlist. Calling this method while the 3562 * loader is in the HAVE_NOTHING causes an error to be emitted 3563 * but otherwise has no effect. 3564 * 3565 * @param {Object=} playlist the parsed media playlist 3566 * object to switch to 3567 * @return {Playlist} the current loaded media 3568 */ 3569 loader.media = function (playlist) { 3570 var startingState = loader.state; 3571 var mediaChange = undefined; 3572 3573 // getter 3574 if (!playlist) { 3575 return loader.media_; 3576 } 3577 3578 // setter 3579 if (loader.state === 'HAVE_NOTHING') { 3580 throw new Error('Cannot switch media playlist from ' + loader.state); 3581 } 3582 3583 // find the playlist object if the target playlist has been 3584 // specified by URI 3585 if (typeof playlist === 'string') { 3586 if (!loader.master.playlists[playlist]) { 3587 throw new Error('Unknown playlist URI: ' + playlist); 3588 } 3589 playlist = loader.master.playlists[playlist]; 3590 } 3591 3592 mediaChange = !loader.media_ || playlist.uri !== loader.media_.uri; 3593 3594 // switch to fully loaded playlists immediately 3595 if (loader.master.playlists[playlist.uri].endList) { 3596 // abort outstanding playlist requests 3597 if (request) { 3598 request.onreadystatechange = null; 3599 request.abort(); 3600 request = null; 3601 } 3602 loader.state = 'HAVE_METADATA'; 3603 loader.media_ = playlist; 3604 3605 // trigger media change if the active media has been updated 3606 if (mediaChange) { 3607 loader.trigger('mediachanging'); 3608 loader.trigger('mediachange'); 3609 } 3610 return; 3611 } 3612 3613 // switching to the active playlist is a no-op 3614 if (!mediaChange) { 3615 return; 3616 } 3617 3618 loader.state = 'SWITCHING_MEDIA'; 3619 3620 // there is already an outstanding playlist request 3621 if (request) { 3622 if ((0, _resolveUrl2['default'])(loader.master.uri, playlist.uri) === request.url) { 3623 // requesting to switch to the same playlist multiple times 3624 // has no effect after the first 3625 return; 3626 } 3627 request.onreadystatechange = null; 3628 request.abort(); 3629 request = null; 3630 } 3631 3632 // request the new playlist 3633 if (this.media_) { 3634 this.trigger('mediachanging'); 3635 } 3636 3637 request = this.hls_.xhr({ 3638 uri: (0, _resolveUrl2['default'])(loader.master.uri, playlist.uri), 3639 withCredentials: withCredentials 3640 }, function (error, req) { 3641 // disposed 3642 if (!request) { 3643 return; 3644 } 3645 3646 if (error) { 3647 return playlistRequestError(request, playlist.uri, startingState); 3648 } 3649 3650 haveMetadata(req, playlist.uri); 3651 3652 // fire loadedmetadata the first time a media playlist is loaded 3653 if (startingState === 'HAVE_MASTER') { 3654 loader.trigger('loadedmetadata'); 3655 } else { 3656 loader.trigger('mediachange'); 3657 } 3658 }); 3659 }; 3660 3661 /** 3662 * set the bandwidth on an xhr to the bandwidth on the playlist 3663 */ 3664 loader.setBandwidth = function (xhr) { 3665 loader.bandwidth = xhr.bandwidth; 3666 }; 3667 3668 // live playlist staleness timeout 3669 loader.on('mediaupdatetimeout', function () { 3670 if (loader.state !== 'HAVE_METADATA') { 3671 // only refresh the media playlist if no other activity is going on 3672 return; 3673 } 3674 3675 loader.state = 'HAVE_CURRENT_METADATA'; 3676 3677 request = this.hls_.xhr({ 3678 uri: (0, _resolveUrl2['default'])(loader.master.uri, loader.media().uri), 3679 withCredentials: withCredentials 3680 }, function (error, req) { 3681 // disposed 3682 if (!request) { 3683 return; 3684 } 3685 3686 if (error) { 3687 return playlistRequestError(request, loader.media().uri, 'HAVE_METADATA'); 3688 } 3689 3690 haveMetadata(request, loader.media().uri); 3691 }); 3692 }); 3693 3694 /** 3695 * pause loading of the playlist 3696 */ 3697 loader.pause = function () { 3698 loader.stopRequest(); 3699 _globalWindow2['default'].clearTimeout(mediaUpdateTimeout); 3700 if (loader.state === 'HAVE_NOTHING') { 3701 // If we pause the loader before any data has been retrieved, its as if we never 3702 // started, so reset to an unstarted state. 3703 loader.started = false;
3704 } 3705 // Need to restore state now that no activity is happening 3706 if (loader.state === 'SWITCHING_MEDIA') { 3707 // if the loader was in the process of switching media, it should either return to 3708 // HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media 3709 // playlist yet. This is determined by the existence of loader.media_ 3710 if (loader.media_) { 3711 loader.state = 'HAVE_METADATA'; 3712 } else { 3713 loader.state = 'HAVE_MASTER'; 3714 } 3715 } else if (loader.state === 'HAVE_CURRENT_METADATA') { 3716 loader.state = 'HAVE_METADATA'; 3717 } 3718 }; 3719 3720 /** 3721 * start loading of the playlist 3722 */ 3723 loader.load = function (isFinalRendition) { 3724 var media = loader.media(); 3725 3726 _globalWindow2['default'].clearTimeout(mediaUpdateTimeout); 3727 3728 if (isFinalRendition) { 3729 var refreshDelay = media ? media.targetDuration / 2 * 1000 : 5 * 1000; 3730 3731 mediaUpdateTimeout = _globalWindow2['default'].setTimeout(loader.load.bind(null, false), refreshDelay); 3732 return; 3733 } 3734 3735 if (!loader.started) { 3736 loader.start(); 3737 return; 3738 } 3739 3740 if (media && !media.endList) { 3741 loader.trigger('mediaupdatetimeout'); 3742 } else { 3743 loader.trigger('loadedplaylist'); 3744 } 3745 }; 3746 3747 /** 3748 * start loading of the playlist 3749 */ 3750 loader.start = function () { 3751 loader.started = true; 3752 3753 // request the specified URL 3754 request = _this.hls_.xhr({ 3755 uri: srcUrl, 3756 withCredentials: withCredentials 3757 }, function (error, req) { 3758 var parser = undefined; 3759 var playlist = undefined; 3760 var i = undefined; 3761 3762 // disposed 3763 if (!request) { 3764 return; 3765 } 3766 3767 // clear the loader's request reference 3768 request = null; 3769 3770 if (error) { 3771 loader.error = { 3772 status: req.status, 3773 message: 'HLS playlist request error at URL: ' + srcUrl, 3774 responseText: req.responseText, 3775 // MEDIA_ERR_NETWORK 3776 code: 2 3777 }; 3778 if (loader.state === 'HAVE_NOTHING') { 3779 loader.started = false; 3780 } 3781 return loader.trigger('error'); 3782 } 3783 3784 parser = new _m3u8Parser2['default'].Parser(); 3785 parser.push(req.responseText); 3786 parser.end(); 3787 3788 loader.state = 'HAVE_MASTER'; 3789 3790 parser.manifest.uri = srcUrl; 3791 3792 // loaded a master playlist 3793 if (parser.manifest.playlists) { 3794 loader.master = parser.manifest; 3795 3796 // setup by-URI lookups and resolve media playlist URIs 3797 i = loader.master.playlists.length; 3798 while (i--) { 3799 playlist = loader.master.playlists[i]; 3800 loader.master.playlists[playlist.uri] = playlist; 3801 playlist.resolvedUri = (0, _resolveUrl2['default'])(loader.master.uri, playlist.uri); 3802 3803 if (!playlist.attributes) { 3804 // Although the spec states an #EXT-X-STREAM-INF tag MUST have a 3805 // BANDWIDTH attribute, we can play the stream without it. This means a poorly 3806 // formatted master playlist may not have an attribute list. An attributes 3807 // property is added here to prevent undefined references when we encounter 3808 // this scenario. 3809 playlist.attributes = {}; 3810 3811 _videoJs.log.warn('Invalid playlist STREAM-INF detected. Missing BANDWIDTH attribute.'); 3812 } 3813 } 3814 3815 // resolve any media group URIs 3816 ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) { 3817 for (var groupKey in loader.master.mediaGroups[mediaType]) { 3818 for (var labelKey in loader.master.mediaGroups[mediaType][groupKey]) { 3819 var mediaProperties = loader.master.mediaGroups[mediaType][groupKey][labelKey]; 3820 3821 if (mediaProperties.uri) { 3822 mediaProperties.resolvedUri = (0, _resolveUrl2['default'])(loader.master.uri, mediaProperties.uri); 3823 } 3824 } 3825 } 3826 }); 3827 3828 loader.trigger('loadedplaylist'); 3829 if (!request) { 3830 // no media playlist was specifically selected so start 3831 // from the first listed one 3832 loader.media(parser.manifest.playlists[0]); 3833 } 3834 return; 3835 } 3836 3837 // loaded a media playlist 3838 // infer a master playlist if none was previously requested 3839 loader.master = { 3840 mediaGroups: { 3841 'AUDIO': {}, 3842 'VIDEO': {}, 3843 'CLOSED-CAPTIONS': {}, 3844 'SUBTITLES': {} 3845 }, 3846 uri: _globalWindow2['default'].location.href, 3847 playlists: [{ 3848 uri: srcUrl 3849 }] 3850 }; 3851 loader.master.playlists[srcUrl] = loader.master.playlists[0]; 3852 loader.master.playlists[0].resolvedUri = srcUrl; 3853 // m3u8-parser does not attach an attributes property to media playlists so make 3854 // sure that the property is attached to avoid undefined reference errors 3855 loader.master.playlists[0].attributes = loader.master.playlists[0].attributes || {}; 3856 haveMetadata(req, srcUrl); 3857 return loader.trigger('loadedmetadata'); 3858 }); 3859 }; 3860}; 3861 3862PlaylistLoader.prototype = new _videoJs.EventTarget(); 3863 3864exports['default'] = PlaylistLoader; 3865module.exports = exports['default']; 3866}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 3867},{"./playlist.js":11,"./resolve-url":15,"global/window":32,"m3u8-parser":33}],10:[function(require,module,exports){ 3868'use strict'; 3869 3870Object.defineProperty(exports, '__esModule', { 3871 value: true 3872}); 3873 3874function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 3875 3876var _config = require('./config'); 3877 3878var _config2 = _interopRequireDefault(_config); 3879 3880var _playlist = require('./playlist'); 3881 3882var _playlist2 = _interopRequireDefault(_playlist); 3883 3884var _utilCodecsJs = require('./util/codecs.js'); 3885 3886// Utilities 3887 3888/** 3889 * Returns the CSS value for the specified property on an element 3890 * using `getComputedStyle`. Firefox has a long-standing issue where 3891 * getComputedStyle() may return null when running in an iframe with 3892 * `display: none`. 3893 * 3894 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 3895 * @param {HTMLElement} el the htmlelement to work on 3896 * @param {string} the proprety to get the style for 3897 */ 3898var safeGetComputedStyle = function safeGetComputedStyle(el, property) { 3899 var result = undefined; 3900 3901 if (!el) { 3902 return ''; 3903 } 3904 3905 result = window.getComputedStyle(el); 3906 if (!result) { 3907 return ''; 3908 } 3909 3910 return result[property]; 3911}; 3912 3913/** 3914 * Resuable stable sort function 3915 * 3916 * @param {Playlists} array 3917 * @param {Function}
3917 sortFn Different comparators 3918 * @function stableSort 3919 */ 3920var stableSort = function stableSort(array, sortFn) { 3921 var newArray = array.slice(); 3922 3923 array.sort(function (left, right) { 3924 var cmp = sortFn(left, right); 3925 3926 if (cmp === 0) { 3927 return newArray.indexOf(left) - newArray.indexOf(right); 3928 } 3929 return cmp; 3930 }); 3931}; 3932 3933/** 3934 * A comparator function to sort two playlist object by bandwidth. 3935 * 3936 * @param {Object} left a media playlist object 3937 * @param {Object} right a media playlist object 3938 * @return {Number} Greater than zero if the bandwidth attribute of 3939 * left is greater than the corresponding attribute of right. Less 3940 * than zero if the bandwidth of right is greater than left and 3941 * exactly zero if the two are equal. 3942 */ 3943var comparePlaylistBandwidth = function comparePlaylistBandwidth(left, right) { 3944 var leftBandwidth = undefined; 3945 var rightBandwidth = undefined; 3946 3947 if (left.attributes.BANDWIDTH) { 3948 leftBandwidth = left.attributes.BANDWIDTH; 3949 } 3950 leftBandwidth = leftBandwidth || window.Number.MAX_VALUE; 3951 if (right.attributes.BANDWIDTH) { 3952 rightBandwidth = right.attributes.BANDWIDTH; 3953 } 3954 rightBandwidth = rightBandwidth || window.Number.MAX_VALUE; 3955 3956 return leftBandwidth - rightBandwidth; 3957}; 3958 3959exports.comparePlaylistBandwidth = comparePlaylistBandwidth; 3960/** 3961 * A comparator function to sort two playlist object by resolution (width). 3962 * @param {Object} left a media playlist object 3963 * @param {Object} right a media playlist object 3964 * @return {Number} Greater than zero if the resolution.width attribute of 3965 * left is greater than the corresponding attribute of right. Less 3966 * than zero if the resolution.width of right is greater than left and 3967 * exactly zero if the two are equal. 3968 */ 3969var comparePlaylistResolution = function comparePlaylistResolution(left, right) { 3970 var leftWidth = undefined; 3971 var rightWidth = undefined; 3972 3973 if (left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) { 3974 leftWidth = left.attributes.RESOLUTION.width; 3975 } 3976 3977 leftWidth = leftWidth || window.Number.MAX_VALUE; 3978 3979 if (right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) { 3980 rightWidth = right.attributes.RESOLUTION.width; 3981 } 3982 3983 rightWidth = rightWidth || window.Number.MAX_VALUE; 3984 3985 // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions 3986 // have the same media dimensions/ resolution 3987 if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) { 3988 return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH; 3989 } 3990 return leftWidth - rightWidth; 3991}; 3992 3993exports.comparePlaylistResolution = comparePlaylistResolution; 3994/** 3995 * Chooses the appropriate media playlist based on bandwidth and player size 3996 * 3997 * @param {Object} master 3998 * Object representation of the master manifest 3999 * @param {Number} playerBandwidth 4000 * Current calculated bandwidth of the player 4001 * @param {Number} playerWidth 4002 * Current width of the player element 4003 * @param {Number} playerHeight 4004 * Current height of the player element 4005 * @return {Playlist} the highest bitrate playlist less than the 4006 * currently detected bandwidth, accounting for some amount of 4007 * bandwidth variance 4008 */ 4009var simpleSelector = function simpleSelector(master, playerBandwidth, playerWidth, playerHeight) { 4010 // convert the playlists to an intermediary representation to make comparisons easier 4011 var sortedPlaylistReps = master.playlists.map(function (playlist) { 4012 var width = undefined; 4013 var height = undefined; 4014 var bandwidth = undefined; 4015 4016 width = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.width; 4017 height = playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height; 4018 bandwidth = playlist.attributes.BANDWIDTH; 4019 4020 bandwidth = bandwidth || window.Number.MAX_VALUE; 4021 4022 return { 4023 bandwidth: bandwidth, 4024 width: width, 4025 height: height, 4026 playlist: playlist 4027 }; 4028 }); 4029 4030 stableSort(sortedPlaylistReps, function (left, right) { 4031 return left.bandwidth - right.bandwidth; 4032 }); 4033 4034 // filter out any playlists that have been excluded due to 4035 // incompatible configurations or playback errors 4036 sortedPlaylistReps = sortedPlaylistReps.filter(function (rep) { 4037 return _playlist2['default'].isEnabled(rep.playlist); 4038 }); 4039 4040 // filter out any variant that has greater effective bitrate 4041 // than the current estimated bandwidth 4042 var bandwidthPlaylistReps = sortedPlaylistReps.filter(function (rep) { 4043 return rep.bandwidth * _config2['default'].BANDWIDTH_VARIANCE < playerBandwidth; 4044 }); 4045 4046 var highestRemainingBandwidthRep = bandwidthPlaylistReps[bandwidthPlaylistReps.length - 1]; 4047 4048 // get all of the renditions with the same (highest) bandwidth 4049 // and then taking the very first element 4050 var bandwidthBestRep = bandwidthPlaylistReps.filter(function (rep) { 4051 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 4052 })[0]; 4053 4054 // filter out playlists without resolution information 4055 var haveResolution = bandwidthPlaylistReps.filter(function (rep) { 4056 return rep.width && rep.height; 4057 }); 4058 4059 // sort variants by resolution 4060 stableSort(haveResolution, function (left, right) { 4061 return left.width - right.width; 4062 }); 4063 4064 // if we have the exact resolution as the player use it 4065 var resolutionBestRepList = haveResolution.filter(function (rep) {
4066 return rep.width === playerWidth && rep.height === playerHeight; 4067 }); 4068 4069 highestRemainingBandwidthRep = resolutionBestRepList[resolutionBestRepList.length - 1]; 4070 // ensure that we pick the highest bandwidth variant that have exact resolution 4071 var resolutionBestRep = resolutionBestRepList.filter(function (rep) { 4072 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 4073 })[0]; 4074 4075 var resolutionPlusOneList = undefined; 4076 var resolutionPlusOneSmallest = undefined; 4077 var resolutionPlusOneRep = undefined; 4078 4079 // find the smallest variant that is larger than the player 4080 // if there is no match of exact resolution 4081 if (!resolutionBestRep) { 4082 resolutionPlusOneList = haveResolution.filter(function (rep) { 4083 return rep.width > playerWidth || rep.height > playerHeight; 4084 }); 4085 4086 // find all the variants have the same smallest resolution 4087 resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (rep) { 4088 return rep.width === resolutionPlusOneList[0].width && rep.height === resolutionPlusOneList[0].height; 4089 }); 4090 4091 // ensure that we also pick the highest bandwidth variant that 4092 // is just-larger-than the video player 4093 highestRemainingBandwidthRep = resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1]; 4094 resolutionPlusOneRep = resolutionPlusOneSmallest.filter(function (rep) { 4095 return rep.bandwidth === highestRemainingBandwidthRep.bandwidth; 4096 })[0]; 4097 } 4098 4099 // fallback chain of variants 4100 return (resolutionPlusOneRep || resolutionBestRep || bandwidthBestRep || sortedPlaylistReps[0]).playlist; 4101}; 4102 4103exports.simpleSelector = simpleSelector; 4104// Playlist Selectors 4105 4106/** 4107 * Chooses the appropriate media playlist based on the most recent 4108 * bandwidth estimate and the player size. 4109 * 4110 * Expects to be called within the context of an instance of HlsHandler 4111 * 4112 * @return {Playlist} the highest bitrate playlist less than the 4113 * currently detected bandwidth, accounting for some amount of 4114 * bandwidth variance 4115 */ 4116var lastBandwidthSelector = function lastBandwidthSelector() { 4117 return simpleSelector(this.playlists.master, this.systemBandwidth, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10)); 4118}; 4119 4120exports.lastBandwidthSelector = lastBandwidthSelector; 4121/** 4122 * Chooses the appropriate media playlist based on an 4123 * exponential-weighted moving average of the bandwidth after 4124 * filtering for player size. 4125 * 4126 * Expects to be called within the context of an instance of HlsHandler 4127 * 4128 * @param {Number} decay - a number between 0 and 1. Higher values of 4129 * this parameter will cause previous bandwidth estimates to lose 4130 * significance more quickly. 4131 * @return {Function} a function which can be invoked to create a new 4132 * playlist selector function. 4133 * @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average 4134 */ 4135var movingAverageBandwidthSelector = function movingAverageBandwidthSelector(decay) { 4136 var average = -1; 4137 4138 if (decay < 0 || decay > 1) { 4139 throw new Error('Moving average bandwidth decay must be between 0 and 1.'); 4140 } 4141 4142 return function () { 4143 if (average < 0) { 4144 average = this.systemBandwidth; 4145 } 4146 4147 average = decay * this.systemBandwidth + (1 - decay) * average; 4148 return simpleSelector(this.playlists.master, average, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10), parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10)); 4149 }; 4150}; 4151 4152exports.movingAverageBandwidthSelector = movingAverageBandwidthSelector; 4153/** 4154 * Chooses the appropriate media playlist based on the potential to rebuffer 4155 * 4156 * @param {Object} settings 4157 * Object of information required to use this selector 4158 * @param {Object} settings.master 4159 * Object representation of the master manifest 4160 * @param {Number} settings.currentTime 4161 * The current time of the player 4162 * @param {Number} settings.bandwidth 4163 * Current measured bandwidth 4164 * @param {Number} settings.duration 4165 * Duration of the media 4166 * @param {Number} settings.segmentDuration 4167 * Segment duration to be used in round trip time calculations 4168 * @param {Number} settings.timeUntilRebuffer 4169 * Time left in seconds until the player has to rebuffer 4170 * @param {Number} settings.currentTimeline 4171 * The current timeline segments are being loaded from 4172 * @param {SyncController} settings.syncController 4173 * SyncController for determining if we have a sync point for a given playlist 4174 * @return {Object|null} 4175 * {Object} return.playlist 4176 * The highest bandwidth playlist with the least amount of rebuffering 4177 * {Number} return.rebufferingImpact 4178 * The amount of time in seconds switching to this playlist will rebuffer. A 4179 * negative value means that switching will cause zero rebuffering. 4180 */ 4181var minRebufferMaxBandwidthSelector = function minRebufferMaxBandwidthSelector(settings) { 4182 var master = settings.master; 4183 var currentTime = settings.currentTime; 4184 var bandwidth = settings.bandwidth; 4185 var duration = settings.duration; 4186 var segmentDuration = settings.segmentDuration; 4187 var timeUntilRebuffer = settings.timeUntilRebuffer; 4188 var currentTimeline = settings.currentTimeline; 4189 var syncController = settings.syncController; 4190 4191 var bandwidthPlaylists = master.playlists.filter(function (playlist) { 4192 return _playlist2['default'].isEnabled(playlist) && _playlist2['default'].hasAttribute('BANDWIDTH', playlist); 4193 }); 4194 4195 var rebufferingEstimates = bandwidthPlaylists.map(function (playlist) { 4196 var syncPoint = syncController.getSyncPoint(playlist, duration, currentTimeline, currentTime); 4197 // If there is no sync point for this playlist, switching to it will require a 4198 // sync request first. This will double the request time 4199 var numRequests = syncPoint ? 1 : 2; 4200 var requestTimeEstimate = _playlist2['default'].estimateSegmentRequestTime(segmentDuration, bandwidth, playlist); 4201 var rebufferingImpact = requestTimeEstimate * numRequests - timeUntilRebuffer; 4202 4203 return { 4204 playlist: playlist, 4205 rebufferingImpact: rebufferingImpact 4206 }; 4207 }); 4208 4209 var noRebufferingPlaylists = rebufferingEstimates.filter(function (estimate) { 4210 return estimate.rebufferingImpact <= 0; 4211 }); 4212 4213 // Sort by bandwidth DESC 4214 stableSort(noRebufferingPlaylists, function (a, b) { 4215 return comparePlaylistBandwidth(b.playlist, a.playlist); 4216 }); 4217 4218 if (noRebufferingPlaylists.length) { 4219 return noRebufferingPlaylists[0]; 4220 } 4221 4222 stableSort(rebufferingEstimates, function (a, b) { 4223 return a.rebufferingImpact - b.rebufferingImpact; 4224 }); 4225 4226 return rebufferingEstimates[0] || null; 4227}; 4228 4229exports.minRebufferMaxBandwidthSelector = minRebufferMaxBandwidthSelector; 4230/** 4231 * Chooses the appropriate media playlist, which in this case is the lowest bitrate 4232 * one with video. If no renditions with video exist, return the lowest audio rendition. 4233 * 4234 * Expects to be called within the context of an instance of HlsHandler 4235 * 4236 * @return {Object|null} 4237 * {Object} return.playlist 4238 * The lowest bitrate playlist that contains a video codec. If no such rendition 4239 * exists pick the lowest audio rendition. 4240 */ 4241var lowestBitrateCompatibleVariantSelector = function lowestBitrateCompatibleVariantSelector() {
4242 // filter out any playlists that have been excluded due to 4243 // incompatible configurations or playback errors 4244 var playlists = this.playlists.master.playlists.filter(_playlist2['default'].isEnabled); 4245 4246 // Sort ascending by bitrate 4247 stableSort(playlists, function (a, b) { 4248 return comparePlaylistBandwidth(a, b); 4249 }); 4250 4251 // Parse and assume that playlists with no video codec have no video 4252 // (this is not necessarily true, although it is generally true). 4253 // 4254 // If an entire manifest has no valid videos everything will get filtered 4255 // out. 4256 var playlistsWithVideo = playlists.filter(function (playlist) { 4257 return (0, _utilCodecsJs.parseCodecs)(playlist.attributes.CODECS).videoCodec; 4258 }); 4259 4260 return playlistsWithVideo[0] || null; 4261}; 4262exports.lowestBitrateCompatibleVariantSelector = lowestBitrateCompatibleVariantSelector; 4263},{"./config":3,"./playlist":11,"./util/codecs.js":19}],11:[function(require,module,exports){ 4264(function (global){ 4265/** 4266 * @file playlist.js 4267 * 4268 * Playlist related utilities. 4269 */ 4270'use strict'; 4271 4272Object.defineProperty(exports, '__esModule', { 4273 value: true 4274}); 4275 4276function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 4277 4278var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 4279 4280var _globalWindow = require('global/window'); 4281 4282var _globalWindow2 = _interopRequireDefault(_globalWindow); 4283 4284var Playlist = { 4285 /** 4286 * The number of segments that are unsafe to start playback at in 4287 * a live stream. Changing this value can cause playback stalls. 4288 * See HTTP Live Streaming, "Playing the Media Playlist File" 4289 * https://tools.ietf.org/html/draft-pantos-http-live-streaming-18#section-6.3.3 4290 */ 4291 UNSAFE_LIVE_SEGMENTS: 3 4292}; 4293 4294/** 4295 * walk backward until we find a duration we can use 4296 * or return a failure 4297 * 4298 * @param {Playlist} playlist the playlist to walk through 4299 * @param {Number} endSequence the mediaSequence to stop walking on 4300 */ 4301 4302var backwardDuration = function backwardDuration(playlist, endSequence) { 4303 var result = 0; 4304 var i = endSequence - playlist.mediaSequence; 4305 // if a start time is available for segment immediately following 4306 // the interval, use it 4307 var segment = playlist.segments[i]; 4308 4309 // Walk backward until we find the latest segment with timeline 4310 // information that is earlier than endSequence 4311 if (segment) { 4312 if (typeof segment.start !== 'undefined') { 4313 return { result: segment.start, precise: true }; 4314 } 4315 if (typeof segment.end !== 'undefined') { 4316 return { 4317 result: segment.end - segment.duration, 4318 precise: true 4319 }; 4320 } 4321 } 4322 while (i--) { 4323 segment = playlist.segments[i]; 4324 if (typeof segment.end !== 'undefined') { 4325 return { result: result + segment.end, precise: true }; 4326 } 4327 4328 result += segment.duration; 4329 4330 if (typeof segment.start !== 'undefined') { 4331 return { result: result + segment.start, precise: true }; 4332 } 4333 } 4334 return { result: result, precise: false }; 4335}; 4336 4337/** 4338 * walk forward until we find a duration we can use 4339 * or return a failure 4340 * 4341 * @param {Playlist} playlist the playlist to walk through 4342 * @param {Number} endSequence the mediaSequence to stop walking on 4343 */ 4344var forwardDuration = function forwardDuration(playlist, endSequence) { 4345 var result = 0; 4346 var segment = undefined; 4347 var i = endSequence - playlist.mediaSequence; 4348 // Walk forward until we find the earliest segment with timeline 4349 // information 4350 4351 for (; i < playlist.segments.length; i++) { 4352 segment = playlist.segments[i]; 4353 if (typeof segment.start !== 'undefined') { 4354 return { 4355 result: segment.start - result, 4356 precise: true 4357 }; 4358 } 4359 4360 result += segment.duration; 4361 4362 if (typeof segment.end !== 'undefined') { 4363 return { 4364 result: segment.end - result, 4365 precise: true 4366 }; 4367 } 4368 } 4369 // indicate we didn't find a useful duration estimate 4370 return { result: -1, precise: false }; 4371}; 4372 4373/** 4374 * Calculate the media duration from the segments associated with a 4375 * playlist. The duration of a subinterval of the available segments 4376 * may be calculated by specifying an end index. 4377 * 4378 * @param {Object} playlist a media playlist object 4379 * @param {Number=} endSequence an exclusive upper boundary 4380 * for the playlist. Defaults to playlist length. 4381 * @param {Number} expired the amount of time that has dropped 4382 * off the front of the playlist in a live scenario 4383 * @return {Number} the duration between the first available segment 4384 * and end index. 4385 */ 4386var intervalDuration = function intervalDuration(playlist, endSequence, expired) { 4387 var backward = undefined; 4388 var forward = undefined; 4389 4390 if (typeof endSequence === 'undefined') { 4391 endSequence = playlist.mediaSequence + playlist.segments.length; 4392 } 4393 4394 if (endSequence < playlist.mediaSequence) { 4395 return 0; 4396 } 4397
4398 // do a backward walk to estimate the duration 4399 backward = backwardDuration(playlist, endSequence); 4400 if (backward.precise) { 4401 // if we were able to base our duration estimate on timing 4402 // information provided directly from the Media Source, return 4403 // it 4404 return backward.result; 4405 } 4406 4407 // walk forward to see if a precise duration estimate can be made 4408 // that way 4409 forward = forwardDuration(playlist, endSequence); 4410 if (forward.precise) { 4411 // we found a segment that has been buffered and so it's 4412 // position is known precisely 4413 return forward.result; 4414 } 4415 4416 // return the less-precise, playlist-based duration estimate 4417 return backward.result + expired; 4418}; 4419 4420/** 4421 * Calculates the duration of a playlist. If a start and end index 4422 * are specified, the duration will be for the subset of the media 4423 * timeline between those two indices. The total duration for live 4424 * playlists is always Infinity. 4425 * 4426 * @param {Object} playlist a media playlist object 4427 * @param {Number=} endSequence an exclusive upper 4428 * boundary for the playlist. Defaults to the playlist media 4429 * sequence number plus its length. 4430 * @param {Number=} expired the amount of time that has 4431 * dropped off the front of the playlist in a live scenario 4432 * @return {Number} the duration between the start index and end 4433 * index. 4434 */ 4435var duration = function duration(playlist, endSequence, expired) { 4436 if (!playlist) { 4437 return 0; 4438 } 4439 4440 if (typeof expired !== 'number') { 4441 expired = 0; 4442 } 4443 4444 // if a slice of the total duration is not requested, use 4445 // playlist-level duration indicators when they're present 4446 if (typeof endSequence === 'undefined') { 4447 // if present, use the duration specified in the playlist 4448 if (playlist.totalDuration) { 4449 return playlist.totalDuration; 4450 } 4451 4452 // duration should be Infinity for live playlists 4453 if (!playlist.endList) { 4454 return _globalWindow2['default'].Infinity; 4455 } 4456 } 4457 4458 // calculate the total duration based on the segment durations 4459 return intervalDuration(playlist, endSequence, expired); 4460}; 4461 4462exports.duration = duration; 4463/** 4464 * Calculate the time between two indexes in the current playlist 4465 * neight the start- nor the end-index need to be within the current 4466 * playlist in which case, the targetDuration of the playlist is used 4467 * to approximate the durations of the segments 4468 * 4469 * @param {Object} playlist a media playlist object 4470 * @param {Number} startIndex 4471 * @param {Number} endIndex 4472 * @return {Number} the number of seconds between startIndex and endIndex 4473 */ 4474var sumDurations = function sumDurations(playlist, startIndex, endIndex) { 4475 var durations = 0; 4476 4477 if (startIndex > endIndex) { 4478 var _ref = [endIndex, startIndex]; 4479 startIndex = _ref[0]; 4480 endIndex = _ref[1]; 4481 } 4482 4483 if (startIndex < 0) { 4484 for (var i = startIndex; i < Math.min(0, endIndex); i++) { 4485 durations += playlist.targetDuration; 4486 } 4487 startIndex = 0; 4488 } 4489 4490 for (var i = startIndex; i < endIndex; i++) { 4491 durations += playlist.segments[i].duration; 4492 } 4493 4494 return durations; 4495}; 4496 4497exports.sumDurations = sumDurations; 4498/** 4499 * Calculates the playlist end time 4500 * 4501 * @param {Object} playlist a media playlist object 4502 * @param {Number=} expired the amount of time that has 4503 * dropped off the front of the playlist in a live scenario 4504 * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the 4505 * playlist end calculation should consider the safe live end 4506 * (truncate the playlist end by three segments). This is normally 4507 * used for calculating the end of the playlist's seekable range. 4508 * @returns {Number} the end time of playlist 4509 * @function playlistEnd 4510 */ 4511var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) { 4512 if (!playlist || !playlist.segments) { 4513 return null; 4514 } 4515 if (playlist.endList) { 4516 return duration(playlist); 4517 } 4518 4519 if (expired === null) { 4520 return null; 4521 } 4522 4523 expired = expired || 0; 4524 4525 var endSequence = useSafeLiveEnd ? Math.max(0, playlist.segments.length - Playlist.UNSAFE_LIVE_SEGMENTS) : Math.max(0, playlist.segments.length); 4526 4527 return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired); 4528}; 4529 4530exports.playlistEnd = playlistEnd; 4531/** 4532 * Calculates the interval of time that is currently seekable in a 4533 * playlist. The returned time ranges are relative to the earliest 4534 * moment in the specified playlist that is still available. A full 4535 * seekable implementation for live streams would need to offset 4536 * these values by the duration of content that has expired from the 4537 * stream. 4538 * 4539 * @param {Object} playlist a media playlist object 4540 * dropped off the front of the playlist in a live scenario 4541 * @param {Number=} expired the amount of time that has 4542 * dropped off the front of the playlist in a live scenario 4543 * @return {TimeRanges}
4543 the periods of time that are valid targets 4544 * for seeking 4545 */ 4546var seekable = function seekable(playlist, expired) { 4547 var useSafeLiveEnd = true; 4548 var seekableStart = expired || 0; 4549 var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd); 4550 4551 if (seekableEnd === null) { 4552 return (0, _videoJs.createTimeRange)(); 4553 } 4554 return (0, _videoJs.createTimeRange)(seekableStart, seekableEnd); 4555}; 4556 4557exports.seekable = seekable; 4558var isWholeNumber = function isWholeNumber(num) { 4559 return num - Math.floor(num) === 0; 4560}; 4561 4562var roundSignificantDigit = function roundSignificantDigit(increment, num) { 4563 // If we have a whole number, just add 1 to it 4564 if (isWholeNumber(num)) { 4565 return num + increment * 0.1; 4566 } 4567 4568 var numDecimalDigits = num.toString().split('.')[1].length; 4569 4570 for (var i = 1; i <= numDecimalDigits; i++) { 4571 var scale = Math.pow(10, i); 4572 var temp = num * scale; 4573 4574 if (isWholeNumber(temp) || i === numDecimalDigits) { 4575 return (temp + increment) / scale; 4576 } 4577 } 4578}; 4579 4580var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1); 4581var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1); 4582 4583/** 4584 * Determine the index and estimated starting time of the segment that 4585 * contains a specified playback position in a media playlist. 4586 * 4587 * @param {Object} playlist the media playlist to query 4588 * @param {Number} currentTime The number of seconds since the earliest 4589 * possible position to determine the containing segment for 4590 * @param {Number} startIndex 4591 * @param {Number} startTime 4592 * @return {Object} 4593 */ 4594var getMediaInfoForTime = function getMediaInfoForTime(playlist, currentTime, startIndex, startTime) { 4595 var i = undefined; 4596 var segment = undefined; 4597 var numSegments = playlist.segments.length; 4598 4599 var time = currentTime - startTime; 4600 4601 if (time < 0) { 4602 // Walk backward from startIndex in the playlist, adding durations 4603 // until we find a segment that contains `time` and return it 4604 if (startIndex > 0) { 4605 for (i = startIndex - 1; i >= 0; i--) { 4606 segment = playlist.segments[i]; 4607 time += floorLeastSignificantDigit(segment.duration); 4608 if (time > 0) { 4609 return { 4610 mediaIndex: i, 4611 startTime: startTime - sumDurations(playlist, startIndex, i) 4612 }; 4613 } 4614 } 4615 } 4616 // We were unable to find a good segment within the playlist 4617 // so select the first segment 4618 return { 4619 mediaIndex: 0, 4620 startTime: currentTime 4621 }; 4622 } 4623 4624 // When startIndex is negative, we first walk forward to first segment 4625 // adding target durations. If we "run out of time" before getting to 4626 // the first segment, return the first segment 4627 if (startIndex < 0) { 4628 for (i = startIndex; i < 0; i++) { 4629 time -= playlist.targetDuration; 4630 if (time < 0) { 4631 return { 4632 mediaIndex: 0, 4633 startTime: currentTime 4634 }; 4635 } 4636 } 4637 startIndex = 0; 4638 } 4639 4640 // Walk forward from startIndex in the playlist, subtracting durations 4641 // until we find a segment that contains `time` and return it 4642 for (i = startIndex; i < numSegments; i++) { 4643 segment = playlist.segments[i]; 4644 time -= ceilLeastSignificantDigit(segment.duration); 4645 if (time < 0) { 4646 return { 4647 mediaIndex: i, 4648 startTime: startTime + sumDurations(playlist, startIndex, i) 4649 }; 4650 } 4651 } 4652 4653 // We are out of possible candidates so load the last one... 4654 return { 4655 mediaIndex: numSegments - 1, 4656 startTime: currentTime 4657 }; 4658}; 4659 4660exports.getMediaInfoForTime = getMediaInfoForTime; 4661/** 4662 * Check whether the playlist is blacklisted or not. 4663 * 4664 * @param {Object} playlist the media playlist object 4665 * @return {boolean} whether the playlist is blacklisted or not 4666 * @function isBlacklisted 4667 */ 4668var isBlacklisted = function isBlacklisted(playlist) { 4669 return playlist.excludeUntil && playlist.excludeUntil > Date.now(); 4670}; 4671 4672exports.isBlacklisted = isBlacklisted; 4673/** 4674 * Check whether the playlist is enabled or not. 4675 * 4676 * @param {Object} playlist the media playlist object 4677 * @return {boolean} whether the playlist is enabled or not 4678 * @function isEnabled 4679 */ 4680var isEnabled = function isEnabled(playlist) { 4681 var blacklisted = isBlacklisted(playlist); 4682 4683 return !playlist.disabled && !blacklisted; 4684}; 4685 4686exports.isEnabled = isEnabled; 4687/** 4688 * Returns whether the current playlist is an AES encrypted HLS stream 4689 * 4690 * @return {Boolean} true if it's an AES encrypted HLS stream 4691 */ 4692var isAes = function isAes(media) { 4693 for (var i = 0; i < media.segments.length; i++) { 4694 if (media.segments[i].key) { 4695 return true; 4696 } 4697 } 4698 return false;
4699}; 4700 4701exports.isAes = isAes; 4702/** 4703 * Returns whether the current playlist contains fMP4 4704 * 4705 * @return {Boolean} true if the playlist contains fMP4 4706 */ 4707var isFmp4 = function isFmp4(media) { 4708 for (var i = 0; i < media.segments.length; i++) { 4709 if (media.segments[i].map) { 4710 return true; 4711 } 4712 } 4713 return false; 4714}; 4715 4716exports.isFmp4 = isFmp4; 4717/** 4718 * Checks if the playlist has a value for the specified attribute 4719 * 4720 * @param {String} attr 4721 * Attribute to check for 4722 * @param {Object} playlist 4723 * The media playlist object 4724 * @return {Boolean} 4725 * Whether the playlist contains a value for the attribute or not 4726 * @function hasAttribute 4727 */ 4728var hasAttribute = function hasAttribute(attr, playlist) { 4729 return playlist.attributes && playlist.attributes[attr]; 4730}; 4731 4732exports.hasAttribute = hasAttribute; 4733/** 4734 * Estimates the time required to complete a segment download from the specified playlist 4735 * 4736 * @param {Number} segmentDuration 4737 * Duration of requested segment 4738 * @param {Number} bandwidth 4739 * Current measured bandwidth of the player 4740 * @param {Object} playlist 4741 * The media playlist object 4742 * @param {Number=} bytesReceived 4743 * Number of bytes already received for the request. Defaults to 0 4744 * @return {Number|NaN} 4745 * The estimated time to request the segment. NaN if bandwidth information for 4746 * the given playlist is unavailable 4747 * @function estimateSegmentRequestTime 4748 */ 4749var estimateSegmentRequestTime = function estimateSegmentRequestTime(segmentDuration, bandwidth, playlist) { 4750 var bytesReceived = arguments.length <= 3 || arguments[3] === undefined ? 0 : arguments[3]; 4751 4752 if (!hasAttribute('BANDWIDTH', playlist)) { 4753 return NaN; 4754 } 4755 4756 var size = segmentDuration * playlist.attributes.BANDWIDTH; 4757 4758 return (size - bytesReceived * 8) / bandwidth; 4759}; 4760 4761exports.estimateSegmentRequestTime = estimateSegmentRequestTime; 4762Playlist.duration = duration; 4763Playlist.seekable = seekable; 4764Playlist.getMediaInfoForTime = getMediaInfoForTime; 4765Playlist.isEnabled = isEnabled; 4766Playlist.isBlacklisted = isBlacklisted; 4767Playlist.playlistEnd = playlistEnd; 4768Playlist.isAes = isAes; 4769Playlist.isFmp4 = isFmp4; 4770Playlist.hasAttribute = hasAttribute; 4771Playlist.estimateSegmentRequestTime = estimateSegmentRequestTime; 4772 4773// exports 4774exports['default'] = Playlist; 4775}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 4776},{"global/window":32}],12:[function(require,module,exports){ 4777(function (global){ 4778/** 4779 * ranges 4780 * 4781 * Utilities for working with TimeRanges. 4782 * 4783 */ 4784 4785'use strict'; 4786 4787Object.defineProperty(exports, '__esModule', { 4788 value: true 4789}); 4790 4791var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })(); 4792 4793function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 4794 4795var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 4796 4797var _videoJs2 = _interopRequireDefault(_videoJs); 4798 4799// Fudge factor to account for TimeRanges rounding 4800var TIME_FUDGE_FACTOR = 1 / 30; 4801 4802/** 4803 * Clamps a value to within a range 4804 * @param {Number} num - the value to clamp 4805 * @param {Number} start - the start of the range to clamp within, inclusive 4806 * @param {Number} end - the end of the range to clamp within, inclusive 4807 * @return {Number} 4808 */ 4809var clamp = function clamp(num, _ref) { 4810 var _ref2 = _slicedToArray(_ref, 2); 4811 4812 var start = _ref2[0]; 4813 var end = _ref2[1]; 4814 4815 return Math.min(Math.max(start, num), end); 4816}; 4817var filterRanges = function filterRanges(timeRanges, predicate) { 4818 var results = []; 4819 var i = undefined; 4820 4821 if (timeRanges && timeRanges.length) { 4822 // Search for ranges that match the predicate 4823 for (i = 0; i < timeRanges.length; i++) { 4824 if (predicate(timeRanges.start(i), timeRanges.end(i))) { 4825 results.push([timeRanges.start(i), timeRanges.end(i)]); 4826 } 4827 } 4828 } 4829 4830 return _videoJs2['default'].createTimeRanges(results); 4831}; 4832 4833/** 4834 * Attempts to find the buffered TimeRange that contains the specified 4835 * time. 4836 * @param {TimeRanges} buffered - the TimeRanges object to query 4837 * @param {number} time - the time to filter on. 4838 * @returns {TimeRanges} a new TimeRanges object 4839 */ 4840var findRange = function findRange(buffered, time) { 4841 return filterRanges(buffered, function (start, end) {
4842 return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time; 4843 }); 4844}; 4845 4846/** 4847 * Returns the TimeRanges that begin later than the specified time. 4848 * @param {TimeRanges} timeRanges - the TimeRanges object to query 4849 * @param {number} time - the time to filter on. 4850 * @returns {TimeRanges} a new TimeRanges object. 4851 */ 4852var findNextRange = function findNextRange(timeRanges, time) { 4853 return filterRanges(timeRanges, function (start) { 4854 return start - TIME_FUDGE_FACTOR >= time; 4855 }); 4856}; 4857 4858/** 4859 * Returns gaps within a list of TimeRanges 4860 * @param {TimeRanges} buffered - the TimeRanges object 4861 * @return {TimeRanges} a TimeRanges object of gaps 4862 */ 4863var findGaps = function findGaps(buffered) { 4864 if (buffered.length < 2) { 4865 return _videoJs2['default'].createTimeRanges(); 4866 } 4867 4868 var ranges = []; 4869 4870 for (var i = 1; i < buffered.length; i++) { 4871 var start = buffered.end(i - 1); 4872 var end = buffered.start(i); 4873 4874 ranges.push([start, end]); 4875 } 4876 4877 return _videoJs2['default'].createTimeRanges(ranges); 4878}; 4879 4880/** 4881 * Search for a likely end time for the segment that was just appened 4882 * based on the state of the `buffered` property before and after the 4883 * append. If we fin only one such uncommon end-point return it. 4884 * @param {TimeRanges} original - the buffered time ranges before the update 4885 * @param {TimeRanges} update - the buffered time ranges after the update 4886 * @returns {Number|null} the end time added between `original` and `update`, 4887 * or null if one cannot be unambiguously determined. 4888 */ 4889var findSoleUncommonTimeRangesEnd = function findSoleUncommonTimeRangesEnd(original, update) { 4890 var i = undefined; 4891 var start = undefined; 4892 var end = undefined; 4893 var result = []; 4894 var edges = []; 4895 4896 // In order to qualify as a possible candidate, the end point must: 4897 // 1) Not have already existed in the `original` ranges 4898 // 2) Not result from the shrinking of a range that already existed 4899 // in the `original` ranges 4900 // 3) Not be contained inside of a range that existed in `original` 4901 var overlapsCurrentEnd = function overlapsCurrentEnd(span) { 4902 return span[0] <= end && span[1] >= end; 4903 }; 4904 4905 if (original) { 4906 // Save all the edges in the `original` TimeRanges object 4907 for (i = 0; i < original.length; i++) { 4908 start = original.start(i); 4909 end = original.end(i); 4910 4911 edges.push([start, end]); 4912 } 4913 } 4914 4915 if (update) { 4916 // Save any end-points in `update` that are not in the `original` 4917 // TimeRanges object 4918 for (i = 0; i < update.length; i++) { 4919 start = update.start(i); 4920 end = update.end(i); 4921 4922 if (edges.some(overlapsCurrentEnd)) { 4923 continue; 4924 } 4925 4926 // at this point it must be a unique non-shrinking end edge 4927 result.push(end); 4928 } 4929 } 4930 4931 // we err on the side of caution and return null if didn't find 4932 // exactly *one* differing end edge in the search above 4933 if (result.length !== 1) { 4934 return null; 4935 } 4936 4937 return result[0]; 4938}; 4939 4940/** 4941 * Calculate the intersection of two TimeRanges 4942 * @param {TimeRanges} bufferA 4943 * @param {TimeRanges} bufferB 4944 * @returns {TimeRanges} The interesection of `bufferA` with `bufferB` 4945 */ 4946var bufferIntersection = function bufferIntersection(bufferA, bufferB) { 4947 var start = null; 4948 var end = null; 4949 var arity = 0; 4950 var extents = []; 4951 var ranges = []; 4952 4953 if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) { 4954 return _videoJs2['default'].createTimeRange(); 4955 } 4956 4957 // Handle the case where we have both buffers and create an 4958 // intersection of the two 4959 var count = bufferA.length; 4960 4961 // A) Gather up all start and end times 4962 while (count--) { 4963 extents.push({ time: bufferA.start(count), type: 'start' }); 4964 extents.push({ time: bufferA.end(count), type: 'end' }); 4965 } 4966 count = bufferB.length; 4967 while (count--) { 4968 extents.push({ time: bufferB.start(count), type: 'start' }); 4969 extents.push({ time: bufferB.end(count), type: 'end' }); 4970 } 4971 // B) Sort them by time 4972 extents.sort(function (a, b) { 4973 return a.time - b.time; 4974 }); 4975 4976 // C) Go along one by one incrementing arity for start and decrementing 4977 // arity for ends 4978 for (count = 0; count < extents.length; count++) { 4979 if (extents[count].type === 'start') { 4980 arity++; 4981 4982 // D) If arity is ever incremented to 2 we are entering an 4983 // overlapping range 4984 if (arity === 2) { 4985 start = extents[count].time; 4986 } 4987 } else if (extents[count].type === 'end') { 4988 arity--; 4989
4990 // E) If arity is ever decremented to 1 we leaving an 4991 // overlapping range 4992 if (arity === 1) { 4993 end = extents[count].time; 4994 } 4995 } 4996 4997 // F) Record overlapping ranges 4998 if (start !== null && end !== null) { 4999 ranges.push([start, end]); 5000 start = null; 5001 end = null; 5002 } 5003 } 5004 5005 return _videoJs2['default'].createTimeRanges(ranges); 5006}; 5007 5008/** 5009 * Calculates the percentage of `segmentRange` that overlaps the 5010 * `buffered` time ranges. 5011 * @param {TimeRanges} segmentRange - the time range that the segment 5012 * covers adjusted according to currentTime 5013 * @param {TimeRanges} referenceRange - the original time range that the 5014 * segment covers 5015 * @param {Number} currentTime - time in seconds where the current playback 5016 * is at 5017 * @param {TimeRanges} buffered - the currently buffered time ranges 5018 * @returns {Number} percent of the segment currently buffered 5019 */ 5020var calculateBufferedPercent = function calculateBufferedPercent(adjustedRange, referenceRange, currentTime, buffered) { 5021 var referenceDuration = referenceRange.end(0) - referenceRange.start(0); 5022 var adjustedDuration = adjustedRange.end(0) - adjustedRange.start(0); 5023 var bufferMissingFromAdjusted = referenceDuration - adjustedDuration; 5024 var adjustedIntersection = bufferIntersection(adjustedRange, buffered); 5025 var referenceIntersection = bufferIntersection(referenceRange, buffered); 5026 var adjustedOverlap = 0; 5027 var referenceOverlap = 0; 5028 5029 var count = adjustedIntersection.length; 5030 5031 while (count--) { 5032 adjustedOverlap += adjustedIntersection.end(count) - adjustedIntersection.start(count); 5033 5034 // If the current overlap segment starts at currentTime, then increase the 5035 // overlap duration so that it actually starts at the beginning of referenceRange 5036 // by including the difference between the two Range's durations 5037 // This is a work around for the way Flash has no buffer before currentTime 5038 if (adjustedIntersection.start(count) === currentTime) { 5039 adjustedOverlap += bufferMissingFromAdjusted; 5040 } 5041 } 5042 5043 count = referenceIntersection.length; 5044 5045 while (count--) { 5046 referenceOverlap += referenceIntersection.end(count) - referenceIntersection.start(count); 5047 } 5048 5049 // Use whichever value is larger for the percentage-buffered since that value 5050 // is likely more accurate because the only way 5051 return Math.max(adjustedOverlap, referenceOverlap) / referenceDuration * 100; 5052}; 5053 5054/** 5055 * Return the amount of a range specified by the startOfSegment and segmentDuration 5056 * overlaps the current buffered content. 5057 * 5058 * @param {Number} startOfSegment - the time where the segment begins 5059 * @param {Number} segmentDuration - the duration of the segment in seconds 5060 * @param {Number} currentTime - time in seconds where the current playback 5061 * is at 5062 * @param {TimeRanges} buffered - the state of the buffer 5063 * @returns {Number} percentage of the segment's time range that is 5064 * already in `buffered` 5065 */ 5066var getSegmentBufferedPercent = function getSegmentBufferedPercent(startOfSegment, segmentDuration, currentTime, buffered) { 5067 var endOfSegment = startOfSegment + segmentDuration; 5068 5069 // The entire time range of the segment 5070 var originalSegmentRange = _videoJs2['default'].createTimeRanges([[startOfSegment, endOfSegment]]); 5071 5072 // The adjusted segment time range that is setup such that it starts 5073 // no earlier than currentTime 5074 // Flash has no notion of a back-buffer so adjustedSegmentRange adjusts 5075 // for that and the function will still return 100% if a only half of a 5076 // segment is actually in the buffer as long as the currentTime is also 5077 // half-way through the segment 5078 var adjustedSegmentRange = _videoJs2['default'].createTimeRanges([[clamp(startOfSegment, [currentTime, endOfSegment]), endOfSegment]]); 5079 5080 // This condition happens when the currentTime is beyond the segment's 5081 // end time 5082 if (adjustedSegmentRange.start(0) === adjustedSegmentRange.end(0)) { 5083 return 0; 5084 } 5085 5086 var percent = calculateBufferedPercent(adjustedSegmentRange, originalSegmentRange, currentTime, buffered); 5087 5088 // If the segment is reported as having a zero duration, return 0% 5089 // since it is likely that we will need to fetch the segment 5090 if (isNaN(percent) || percent === Infinity || percent === -Infinity) { 5091 return 0; 5092 } 5093 5094 return percent; 5095}; 5096 5097/** 5098 * Gets a human readable string for a TimeRange 5099 * 5100 * @param {TimeRange} range 5101 * @returns {String} a human readable string 5102 */ 5103var printableRange = function printableRange(range) { 5104 var strArr = []; 5105 5106 if (!range || !range.length) { 5107 return ''; 5108 } 5109 5110 for (var i = 0; i < range.length; i++) { 5111 strArr.push(range.start(i) + ' => ' + range.end(i)); 5112 } 5113 5114 return strArr.join(', '); 5115}; 5116 5117/** 5118 * Calculates the amount of time left in seconds until the player hits the end of the 5119 * buffer and causes a rebuffer 5120 * 5121 * @param {TimeRange} buffered 5122 * The state of the buffer 5123 * @param {Numnber} currentTime 5124 * The current time of the player 5125 * @param {Number} playbackRate 5126 * The current playback rate of the player. Defaults to 1. 5127 * @return {Number} 5128 * Time until the player has to start rebuffering in seconds. 5129 * @function timeUntilRebuffer 5130 */ 5131var timeUntilRebuffer = function timeUntilRebuffer(buffered, currentTime) { 5132 var playbackRate = arguments.length <= 2 || arguments[2] === undefined ? 1 : arguments[2]; 5133 5134 var bufferedEnd = buffered.length ? buffered.end(buffered.length - 1) : 0; 5135 5136 return (bufferedEnd - currentTime) / playbackRate; 5137}; 5138 5139exports['default'] = { 5140 findRange: findRange, 5141 findNextRange: findNextRange, 5142 findGaps: findGaps, 5143 findSoleUncommonTimeRangesEnd: findSoleUncommonTimeRangesEnd, 5144 getSegmentBufferedPercent: getSegmentBufferedPercent, 5145 TIME_FUDGE_FACTOR: TIME_FUDGE_FACTOR, 5146 printableRange: printableRange, 5147 timeUntilRebuffer: timeUntilRebuffer 5148}; 5149module.exports = exports['default']; 5150}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 5151},{}],13:[function(require,module,exports){ 5152(function (global){ 5153'use strict'; 5154 5155Object.defineProperty(exports, '__esModule', { 5156 value: true 5157}); 5158 5159function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 5160 5161var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 5162 5163var _videoJs2 = _interopRequireDefault(_videoJs); 5164 5165var defaultOptions = { 5166 errorInterval: 30, 5167 getSource: function getSource(next) { 5168 var tech = this.tech({ IWillNotUseThisInPlugins: true }); 5169 var sourceObj = tech.currentSource_; 5170 5171 return next(sourceObj); 5172 } 5173}; 5174 5175/** 5176 * Main entry point for the plugin 5177 * 5178 * @param {Player} player a reference to a videojs Player instance 5179 * @param {Object} [options] an object with plugin options 5180 * @private 5181 */ 5182var initPlugin = function initPlugin(player, options) { 5183 var lastCalled = 0; 5184 var seekTo = 0; 5185 var localOptions = _videoJs2['default'].mergeOptions(defaultOptions, options); 5186 5187 player.ready(function () { 5188 player.trigger({ type: 'usage', name: 'hls-error-reload-initialized' }); 5189 }); 5190 5191 /** 5192 * Player modifications to perform that must wait until `loadedmetadata` 5193 * has been triggered 5194 * 5195 * @private 5196 */ 5197 var loadedMetadataHandler = function loadedMetadataHandler() { 5198 if (seekTo) { 5199 player.currentTime(seekTo); 5200 } 5201 }; 5202 5203 /** 5204 * Set the source on the player element, play, and seek if necessary 5205 * 5206 * @param {Object} sourceObj An object specifying the source url and mime-type to play 5207 * @private 5208 */ 5209 var setSource = function setSource(sourceObj) { 5210 if (sourceObj === null || sourceObj === undefined) { 5211 return; 5212 } 5213 seekTo = player.duration() !== Infinity && player.currentTime() || 0; 5214 5215 player.one('loadedmetadata', loadedMetadataHandler); 5216 5217 player.src(sourceObj); 5218 player.trigger({ type: 'usage', name: 'hls-error-reload' }); 5219 player.play(); 5220 }; 5221 5222 /** 5223 * Attempt to get a source from either the built-in getSource function 5224 * or a custom function provided via the options 5225 * 5226 * @private 5227 */ 5228 var errorHandler = function errorHandler() {
5229 // Do not attempt to reload the source if a source-reload occurred before 5230 // 'errorInterval' time has elapsed since the last source-reload 5231 if (Date.now() - lastCalled < localOptions.errorInterval * 1000) { 5232 player.trigger({ type: 'usage', name: 'hls-error-reload-canceled' }); 5233 return; 5234 } 5235 5236 if (!localOptions.getSource || typeof localOptions.getSource !== 'function') { 5237 _videoJs2['default'].log.error('ERROR: reloadSourceOnError - The option getSource must be a function!'); 5238 return; 5239 } 5240 lastCalled = Date.now(); 5241 5242 return localOptions.getSource.call(player, setSource); 5243 }; 5244 5245 /** 5246 * Unbind any event handlers that were bound by the plugin 5247 * 5248 * @private 5249 */ 5250 var cleanupEvents = function cleanupEvents() { 5251 player.off('loadedmetadata', loadedMetadataHandler); 5252 player.off('error', errorHandler); 5253 player.off('dispose', cleanupEvents); 5254 }; 5255 5256 /** 5257 * Cleanup before re-initializing the plugin 5258 * 5259 * @param {Object} [newOptions] an object with plugin options 5260 * @private 5261 */ 5262 var reinitPlugin = function reinitPlugin(newOptions) { 5263 cleanupEvents(); 5264 initPlugin(player, newOptions); 5265 }; 5266 5267 player.on('error', errorHandler); 5268 player.on('dispose', cleanupEvents); 5269 5270 // Overwrite the plugin function so that we can correctly cleanup before 5271 // initializing the plugin 5272 player.reloadSourceOnError = reinitPlugin; 5273}; 5274 5275/** 5276 * Reload the source when an error is detected as long as there 5277 * wasn't an error previously within the last 30 seconds 5278 * 5279 * @param {Object} [options] an object with plugin options 5280 */ 5281var reloadSourceOnError = function reloadSourceOnError(options) { 5282 initPlugin(this, options); 5283}; 5284 5285exports['default'] = reloadSourceOnError; 5286module.exports = exports['default']; 5287}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 5288},{}],14:[function(require,module,exports){ 5289'use strict'; 5290 5291Object.defineProperty(exports, '__esModule', { 5292 value: true 5293}); 5294 5295function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 5296 5297var _playlistJs = require('./playlist.js'); 5298 5299/** 5300 * Enable/disable playlist function. It is intended to have the first two 5301 * arguments partially-applied in order to create the final per-playlist 5302 * function. 5303 * 5304 * @param {PlaylistLoader} playlist - The rendition or media-playlist 5305 * @param {Function} changePlaylistFn - A function to be called after a 5306 * playlist's enabled-state has been changed. Will NOT be called if a 5307 * playlist's enabled-state is unchanged 5308 * @param {Boolean=} enable - Value to set the playlist enabled-state to 5309 * or if undefined returns the current enabled-state for the playlist 5310 * @return {Boolean} The current enabled-state of the playlist 5311 */ 5312var enableFunction = function enableFunction(loader, playlistUri, changePlaylistFn, enable) { 5313 var playlist = loader.master.playlists[playlistUri]; 5314 var blacklisted = (0, _playlistJs.isBlacklisted)(playlist); 5315 var currentlyEnabled = (0, _playlistJs.isEnabled)(playlist); 5316 5317 if (typeof enable === 'undefined') { 5318 return currentlyEnabled; 5319 } 5320 5321 if (enable) { 5322 delete playlist.disabled; 5323 } else { 5324 playlist.disabled = true; 5325 } 5326 5327 if (enable !== currentlyEnabled && !blacklisted) { 5328 // Ensure the outside world knows about our changes 5329 changePlaylistFn(); 5330 if (enable) { 5331 loader.trigger('renditionenabled'); 5332 } else { 5333 loader.trigger('renditiondisabled'); 5334 } 5335 } 5336 return enable; 5337}; 5338 5339/** 5340 * The representation object encapsulates the publicly visible information 5341 * in a media playlist along with a setter/getter-type function (enabled) 5342 * for changing the enabled-state of a particular playlist entry 5343 * 5344 * @class Representation 5345 */ 5346 5347var Representation = function Representation(hlsHandler, playlist, id) { 5348 _classCallCheck(this, Representation); 5349 5350 // Get a reference to a bound version of fastQualityChange_ 5351 var fastChangeFunction = hlsHandler.masterPlaylistController_.fastQualityChange_.bind(hlsHandler.masterPlaylistController_); 5352 5353 // some playlist attributes are optional 5354 if (playlist.attributes.RESOLUTION) { 5355 var resolution = playlist.attributes.RESOLUTION; 5356 5357 this.width = resolution.width; 5358 this.height = resolution.height; 5359 } 5360 5361 this.bandwidth = playlist.attributes.BANDWIDTH; 5362 5363 // The id is simply the ordinality of the media playlist 5364 // within the master playlist 5365 this.id = id; 5366 5367 // Partially-apply the enableFunction to create a playlist- 5368 // specific variant 5369 this.enabled = enableFunction.bind(this, hlsHandler.playlists, playlist.uri, fastChangeFunction); 5370} 5371 5372/** 5373 * A mixin function that adds the `representations` api to an instance 5374 * of the HlsHandler class 5375 * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the 5376 * representation API into 5377 */ 5378; 5379 5380var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) { 5381 var playlists = hlsHandler.playlists; 5382 5383 // Add a single API-specific function to the HlsHandler instance 5384 hlsHandler.representations = function () { 5385 return playlists.master.playlists.filter(function (media) { 5386 return !(0, _playlistJs.isBlacklisted)(media); 5387 }).map(function (e, i) { 5388 return new Representation(hlsHandler, e, e.uri); 5389 }); 5390 }; 5391}; 5392 5393exports['default'] = renditionSelectionMixin; 5394module.exports = exports['default']; 5395},{"./playlist.js":11}],15:[function(require,module,exports){ 5396/** 5397 * @file resolve-url.js 5398 */ 5399 5400'use strict'; 5401 5402Object.defineProperty(exports, '__esModule', { 5403 value: true 5404}); 5405 5406function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 5407 5408var _urlToolkit = require('url-toolkit'); 5409 5410var _urlToolkit2 = _interopRequireDefault(_urlToolkit); 5411 5412var _globalWindow = require('global/window'); 5413 5414var _globalWindow2 = _interopRequireDefault(_globalWindow); 5415 5416var resolveUrl = function resolveUrl(baseURL, relativeURL) { 5417 // return early if we don't need to resolve 5418 if (/^[a-z]+:/i.test(relativeURL)) { 5419 return relativeURL; 5420 } 5421 5422 // if the base URL is relative then combine with the current location 5423 if (!/\/\//i.test(baseURL)) { 5424 baseURL = _urlToolkit2['default'].buildAbsoluteURL(_globalWindow2['default'].location.href, baseURL); 5425 } 5426 5427 return _urlToolkit2['default'].buildAbsoluteURL(baseURL, relativeURL); 5428}; 5429 5430exports['default'] = resolveUrl; 5431module.exports = exports['default']; 5432},{"global/window":32,"url-toolkit":63}],16:[function(require,module,exports){ 5433(function (global){ 5434/** 5435 * @file segment-loader.js 5436 */ 5437'use strict'; 5438 5439Object.defineProperty(exports, '__esModule', { 5440 value: true 5441}); 5442 5443var _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; }; })(); 5444 5445var _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); } } }; 5446 5447function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 5448 5449function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 5450 5451function _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, { c
5451onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 5452 5453var _playlist = require('./playlist'); 5454 5455var _playlist2 = _interopRequireDefault(_playlist); 5456 5457var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 5458 5459var _videoJs2 = _interopRequireDefault(_videoJs); 5460 5461var _sourceUpdater = require('./source-updater'); 5462 5463var _sourceUpdater2 = _interopRequireDefault(_sourceUpdater); 5464 5465var _config = require('./config'); 5466 5467var _config2 = _interopRequireDefault(_config); 5468 5469var _globalWindow = require('global/window'); 5470 5471var _globalWindow2 = _interopRequireDefault(_globalWindow); 5472 5473var _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs = require('videojs-contrib-media-sources/es5/remove-cues-from-track.js'); 5474 5475var _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs2 = _interopRequireDefault(_videojsContribMediaSourcesEs5RemoveCuesFromTrackJs); 5476 5477var _binUtils = require('./bin-utils'); 5478 5479var _mediaSegmentRequest = require('./media-segment-request'); 5480 5481var _ranges = require('./ranges'); 5482 5483var _playlistSelectors = require('./playlist-selectors'); 5484 5485// in ms 5486var CHECK_BUFFER_DELAY = 500; 5487 5488/** 5489 * Determines if we should call endOfStream on the media source based 5490 * on the state of the buffer or if appened segment was the final 5491 * segment in the playlist. 5492 * 5493 * @param {Object} playlist a media playlist object 5494 * @param {Object} mediaSource the MediaSource object 5495 * @param {Number} segmentIndex the index of segment we last appended 5496 * @returns {Boolean} do we need to call endOfStream on the MediaSource 5497 */ 5498var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) { 5499 if (!playlist || !mediaSource) { 5500 return false; 5501 } 5502 5503 var segments = playlist.segments; 5504 5505 // determine a few boolean values to help make the branch below easier 5506 // to read 5507 var appendedLastSegment = segmentIndex === segments.length; 5508 5509 // if we've buffered to the end of the video, we need to call endOfStream 5510 // so that MediaSources can trigger the `ended` event when it runs out of 5511 // buffered data instead of waiting for me 5512 return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment; 5513}; 5514 5515var finite = function finite(num) { 5516 return typeof num === 'number' && isFinite(num); 5517}; 5518 5519var illegalMediaSwitch = function illegalMediaSwitch(loaderType, startingMedia, newSegmentMedia) { 5520 // Although these checks should most likely cover non 'main' types, for now it narrows 5521 // the scope of our checks. 5522 if (loaderType !== 'main' || !startingMedia || !newSegmentMedia) { 5523 return null; 5524 } 5525 5526 if (!newSegmentMedia.containsAudio && !newSegmentMedia.containsVideo) { 5527 return 'Neither audio nor video found in segment.'; 5528 } 5529 5530 if (startingMedia.containsVideo && !newSegmentMedia.containsVideo) { 5531 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.'; 5532 } 5533 5534 if (!startingMedia.containsVideo && newSegmentMedia.containsVideo) { 5535 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.'; 5536 } 5537 5538 return null; 5539}; 5540 5541exports.illegalMediaSwitch = illegalMediaSwitch; 5542/** 5543 * An object that manages segment loading and appending. 5544 * 5545 * @class SegmentLoader 5546 * @param {Object} options required and optional options 5547 * @extends videojs.EventTarget 5548 */ 5549 5550var SegmentLoader = (function (_videojs$EventTarget) { 5551 _inherits(SegmentLoader, _videojs$EventTarget); 5552 5553 function SegmentLoader(settings) { 5554 var _this = this; 5555 5556 var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; 5557 5558 _classCallCheck(this, SegmentLoader); 5559 5560 _get(Object.getPrototypeOf(SegmentLoader.prototype), 'constructor', this).call(this); 5561 // check pre-conditions 5562 if (!settings) { 5563 throw new TypeError('Initialization settings are required'); 5564 } 5565 if (typeof settings.currentTime !== 'function') { 5566 throw new TypeError('No currentTime getter specified'); 5567 } 5568 if (!settings.mediaSource) { 5569 throw new TypeError('No MediaSource specified'); 5570 } 5571 // public properties 5572 this.state = 'INIT'; 5573 this.bandwidth = settings.bandwidth; 5574 this.throughput = { rate: 0, count: 0 }; 5575 this.roundTrip = NaN; 5576 this.resetStats_(); 5577 this.mediaIndex = null; 5578 5579 // private settings 5580 this.hasPlayed_ = settings.hasPlayed; 5581 this.currentTime_ = settings.currentTime; 5582 this.seekable_ = settings.seekable; 5583 this.seeking_ = settings.seeking; 5584 this.duration_ = settings.duration; 5585 this.mediaSource_ = settings.mediaSource; 5586 this.hls_ = settings.hls; 5587 this.loaderType_ = settings.loaderType; 5588 this.startingMedia_ = void 0; 5589 this.segmentMetadataTrack_ = settings.segmentMetadataTrack; 5590 this.goalBufferLength_ = settings.goalBufferLength; 5591 5592 // private instance variables 5593 this.checkBufferTimeout_ = null; 5594 this.error_ = void 0; 5595 this.currentTimeline_ = -1; 5596 this.pendingSegment_ = null; 5597 this.mimeType_ = null; 5598 this.sourceUpdater_ = null; 5599 this.xhrOptions_ = null; 5600 5601 // Fragmented mp4 playback 5602 this.activeInitSegmentId_ = null; 5603 this.initSegments_ = {}; 5604 5605 this.decrypter_ = settings.decrypter; 5606 5607 // Manages the tracking and generation of sync-points, mappings 5608 // between a time in the display time and a segment index within 5609 // a playlist 5610 this.syncController_ = settings.syncController; 5611 this.syncPoint_ = { 5612 segmentIndex: 0, 5613 time: 0 5614 }; 5615 5616 this.syncController_.on('syncinfoupdate', function () { 5617 return _this.trigger('syncinfoupdate'); 5618 }); 5619 5620 this.mediaSource_.addEventListener('sourceopen', function () { 5621 return _this.ended_ = false;
5622 }); 5623 5624 // ...for determining the fetch location 5625 this.fetchAtBuffer_ = false; 5626 5627 if (options.debug) { 5628 this.logger_ = _videoJs2['default'].log.bind(_videoJs2['default'], 'segment-loader', this.loaderType_, '->'); 5629 } 5630 } 5631 5632 /** 5633 * reset all of our media stats 5634 * 5635 * @private 5636 */ 5637 5638 _createClass(SegmentLoader, [{ 5639 key: 'resetStats_', 5640 value: function resetStats_() { 5641 this.mediaBytesTransferred = 0; 5642 this.mediaRequests = 0; 5643 this.mediaRequestsAborted = 0; 5644 this.mediaRequestsTimedout = 0; 5645 this.mediaRequestsErrored = 0; 5646 this.mediaTransferDuration = 0; 5647 this.mediaSecondsLoaded = 0; 5648 } 5649 5650 /** 5651 * dispose of the SegmentLoader and reset to the default state 5652 */ 5653 }, { 5654 key: 'dispose', 5655 value: function dispose() { 5656 this.state = 'DISPOSED'; 5657 this.pause(); 5658 this.abort_(); 5659 if (this.sourceUpdater_) { 5660 this.sourceUpdater_.dispose(); 5661 } 5662 this.resetStats_(); 5663 } 5664 5665 /** 5666 * abort anything that is currently doing on with the SegmentLoader 5667 * and reset to a default state 5668 */ 5669 }, { 5670 key: 'abort', 5671 value: function abort() { 5672 if (this.state !== 'WAITING') { 5673 if (this.pendingSegment_) { 5674 this.pendingSegment_ = null; 5675 } 5676 return; 5677 } 5678 5679 this.abort_(); 5680 5681 // We aborted the requests we were waiting on, so reset the loader's state to READY 5682 // since we are no longer "waiting" on any requests. XHR callback is not always run 5683 // when the request is aborted. This will prevent the loader from being stuck in the 5684 // WAITING state indefinitely. 5685 this.state = 'READY'; 5686 5687 // don't wait for buffer check timeouts to begin fetching the 5688 // next segment 5689 if (!this.paused()) { 5690 this.monitorBuffer_(); 5691 } 5692 } 5693 5694 /** 5695 * abort all pending xhr requests and null any pending segements 5696 * 5697 * @private 5698 */ 5699 }, { 5700 key: 'abort_', 5701 value: function abort_() { 5702 if (this.pendingSegment_) { 5703 this.pendingSegment_.abortRequests(); 5704 } 5705 5706 // clear out the segment being processed 5707 this.pendingSegment_ = null; 5708 } 5709 5710 /** 5711 * set an error on the segment loader and null out any pending segements 5712 * 5713 * @param {Error} error the error to set on the SegmentLoader 5714 * @return {Error} the error that was set or that is currently set 5715 */ 5716 }, { 5717 key: 'error', 5718 value: function error(_error) { 5719 if (typeof _error !== 'undefined') { 5720 this.error_ = _error; 5721 } 5722 5723 this.pendingSegment_ = null; 5724 return this.error_; 5725 } 5726 }, { 5727 key: 'endOfStream', 5728 value: function endOfStream() { 5729 this.ended_ = true; 5730 this.pause(); 5731 this.trigger('ended'); 5732 } 5733 5734 /** 5735 * Indicates which time ranges are buffered 5736 * 5737 * @return {TimeRange} 5738 * TimeRange object representing the current buffered ranges 5739 */ 5740 }, { 5741 key: 'buffered_', 5742 value: function buffered_() { 5743 if (!this.sourceUpdater_) { 5744 return _videoJs2['default'].createTimeRanges(); 5745 } 5746 5747 return this.sourceUpdater_.buffered(); 5748 } 5749 5750 /** 5751 * Gets and sets init segment for the provided map 5752 * 5753 * @param {Object} map 5754 * The map object representing the init segment to get or set 5755 * @param {Boolean=} set 5756 * If true, the init segment for the provided map should be saved 5757 * @return {Object} 5758 * map object for desired init segment 5759 */ 5760 }, { 5761 key: 'initSegment', 5762 value: function initSegment(map) { 5763 var set = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 5764 5765 if (!map) { 5766 return null; 5767 } 5768 5769 var id = (0, _binUtils.initSegmentId)(map); 5770 var storedMap = this.initSegments_[id]; 5771 5772 if (set && !storedMap && map.bytes) { 5773 this.initSegments_[id] = storedMap = { 5774 resolvedUri: map.resolvedUri, 5775 byterange: map.byterange, 5776 bytes: map.bytes 5777 }; 5778 } 5779 5780 return storedMap || map; 5781 } 5782 5783 /**
5784 * Returns true if all configuration required for loading is present, otherwise false. 5785 * 5786 * @return {Boolean} True if the all configuration is ready for loading 5787 * @private 5788 */ 5789 }, { 5790 key: 'couldBeginLoading_', 5791 value: function couldBeginLoading_() { 5792 return this.playlist_ && ( 5793 // the source updater is created when init_ is called, so either having a 5794 // source updater or being in the INIT state with a mimeType is enough 5795 // to say we have all the needed configuration to start loading. 5796 this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused(); 5797 } 5798 5799 /** 5800 * load a playlist and start to fill the buffer 5801 */ 5802 }, { 5803 key: 'load', 5804 value: function load() { 5805 // un-pause 5806 this.monitorBuffer_(); 5807 5808 // if we don't have a playlist yet, keep waiting for one to be 5809 // specified 5810 if (!this.playlist_) { 5811 return; 5812 } 5813 5814 // not sure if this is the best place for this 5815 this.syncController_.setDateTimeMapping(this.playlist_); 5816 5817 // if all the configuration is ready, initialize and begin loading 5818 if (this.state === 'INIT' && this.couldBeginLoading_()) { 5819 return this.init_(); 5820 } 5821 5822 // if we're in the middle of processing a segment already, don't 5823 // kick off an additional segment request 5824 if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') { 5825 return; 5826 } 5827 5828 this.state = 'READY'; 5829 } 5830 5831 /** 5832 * Once all the starting parameters have been specified, begin 5833 * operation. This method should only be invoked from the INIT 5834 * state. 5835 * 5836 * @private 5837 */ 5838 }, { 5839 key: 'init_', 5840 value: function init_() { 5841 this.state = 'READY'; 5842 this.sourceUpdater_ = new _sourceUpdater2['default'](this.mediaSource_, this.mimeType_); 5843 this.resetEverything(); 5844 return this.monitorBuffer_(); 5845 } 5846 5847 /** 5848 * set a playlist on the segment loader 5849 * 5850 * @param {PlaylistLoader} media the playlist to set on the segment loader 5851 */ 5852 }, { 5853 key: 'playlist', 5854 value: function playlist(newPlaylist) { 5855 var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; 5856 5857 if (!newPlaylist) { 5858 return; 5859 } 5860 5861 var oldPlaylist = this.playlist_; 5862 var segmentInfo = this.pendingSegment_; 5863 5864 this.playlist_ = newPlaylist; 5865 this.xhrOptions_ = options; 5866 5867 // when we haven't started playing yet, the start of a live playlist 5868 // is always our zero-time so force a sync update each time the playlist 5869 // is refreshed from the server 5870 if (!this.hasPlayed_()) { 5871 newPlaylist.syncInfo = { 5872 mediaSequence: newPlaylist.mediaSequence, 5873 time: 0 5874 }; 5875 } 5876 5877 // in VOD, this is always a rendition switch (or we updated our syncInfo above) 5878 // in LIVE, we always want to update with new playlists (including refreshes) 5879 this.trigger('syncinfoupdate'); 5880 5881 // if we were unpaused but waiting for a playlist, start 5882 // buffering now 5883 if (this.state === 'INIT' && this.couldBeginLoading_()) { 5884 return this.init_(); 5885 } 5886 5887 if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) { 5888 if (this.mediaIndex !== null) { 5889 // we must "resync" the segment loader when we switch renditions and 5890 // the segment loader is already synced to the previous rendition 5891 this.resyncLoader(); 5892 } 5893 5894 // the rest of this function depends on `oldPlaylist` being defined 5895 return; 5896 } 5897 5898 // we reloaded the same playlist so we are in a live scenario 5899 // and we will likely need to adjust the mediaIndex 5900 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; 5901 5902 this.logger_('mediaSequenceDiff', mediaSequenceDiff); 5903 5904 // update the mediaIndex on the SegmentLoader 5905 // this is important because we can abort a request and this value must be 5906 // equal to the last appended mediaIndex 5907 if (this.mediaIndex !== null) { 5908 this.mediaIndex -= mediaSequenceDiff; 5909 } 5910 5911 // update the mediaIndex on the SegmentInfo object 5912 // this is important because we will update this.mediaIndex with this value 5913 // in `handleUpdateEnd_` after the segment has been successfully appended 5914 if (segmentInfo) { 5915 segmentInfo.mediaIndex -= mediaSequenceDiff; 5916 5917 // we need to update the referenced segment so that timing information is 5918 // saved for the new playlist's segment, however, if the segment fell off the 5919 // playlist, we can leave the old reference and just lose the timing info 5920 if (segmentInfo.mediaIndex >= 0) { 5921 segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex]; 5922 } 5923 } 5924 5925 this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist); 5926 } 5927 5928 /** 5929 * Prevent the loader from fetching additional segments. If there 5930 * is a segment request outstanding, it will finish processing 5931 * before the loader halts. A segment loader can be unpaused by 5932 * calling load(). 5933 */ 5934 }, { 5935 key: 'pause', 5936 value: function pause() { 5937 if (this.checkBufferTimeout_) { 5938 _globalWindow2['default'].clearTimeout(this.checkBufferTimeout_); 5939 5940 this.checkBufferTimeout_ = null; 5941 } 5942 } 5943 5944 /** 5945 * Returns whether the segment loader is fetching additional 5946 * segments when given the opportunity. This property can be 5947 * modified through calls to pause() and load(). 5948 */ 5949 }, { 5950 key: 'paused', 5951 value: function paused() { 5952 return this.checkBufferTimeout_ === null; 5953 } 5954 5955 /** 5956 * create/set the following mimetype on the SourceBuffer through a 5957 * SourceUpdater 5958 * 5959 * @param {String} mimeType the mime type string to use 5960 */ 5961 }, { 5962 key: 'mimeType', 5963 value: function mimeType(_mimeType) { 5964 if (this.mimeType_) { 5965 return; 5966 } 5967 5968 this.mimeType_ = _mimeType; 5969 // if we were unpaused but waiting for a sourceUpdater, start 5970 // buffering now 5971 if (this.state === 'INIT' && this.couldBeginLoading_()) { 5972 this.init_(); 5973 } 5974 } 5975 5976 /** 5977 * Delete all the buffered data and reset the SegmentLoader 5978 */ 5979 }, { 5980 key: 'resetEverything', 5981 value: function resetEverything() { 5982 this.ended_ = false;
5983 this.resetLoader(); 5984 this.remove(0, this.duration_()); 5985 this.trigger('reseteverything'); 5986 } 5987 5988 /** 5989 * Force the SegmentLoader to resync and start loading around the currentTime instead 5990 * of starting at the end of the buffer 5991 * 5992 * Useful for fast quality changes 5993 */ 5994 }, { 5995 key: 'resetLoader', 5996 value: function resetLoader() { 5997 this.fetchAtBuffer_ = false; 5998 this.resyncLoader(); 5999 } 6000 6001 /** 6002 * Force the SegmentLoader to restart synchronization and make a conservative guess 6003 * before returning to the simple walk-forward method 6004 */ 6005 }, { 6006 key: 'resyncLoader', 6007 value: function resyncLoader() { 6008 this.mediaIndex = null; 6009 this.syncPoint_ = null; 6010 this.abort(); 6011 } 6012 6013 /** 6014 * Remove any data in the source buffer between start and end times 6015 * @param {Number} start - the start time of the region to remove from the buffer 6016 * @param {Number} end - the end time of the region to remove from the buffer 6017 */ 6018 }, { 6019 key: 'remove', 6020 value: function remove(start, end) { 6021 if (this.sourceUpdater_) { 6022 this.sourceUpdater_.remove(start, end); 6023 } 6024 (0, _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs2['default'])(start, end, this.segmentMetadataTrack_); 6025 } 6026 6027 /** 6028 * (re-)schedule monitorBufferTick_ to run as soon as possible 6029 * 6030 * @private 6031 */ 6032 }, { 6033 key: 'monitorBuffer_', 6034 value: function monitorBuffer_() { 6035 if (this.checkBufferTimeout_) { 6036 _globalWindow2['default'].clearTimeout(this.checkBufferTimeout_); 6037 } 6038 6039 this.checkBufferTimeout_ = _globalWindow2['default'].setTimeout(this.monitorBufferTick_.bind(this), 1); 6040 } 6041 6042 /** 6043 * As long as the SegmentLoader is in the READY state, periodically 6044 * invoke fillBuffer_(). 6045 * 6046 * @private 6047 */ 6048 }, { 6049 key: 'monitorBufferTick_', 6050 value: function monitorBufferTick_() { 6051 if (this.state === 'READY') { 6052 this.fillBuffer_(); 6053 } 6054 6055 if (this.checkBufferTimeout_) { 6056 _globalWindow2['default'].clearTimeout(this.checkBufferTimeout_); 6057 } 6058 6059 this.checkBufferTimeout_ = _globalWindow2['default'].setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY); 6060 } 6061 6062 /** 6063 * fill the buffer with segements unless the sourceBuffers are 6064 * currently updating 6065 * 6066 * Note: this function should only ever be called by monitorBuffer_ 6067 * and never directly 6068 * 6069 * @private 6070 */ 6071 }, { 6072 key: 'fillBuffer_', 6073 value: function fillBuffer_() { 6074 if (this.sourceUpdater_.updating()) { 6075 return; 6076 } 6077 6078 if (!this.syncPoint_) { 6079 this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_()); 6080 } 6081 6082 // see if we need to begin loading immediately 6083 var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_); 6084 6085 if (!segmentInfo) { 6086 return; 6087 } 6088 6089 var isEndOfStream = detectEndOfStream(this.playlist_, this.mediaSource_, segmentInfo.mediaIndex); 6090 6091 if (isEndOfStream) { 6092 this.endOfStream(); 6093 return; 6094 } 6095 6096 if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) { 6097 return; 6098 } 6099 6100 // We will need to change timestampOffset of the sourceBuffer if either of 6101 // the following conditions are true: 6102 // - The segment.timeline !== this.currentTimeline 6103 // (we are crossing a discontinuity somehow) 6104 // - The "timestampOffset" for the start of this segment is less than 6105 // the currently set timestampOffset 6106 if (segmentInfo.timeline !== this.currentTimeline_ || segmentInfo.startOfSegment !== null && segmentInfo.startOfSegment < this.sourceUpdater_.timestampOffset()) { 6107 this.syncController_.reset(); 6108 segmentInfo.timestampOffset = segmentInfo.startOfSegment; 6109 } 6110 6111 this.loadSegment_(segmentInfo); 6112 } 6113 6114 /** 6115 * Determines what segment request should be made, given current playback 6116 * state. 6117 * 6118 * @param {TimeRanges} buffered - the state of the buffer 6119 * @param {Object} playlist - the playlist object to fetch segments from 6120 * @param {Number} mediaIndex - the previous mediaIndex fetched or null 6121 * @param {Boolean} hasPlayed - a flag indicating whether we have played or not 6122 * @param {Number} currentTime - the playback position in seconds 6123 * @param {Object} syncPoint - a segment info object that describes the 6124 * @returns {Object} a segment request object that describes the segment to load 6125 */ 6126 }, { 6127 key: 'checkBuffer_', 6128 value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) { 6129 var lastBufferedEnd = 0; 6130 var startOfSegment = undefined; 6131 6132 if (buffered.length) { 6133 lastBufferedEnd = buffered.end(buffered.length - 1); 6134 } 6135 6136 var bufferedTime = Math.max(0, lastBufferedEnd - currentTime); 6137 6138 if (!playlist.segments.length) { 6139 return null; 6140 } 6141 6142 // if there is plenty of content buffered, and the video has 6143 // been played before relax for awhile 6144 if (bufferedTime >= this.goalBufferLength_()) { 6145 return null; 6146 } 6147 6148 // if the video has not yet played once, and we already have 6149 // one segment downloaded do nothing 6150 if (!hasPlayed && bufferedTime >= 1) { 6151 return null; 6152 } 6153 6154 this.logger_('checkBuffer_', 'mediaIndex:', mediaIndex, 'hasPlayed:', hasPlayed, 'currentTime:', currentTime, 'syncPoint:', syncPoint, 'fetchAtBuffer:', this.fetchAtBuffer_, 'bufferedTime:', bufferedTime); 6155 6156 // When the syncPoint is null, there is no way of determining a good 6157 // conservative segment index to fetch from 6158 // The best thing to do here is to get the kind of sync-point data by 6159 // making a request 6160 if (syncPoint === null) {
6161 mediaIndex = this.getSyncSegmentCandidate_(playlist); 6162 this.logger_('getSync', 'mediaIndex:', mediaIndex); 6163 return this.generateSegmentInfo_(playlist, mediaIndex, null, true); 6164 } 6165 6166 // Under normal playback conditions fetching is a simple walk forward 6167 if (mediaIndex !== null) { 6168 this.logger_('walkForward', 'mediaIndex:', mediaIndex + 1); 6169 var segment = playlist.segments[mediaIndex]; 6170 6171 if (segment && segment.end) { 6172 startOfSegment = segment.end; 6173 } else { 6174 startOfSegment = lastBufferedEnd; 6175 } 6176 return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false); 6177 } 6178 6179 // There is a sync-point but the lack of a mediaIndex indicates that 6180 // we need to make a good conservative guess about which segment to 6181 // fetch 6182 if (this.fetchAtBuffer_) { 6183 // Find the segment containing the end of the buffer 6184 var mediaSourceInfo = _playlist2['default'].getMediaInfoForTime(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time); 6185 6186 mediaIndex = mediaSourceInfo.mediaIndex; 6187 startOfSegment = mediaSourceInfo.startTime; 6188 } else { 6189 // Find the segment containing currentTime 6190 var mediaSourceInfo = _playlist2['default'].getMediaInfoForTime(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time); 6191 6192 mediaIndex = mediaSourceInfo.mediaIndex; 6193 startOfSegment = mediaSourceInfo.startTime; 6194 } 6195 this.logger_('getMediaIndexForTime', 'mediaIndex:', mediaIndex, 'startOfSegment:', startOfSegment); 6196 6197 return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false); 6198 } 6199 6200 /** 6201 * The segment loader has no recourse except to fetch a segment in the 6202 * current playlist and use the internal timestamps in that segment to 6203 * generate a syncPoint. This function returns a good candidate index 6204 * for that process. 6205 * 6206 * @param {Object} playlist - the playlist object to look for a 6207 * @returns {Number} An index of a segment from the playlist to load 6208 */ 6209 }, { 6210 key: 'getSyncSegmentCandidate_', 6211 value: function getSyncSegmentCandidate_(playlist) { 6212 var _this2 = this; 6213 6214 if (this.currentTimeline_ === -1) { 6215 return 0; 6216 } 6217 6218 var segmentIndexArray = playlist.segments.map(function (s, i) { 6219 return { 6220 timeline: s.timeline, 6221 segmentIndex: i 6222 }; 6223 }).filter(function (s) { 6224 return s.timeline === _this2.currentTimeline_; 6225 }); 6226 6227 if (segmentIndexArray.length) { 6228 return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex; 6229 } 6230 6231 return Math.max(playlist.segments.length - 1, 0); 6232 } 6233 }, { 6234 key: 'generateSegmentInfo_', 6235 value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) { 6236 if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) { 6237 return null; 6238 } 6239 6240 var segment = playlist.segments[mediaIndex]; 6241 6242 return { 6243 requestId: 'segment-loader-' + Math.random(), 6244 // resolve the segment URL relative to the playlist 6245 uri: segment.resolvedUri, 6246 // the segment's mediaIndex at the time it was requested 6247 mediaIndex: mediaIndex, 6248 // whether or not to update the SegmentLoader's state with this 6249 // segment's mediaIndex 6250 isSyncRequest: isSyncRequest, 6251 startOfSegment: startOfSegment, 6252 // the segment's playlist 6253 playlist: playlist, 6254 // unencrypted bytes of the segment 6255 bytes: null, 6256 // when a key is defined for this segment, the encrypted bytes 6257 encryptedBytes: null, 6258 // The target timestampOffset for this segment when we append it 6259 // to the source buffer 6260 timestampOffset: null, 6261 // The timeline that the segment is in 6262 timeline: segment.timeline, 6263 // The expected duration of the segment in seconds 6264 duration: segment.duration, 6265 // retain the segment in case the playlist updates while doing an async process 6266 segment: segment 6267 }; 6268 } 6269 6270 /** 6271 * Determines if the network has enough bandwidth to complete the current segment 6272 * request in a timely manner. If not, the request will be aborted early and bandwidth 6273 * updated to trigger a playlist switch. 6274 * 6275 * @param {Object} stats 6276 * Object containing stats about the request timing and size 6277 * @return {Boolean} True if the request was aborted, false otherwise 6278 * @private 6279 */ 6280 }, { 6281 key: 'abortRequestEarly_', 6282 value: function abortRequestEarly_(stats) { 6283 if (this.hls_.tech_.paused() || 6284 // Don't abort if the current playlist is on the lowestEnabledRendition 6285 // TODO: Replace using timeout with a boolean indicating whether this playlist is 6286 // the lowestEnabledRendition. 6287 !this.xhrOptions_.timeout || 6288 // Don't abort if we have no bandwidth information to estimate segment sizes 6289 !this.playlist_.attributes.BANDWIDTH) { 6290 return false;
6291 } 6292 6293 // Wait at least 1 second since the first byte of data has been received before 6294 // using the calculated bandwidth from the progress event to allow the bitrate 6295 // to stabilize 6296 if (Date.now() - (stats.firstBytesReceivedAt || Date.now()) < 1000) { 6297 return false; 6298 } 6299 6300 var currentTime = this.currentTime_(); 6301 var measuredBandwidth = stats.bandwidth; 6302 var segmentDuration = this.pendingSegment_.duration; 6303 6304 var requestTimeRemaining = _playlist2['default'].estimateSegmentRequestTime(segmentDuration, measuredBandwidth, this.playlist_, stats.bytesReceived); 6305 6306 // Subtract 1 from the timeUntilRebuffer so we still consider an early abort 6307 // if we are only left with less than 1 second when the request completes. 6308 // A negative timeUntilRebuffering indicates we are already rebuffering 6309 var timeUntilRebuffer = (0, _ranges.timeUntilRebuffer)(this.buffered_(), currentTime, this.hls_.tech_.playbackRate()) - 1; 6310 6311 // Only consider aborting early if the estimated time to finish the download 6312 // is larger than the estimated time until the player runs out of forward buffer 6313 if (requestTimeRemaining <= timeUntilRebuffer) { 6314 return false; 6315 } 6316 6317 var switchCandidate = (0, _playlistSelectors.minRebufferMaxBandwidthSelector)({ 6318 master: this.hls_.playlists.master, 6319 currentTime: currentTime, 6320 bandwidth: measuredBandwidth, 6321 duration: this.duration_(), 6322 segmentDuration: segmentDuration, 6323 timeUntilRebuffer: timeUntilRebuffer, 6324 currentTimeline: this.currentTimeline_, 6325 syncController: this.syncController_ 6326 }); 6327 6328 if (!switchCandidate) { 6329 return; 6330 } 6331 6332 var rebufferingImpact = requestTimeRemaining - timeUntilRebuffer; 6333 6334 var timeSavedBySwitching = rebufferingImpact - switchCandidate.rebufferingImpact; 6335 6336 var minimumTimeSaving = 0.5; 6337 6338 // If we are already rebuffering, increase the amount of variance we add to the 6339 // potential round trip time of the new request so that we are not too aggressive 6340 // with switching to a playlist that might save us a fraction of a second. 6341 if (timeUntilRebuffer <= _ranges.TIME_FUDGE_FACTOR) { 6342 minimumTimeSaving = 1; 6343 } 6344 6345 if (!switchCandidate.playlist || switchCandidate.playlist.uri === this.playlist_.uri || timeSavedBySwitching < minimumTimeSaving) { 6346 return false; 6347 } 6348 6349 // set the bandwidth to that of the desired playlist being sure to scale by 6350 // BANDWIDTH_VARIANCE and add one so the playlist selector does not exclude it 6351 // don't trigger a bandwidthupdate as the bandwidth is artifial 6352 this.bandwidth = switchCandidate.playlist.attributes.BANDWIDTH * _config2['default'].BANDWIDTH_VARIANCE + 1; 6353 this.abort(); 6354 this.trigger('earlyabort'); 6355 return true; 6356 } 6357 6358 /** 6359 * XHR `progress` event handler 6360 * 6361 * @param {Event} 6362 * The XHR `progress` event 6363 * @param {Object} simpleSegment 6364 * A simplified segment object copy 6365 * @private 6366 */ 6367 }, { 6368 key: 'handleProgress_', 6369 value: function handleProgress_(event, simpleSegment) { 6370 if (!this.pendingSegment_ || simpleSegment.requestId !== this.pendingSegment_.requestId || this.abortRequestEarly_(simpleSegment.stats)) { 6371 return; 6372 } 6373 6374 this.trigger('progress'); 6375 } 6376 6377 /** 6378 * load a specific segment from a request into the buffer 6379 * 6380 * @private 6381 */ 6382 }, { 6383 key: 'loadSegment_', 6384 value: function loadSegment_(segmentInfo) { 6385 this.state = 'WAITING'; 6386 this.pendingSegment_ = segmentInfo; 6387 this.trimBackBuffer_(segmentInfo); 6388 6389 segmentInfo.abortRequests = (0, _mediaSegmentRequest.mediaSegmentRequest)(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.createSimplifiedSegmentObj_(segmentInfo), 6390 // progress callback 6391 this.handleProgress_.bind(this), this.segmentRequestFinished_.bind(this)); 6392 } 6393 6394 /** 6395 * trim the back buffer so that we don't have too much data 6396 * in the source buffer 6397 * 6398 * @private 6399 * 6400 * @param {Object} segmentInfo - the current segment 6401 */ 6402 }, { 6403 key: 'trimBackBuffer_', 6404 value: function trimBackBuffer_(segmentInfo) { 6405 var seekable = this.seekable_(); 6406 var currentTime = this.currentTime_(); 6407 var removeToTime = 0; 6408 6409 // Chrome has a hard limit of 150MB of 6410 // buffer and a very conservative "garbage collector" 6411 // We manually clear out the old buffer to ensure 6412 // we don't trigger the QuotaExceeded error 6413 // on the source buffer during subsequent appends 6414 6415 // If we have a seekable range use that as the limit for what can be removed safely 6416 // otherwise remove anything older than 30 seconds before the current play head 6417 if (seekable.length && seekable.start(0) > 0 && seekable.start(0) < currentTime) { 6418 removeToTime = seekable.start(0); 6419 } else { 6420 removeToTime = currentTime - 30; 6421 } 6422 6423 if (removeToTime > 0) { 6424 this.remove(0, removeToTime); 6425 } 6426 } 6427 6428 /** 6429 * created a simplified copy of the segment object with just the 6430 * information necessary to perform the XHR and decryption 6431 * 6432 * @private 6433 * 6434 * @param {Object} segmentInfo - the current segment 6435 * @returns {Object}
vendor: 9,589 bytes, lines 6435-6708
6435 a simplified segment object copy 6436 */ 6437 }, { 6438 key: 'createSimplifiedSegmentObj_', 6439 value: function createSimplifiedSegmentObj_(segmentInfo) { 6440 var segment = segmentInfo.segment; 6441 var simpleSegment = { 6442 resolvedUri: segment.resolvedUri, 6443 byterange: segment.byterange, 6444 requestId: segmentInfo.requestId 6445 }; 6446 6447 if (segment.key) { 6448 // if the media sequence is greater than 2^32, the IV will be incorrect 6449 // assuming 10s segments, that would be about 1300 years 6450 var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]); 6451 6452 simpleSegment.key = { 6453 resolvedUri: segment.key.resolvedUri, 6454 iv: iv 6455 }; 6456 } 6457 6458 if (segment.map) { 6459 simpleSegment.map = this.initSegment(segment.map); 6460 } 6461 6462 return simpleSegment; 6463 } 6464 6465 /** 6466 * Handle the callback from the segmentRequest function and set the 6467 * associated SegmentLoader state and errors if necessary 6468 * 6469 * @private 6470 */ 6471 }, { 6472 key: 'segmentRequestFinished_', 6473 value: function segmentRequestFinished_(error, simpleSegment) { 6474 // every request counts as a media request even if it has been aborted 6475 // or canceled due to a timeout 6476 this.mediaRequests += 1; 6477 6478 if (simpleSegment.stats) { 6479 this.mediaBytesTransferred += simpleSegment.stats.bytesReceived; 6480 this.mediaTransferDuration += simpleSegment.stats.roundTripTime; 6481 } 6482 6483 // The request was aborted and the SegmentLoader has already been reset 6484 if (!this.pendingSegment_) { 6485 this.mediaRequestsAborted += 1; 6486 return; 6487 } 6488 6489 // the request was aborted and the SegmentLoader has already started 6490 // another request. this can happen when the timeout for an aborted 6491 // request triggers due to a limitation in the XHR library 6492 // do not count this as any sort of request or we risk double-counting 6493 if (simpleSegment.requestId !== this.pendingSegment_.requestId) { 6494 return; 6495 } 6496 6497 // an error occurred from the active pendingSegment_ so reset everything 6498 if (error) { 6499 this.pendingSegment_ = null; 6500 this.state = 'READY'; 6501 6502 // the requests were aborted just record the aborted stat and exit 6503 // this is not a true error condition and nothing corrective needs 6504 // to be done 6505 if (error.code === _mediaSegmentRequest.REQUEST_ERRORS.ABORTED) { 6506 this.mediaRequestsAborted += 1; 6507 return; 6508 } 6509 6510 this.pause(); 6511 6512 // the error is really just that at least one of the requests timed-out 6513 // set the bandwidth to a very low value and trigger an ABR switch to 6514 // take emergency action 6515 if (error.code === _mediaSegmentRequest.REQUEST_ERRORS.TIMEOUT) { 6516 this.mediaRequestsTimedout += 1; 6517 this.bandwidth = 1; 6518 this.roundTrip = NaN; 6519 this.trigger('bandwidthupdate'); 6520 return; 6521 } 6522 6523 // if control-flow has arrived here, then the error is real 6524 // emit an error event to blacklist the current playlist 6525 this.mediaRequestsErrored += 1; 6526 this.error(error); 6527 this.trigger('error'); 6528 return; 6529 } 6530 6531 // the response was a success so set any bandwidth stats the request 6532 // generated for ABR purposes 6533 this.bandwidth = simpleSegment.stats.bandwidth; 6534 this.roundTrip = simpleSegment.stats.roundTripTime; 6535 6536 // if this request included an initialization segment, save that data 6537 // to the initSegment cache 6538 if (simpleSegment.map) { 6539 simpleSegment.map = this.initSegment(simpleSegment.map, true); 6540 } 6541 6542 this.processSegmentResponse_(simpleSegment); 6543 } 6544 6545 /** 6546 * Move any important data from the simplified segment object 6547 * back to the real segment object for future phases 6548 * 6549 * @private 6550 */ 6551 }, { 6552 key: 'processSegmentResponse_', 6553 value: function processSegmentResponse_(simpleSegment) { 6554 var segmentInfo = this.pendingSegment_; 6555 6556 segmentInfo.bytes = simpleSegment.bytes; 6557 if (simpleSegment.map) { 6558 segmentInfo.segment.map.bytes = simpleSegment.map.bytes; 6559 } 6560 6561 segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; 6562 this.handleSegment_(); 6563 } 6564 6565 /** 6566 * append a decrypted segement to the SourceBuffer through a SourceUpdater 6567 * 6568 * @private 6569 */ 6570 }, { 6571 key: 'handleSegment_', 6572 value: function handleSegment_() { 6573 var _this3 = this; 6574 6575 if (!this.pendingSegment_) { 6576 this.state = 'READY'; 6577 return; 6578 } 6579 6580 var segmentInfo = this.pendingSegment_; 6581 var segment = segmentInfo.segment; 6582 var timingInfo = this.syncController_.probeSegmentInfo(segmentInfo); 6583 6584 // When we have our first timing info, determine what media types this loader is 6585 // dealing with. Although we're maintaining extra state, it helps to preserve the 6586 // separation of segment loader from the actual source buffers. 6587 if (typeof this.startingMedia_ === 'undefined' && timingInfo && ( 6588 // Guard against cases where we're not getting timing info at all until we are 6589 // certain that all streams will provide it. 6590 timingInfo.containsAudio || timingInfo.containsVideo)) { 6591 this.startingMedia_ = { 6592 containsAudio: timingInfo.containsAudio, 6593 containsVideo: timingInfo.containsVideo 6594 }; 6595 } 6596 6597 var illegalMediaSwitchError = illegalMediaSwitch(this.loaderType_, this.startingMedia_, timingInfo); 6598 6599 if (illegalMediaSwitchError) { 6600 this.error({ 6601 message: illegalMediaSwitchError 6602 }); 6603 this.trigger('error'); 6604 return; 6605 } 6606 6607 this.state = 'APPENDING'; 6608 6609 if (segmentInfo.isSyncRequest) { 6610 this.trigger('syncinfoupdate'); 6611 this.pendingSegment_ = null; 6612 this.state = 'READY'; 6613 return; 6614 } 6615 6616 if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) { 6617 this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); 6618 // fired when a timestamp offset is set in HLS (can also identify discontinuities) 6619 this.trigger('timestampoffset'); 6620 } 6621 6622 // if the media initialization segment is changing, append it 6623 // before the content segment 6624 if (segment.map) { 6625 (function () { 6626 var initId = (0, _binUtils.initSegmentId)(segment.map); 6627 6628 if (!_this3.activeInitSegmentId_ || _this3.activeInitSegmentId_ !== initId) { 6629 var initSegment = _this3.initSegment(segment.map); 6630 6631 _this3.sourceUpdater_.appendBuffer(initSegment.bytes, function () { 6632 _this3.activeInitSegmentId_ = initId; 6633 }); 6634 } 6635 })(); 6636 } 6637 6638 segmentInfo.byteLength = segmentInfo.bytes.byteLength; 6639 if (typeof segment.start === 'number' && typeof segment.end === 'number') { 6640 this.mediaSecondsLoaded += segment.end - segment.start; 6641 } else { 6642 this.mediaSecondsLoaded += segment.duration; 6643 } 6644 6645 this.sourceUpdater_.appendBuffer(segmentInfo.bytes, this.handleUpdateEnd_.bind(this)); 6646 } 6647 6648 /** 6649 * callback to run when appendBuffer is finished. detects if we are 6650 * in a good state to do things with the data we got, or if we need 6651 * to wait for more 6652 * 6653 * @private 6654 */ 6655 }, { 6656 key: 'handleUpdateEnd_', 6657 value: function handleUpdateEnd_() { 6658 this.logger_('handleUpdateEnd_', 'segmentInfo:', this.pendingSegment_); 6659 6660 if (!this.pendingSegment_) { 6661 this.state = 'READY'; 6662 if (!this.paused()) { 6663 this.monitorBuffer_(); 6664 } 6665 return; 6666 } 6667 6668 var segmentInfo = this.pendingSegment_; 6669 var segment = segmentInfo.segment; 6670 var isWalkingForward = this.mediaIndex !== null; 6671 6672 this.pendingSegment_ = null; 6673 this.recordThroughput_(segmentInfo); 6674 this.addSegmentMetadataCue_(segmentInfo); 6675 6676 this.state = 'READY'; 6677 6678 this.mediaIndex = segmentInfo.mediaIndex; 6679 this.fetchAtBuffer_ = true; 6680 this.currentTimeline_ = segmentInfo.timeline; 6681 6682 // We must update the syncinfo to recalculate the seekable range before 6683 // the following conditional otherwise it may consider this a bad "guess" 6684 // and attempt to resync when the post-update seekable window and live 6685 // point would mean that this was the perfect segment to fetch 6686 this.trigger('syncinfoupdate'); 6687 6688 // If we previously appended a segment that ends more than 3 targetDurations before 6689 // the currentTime_ that means that our conservative guess was too conservative. 6690 // In that case, reset the loader state so that we try to use any information gained 6691 // from the previous request to create a new, more accurate, sync-point. 6692 if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) { 6693 this.resetEverything(); 6694 return; 6695 } 6696 6697 // Don't do a rendition switch unless we have enough time to get a sync segment 6698 // and conservatively guess 6699 if (isWalkingForward) { 6700 this.trigger('bandwidthupdate'); 6701 } 6702 this.trigger('progress'); 6703 6704 // any time an update finishes and the last segment is in the 6705 // buffer, end the stream. this ensures the "ended" event will 6706 // fire if playback reaches that point. 6707 var isEndOfStream = detectEndOfStream(segmentInfo.playlist, this.mediaSource_, segmentInfo.mediaIndex + 1); 6708
6709 if (isEndOfStream) { 6710 this.endOfStream(); 6711 } 6712 6713 if (!this.paused()) { 6714 this.monitorBuffer_(); 6715 } 6716 } 6717 6718 /** 6719 * Records the current throughput of the decrypt, transmux, and append 6720 * portion of the semgment pipeline. `throughput.rate` is a the cumulative 6721 * moving average of the throughput. `throughput.count` is the number of 6722 * data points in the average. 6723 * 6724 * @private 6725 * @param {Object} segmentInfo the object returned by loadSegment 6726 */ 6727 }, { 6728 key: 'recordThroughput_', 6729 value: function recordThroughput_(segmentInfo) { 6730 var rate = this.throughput.rate; 6731 // Add one to the time to ensure that we don't accidentally attempt to divide 6732 // by zero in the case where the throughput is ridiculously high 6733 var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; 6734 // Multiply by 8000 to convert from bytes/millisecond to bits/second 6735 var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); 6736 6737 // This is just a cumulative moving average calculation: 6738 // newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1) 6739 this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count; 6740 } 6741 6742 /** 6743 * A debugging logger noop that is set to console.log only if debugging 6744 * is enabled globally 6745 * 6746 * @private 6747 */ 6748 }, { 6749 key: 'logger_', 6750 value: function logger_() {} 6751 6752 /** 6753 * Adds a cue to the segment-metadata track with some metadata information about the 6754 * segment 6755 * 6756 * @private 6757 * @param {Object} segmentInfo 6758 * the object returned by loadSegment 6759 * @method addSegmentMetadataCue_ 6760 */ 6761 }, { 6762 key: 'addSegmentMetadataCue_', 6763 value: function addSegmentMetadataCue_(segmentInfo) { 6764 if (!this.segmentMetadataTrack_) { 6765 return; 6766 } 6767 6768 var segment = segmentInfo.segment; 6769 var start = segment.start; 6770 var end = segment.end; 6771 6772 // Do not try adding the cue if the start and end times are invalid. 6773 if (!finite(start) || !finite(end)) { 6774 return; 6775 } 6776 6777 (0, _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs2['default'])(start, end, this.segmentMetadataTrack_); 6778 6779 var Cue = _globalWindow2['default'].WebKitDataCue || _globalWindow2['default'].VTTCue; 6780 var value = { 6781 uri: segmentInfo.uri, 6782 timeline: segmentInfo.timeline, 6783 playlist: segmentInfo.playlist.uri, 6784 start: start, 6785 end: end 6786 }; 6787 var data = JSON.stringify(value); 6788 var cue = new Cue(start, end, data); 6789 6790 // Attach the metadata to the value property of the cue to keep consistency between 6791 // the differences of WebKitDataCue in safari and VTTCue in other browsers 6792 cue.value = value; 6793 6794 this.segmentMetadataTrack_.addCue(cue); 6795 } 6796 }]); 6797 6798 return SegmentLoader; 6799})(_videoJs2['default'].EventTarget); 6800 6801exports['default'] = SegmentLoader; 6802}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 6803},{"./bin-utils":2,"./config":3,"./media-segment-request":7,"./playlist":11,"./playlist-selectors":10,"./ranges":12,"./source-updater":17,"global/window":32,"videojs-contrib-media-sources/es5/remove-cues-from-track.js":72}],17:[function(require,module,exports){ 6804(function (global){ 6805/** 6806 * @file source-updater.js 6807 */ 6808'use strict'; 6809 6810Object.defineProperty(exports, '__esModule', { 6811 value: true 6812}); 6813 6814var _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; }; })(); 6815 6816function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 6817 6818function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 6819 6820var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 6821 6822var _videoJs2 = _interopRequireDefault(_videoJs); 6823 6824var noop = function noop() {}; 6825 6826/** 6827 * A queue of callbacks to be serialized and applied when a
6828 * MediaSource and its associated SourceBuffers are not in the 6829 * updating state. It is used by the segment loader to update the 6830 * underlying SourceBuffers when new data is loaded, for instance. 6831 * 6832 * @class SourceUpdater 6833 * @param {MediaSource} mediaSource the MediaSource to create the 6834 * SourceBuffer from 6835 * @param {String} mimeType the desired MIME type of the underlying 6836 * SourceBuffer 6837 */ 6838 6839var SourceUpdater = (function () { 6840 function SourceUpdater(mediaSource, mimeType) { 6841 var _this = this; 6842 6843 _classCallCheck(this, SourceUpdater); 6844 6845 var createSourceBuffer = function createSourceBuffer() { 6846 _this.sourceBuffer_ = mediaSource.addSourceBuffer(mimeType); 6847 6848 // run completion handlers and process callbacks as updateend 6849 // events fire 6850 _this.onUpdateendCallback_ = function () { 6851 var pendingCallback = _this.pendingCallback_; 6852 6853 _this.pendingCallback_ = null; 6854 6855 if (pendingCallback) { 6856 pendingCallback(); 6857 } 6858 6859 _this.runCallback_(); 6860 }; 6861 6862 _this.sourceBuffer_.addEventListener('updateend', _this.onUpdateendCallback_); 6863 6864 _this.runCallback_(); 6865 }; 6866 6867 this.callbacks_ = []; 6868 this.pendingCallback_ = null; 6869 this.timestampOffset_ = 0; 6870 this.mediaSource = mediaSource; 6871 this.processedAppend_ = false; 6872 6873 if (mediaSource.readyState === 'closed') { 6874 mediaSource.addEventListener('sourceopen', createSourceBuffer); 6875 } else { 6876 createSourceBuffer(); 6877 } 6878 } 6879 6880 /** 6881 * Aborts the current segment and resets the segment parser. 6882 * 6883 * @param {Function} done function to call when done 6884 * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void 6885 */ 6886 6887 _createClass(SourceUpdater, [{ 6888 key: 'abort', 6889 value: function abort(done) { 6890 var _this2 = this; 6891 6892 if (this.processedAppend_) { 6893 this.queueCallback_(function () { 6894 _this2.sourceBuffer_.abort(); 6895 }, done); 6896 } 6897 } 6898 6899 /** 6900 * Queue an update to append an ArrayBuffer. 6901 * 6902 * @param {ArrayBuffer} bytes 6903 * @param {Function} done the function to call when done 6904 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data 6905 */ 6906 }, { 6907 key: 'appendBuffer', 6908 value: function appendBuffer(bytes, done) { 6909 var _this3 = this; 6910 6911 this.processedAppend_ = true; 6912 6913 this.queueCallback_(function () { 6914 _this3.sourceBuffer_.appendBuffer(bytes); 6915 }, done); 6916 } 6917 6918 /** 6919 * Indicates what TimeRanges are buffered in the managed SourceBuffer. 6920 * 6921 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered 6922 */ 6923 }, { 6924 key: 'buffered', 6925 value: function buffered() { 6926 if (!this.sourceBuffer_) { 6927 return _videoJs2['default'].createTimeRanges(); 6928 } 6929 return this.sourceBuffer_.buffered; 6930 } 6931 6932 /** 6933 * Queue an update to remove a time range from the buffer. 6934 * 6935 * @param {Number} start where to start the removal 6936 * @param {Number} end where to end the removal 6937 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end 6938 */ 6939 }, { 6940 key: 'remove', 6941 value: function remove(start, end) { 6942 var _this4 = this; 6943 6944 if (this.processedAppend_) { 6945 this.queueCallback_(function () { 6946 _this4.sourceBuffer_.remove(start, end); 6947 }, noop); 6948 } 6949 } 6950 6951 /** 6952 * Whether the underlying sourceBuffer is updating or not 6953 * 6954 * @return {Boolean} the updating status of the SourceBuffer 6955 */ 6956 }, { 6957 key: 'updating', 6958 value: function updating() { 6959 return !this.sourceBuffer_ || this.sourceBuffer_.updating || this.pendingCallback_; 6960 } 6961 6962 /** 6963 * Set/get the timestampoffset on the SourceBuffer 6964 * 6965 * @return {Number} the timestamp offset 6966 */ 6967 }, { 6968 key: 'timestampOffset', 6969 value: function timestampOffset(offset) { 6970 var _this5 = this; 6971 6972 if (typeof offset !== 'undefined') { 6973 this.queueCallback_(function () { 6974 _this5.sourceBuffer_.timestampOffset = offset; 6975 }); 6976 this.timestampOffset_ = offset; 6977 } 6978 return this.timestampOffset_; 6979 } 6980 6981 /** 6982 * Queue a callback to run 6983 */ 6984 }, { 6985 key: 'queueCallback_', 6986 value: function queueCallback_(callback, done) { 6987 this.callbacks_.push([callback.bind(this), done]); 6988 this.runCallback_(); 6989 } 6990 6991 /** 6992 * Run a queued callback 6993 */ 6994 }, { 6995 key: 'runCallback_', 6996 value: function runCallback_() { 6997 var callbacks = undefined; 6998 6999 if (!this.updating() && this.callbacks_.length) { 7000 callbacks = this.callbacks_.shift(); 7001 this.pendingCallback_ = callbacks[1]; 7002 callbacks[0](); 7003 } 7004 } 7005 7006 /** 7007 * dispose of the source updater and the underlying sourceBuffer 7008 */ 7009 }, { 7010 key: 'dispose', 7011 value: function dispose() { 7012 this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_); 7013 if (this.sourceBuffer_ && this.mediaSource.readyState === 'open') { 7014 this.sourceBuffer_.abort(); 7015 } 7016 } 7017 }]); 7018 7019 return SourceUpdater; 7020})(); 7021 7022exports['default'] = SourceUpdater; 7023module.exports = exports['default']; 7024}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 7025},{}],18:[function(require,module,exports){ 7026(function (global){ 7027/** 7028 * @file sync-controller.js 7029 */ 7030 7031'use strict'; 7032 7033Object.defineProperty(exports, '__esModule', { 7034 value: true 7035}); 7036 7037var _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; }; })(); 7038 7039var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _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 { _x2 = parent; _x3 = property; _x4 = 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); } } }; 7040 7041function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 7042 7043function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 7044 7045function _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, { c
7045onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 7046 7047var _muxJsLibMp4Probe = require('mux.js/lib/mp4/probe'); 7048 7049var _muxJsLibMp4Probe2 = _interopRequireDefault(_muxJsLibMp4Probe); 7050 7051var _muxJsLibToolsTsInspectorJs = require('mux.js/lib/tools/ts-inspector.js'); 7052 7053var _playlist = require('./playlist'); 7054 7055var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 7056 7057var _videoJs2 = _interopRequireDefault(_videoJs); 7058 7059var syncPointStrategies = [ 7060// Stategy "VOD": Handle the VOD-case where the sync-point is *always* 7061// the equivalence display-time 0 === segment-index 0 7062{ 7063 name: 'VOD', 7064 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 7065 if (duration !== Infinity) { 7066 var syncPoint = { 7067 time: 0, 7068 segmentIndex: 0 7069 }; 7070 7071 return syncPoint; 7072 } 7073 return null; 7074 } 7075}, 7076// Stategy "ProgramDateTime": We have a program-date-time tag in this playlist 7077{ 7078 name: 'ProgramDateTime', 7079 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 7080 if (syncController.datetimeToDisplayTime && playlist.dateTimeObject) { 7081 var playlistTime = playlist.dateTimeObject.getTime() / 1000; 7082 var playlistStart = playlistTime + syncController.datetimeToDisplayTime; 7083 var syncPoint = { 7084 time: playlistStart, 7085 segmentIndex: 0 7086 }; 7087 7088 return syncPoint; 7089 } 7090 return null; 7091 } 7092}, 7093// Stategy "Segment": We have a known time mapping for a timeline and a 7094// segment in the current timeline with timing data 7095{ 7096 name: 'Segment', 7097 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 7098 var segments = playlist.segments || []; 7099 var syncPoint = null; 7100 var lastDistance = null; 7101 7102 currentTime = currentTime || 0; 7103 7104 for (var i = 0; i < segments.length; i++) { 7105 var segment = segments[i]; 7106 7107 if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') { 7108 var distance = Math.abs(currentTime - segment.start); 7109 7110 // Once the distance begins to increase, we have passed 7111 // currentTime and can stop looking for better candidates 7112 if (lastDistance !== null && lastDistance < distance) { 7113 break; 7114 } 7115 7116 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 7117 lastDistance = distance; 7118 syncPoint = { 7119 time: segment.start, 7120 segmentIndex: i 7121 }; 7122 } 7123 } 7124 } 7125 return syncPoint; 7126 } 7127}, 7128// Stategy "Discontinuity": We have a discontinuity with a known 7129// display-time 7130{ 7131 name: 'Discontinuity', 7132 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 7133 var syncPoint = null; 7134 7135 currentTime = currentTime || 0; 7136 7137 if (playlist.discontinuityStarts && playlist.discontinuityStarts.length) { 7138 var lastDistance = null; 7139 7140 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 7141 var segmentIndex = playlist.discontinuityStarts[i]; 7142 var discontinuity = playlist.discontinuitySequence + i + 1; 7143 var discontinuitySync = syncController.discontinuities[discontinuity]; 7144 7145 if (discontinuitySync) { 7146 var distance = Math.abs(currentTime - discontinuitySync.time); 7147 7148 // Once the distance begins to increase, we have passed 7149 // currentTime and can stop looking for better candidates 7150 if (lastDistance !== null && lastDistance < distance) { 7151 break; 7152 } 7153 7154 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 7155 lastDistance = distance; 7156 syncPoint = { 7157 time: discontinuitySync.time, 7158 segmentIndex: segmentIndex 7159 }; 7160 } 7161 } 7162 } 7163 } 7164 return syncPoint; 7165 } 7166}, 7167// Stategy "Playlist": We have a playlist with a known mapping of 7168// segment index to display time 7169{ 7170 name: 'Playlist', 7171 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 7172 if (playlist.syncInfo) { 7173 var syncPoint = { 7174 time: playlist.syncInfo.time, 7175 segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence 7176 }; 7177 7178 return syncPoint; 7179 } 7180 return null; 7181 } 7182}]; 7183 7184exports.syncPointStrategies = syncPointStrategies; 7185 7186var SyncController = (function (_videojs$EventTarget) { 7187 _inherits(SyncController, _videojs$EventTarget); 7188 7189 function SyncController() { 7190 var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 7191 7192 _classCallCheck(this, SyncController); 7193 7194 _get(Object.getPrototypeOf(SyncController.prototype), 'constructor', this).call(this); 7195 // Segment Loader state variables... 7196 // ...for synching across variants 7197 this.inspectCache_ = undefined; 7198 7199 // ...for synching across variants 7200 this.timelines = []; 7201 this.discontinuities = []; 7202 this.datetimeToDisplayTime = null; 7203 7204 if (options.debug) { 7205 this.logger_ = _videoJs2['default'].log.bind(_videoJs2['default'], 'sync-controller ->'); 7206 } 7207 } 7208 7209 /** 7210 * Find a sync-point for the playlist specified 7211 * 7212 * A sync-point is defined as a known mapping from display-time to 7213 * a segment-index in the current playlist. 7214 * 7215 * @param {Playlist} playlist 7216 * The playlist that needs a sync-point 7217 * @param {Number} duration 7218 * Duration of the MediaSource (Infinite if playing a live source) 7219 * @param {Number} currentTimeline 7220 * The last timeline from which a segment was loaded 7221 * @returns {Object} 7222 * A sync-point object 7223 */ 7224 7225 _createClass(SyncController, [{ 7226 key: 'getSyncPoint', 7227 value: function getSyncPoint(playlist, duration, currentTimeline, currentTime) { 7228 var syncPoints = this.runStrategies_(playlist, duration, currentTimeline, currentTime); 7229 7230 if (!syncPoints.length) { 7231 // Signal that we need to attempt to get a sync-point manually 7232 // by fetching a segment in the playlist and constructing 7233 // a sync-point from that information 7234 return null; 7235 } 7236 7237 // Now find the sync-point that is closest to the currentTime because 7238 // that should result in the most accurate guess about which segment 7239 // to fetch 7240 return this.selectSyncPoint_(syncPoints, { key: 'time', value: currentTime }); 7241 } 7242 7243 /** 7244 * Calculate the amount of time that has expired off the playlist during playback 7245 * 7246 * @param {Playlist} playlist 7247 * Playlist object to calculate expired from 7248 * @param {Number} duration 7249 * Duration of the MediaSource (Infinity if playling a live source) 7250 * @returns {Number|null} 7251 * The amount of time that has expired off the playlist during playback. Null 7252 * if no sync-points for the playlist can be found. 7253 */ 7254 }, { 7255 key: 'getExpiredTime', 7256 value: function getExpiredTime(playlist, duration) { 7257 if (!playlist || !playlist.segments) { 7258 return null; 7259 } 7260 7261 var syncPoints = this.runStrategies_(playlist, duration, playlist.discontinuitySequence, 0); 7262
vendor: 10,174 bytes, lines 7263-7554
7263 // Without sync-points, there is not enough information to determine the expired time 7264 if (!syncPoints.length) { 7265 return null; 7266 } 7267 7268 var syncPoint = this.selectSyncPoint_(syncPoints, { 7269 key: 'segmentIndex', 7270 value: 0 7271 }); 7272 7273 // If the sync-point is beyond the start of the playlist, we want to subtract the 7274 // duration from index 0 to syncPoint.segmentIndex instead of adding. 7275 if (syncPoint.segmentIndex > 0) { 7276 syncPoint.time *= -1; 7277 } 7278 7279 return Math.abs(syncPoint.time + (0, _playlist.sumDurations)(playlist, syncPoint.segmentIndex, 0)); 7280 } 7281 7282 /** 7283 * Runs each sync-point strategy and returns a list of sync-points returned by the 7284 * strategies 7285 * 7286 * @private 7287 * @param {Playlist} playlist 7288 * The playlist that needs a sync-point 7289 * @param {Number} duration 7290 * Duration of the MediaSource (Infinity if playing a live source) 7291 * @param {Number} currentTimeline 7292 * The last timeline from which a segment was loaded 7293 * @returns {Array} 7294 * A list of sync-point objects 7295 */ 7296 }, { 7297 key: 'runStrategies_', 7298 value: function runStrategies_(playlist, duration, currentTimeline, currentTime) { 7299 var syncPoints = []; 7300 7301 // Try to find a sync-point in by utilizing various strategies... 7302 for (var i = 0; i < syncPointStrategies.length; i++) { 7303 var strategy = syncPointStrategies[i]; 7304 var syncPoint = strategy.run(this, playlist, duration, currentTimeline, currentTime); 7305 7306 if (syncPoint) { 7307 syncPoint.strategy = strategy.name; 7308 syncPoints.push({ 7309 strategy: strategy.name, 7310 syncPoint: syncPoint 7311 }); 7312 this.logger_('syncPoint found via <' + strategy.name + '>:', syncPoint); 7313 } 7314 } 7315 7316 return syncPoints; 7317 } 7318 7319 /** 7320 * Selects the sync-point nearest the specified target 7321 * 7322 * @private 7323 * @param {Array} syncPoints 7324 * List of sync-points to select from 7325 * @param {Object} target 7326 * Object specifying the property and value we are targeting 7327 * @param {String} target.key 7328 * Specifies the property to target. Must be either 'time' or 'segmentIndex' 7329 * @param {Number} target.value 7330 * The value to target for the specified key. 7331 * @returns {Object} 7332 * The sync-point nearest the target 7333 */ 7334 }, { 7335 key: 'selectSyncPoint_', 7336 value: function selectSyncPoint_(syncPoints, target) { 7337 var bestSyncPoint = syncPoints[0].syncPoint; 7338 var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value); 7339 var bestStrategy = syncPoints[0].strategy; 7340 7341 for (var i = 1; i < syncPoints.length; i++) { 7342 var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value); 7343 7344 if (newDistance < bestDistance) { 7345 bestDistance = newDistance; 7346 bestSyncPoint = syncPoints[i].syncPoint; 7347 bestStrategy = syncPoints[i].strategy; 7348 } 7349 } 7350 7351 this.logger_('syncPoint with strategy <' + bestStrategy + '> chosen: ', bestSyncPoint); 7352 return bestSyncPoint; 7353 } 7354 7355 /** 7356 * Save any meta-data present on the segments when segments leave 7357 * the live window to the playlist to allow for synchronization at the 7358 * playlist level later. 7359 * 7360 * @param {Playlist} oldPlaylist - The previous active playlist 7361 * @param {Playlist} newPlaylist - The updated and most current playlist 7362 */ 7363 }, { 7364 key: 'saveExpiredSegmentInfo', 7365 value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) { 7366 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; 7367 7368 // When a segment expires from the playlist and it has a start time 7369 // save that information as a possible sync-point reference in future 7370 for (var i = mediaSequenceDiff - 1; i >= 0; i--) { 7371 var lastRemovedSegment = oldPlaylist.segments[i]; 7372 7373 if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') { 7374 newPlaylist.syncInfo = { 7375 mediaSequence: oldPlaylist.mediaSequence + i, 7376 time: lastRemovedSegment.start 7377 }; 7378 this.logger_('playlist sync:', newPlaylist.syncInfo); 7379 this.trigger('syncinfoupdate'); 7380 break; 7381 } 7382 } 7383 } 7384 7385 /** 7386 * Save the mapping from playlist's ProgramDateTime to display. This should 7387 * only ever happen once at the start of playback. 7388 * 7389 * @param {Playlist} playlist - The currently active playlist 7390 */ 7391 }, { 7392 key: 'setDateTimeMapping', 7393 value: function setDateTimeMapping(playlist) { 7394 if (!this.datetimeToDisplayTime && playlist.dateTimeObject) { 7395 var playlistTimestamp = playlist.dateTimeObject.getTime() / 1000; 7396 7397 this.datetimeToDisplayTime = -playlistTimestamp; 7398 } 7399 } 7400 7401 /** 7402 * Reset the state of the inspection cache when we do a rendition 7403 * switch 7404 */ 7405 }, { 7406 key: 'reset', 7407 value: function reset() { 7408 this.inspectCache_ = undefined; 7409 } 7410 7411 /** 7412 * Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start 7413 * and end of the segment in it's internal "media time". Used to generate 7414 * mappings from that internal "media time" to the display time that is 7415 * shown on the player. 7416 * 7417 * @param {SegmentInfo} segmentInfo - The current active request information 7418 */ 7419 }, { 7420 key: 'probeSegmentInfo', 7421 value: function probeSegmentInfo(segmentInfo) { 7422 var segment = segmentInfo.segment; 7423 var playlist = segmentInfo.playlist; 7424 var timingInfo = undefined; 7425 7426 if (segment.map) { 7427 timingInfo = this.probeMp4Segment_(segmentInfo); 7428 } else { 7429 timingInfo = this.probeTsSegment_(segmentInfo); 7430 } 7431 7432 if (timingInfo) { 7433 if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) { 7434 this.saveDiscontinuitySyncInfo_(segmentInfo); 7435 7436 // If the playlist does not have sync information yet, record that information 7437 // now with segment timing information 7438 if (!playlist.syncInfo) { 7439 playlist.syncInfo = { 7440 mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex, 7441 time: segment.start 7442 }; 7443 } 7444 } 7445 } 7446 7447 return timingInfo; 7448 } 7449 7450 /** 7451 * Probe an fmp4 or an mpeg2-ts segment to determine the start of the segment 7452 * in it's internal "media time". 7453 * 7454 * @private 7455 * @param {SegmentInfo} segmentInfo - The current active request information 7456 * @return {object} The start and end time of the current segment in "media time" 7457 */ 7458 }, { 7459 key: 'probeMp4Segment_', 7460 value: function probeMp4Segment_(segmentInfo) { 7461 var segment = segmentInfo.segment; 7462 var timescales = _muxJsLibMp4Probe2['default'].timescale(segment.map.bytes); 7463 var startTime = _muxJsLibMp4Probe2['default'].startTime(timescales, segmentInfo.bytes); 7464 7465 if (segmentInfo.timestampOffset !== null) { 7466 segmentInfo.timestampOffset -= startTime; 7467 } 7468 7469 return { 7470 start: startTime, 7471 end: startTime + segment.duration 7472 }; 7473 } 7474 7475 /** 7476 * Probe an mpeg2-ts segment to determine the start and end of the segment 7477 * in it's internal "media time". 7478 * 7479 * @private 7480 * @param {SegmentInfo} segmentInfo - The current active request information 7481 * @return {object} The start and end time of the current segment in "media time" 7482 */ 7483 }, { 7484 key: 'probeTsSegment_', 7485 value: function probeTsSegment_(segmentInfo) { 7486 var timeInfo = (0, _muxJsLibToolsTsInspectorJs.inspect)(segmentInfo.bytes, this.inspectCache_); 7487 var segmentStartTime = undefined; 7488 var segmentEndTime = undefined; 7489 7490 if (!timeInfo) { 7491 return null; 7492 } 7493 7494 if (timeInfo.video && timeInfo.video.length === 2) { 7495 this.inspectCache_ = timeInfo.video[1].dts; 7496 segmentStartTime = timeInfo.video[0].dtsTime; 7497 segmentEndTime = timeInfo.video[1].dtsTime; 7498 } else if (timeInfo.audio && timeInfo.audio.length === 2) { 7499 this.inspectCache_ = timeInfo.audio[1].dts; 7500 segmentStartTime = timeInfo.audio[0].dtsTime; 7501 segmentEndTime = timeInfo.audio[1].dtsTime; 7502 } 7503 7504 return { 7505 start: segmentStartTime, 7506 end: segmentEndTime, 7507 containsVideo: timeInfo.video && timeInfo.video.length === 2, 7508 containsAudio: timeInfo.audio && timeInfo.audio.length === 2 7509 }; 7510 } 7511 }, { 7512 key: 'timestampOffsetForTimeline', 7513 value: function timestampOffsetForTimeline(timeline) { 7514 if (typeof this.timelines[timeline] === 'undefined') { 7515 return null; 7516 } 7517 return this.timelines[timeline].time; 7518 } 7519 7520 /** 7521 * Use the "media time" for a segment to generate a mapping to "display time" and 7522 * save that display time to the segment. 7523 * 7524 * @private 7525 * @param {SegmentInfo} segmentInfo 7526 * The current active request information 7527 * @param {object} timingInfo 7528 * The start and end time of the current segment in "media time" 7529 * @returns {Boolean} 7530 * Returns false if segment time mapping could not be calculated 7531 */ 7532 }, { 7533 key: 'calculateSegmentTimeMapping_', 7534 value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) { 7535 var segment = segmentInfo.segment; 7536 var mappingObj = this.timelines[segmentInfo.timeline]; 7537 7538 if (segmentInfo.timestampOffset !== null) { 7539 this.logger_('tsO:', segmentInfo.timestampOffset); 7540 7541 mappingObj = { 7542 time: segmentInfo.startOfSegment, 7543 mapping: segmentInfo.startOfSegment - timingInfo.start 7544 }; 7545 this.timelines[segmentInfo.timeline] = mappingObj; 7546 this.trigger('timestampoffset'); 7547 7548 segment.start = segmentInfo.startOfSegment; 7549 segment.end = timingInfo.end + mappingObj.mapping; 7550 } else if (mappingObj) { 7551 segment.start = timingInfo.start + mappingObj.mapping; 7552 segment.end = timingInfo.end + mappingObj.mapping; 7553 } else { 7554 return false;
7555 } 7556 7557 return true; 7558 } 7559 7560 /** 7561 * Each time we have discontinuity in the playlist, attempt to calculate the location 7562 * in display of the start of the discontinuity and save that. We also save an accuracy 7563 * value so that we save values with the most accuracy (closest to 0.) 7564 * 7565 * @private 7566 * @param {SegmentInfo} segmentInfo - The current active request information 7567 */ 7568 }, { 7569 key: 'saveDiscontinuitySyncInfo_', 7570 value: function saveDiscontinuitySyncInfo_(segmentInfo) { 7571 var playlist = segmentInfo.playlist; 7572 var segment = segmentInfo.segment; 7573 7574 // If the current segment is a discontinuity then we know exactly where 7575 // the start of the range and it's accuracy is 0 (greater accuracy values 7576 // mean more approximation) 7577 if (segment.discontinuity) { 7578 this.discontinuities[segment.timeline] = { 7579 time: segment.start, 7580 accuracy: 0 7581 }; 7582 } else if (playlist.discontinuityStarts.length) { 7583 // Search for future discontinuities that we can provide better timing 7584 // information for and save that information for sync purposes 7585 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 7586 var segmentIndex = playlist.discontinuityStarts[i]; 7587 var discontinuity = playlist.discontinuitySequence + i + 1; 7588 var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex; 7589 var accuracy = Math.abs(mediaIndexDiff); 7590 7591 if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) { 7592 var time = undefined; 7593 7594 if (mediaIndexDiff < 0) { 7595 time = segment.start - (0, _playlist.sumDurations)(playlist, segmentInfo.mediaIndex, segmentIndex); 7596 } else { 7597 time = segment.end + (0, _playlist.sumDurations)(playlist, segmentInfo.mediaIndex + 1, segmentIndex); 7598 } 7599 7600 this.discontinuities[discontinuity] = { 7601 time: time, 7602 accuracy: accuracy 7603 }; 7604 } 7605 } 7606 } 7607 } 7608 7609 /** 7610 * A debugging logger noop that is set to console.log only if debugging 7611 * is enabled globally 7612 * 7613 * @private 7614 */ 7615 }, { 7616 key: 'logger_', 7617 value: function logger_() {} 7618 }]); 7619 7620 return SyncController; 7621})(_videoJs2['default'].EventTarget); 7622 7623exports['default'] = SyncController; 7624}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 7625},{"./playlist":11,"mux.js/lib/mp4/probe":57,"mux.js/lib/tools/ts-inspector.js":59}],19:[function(require,module,exports){ 7626 7627/** 7628 * @file - codecs.js - Handles tasks regarding codec strings such as translating them to 7629 * codec strings, or translating codec strings into objects that can be examined. 7630 */ 7631 7632/** 7633 * Parses a codec string to retrieve the number of codecs specified, 7634 * the video codec and object type indicator, and the audio profile. 7635 */ 7636 7637'use strict'; 7638 7639Object.defineProperty(exports, '__esModule', { 7640 value: true 7641}); 7642var parseCodecs = function parseCodecs() { 7643 var codecs = arguments.length <= 0 || arguments[0] === undefined ? '' : arguments[0]; 7644 7645 var result = { 7646 codecCount: 0 7647 }; 7648 var parsed = undefined; 7649 7650 result.codecCount = codecs.split(',').length; 7651 result.codecCount = result.codecCount || 2; 7652 7653 // parse the video codec 7654 parsed = /(^|\s|,)+(avc1)([^ ,]*)/i.exec(codecs); 7655 if (parsed) { 7656 result.videoCodec = parsed[2]; 7657 result.videoObjectTypeIndicator = parsed[3]; 7658 } 7659 7660 // parse the last field of the audio codec 7661 result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs); 7662 result.audioProfile = result.audioProfile && result.audioProfile[2]; 7663 7664 return result; 7665}; 7666exports.parseCodecs = parseCodecs; 7667},{}],20:[function(require,module,exports){ 7668(function (global){ 7669/** 7670 * @file vtt-segment-loader.js 7671 */ 7672'use strict'; 7673 7674Object.defineProperty(exports, '__esModule', { 7675 value: true 7676}); 7677 7678var _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; }; })(); 7679 7680var _get = function get(_x3, _x4, _x5) { var _again = true; _function: while (_again) { var object = _x3, property = _x4, receiver = _x5; _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 { _x3 = parent; _x4 = property; _x5 = 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); } } }; 7681 7682function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 7683 7684function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 7685 7686function _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, { c
7686onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 7687 7688var _segmentLoader = require('./segment-loader'); 7689 7690var _segmentLoader2 = _interopRequireDefault(_segmentLoader); 7691 7692var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 7693 7694var _videoJs2 = _interopRequireDefault(_videoJs); 7695 7696var _globalWindow = require('global/window'); 7697 7698var _globalWindow2 = _interopRequireDefault(_globalWindow); 7699 7700var _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs = require('videojs-contrib-media-sources/es5/remove-cues-from-track.js'); 7701 7702var _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs2 = _interopRequireDefault(_videojsContribMediaSourcesEs5RemoveCuesFromTrackJs); 7703 7704var _binUtils = require('./bin-utils'); 7705 7706var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (char) { 7707 return char.charCodeAt(0); 7708})); 7709 7710var uintToString = function uintToString(uintArray) { 7711 return String.fromCharCode.apply(null, uintArray); 7712}; 7713 7714/** 7715 * An object that manages segment loading and appending. 7716 * 7717 * @class VTTSegmentLoader 7718 * @param {Object} options required and optional options 7719 * @extends videojs.EventTarget 7720 */ 7721 7722var VTTSegmentLoader = (function (_SegmentLoader) { 7723 _inherits(VTTSegmentLoader, _SegmentLoader); 7724 7725 function VTTSegmentLoader(settings) { 7726 var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; 7727 7728 _classCallCheck(this, VTTSegmentLoader); 7729 7730 _get(Object.getPrototypeOf(VTTSegmentLoader.prototype), 'constructor', this).call(this, settings, options); 7731 7732 // SegmentLoader requires a MediaSource be specified or it will throw an error; 7733 // however, VTTSegmentLoader has no need of a media source, so delete the reference 7734 this.mediaSource_ = null; 7735 7736 this.subtitlesTrack_ = null; 7737 } 7738 7739 /** 7740 * Indicates which time ranges are buffered 7741 * 7742 * @return {TimeRange} 7743 * TimeRange object representing the current buffered ranges 7744 */ 7745 7746 _createClass(VTTSegmentLoader, [{ 7747 key: 'buffered_', 7748 value: function buffered_() { 7749 if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) { 7750 return _videoJs2['default'].createTimeRanges(); 7751 } 7752 7753 var cues = this.subtitlesTrack_.cues; 7754 var start = cues[0].startTime; 7755 var end = cues[cues.length - 1].startTime; 7756 7757 return _videoJs2['default'].createTimeRanges([[start, end]]); 7758 } 7759 7760 /** 7761 * Gets and sets init segment for the provided map 7762 * 7763 * @param {Object} map 7764 * The map object representing the init segment to get or set 7765 * @param {Boolean=} set 7766 * If true, the init segment for the provided map should be saved 7767 * @return {Object} 7768 * map object for desired init segment 7769 */ 7770 }, { 7771 key: 'initSegment', 7772 value: function initSegment(map) { 7773 var set = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 7774 7775 if (!map) { 7776 return null; 7777 } 7778 7779 var id = (0, _binUtils.initSegmentId)(map); 7780 var storedMap = this.initSegments_[id]; 7781 7782 if (set && !storedMap && map.bytes) { 7783 // append WebVTT line terminators to the media initialization segment if it exists 7784 // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that 7785 // requires two or more WebVTT line terminators between the WebVTT header and the 7786 // rest of the file 7787 var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength; 7788 var combinedSegment = new Uint8Array(combinedByteLength); 7789 7790 combinedSegment.set(map.bytes); 7791 combinedSegment.set(VTT_LINE_TERMINATORS, map.bytes.byteLength); 7792 7793 this.initSegments_[id] = storedMap = { 7794 resolvedUri: map.resolvedUri, 7795 byterange: map.byterange, 7796 bytes: combinedSegment 7797 }; 7798 } 7799 7800 return storedMap || map; 7801 } 7802 7803 /** 7804 * Returns true if all configuration required for loading is present, otherwise false. 7805 * 7806 * @return {Boolean} True if the all configuration is ready for loading 7807 * @private 7808 */ 7809 }, { 7810 key: 'couldBeginLoading_', 7811 value: function couldBeginLoading_() { 7812 return this.playlist_ && this.subtitlesTrack_ && !this.paused(); 7813 } 7814 7815 /** 7816 * Once all the starting parameters have been specified, begin 7817 * operation. This method should only be invoked from the INIT 7818 * state. 7819 * 7820 * @private 7821 */ 7822 }, { 7823 key: 'init_', 7824 value: function init_() { 7825 this.state = 'READY'; 7826 this.resetEverything(); 7827 return this.monitorBuffer_(); 7828 } 7829 7830 /** 7831 * Set a subtitle track on the segment loader to add subtitles to 7832 * 7833 * @param {TextTrack=} track 7834 * The text track to add loaded subtitles to 7835 * @return {TextTrack} 7836 * Returns the subtitles track 7837 */ 7838 }, { 7839 key: 'track', 7840 value: function track(_track) { 7841 if (typeof _track === 'undefined') { 7842 return this.subtitlesTrack_; 7843 } 7844 7845 this.subtitlesTrack_ = _track; 7846 7847 // if we were unpaused but waiting for a sourceUpdater, start 7848 // buffering now 7849 if (this.state === 'INIT' && this.couldBeginLoading_()) { 7850 this.init_(); 7851 } 7852 7853 return this.subtitlesTrack_; 7854 } 7855 7856 /** 7857 * Remove any data in the source buffer between start and end times 7858 * @param {Number} start - the start time of the region to remove from the buffer 7859 * @param {Number} end - the end time of the region to remove from the buffer 7860 */ 7861 }, { 7862 key: 'remove', 7863 value: function remove(start, end) { 7864 (0, _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs2['default'])(start, end, this.subtitlesTrack_); 7865 } 7866 7867 /** 7868 * fill the buffer with segements unless the sourceBuffers are 7869 * currently updating 7870 * 7871 * Note: this function should only ever be called by monitorBuffer_ 7872 * and never directly 7873 * 7874 * @private 7875 */ 7876 }, { 7877 key: 'fillBuffer_', 7878 value: function fillBuffer_() { 7879 var _this = this; 7880 7881 if (!this.syncPoint_) { 7882 this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_()); 7883 } 7884 7885 // see if we need to begin loading immediately 7886 var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_); 7887 7888 segmentInfo = this.skipEmptySegments_(segmentInfo); 7889 7890 if (!segmentInfo) { 7891 return; 7892 } 7893 7894 if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) { 7895 // We don't have the timestamp offset that we need to sync subtitles. 7896 // Rerun on a timestamp offset or user interaction. 7897 var checkTimestampOffset = function checkTimestampOffset() { 7898 _this.state = 'READY'; 7899 if (!_this.paused()) { 7900 // if not paused, queue a buffer check as soon as possible 7901 _this.monitorBuffer_(); 7902 } 7903 }; 7904 7905 this.syncController_.one('timestampoffset', checkTimestampOffset); 7906 this.state = 'WAITING_ON_TIMELINE'; 7907 return; 7908 } 7909 7910 this.loadSegment_(segmentInfo); 7911 } 7912 7913 /** 7914 * Prevents the segment loader from requesting segments we know contain no subtitles 7915 * by walking forward until we find the next segment that we don't know
7915whether it is 7916 * empty or not. 7917 * 7918 * @param {Object} segmentInfo 7919 * a segment info object that describes the current segment 7920 * @return {Object} 7921 * a segment info object that describes the current segment 7922 */ 7923 }, { 7924 key: 'skipEmptySegments_', 7925 value: function skipEmptySegments_(segmentInfo) { 7926 while (segmentInfo && segmentInfo.segment.empty) { 7927 segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest); 7928 } 7929 return segmentInfo; 7930 } 7931 7932 /** 7933 * append a decrypted segement to the SourceBuffer through a SourceUpdater 7934 * 7935 * @private 7936 */ 7937 }, { 7938 key: 'handleSegment_', 7939 value: function handleSegment_() { 7940 var _this2 = this; 7941 7942 if (!this.pendingSegment_ || !this.subtitlesTrack_) { 7943 this.state = 'READY'; 7944 return; 7945 } 7946 7947 this.state = 'APPENDING'; 7948 7949 var segmentInfo = this.pendingSegment_; 7950 var segment = segmentInfo.segment; 7951 7952 // Make sure that vttjs has loaded, otherwise, wait till it finished loading 7953 if (typeof _globalWindow2['default'].WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) { 7954 var _ret = (function () { 7955 7956 var loadHandler = function loadHandler() { 7957 _this2.handleSegment_(); 7958 }; 7959 7960 _this2.state = 'WAITING_ON_VTTJS'; 7961 _this2.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler); 7962 _this2.subtitlesTrack_.tech_.one('vttjserror', function () { 7963 _this2.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler); 7964 _this2.error({ 7965 message: 'Error loading vtt.js' 7966 }); 7967 _this2.state = 'READY'; 7968 _this2.pause(); 7969 _this2.trigger('error'); 7970 }); 7971 7972 return { 7973 v: undefined 7974 }; 7975 })(); 7976 7977 if (typeof _ret === 'object') return _ret.v; 7978 } 7979 7980 segment.requested = true; 7981 7982 try { 7983 this.parseVTTCues_(segmentInfo); 7984 } catch (e) { 7985 this.error({ 7986 message: e.message 7987 }); 7988 this.state = 'READY'; 7989 this.pause(); 7990 return this.trigger('error'); 7991 } 7992 7993 this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_); 7994 7995 if (segmentInfo.isSyncRequest) { 7996 this.trigger('syncinfoupdate'); 7997 this.pendingSegment_ = null; 7998 this.state = 'READY'; 7999 return; 8000 } 8001 8002 segmentInfo.byteLength = segmentInfo.bytes.byteLength; 8003 8004 this.mediaSecondsLoaded += segment.duration; 8005 8006 if (segmentInfo.cues.length) { 8007 // remove any overlapping cues to prevent doubling 8008 this.remove(segmentInfo.cues[0].endTime, segmentInfo.cues[segmentInfo.cues.length - 1].endTime); 8009 } 8010 8011 segmentInfo.cues.forEach(function (cue) { 8012 _this2.subtitlesTrack_.addCue(cue); 8013 }); 8014 8015 this.handleUpdateEnd_(); 8016 } 8017 8018 /** 8019 * Uses the WebVTT parser to parse the segment response 8020 * 8021 * @param {Object} segmentInfo 8022 * a segment info object that describes the current segment 8023 * @private 8024 */ 8025 }, { 8026 key: 'parseVTTCues_', 8027 value: function parseVTTCues_(segmentInfo) { 8028 var decoder = undefined; 8029 var decodeBytesToString = false; 8030 8031 if (typeof _globalWindow2['default'].TextDecoder === 'function') { 8032 decoder = new _globalWindow2['default'].TextDecoder('utf8'); 8033 } else { 8034 decoder = _globalWindow2['default'].WebVTT.StringDecoder(); 8035 decodeBytesToString = true; 8036 } 8037 8038 var parser = new _globalWindow2['default'].WebVTT.Parser(_globalWindow2['default'], _globalWindow2['default'].vttjs, decoder); 8039 8040 segmentInfo.cues = []; 8041 segmentInfo.timestampmap = { MPEGTS: 0, LOCAL: 0 }; 8042 8043 parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues); 8044 parser.ontimestampmap = function (map) { 8045 return segmentInfo.timestampmap = map; 8046 }; 8047 parser.onparsingerror = function (error) { 8048 _videoJs2['default'].log.warn('Error encountered when parsing cues: ' + error.message); 8049 }; 8050 8051 if (segmentInfo.segment.map) { 8052 var mapData = segmentInfo.segment.map.bytes; 8053 8054 if (decodeBytesToString) { 8055 mapData = uintToString(mapData); 8056 } 8057 8058 parser.parse(mapData); 8059 } 8060 8061 var segmentData = segmentInfo.bytes; 8062 8063 if (decodeBytesToString) { 8064 segmentData = uintToString(segmentData); 8065 } 8066 8067 parser.parse(segmentData); 8068 parser.flush(); 8069 } 8070 8071 /** 8072 * Updates the start and end times of any cues parsed by the WebVTT parser using 8073 * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping 8074 * from the SyncController 8075 * 8076 * @param {Object} segmentInfo 8077 * a segment info object that describes the current segment 8078 * @param {Object} mappingObj
8079 * object containing a mapping from TS to media time 8080 * @param {Object} playlist 8081 * the playlist object containing the segment 8082 * @private 8083 */ 8084 }, { 8085 key: 'updateTimeMapping_', 8086 value: function updateTimeMapping_(segmentInfo, mappingObj, playlist) { 8087 var segment = segmentInfo.segment; 8088 8089 if (!mappingObj) { 8090 // If the sync controller does not have a mapping of TS to Media Time for the 8091 // timeline, then we don't have enough information to update the cue 8092 // start/end times 8093 return; 8094 } 8095 8096 if (!segmentInfo.cues.length) { 8097 // If there are no cues, we also do not have enough information to figure out 8098 // segment timing. Mark that the segment contains no cues so we don't re-request 8099 // an empty segment. 8100 segment.empty = true; 8101 return; 8102 } 8103 8104 var timestampmap = segmentInfo.timestampmap; 8105 var diff = timestampmap.MPEGTS / 90000 - timestampmap.LOCAL + mappingObj.mapping; 8106 8107 segmentInfo.cues.forEach(function (cue) { 8108 // First convert cue time to TS time using the timestamp-map provided within the vtt 8109 cue.startTime += diff; 8110 cue.endTime += diff; 8111 }); 8112 8113 if (!playlist.syncInfo) { 8114 var firstStart = segmentInfo.cues[0].startTime; 8115 var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime; 8116 8117 playlist.syncInfo = { 8118 mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex, 8119 time: Math.min(firstStart, lastStart - segment.duration) 8120 }; 8121 } 8122 } 8123 }]); 8124 8125 return VTTSegmentLoader; 8126})(_segmentLoader2['default']); 8127 8128exports['default'] = VTTSegmentLoader; 8129module.exports = exports['default']; 8130}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 8131},{"./bin-utils":2,"./segment-loader":16,"global/window":32,"videojs-contrib-media-sources/es5/remove-cues-from-track.js":72}],21:[function(require,module,exports){ 8132(function (global){ 8133/** 8134 * @file xhr.js 8135 */ 8136 8137/** 8138 * A wrapper for videojs.xhr that tracks bandwidth. 8139 * 8140 * @param {Object} options options for the XHR 8141 * @param {Function} callback the callback to call when done 8142 * @return {Request} the xhr request that is going to be made 8143 */ 8144'use strict'; 8145 8146Object.defineProperty(exports, '__esModule', { 8147 value: true 8148}); 8149 8150function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 8151 8152var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 8153 8154var _videoJs2 = _interopRequireDefault(_videoJs); 8155 8156var xhrFactory = function xhrFactory() { 8157 var xhr = function XhrFunction(options, callback) { 8158 // Add a default timeout for all hls requests 8159 options = (0, _videoJs.mergeOptions)({ 8160 timeout: 45e3 8161 }, options); 8162 8163 // Allow an optional user-specified function to modify the option 8164 // object before we construct the xhr request 8165 var beforeRequest = XhrFunction.beforeRequest || _videoJs2['default'].Hls.xhr.beforeRequest; 8166 8167 if (beforeRequest && typeof beforeRequest === 'function') { 8168 var newOptions = beforeRequest(options); 8169 8170 if (newOptions) { 8171 options = newOptions; 8172 } 8173 } 8174 8175 var request = (0, _videoJs.xhr)(options, function (error, response) { 8176 var reqResponse = request.response; 8177 8178 if (!error && reqResponse) { 8179 request.responseTime = Date.now(); 8180 request.roundTripTime = request.responseTime - request.requestTime; 8181 request.bytesReceived = reqResponse.byteLength || reqResponse.length; 8182 if (!request.bandwidth) { 8183 request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000); 8184 } 8185 } 8186 8187 // videojs.xhr now uses a specific code on the error 8188 // object to signal that a request has timed out instead 8189 // of setting a boolean on the request object 8190 if (error && error.code === 'ETIMEDOUT') { 8191 request.timedout = true; 8192 } 8193 8194 // videojs.xhr no longer considers status codes outside of 200 and 0 8195 // (for file uris) to be errors, but the old XHR did, so emulate that 8196 // behavior. Status 206 may be used in response to byterange requests. 8197 if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) { 8198 error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText))); 8199 } 8200 8201 callback(error, request); 8202 }); 8203 var originalAbort = request.abort; 8204 8205 request.abort = function () { 8206 request.aborted = true; 8207 return originalAbort.apply(request, arguments); 8208 }; 8209 request.uri = options.uri; 8210 request.requestTime = Date.now(); 8211 return request; 8212 }; 8213 8214 return xhr; 8215}; 8216 8217exports['default'] = xhrFactory; 8218module.exports = exports['default']; 8219}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 8220},{}],22:[function(require,module,exports){ 8221/** 8222 * @file aes.js 8223 * 8224 * This file contains an adaptation of the AES decryption algorithm 8225 * from the Standford Javascript Cryptography Library. That work is 8226 * covered by the following copyright and permissions notice: 8227 * 8228 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. 8229 * All rights reserved. 8230 * 8231 * Redistribution and use in source and binary forms, with or without 8232 * modification, are permitted provided that the following conditions are 8233 * met: 8234 * 8235 * 1. Redistributions of source code must retain the above copyright 8236 * notice, this list of conditions and the following disclaimer. 8237 * 8238 * 2. Redistributions in binary form must reproduce the above 8239 * copyright notice, this list of conditions and the following 8240 * disclaimer in the documentation and/or other materials provided 8241 * with the distribution. 8242 * 8243 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 8244 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 8245 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 8246 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE 8247 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
8248 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 8249 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 8250 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 8251 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 8252 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 8253 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 8254 * 8255 * The views and conclusions contained in the software and documentation 8256 * are those of the authors and should not be interpreted as representing 8257 * official policies, either expressed or implied, of the authors. 8258 */ 8259 8260/** 8261 * Expand the S-box tables. 8262 * 8263 * @private 8264 */ 8265'use strict'; 8266 8267Object.defineProperty(exports, '__esModule', { 8268 value: true 8269}); 8270 8271var _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; }; })(); 8272 8273function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 8274 8275var precompute = function precompute() { 8276 var tables = [[[], [], [], [], []], [[], [], [], [], []]]; 8277 var encTable = tables[0]; 8278 var decTable = tables[1]; 8279 var sbox = encTable[4]; 8280 var sboxInv = decTable[4]; 8281 var i = undefined; 8282 var x = undefined; 8283 var xInv = undefined; 8284 var d = []; 8285 var th = []; 8286 var x2 = undefined; 8287 var x4 = undefined; 8288 var x8 = undefined; 8289 var s = undefined; 8290 var tEnc = undefined; 8291 var tDec = undefined; 8292 8293 // Compute double and third tables 8294 for (i = 0; i < 256; i++) { 8295 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; 8296 } 8297 8298 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 8299 // Compute sbox 8300 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; 8301 s = s >> 8 ^ s & 255 ^ 99; 8302 sbox[x] = s; 8303 sboxInv[s] = x; 8304 8305 // Compute MixColumns 8306 x8 = d[x4 = d[x2 = d[x]]]; 8307 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; 8308 tEnc = d[s] * 0x101 ^ s * 0x1010100; 8309 8310 for (i = 0; i < 4; i++) { 8311 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; 8312 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; 8313 } 8314 } 8315 8316 // Compactify. Considerable speedup on Firefox. 8317 for (i = 0; i < 5; i++) { 8318 encTable[i] = encTable[i].slice(0); 8319 decTable[i] = decTable[i].slice(0); 8320 } 8321 return tables; 8322}; 8323var aesTables = null; 8324 8325/** 8326 * Schedule out an AES key for both encryption and decryption. This 8327 * is a low-level class. Use a cipher mode to do bulk encryption. 8328 * 8329 * @class AES 8330 * @param key {Array} The key as an array of 4, 6 or 8 words. 8331 */ 8332 8333var AES = (function () { 8334 function AES(key) { 8335 _classCallCheck(this, AES); 8336 8337 /** 8338 * The expanded S-box and inverse S-box tables. These will be computed 8339 * on the client so that we don't have to send them down the wire. 8340 * 8341 * There are two tables, _tables[0] is for encryption and 8342 * _tables[1] is for decryption. 8343 * 8344 * The first 4 sub-tables are the expanded S-box with MixColumns. The 8345 * last (_tables[01][4]) is the S-box itself. 8346 * 8347 * @private 8348 */ 8349 // if we have yet to precompute the S-box tables 8350 // do so now 8351 if (!aesTables) { 8352 aesTables = precompute(); 8353 } 8354 // then make a copy of that object for use 8355 this._tables = [[aesTables[0][0].slice(), aesTables[0][1].slice(), aesTables[0][2].slice(), aesTables[0][3].slice(), aesTables[0][4].slice()], [aesTables[1][0].slice(), aesTables[1][1].slice(), aesTables[1][2].slice(), aesTables[1][3].slice(), aesTables[1][4].slice()]]; 8356 var i = undefined; 8357 var j = undefined; 8358 var tmp = undefined; 8359 var encKey = undefined; 8360 var decKey = undefined; 8361 var sbox = this._tables[0][4]; 8362 var decTable = this._tables[1]; 8363 var keyLen = key.length; 8364 var rcon = 1; 8365 8366 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 8367 throw new Error('Invalid aes key size'); 8368 } 8369 8370 encKey = key.slice(0); 8371 decKey = []; 8372 this._key = [encKey, decKey]; 8373 8374 // schedule encryption keys 8375 for (i = keyLen; i < 4 * keyLen + 28; i++) { 8376 tmp = encKey[i - 1]; 8377 8378 // apply sbox
8379 if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) { 8380 tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; 8381 8382 // shift rows and add rcon 8383 if (i % keyLen === 0) { 8384 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; 8385 rcon = rcon << 1 ^ (rcon >> 7) * 283; 8386 } 8387 } 8388 8389 encKey[i] = encKey[i - keyLen] ^ tmp; 8390 } 8391 8392 // schedule decryption keys 8393 for (j = 0; i; j++, i--) { 8394 tmp = encKey[j & 3 ? i : i - 4]; 8395 if (i <= 4 || j < 4) { 8396 decKey[j] = tmp; 8397 } else { 8398 decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; 8399 } 8400 } 8401 } 8402 8403 /** 8404 * Decrypt 16 bytes, specified as four 32-bit words. 8405 * 8406 * @param {Number} encrypted0 the first word to decrypt 8407 * @param {Number} encrypted1 the second word to decrypt 8408 * @param {Number} encrypted2 the third word to decrypt 8409 * @param {Number} encrypted3 the fourth word to decrypt 8410 * @param {Int32Array} out the array to write the decrypted words 8411 * into 8412 * @param {Number} offset the offset into the output array to start 8413 * writing results 8414 * @return {Array} The plaintext. 8415 */ 8416 8417 _createClass(AES, [{ 8418 key: 'decrypt', 8419 value: function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { 8420 var key = this._key[1]; 8421 // state variables a,b,c,d are loaded with pre-whitened data 8422 var a = encrypted0 ^ key[0]; 8423 var b = encrypted3 ^ key[1]; 8424 var c = encrypted2 ^ key[2]; 8425 var d = encrypted1 ^ key[3]; 8426 var a2 = undefined; 8427 var b2 = undefined; 8428 var c2 = undefined; 8429 8430 // key.length === 2 ? 8431 var nInnerRounds = key.length / 4 - 2; 8432 var i = undefined; 8433 var kIndex = 4; 8434 var table = this._tables[1]; 8435 8436 // load up the tables 8437 var table0 = table[0]; 8438 var table1 = table[1]; 8439 var table2 = table[2]; 8440 var table3 = table[3]; 8441 var sbox = table[4]; 8442 8443 // Inner rounds. Cribbed from OpenSSL. 8444 for (i = 0; i < nInnerRounds; i++) { 8445 a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex]; 8446 b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; 8447 c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; 8448 d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; 8449 kIndex += 4; 8450 a = a2;b = b2;c = c2; 8451 } 8452 8453 // Last round. 8454 for (i = 0; i < 4; i++) { 8455 out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; 8456 a2 = a;a = b;b = c;c = d;d = a2; 8457 } 8458 } 8459 }]); 8460 8461 return AES; 8462})(); 8463 8464exports['default'] = AES; 8465module.exports = exports['default']; 8466},{}],23:[function(require,module,exports){ 8467/** 8468 * @file async-stream.js 8469 */ 8470'use strict'; 8471 8472Object.defineProperty(exports, '__esModule', { 8473 value: true 8474}); 8475 8476var _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; }; })(); 8477 8478var _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); } } }; 8479 8480function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 8481 8482function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 8483 8484function _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, { c
8484onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 8485 8486var _stream = require('./stream'); 8487 8488var _stream2 = _interopRequireDefault(_stream); 8489 8490/** 8491 * A wrapper around the Stream class to use setTiemout 8492 * and run stream "jobs" Asynchronously 8493 * 8494 * @class AsyncStream 8495 * @extends Stream 8496 */ 8497 8498var AsyncStream = (function (_Stream) { 8499 _inherits(AsyncStream, _Stream); 8500 8501 function AsyncStream() { 8502 _classCallCheck(this, AsyncStream); 8503 8504 _get(Object.getPrototypeOf(AsyncStream.prototype), 'constructor', this).call(this, _stream2['default']); 8505 this.jobs = []; 8506 this.delay = 1; 8507 this.timeout_ = null; 8508 } 8509 8510 /** 8511 * process an async job 8512 * 8513 * @private 8514 */ 8515 8516 _createClass(AsyncStream, [{ 8517 key: 'processJob_', 8518 value: function processJob_() { 8519 this.jobs.shift()(); 8520 if (this.jobs.length) { 8521 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 8522 } else { 8523 this.timeout_ = null; 8524 } 8525 } 8526 8527 /** 8528 * push a job into the stream 8529 * 8530 * @param {Function} job the job to push into the stream 8531 */ 8532 }, { 8533 key: 'push', 8534 value: function push(job) { 8535 this.jobs.push(job); 8536 if (!this.timeout_) { 8537 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 8538 } 8539 } 8540 }]); 8541 8542 return AsyncStream; 8543})(_stream2['default']); 8544 8545exports['default'] = AsyncStream; 8546module.exports = exports['default']; 8547},{"./stream":26}],24:[function(require,module,exports){ 8548/** 8549 * @file decrypter.js 8550 * 8551 * An asynchronous implementation of AES-128 CBC decryption with 8552 * PKCS#7 padding. 8553 */ 8554 8555'use strict'; 8556 8557Object.defineProperty(exports, '__esModule', { 8558 value: true 8559}); 8560 8561var _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; }; })(); 8562 8563function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 8564 8565function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 8566 8567var _aes = require('./aes'); 8568 8569var _aes2 = _interopRequireDefault(_aes); 8570 8571var _asyncStream = require('./async-stream'); 8572 8573var _asyncStream2 = _interopRequireDefault(_asyncStream); 8574 8575var _pkcs7 = require('pkcs7'); 8576 8577/** 8578 * Convert network-order (big-endian) bytes into their little-endian 8579 * representation. 8580 */ 8581var ntoh = function ntoh(word) { 8582 return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; 8583}; 8584 8585/** 8586 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. 8587 * 8588 * @param {Uint8Array} encrypted the encrypted bytes 8589 * @param {Uint32Array} key the bytes of the decryption key 8590 * @param {Uint32Array} initVector the initialization vector (IV) to 8591 * use for the first round of CBC. 8592 * @return {Uint8Array} the decrypted bytes 8593 * 8594 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 8595 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 8596 * @see https://tools.ietf.org/html/rfc2315 8597 */ 8598var decrypt = function decrypt(encrypted, key, initVector) { 8599 // word-level access to the encrypted bytes 8600 var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2); 8601 8602 var decipher = new _aes2['default'](Array.prototype.slice.call(key)); 8603 8604 // byte and word-level access for the decrypted output 8605 var decrypted = new Uint8Array(encrypted.byteLength); 8606 var decrypted32 = new Int32Array(decrypted.buffer); 8607 8608 // temporary variables for working with the IV, encrypted, and 8609 // decrypted data 8610 var init0 = undefined; 8611 var init1 = undefined; 8612 var init2 = undefined; 8613 var init3 = undefined; 8614 var encrypted0 = undefined; 8615 var encrypted1 = undefined; 8616 var encrypted2 = undefined; 8617 var encrypted3 = undefined; 8618 8619 // iteration variable 8620 var wordIx = undefined; 8621 8622 // pull out the words of the IV to ensure we don't modify the 8623 // passed-in reference and easier access 8624 init0 = initVector[0]; 8625 init1 = initVector[1]; 8626 init2 = initVector[2]; 8627 init3 = initVector[3]; 8628 8629 // decrypt four word sequences, applying cipher-block chaining (CBC) 8630 // to each decrypted block 8631 for (wordIx = 0;
8631 wordIx < encrypted32.length; wordIx += 4) { 8632 // convert big-endian (network order) words into little-endian 8633 // (javascript order) 8634 encrypted0 = ntoh(encrypted32[wordIx]); 8635 encrypted1 = ntoh(encrypted32[wordIx + 1]); 8636 encrypted2 = ntoh(encrypted32[wordIx + 2]); 8637 encrypted3 = ntoh(encrypted32[wordIx + 3]); 8638 8639 // decrypt the block 8640 decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); 8641 8642 // XOR with the IV, and restore network byte-order to obtain the 8643 // plaintext 8644 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); 8645 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); 8646 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); 8647 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); 8648 8649 // setup the IV for the next round 8650 init0 = encrypted0; 8651 init1 = encrypted1; 8652 init2 = encrypted2; 8653 init3 = encrypted3; 8654 } 8655 8656 return decrypted; 8657}; 8658 8659exports.decrypt = decrypt; 8660/** 8661 * The `Decrypter` class that manages decryption of AES 8662 * data through `AsyncStream` objects and the `decrypt` 8663 * function 8664 * 8665 * @param {Uint8Array} encrypted the encrypted bytes 8666 * @param {Uint32Array} key the bytes of the decryption key 8667 * @param {Uint32Array} initVector the initialization vector (IV) to 8668 * @param {Function} done the function to run when done 8669 * @class Decrypter 8670 */ 8671 8672var Decrypter = (function () { 8673 function Decrypter(encrypted, key, initVector, done) { 8674 _classCallCheck(this, Decrypter); 8675 8676 var step = Decrypter.STEP; 8677 var encrypted32 = new Int32Array(encrypted.buffer); 8678 var decrypted = new Uint8Array(encrypted.byteLength); 8679 var i = 0; 8680 8681 this.asyncStream_ = new _asyncStream2['default'](); 8682 8683 // split up the encryption job and do the individual chunks asynchronously 8684 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 8685 for (i = step; i < encrypted32.length; i += step) { 8686 initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]); 8687 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 8688 } 8689 // invoke the done() callback when everything is finished 8690 this.asyncStream_.push(function () { 8691 // remove pkcs#7 padding from the decrypted bytes 8692 done(null, (0, _pkcs7.unpad)(decrypted)); 8693 }); 8694 } 8695 8696 /** 8697 * a getter for step the maximum number of bytes to process at one time 8698 * 8699 * @return {Number} the value of step 32000 8700 */ 8701 8702 _createClass(Decrypter, [{ 8703 key: 'decryptChunk_', 8704 8705 /** 8706 * @private 8707 */ 8708 value: function decryptChunk_(encrypted, key, initVector, decrypted) { 8709 return function () { 8710 var bytes = decrypt(encrypted, key, initVector); 8711 8712 decrypted.set(bytes, encrypted.byteOffset); 8713 }; 8714 } 8715 }], [{ 8716 key: 'STEP', 8717 get: function get() { 8718 // 4 * 8000; 8719 return 32000; 8720 } 8721 }]); 8722 8723 return Decrypter; 8724})(); 8725 8726exports.Decrypter = Decrypter; 8727exports['default'] = { 8728 Decrypter: Decrypter, 8729 decrypt: decrypt 8730}; 8731},{"./aes":22,"./async-stream":23,"pkcs7":28}],25:[function(require,module,exports){ 8732/** 8733 * @file index.js 8734 * 8735 * Index module to easily import the primary components of AES-128 8736 * decryption. Like this: 8737 * 8738 * ```js 8739 * import {Decrypter, decrypt, AsyncStream} from 'aes-decrypter'; 8740 * ``` 8741 */ 8742'use strict'; 8743 8744Object.defineProperty(exports, '__esModule', { 8745 value: true 8746}); 8747 8748function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 8749 8750var _decrypter = require('./decrypter'); 8751 8752var _asyncStream = require('./async-stream'); 8753 8754var _asyncStream2 = _interopRequireDefault(_asyncStream); 8755 8756exports['default'] = { 8757 decrypt: _decrypter.decrypt, 8758 Decrypter: _decrypter.Decrypter, 8759 AsyncStream: _asyncStream2['default'] 8760}; 8761module.exports = exports['default']; 8762},{"./async-stream":23,"./decrypter":24}],26:[function(require,module,exports){ 8763/** 8764 * @file stream.js 8765 */ 8766/** 8767 * A lightweight readable stream implemention that handles event dispatching. 8768 * 8769 * @class Stream 8770 */ 8771'use strict'; 8772 8773Object.defineProperty(exports, '__esModule', { 8774 value: true 8775}); 8776 8777var _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; }; })(); 8778 8779function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 8780 8781var Stream = (function () { 8782 function Stream() { 8783 _classCallCheck(this, Stream); 8784 8785 this.listeners = {}; 8786 } 8787 8788 /** 8789 * Add a listener for a specified event type. 8790 * 8791 * @param {String} type the event name 8792 * @param {Function} listener the callback to be invoked when an event of 8793 * the specified type occurs 8794 */ 8795 8796 _createClass(Stream, [{ 8797 key: 'on', 8798 value: function on(type, listener) { 8799 if (!this.listeners[type]) { 8800 this.listeners[type] = []; 8801 } 8802 this.listeners[type].push(listener); 8803 } 8804 8805 /** 8806 * Remove a listener for a specified event type. 8807 * 8808 * @param {String} type the event name 8809 * @param {Function} listener a function previously registered for this 8810 * type of event through `on` 8811 * @return {Boolean} if we could turn it off or not 8812 */ 8813 }, { 8814 key: 'off', 8815 value: function off(type, listener) { 8816 var index = undefined; 8817 8818 if (!this.listeners[type]) { 8819 return false;
8820 } 8821 index = this.listeners[type].indexOf(listener); 8822 this.listeners[type].splice(index, 1); 8823 return index > -1; 8824 } 8825 8826 /** 8827 * Trigger an event of the specified type on this stream. Any additional 8828 * arguments to this function are passed as parameters to event listeners. 8829 * 8830 * @param {String} type the event name 8831 */ 8832 }, { 8833 key: 'trigger', 8834 value: function trigger(type) { 8835 var callbacks = undefined; 8836 var i = undefined; 8837 var length = undefined; 8838 var args = undefined; 8839 8840 callbacks = this.listeners[type]; 8841 if (!callbacks) { 8842 return; 8843 } 8844 // Slicing the arguments on every invocation of this method 8845 // can add a significant amount of overhead. Avoid the 8846 // intermediate object creation for the common case of a 8847 // single callback argument 8848 if (arguments.length === 2) { 8849 length = callbacks.length; 8850 for (i = 0; i < length; ++i) { 8851 callbacks[i].call(this, arguments[1]); 8852 } 8853 } else { 8854 args = Array.prototype.slice.call(arguments, 1); 8855 length = callbacks.length; 8856 for (i = 0; i < length; ++i) { 8857 callbacks[i].apply(this, args); 8858 } 8859 } 8860 } 8861 8862 /** 8863 * Destroys the stream and cleans up. 8864 */ 8865 }, { 8866 key: 'dispose', 8867 value: function dispose() { 8868 this.listeners = {}; 8869 } 8870 8871 /** 8872 * Forwards all `data` events on this stream to the destination stream. The 8873 * destination stream should provide a method `push` to receive the data 8874 * events as they arrive. 8875 * 8876 * @param {Stream} destination the stream that will receive all `data` events 8877 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 8878 */ 8879 }, { 8880 key: 'pipe', 8881 value: function pipe(destination) { 8882 this.on('data', function (data) { 8883 destination.push(data); 8884 }); 8885 } 8886 }]); 8887 8888 return Stream; 8889})(); 8890 8891exports['default'] = Stream; 8892module.exports = exports['default']; 8893},{}],27:[function(require,module,exports){ 8894/* 8895 * pkcs7.pad 8896 * https://github.com/brightcove/pkcs7 8897 * 8898 * Copyright (c) 2014 Brightcove 8899 * Licensed under the apache2 license. 8900 */ 8901 8902'use strict'; 8903 8904var PADDING; 8905 8906/** 8907 * Returns a new Uint8Array that is padded with PKCS#7 padding. 8908 * @param plaintext {Uint8Array} the input bytes before encryption 8909 * @return {Uint8Array} the padded bytes 8910 * @see http://tools.ietf.org/html/rfc5652 8911 */ 8912module.exports = function pad(plaintext) { 8913 var padding = PADDING[(plaintext.byteLength % 16) || 0], 8914 result = new Uint8Array(plaintext.byteLength + padding.length); 8915 result.set(plaintext); 8916 result.set(padding, plaintext.byteLength); 8917 return result; 8918}; 8919 8920// pre-define the padding values 8921PADDING = [ 8922 [16, 16, 16, 16, 8923 16, 16, 16, 16, 8924 16, 16, 16, 16, 8925 16, 16, 16, 16], 8926 8927 [15, 15, 15, 15, 8928 15, 15, 15, 15, 8929 15, 15, 15, 15, 8930 15, 15, 15], 8931 8932 [14, 14, 14, 14, 8933 14, 14, 14, 14, 8934 14, 14, 14, 14, 8935 14, 14], 8936 8937 [13, 13, 13, 13, 8938 13, 13, 13, 13, 8939 13, 13, 13, 13, 8940 13], 8941 8942 [12, 12, 12, 12, 8943 12, 12, 12, 12, 8944 12, 12, 12, 12], 8945 8946 [11, 11, 11, 11, 8947 11, 11, 11, 11, 8948 11, 11, 11], 8949 8950 [10, 10, 10, 10, 8951 10, 10, 10, 10, 8952 10, 10], 8953 8954 [9, 9, 9, 9, 8955 9, 9, 9, 9, 8956 9], 8957 8958 [8, 8, 8, 8, 8959 8, 8, 8, 8], 8960 8961 [7, 7, 7, 7, 8962 7, 7, 7], 8963 8964 [6, 6, 6, 6, 8965 6, 6], 8966 8967 [5, 5, 5, 5, 8968 5], 8969 8970 [4, 4, 4, 4], 8971 8972 [3, 3, 3], 8973 8974 [2, 2], 8975 8976 [1] 8977]; 8978 8979},{}],28:[function(require,module,exports){ 8980/* 8981 * pkcs7 8982 * https://github.com/brightcove/pkcs7 8983 * 8984 * Copyright (c) 2014 Brightcove 8985 * Licensed under the apache2 license. 8986 */ 8987 8988'use strict'; 8989 8990exports.pad = require('./pad.js'); 8991exports.unpad = require('./unpad.js'); 8992 8993},{"./pad.js":27,"./unpad.js":29}],29:[function(require,module,exports){ 8994/* 8995 * pkcs7.unpad 8996 * https://github.com/brightcove/pkcs7 8997 * 8998 * Copyright (c) 2014 Brightcove 8999 * Licensed under the apache2 license. 9000 */ 9001 9002'use strict'; 9003 9004/** 9005 * Returns the subarray of a Uint8Array without PKCS#7 padding. 9006 * @param padded {Uint8Array} unencrypted bytes that have been padded 9007 * @return {Uint8Array} the unpadded bytes 9008 * @see http://tools.ietf.org/html/rfc5652 9009 */ 9010module.exports = function unpad(padded) { 9011 return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]); 9012}; 9013 9014},{}],30:[function(require,module,exports){ 9015 9016},{}],31:[function(require,module,exports){ 9017(function (global){ 9018var topLevel = typeof global !== 'undefined' ? global : 9019 typeof window !== 'undefined' ? window : {} 9020var minDoc = require('min-document'); 9021 9022var doccy; 9023 9024if (typeof document !== 'undefined') { 9025 doccy = document; 9026} else { 9027 doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4']; 9028 9029 if (!doccy) { 9030 doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc; 9031 } 9032} 9033 9034module.exports = doccy; 9035 9036}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 9037},{"min-document":30}],32:[function(require,module,exports){ 9038(function (global){ 9039var win; 9040 9041if (typeof window !== "undefined") { 9042 win = window; 9043} else if (typeof global !== "undefined") { 9044 win = global; 9045} else if (typeof self !== "undefined"){ 9046 win = self; 9047} else { 9048 win = {}; 9049} 9050 9051module.exports = win; 9052 9053}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 9054},{}],33:[function(require,module,exports){ 9055'use strict'; 9056 9057var _lineStream = require('./line-stream'); 9058 9059var _lineStream2 = _interopRequireDefault(_lineStream); 9060 9061var _parseStream = require('./parse-stream'); 9062 9063var _parseStream2 = _interopRequireDefault(_parseStream); 9064 9065var _parser = require('./parser'); 9066 9067var _parser2 = _interopRequireDefault(_parser); 9068 9069function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 9070 9071module.exports = { 9072 LineStream: _lineStream2['default'],
9073 ParseStream: _parseStream2['default'], 9074 Parser: _parser2['default'] 9075}; /** 9076 * @file m3u8/index.js 9077 * 9078 * Utilities for parsing M3U8 files. If the entire manifest is available, 9079 * `Parser` will create an object representation with enough detail for managing 9080 * playback. `ParseStream` and `LineStream` are lower-level parsing primitives 9081 * that do not assume the entirety of the manifest is ready and expose a 9082 * ReadableStream-like interface. 9083 */ 9084},{"./line-stream":34,"./parse-stream":35,"./parser":36}],34:[function(require,module,exports){ 9085'use strict'; 9086 9087Object.defineProperty(exports, "__esModule", { 9088 value: true 9089}); 9090 9091var _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; }; }(); 9092 9093var _stream = require('./stream'); 9094 9095var _stream2 = _interopRequireDefault(_stream); 9096 9097function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 9098 9099function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 9100 9101function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 9102 9103function _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; } /** 9104 * @file m3u8/line-stream.js 9105 */ 9106 9107 9108/** 9109 * A stream that buffers string input and generates a `data` event for each 9110 * line. 9111 * 9112 * @class LineStream 9113 * @extends Stream 9114 */ 9115var LineStream = function (_Stream) { 9116 _inherits(LineStream, _Stream); 9117 9118 function LineStream() { 9119 _classCallCheck(this, LineStream); 9120 9121 var _this = _possibleConstructorReturn(this, (LineStream.__proto__ || Object.getPrototypeOf(LineStream)).call(this)); 9122 9123 _this.buffer = ''; 9124 return _this; 9125 } 9126 9127 /** 9128 * Add new data to be parsed. 9129 * 9130 * @param {String} data the text to process 9131 */ 9132 9133 9134 _createClass(LineStream, [{ 9135 key: 'push', 9136 value: function push(data) { 9137 var nextNewline = void 0; 9138 9139 this.buffer += data; 9140 nextNewline = this.buffer.indexOf('\n'); 9141 9142 for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) { 9143 this.trigger('data', this.buffer.substring(0, nextNewline)); 9144 this.buffer = this.buffer.substring(nextNewline + 1); 9145 } 9146 } 9147 }]); 9148 9149 return LineStream; 9150}(_stream2['default']); 9151 9152exports['default'] = LineStream; 9153},{"./stream":37}],35:[function(require,module,exports){ 9154'use strict'; 9155 9156Object.defineProperty(exports, "__esModule", { 9157 value: true 9158}); 9159 9160var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); }
vendor: 60,067 bytes, lines 9160-11012
9160 else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 9161 9162var _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; }; }(); 9163 9164var _stream = require('./stream'); 9165 9166var _stream2 = _interopRequireDefault(_stream); 9167 9168function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 9169 9170function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 9171 9172function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 9173 9174function _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; } /** 9175 * @file m3u8/parse-stream.js 9176 */ 9177 9178 9179/** 9180 * "forgiving" attribute list psuedo-grammar: 9181 * attributes -> keyvalue (',' keyvalue)* 9182 * keyvalue -> key '=' value 9183 * key -> [^=]* 9184 * value -> '"' [^"]* '"' | [^,]* 9185 */ 9186var attributeSeparator = function attributeSeparator() { 9187 var key = '[^=]*'; 9188 var value = '"[^"]*"|[^,]*'; 9189 var keyvalue = '(?:' + key + ')=(?:' + value + ')'; 9190 9191 return new RegExp('(?:^|,)(' + keyvalue + ')'); 9192}; 9193 9194/** 9195 * Parse attributes from a line given the seperator 9196 * 9197 * @param {String} attributes the attibute line to parse 9198 */ 9199var parseAttributes = function parseAttributes(attributes) { 9200 // split the string using attributes as the separator 9201 var attrs = attributes.split(attributeSeparator()); 9202 var result = {}; 9203 var i = attrs.length; 9204 var attr = void 0; 9205 9206 while (i--) { 9207 // filter out unmatched portions of the string 9208 if (attrs[i] === '') { 9209 continue; 9210 } 9211 9212 // split the key and value 9213 attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); 9214 // trim whitespace and remove optional quotes around the value 9215 attr[0] = attr[0].replace(/^\s+|\s+$/g, ''); 9216 attr[1] = attr[1].replace(/^\s+|\s+$/g, ''); 9217 attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1'); 9218 result[attr[0]] = attr[1]; 9219 } 9220 return result; 9221}; 9222 9223/** 9224 * A line-level M3U8 parser event stream. It expects to receive input one 9225 * line at a time and performs a context-free parse of its contents. A stream 9226 * interpretation of a manifest can be useful if the manifest is expected to 9227 * be too large to fit comfortably into memory or the entirety of the input 9228 * is not immediately available. Otherwise, it's probably much easier to work 9229 * with a regular `Parser` object. 9230 * 9231 * Produces `data` events with an object that captures the parser's 9232 * interpretation of the input. That object has a property `tag` that is one 9233 * of `uri`, `comment`, or `tag`. URIs only have a single additional 9234 * property, `line`, which captures the entirety of the input without 9235 * interpretation. Comments similarly have a single additional property 9236 * `text` which is the input without the leading `#`. 9237 * 9238 * Tags always have a property `tagType` which is the lower-cased version of 9239 * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance, 9240 * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized 9241 * tags are given the tag type `unknown` and a single additional property 9242 * `data` with the remainder of the input. 9243 * 9244 * @class ParseStream 9245 * @extends Stream 9246 */ 9247 9248var ParseStream = function (_Stream) { 9249 _inherits(ParseStream, _Stream); 9250 9251 function ParseStream() { 9252 _classCallCheck(this, ParseStream); 9253 9254 return _possibleConstructorReturn(this, (ParseStream.__proto__ || Object.getPrototypeOf(ParseStream)).call(this)); 9255 } 9256 9257 /** 9258 * Parses an additional line of input. 9259 * 9260 * @param {String} line a single line of an M3U8 file to parse 9261 */ 9262 9263 9264 _createClass(ParseStream, [{ 9265 key: 'push', 9266 value: function push(line) { 9267 var match = void 0; 9268 var event = void 0; 9269 9270 // strip whitespace 9271 line = line.replace(/^[\u0000\s]+|[\u0000\s]+$/g, ''); 9272 if (line.length === 0) { 9273 // ignore empty lines 9274 return; 9275 } 9276 9277 // URIs 9278 if (line[0] !== '#') { 9279 this.trigger('data', { 9280 type: 'uri', 9281 uri: line 9282 }); 9283 return; 9284 } 9285 9286 // Comments 9287 if (line.indexOf('#EXT') !== 0) { 9288 this.trigger('data', { 9289 type: 'comment', 9290 text: line.slice(1) 9291 }); 9292 return; 9293 } 9294 9295 // strip off any carriage returns here so the regex matching 9296 // doesn't have to account for them. 9297 line = line.replace('\r', ''); 9298 9299 // Tags 9300 match = /^#EXTM3U/.exec(line); 9301 if (match) { 9302 this.trigger('data', { 9303 type: 'tag', 9304 tagType: 'm3u' 9305 }); 9306 return; 9307 } 9308 match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(line); 9309 if (match) { 9310 event = { 9311 type: 'tag', 9312 tagType: 'inf' 9313 }; 9314 if (match[1]) { 9315 event.duration = parseFloat(match[1]); 9316 } 9317 if (match[2]) { 9318 event.title = match[2]; 9319 } 9320 this.trigger('data', event); 9321 return; 9322 } 9323 match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(line); 9324 if (match) { 9325 event = { 9326 type: 'tag', 9327 tagType: 'targetduration' 9328 }; 9329 if (match[1]) { 9330 event.duration = parseInt(match[1], 10); 9331 } 9332 this.trigger('data', event); 9333 return; 9334 } 9335 match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(line); 9336 if (match) { 9337 event = { 9338 type: 'tag', 9339 tagType: 'totalduration' 9340 }; 9341 if (match[1]) { 9342 event.duration = parseInt(match[1], 10); 9343 } 9344 this.trigger('data', event); 9345 return; 9346 } 9347 match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(line); 9348 if (match) { 9349 event = { 9350 type: 'tag', 9351 tagType: 'version' 9352 }; 9353 if (match[1]) { 9354 event.version = parseInt(match[1], 10); 9355 } 9356 this.trigger('data', event); 9357 return; 9358 } 9359 match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(line); 9360 if (match) { 9361 event = { 9362 type: 'tag', 9363 tagType: 'media-sequence' 9364 }; 9365 if (match[1]) { 9366 event.number = parseInt(match[1], 10); 9367 } 9368 this.trigger('data', event); 9369 return; 9370 } 9371 match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(line); 9372 if (match) { 9373 event = { 9374 type: 'tag', 9375 tagType: 'discontinuity-sequence' 9376 }; 9377 if (match[1]) { 9378 event.number = parseInt(match[1], 10); 9379 } 9380 this.trigger('data', event); 9381 return; 9382 } 9383 match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(line); 9384 if (match) { 9385 event = { 9386 type: 'tag', 9387 tagType: 'playlist-type' 9388 }; 9389 if (match[1]) { 9390 event.playlistType = match[1]; 9391 } 9392 this.trigger('data', event); 9393 return; 9394 } 9395 match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(line); 9396 if (match) { 9397 event = { 9398 type: 'tag', 9399 tagType: 'byterange' 9400 }; 9401 if (match[1]) { 9402 event.length = parseInt(match[1], 10); 9403 } 9404 if (match[2]) { 9405 event.offset = parseInt(match[2], 10); 9406 } 9407 this.trigger('data', event); 9408 return; 9409 } 9410 match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(line); 9411 if (match) { 9412 event = { 9413 type: 'tag', 9414 tagType: 'allow-cache' 9415 }; 9416 if (match[1]) { 9417 event.allowed = !/NO/.test(match[1]); 9418 } 9419 this.trigger('data', event); 9420 return; 9421 } 9422 match = /^#EXT-X-MAP:?(.*)$/.exec(line); 9423 if (match) { 9424 event = { 9425 type: 'tag', 9426 tagType: 'map' 9427 }; 9428 9429 if (match[1]) { 9430 var attributes = parseAttributes(match[1]); 9431 9432 if (attributes.URI) { 9433 event.uri = attributes.URI; 9434 } 9435 if (attributes.BYTERANGE) { 9436 var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'), 9437 _attributes$BYTERANGE2 = _slicedToArray(_attributes$BYTERANGE, 2), 9438 length = _attributes$BYTERANGE2[0], 9439 offset = _attributes$BYTERANGE2[1]; 9440 9441 event.byterange = {}; 9442 if (length) { 9443 event.byterange.length = parseInt(length, 10); 9444 } 9445 if (offset) { 9446 event.byterange.offset = parseInt(offset, 10); 9447 } 9448 } 9449 } 9450 9451 this.trigger('data', event); 9452 return; 9453 } 9454 match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(line); 9455 if (match) { 9456 event = { 9457 type: 'tag', 9458 tagType: 'stream-inf' 9459 }; 9460 if (match[1]) { 9461 event.attributes = parseAttributes(match[1]); 9462 9463 if (event.attributes.RESOLUTION) { 9464 var split = event.attributes.RESOLUTION.split('x'); 9465 var resolution = {}; 9466 9467 if (split[0]) { 9468 resolution.width = parseInt(split[0], 10); 9469 } 9470 if (split[1]) { 9471 resolution.height = parseInt(split[1], 10); 9472 } 9473 event.attributes.RESOLUTION = resolution; 9474 } 9475 if (event.attributes.BANDWIDTH) { 9476 event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); 9477 } 9478 if (event.attributes['PROGRAM-ID']) { 9479 event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10); 9480 } 9481 } 9482 this.trigger('data', event); 9483 return; 9484 } 9485 match = /^#EXT-X-MEDIA:?(.*)$/.exec(line); 9486 if (match) { 9487 event = { 9488 type: 'tag', 9489 tagType: 'media' 9490 }; 9491 if (match[1]) { 9492 event.attributes = parseAttributes(match[1]); 9493 } 9494 this.trigger('data', event); 9495 return; 9496 } 9497 match = /^#EXT-X-ENDLIST/.exec(line); 9498 if (match) { 9499 this.trigger('data', { 9500 type: 'tag', 9501 tagType: 'endlist' 9502 }); 9503 return; 9504 } 9505 match = /^#EXT-X-DISCONTINUITY/.exec(line); 9506 if (match) { 9507 this.trigger('data', { 9508 type: 'tag', 9509 tagType: 'discontinuity' 9510 }); 9511 return; 9512 } 9513 match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(line); 9514 if (match) { 9515 event = { 9516 type: 'tag', 9517 tagType: 'program-date-time' 9518 }; 9519 if (match[1]) { 9520 event.dateTimeString = match[1]; 9521 event.dateTimeObject = new Date(match[1]); 9522 } 9523 this.trigger('data', event); 9524 return; 9525 } 9526 match = /^#EXT-X-KEY:?(.*)$/.exec(line); 9527 if (match) { 9528 event = { 9529 type: 'tag', 9530 tagType: 'key' 9531 }; 9532 if (match[1]) { 9533 event.attributes = parseAttributes(match[1]); 9534 // parse the IV string into a Uint32Array 9535 if (event.attributes.IV) { 9536 if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') { 9537 event.attributes.IV = event.attributes.IV.substring(2); 9538 } 9539 9540 event.attributes.IV = event.attributes.IV.match(/.{8}/g); 9541 event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16); 9542 event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16); 9543 event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16); 9544 event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16); 9545 event.attributes.IV = new Uint32Array(event.attributes.IV); 9546 } 9547 } 9548 this.trigger('data', event); 9549 return; 9550 } 9551 match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(line); 9552 if (match) { 9553 event = { 9554 type: 'tag', 9555 tagType: 'cue-out-cont' 9556 }; 9557 if (match[1]) { 9558 event.data = match[1]; 9559 } else { 9560 event.data = ''; 9561 } 9562 this.trigger('data', event); 9563 return; 9564 } 9565 match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(line); 9566 if (match) { 9567 event = { 9568 type: 'tag', 9569 tagType: 'cue-out' 9570 }; 9571 if (match[1]) { 9572 event.data = match[1]; 9573 } else { 9574 event.data = ''; 9575 } 9576 this.trigger('data', event); 9577 return; 9578 } 9579 match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(line); 9580 if (match) { 9581 event = { 9582 type: 'tag', 9583 tagType: 'cue-in' 9584 }; 9585 if (match[1]) { 9586 event.data = match[1]; 9587 } else { 9588 event.data = ''; 9589 } 9590 this.trigger('data', event); 9591 return; 9592 } 9593 9594 // unknown tag type 9595 this.trigger('data', { 9596 type: 'tag', 9597 data: line.slice(4) 9598 }); 9599 } 9600 }]); 9601 9602 return ParseStream; 9603}(_stream2['default']); 9604 9605exports['default'] = ParseStream; 9606},{"./stream":37}],36:[function(require,module,exports){ 9607'use strict'; 9608 9609Object.defineProperty(exports, "__esModule", { 9610 value: true 9611}); 9612 9613var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 9614 9615var _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; }; }(); 9616 9617var _stream = require('./stream'); 9618 9619var _stream2 = _interopRequireDefault(_stream); 9620 9621var _lineStream = require('./line-stream'); 9622 9623var _lineStream2 = _interopRequireDefault(_lineStream); 9624 9625var _parseStream = require('./parse-stream'); 9626 9627var _parseStream2 = _interopRequireDefault(_parseStream); 9628 9629function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 9630 9631function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 9632 9633function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 9634 9635function _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; } /** 9636 * @file m3u8/parser.js 9637 */ 9638 9639 9640/** 9641 * A parser for M3U8 files. The current interpretation of the input is 9642 * exposed as a property `manifest` on parser objects. It's just two lines to 9643 * create and parse a manifest once you have the contents available as a string: 9644 * 9645 * ```js 9646 * var parser = new m3u8.Parser(); 9647 * parser.push(xhr.responseText); 9648 * ``` 9649 * 9650 * New input can later be applied to update the manifest object by calling 9651 * `push` again. 9652 * 9653 * The parser attempts to create a usable manifest object even if the 9654 * underlying input is somewhat nonsensical. It emits `info` and `warning` 9655 * events during the parse if it encounters input that seems invalid or 9656 * requires some property of the manifest object to be defaulted. 9657 * 9658 * @class Parser 9659 * @extends Stream 9660 */ 9661var Parser = function (_Stream) { 9662 _inherits(Parser, _Stream); 9663 9664 function Parser() { 9665 _classCallCheck(this, Parser); 9666 9667 var _this = _possibleConstructorReturn(this, (Parser.__proto__ || Object.getPrototypeOf(Parser)).call(this)); 9668 9669 _this.lineStream = new _lineStream2['default'](); 9670 _this.parseStream = new _parseStream2['default'](); 9671 _this.lineStream.pipe(_this.parseStream); 9672 /* eslint-disable consistent-this */ 9673 var self = _this; 9674 /* eslint-enable consistent-this */ 9675 var uris = []; 9676 var currentUri = {}; 9677 // if specified, the active EXT-X-MAP definition 9678 var currentMap = void 0; 9679 // if specified, the active decryption key 9680 var _key = void 0; 9681 var noop = function noop() {}; 9682 var defaultMediaGroups = { 9683 'AUDIO': {}, 9684 'VIDEO': {}, 9685 'CLOSED-CAPTIONS': {}, 9686 'SUBTITLES': {} 9687 }; 9688 // group segments into numbered timelines delineated by discontinuities 9689 var currentTimeline = 0; 9690 9691 // the manifest is empty until the parse stream begins delivering data 9692 _this.manifest = { 9693 allowCache: true, 9694 discontinuityStarts: [], 9695 segments: [] 9696 }; 9697 9698 // update the manifest with the m3u8 entry from the parse stream 9699 _this.parseStream.on('data', function (entry) { 9700 var mediaGroup = void 0; 9701 var rendition = void 0; 9702 9703 ({ 9704 tag: function tag() { 9705 // switch based on the tag type 9706 (({ 9707 'allow-cache': function allowCache() { 9708 this.manifest.allowCache = entry.allowed; 9709 if (!('allowed' in entry)) { 9710 this.trigger('info', { 9711 message: 'defaulting allowCache to YES' 9712 }); 9713 this.manifest.allowCache = true; 9714 } 9715 }, 9716 byterange: function byterange() { 9717 var byterange = {}; 9718 9719 if ('length' in entry) { 9720 currentUri.byterange = byterange; 9721 byterange.length = entry.length; 9722 9723 if (!('offset' in entry)) { 9724 this.trigger('info', { 9725 message: 'defaulting offset to zero' 9726 }); 9727 entry.offset = 0; 9728 } 9729 } 9730 if ('offset' in entry) { 9731 currentUri.byterange = byterange; 9732 byterange.offset = entry.offset; 9733 } 9734 }, 9735 endlist: function endlist() { 9736 this.manifest.endList = true; 9737 }, 9738 inf: function inf() { 9739 if (!('mediaSequence' in this.manifest)) { 9740 this.manifest.mediaSequence = 0; 9741 this.trigger('info', { 9742 message: 'defaulting media sequence to zero' 9743 }); 9744 } 9745 if (!('discontinuitySequence' in this.manifest)) { 9746 this.manifest.discontinuitySequence = 0; 9747 this.trigger('info', { 9748 message: 'defaulting discontinuity sequence to zero' 9749 }); 9750 } 9751 if (entry.duration > 0) { 9752 currentUri.duration = entry.duration; 9753 } 9754 9755 if (entry.duration === 0) { 9756 currentUri.duration = 0.01; 9757 this.trigger('info', { 9758 message: 'updating zero segment duration to a small value' 9759 }); 9760 } 9761 9762 this.manifest.segments = uris; 9763 }, 9764 key: function key() { 9765 if (!entry.attributes) { 9766 this.trigger('warn', { 9767 message: 'ignoring key declaration without attribute list' 9768 }); 9769 return; 9770 } 9771 // clear the active encryption key 9772 if (entry.attributes.METHOD === 'NONE') { 9773 _key = null; 9774 return; 9775 } 9776 if (!entry.attributes.URI) { 9777 this.trigger('warn', { 9778 message: 'ignoring key declaration without URI' 9779 }); 9780 return; 9781 } 9782 if (!entry.attributes.METHOD) { 9783 this.trigger('warn', { 9784 message: 'defaulting key method to AES-128' 9785 }); 9786 } 9787 9788 // setup an encryption key for upcoming segments 9789 _key = { 9790 method: entry.attributes.METHOD || 'AES-128', 9791 uri: entry.attributes.URI 9792 }; 9793 9794 if (typeof entry.attributes.IV !== 'undefined') { 9795 _key.iv = entry.attributes.IV; 9796 } 9797 }, 9798 'media-sequence': function mediaSequence() { 9799 if (!isFinite(entry.number)) { 9800 this.trigger('warn', { 9801 message: 'ignoring invalid media sequence: ' + entry.number 9802 }); 9803 return; 9804 } 9805 this.manifest.mediaSequence = entry.number; 9806 }, 9807 'discontinuity-sequence': function discontinuitySequence() { 9808 if (!isFinite(entry.number)) { 9809 this.trigger('warn', { 9810 message: 'ignoring invalid discontinuity sequence: ' + entry.number 9811 }); 9812 return; 9813 } 9814 this.manifest.discontinuitySequence = entry.number; 9815 currentTimeline = entry.number; 9816 }, 9817 'playlist-type': function playlistType() { 9818 if (!/VOD|EVENT/.test(entry.playlistType)) { 9819 this.trigger('warn', { 9820 message: 'ignoring unknown playlist type: ' + entry.playlist 9821 }); 9822 return; 9823 } 9824 this.manifest.playlistType = entry.playlistType; 9825 }, 9826 map: function map() { 9827 currentMap = {}; 9828 if (entry.uri) { 9829 currentMap.uri = entry.uri; 9830 } 9831 if (entry.byterange) { 9832 currentMap.byterange = entry.byterange; 9833 } 9834 }, 9835 'stream-inf': function streamInf() { 9836 this.manifest.playlists = uris; 9837 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 9838 9839 if (!entry.attributes) { 9840 this.trigger('warn', { 9841 message: 'ignoring empty stream-inf attributes' 9842 }); 9843 return; 9844 } 9845 9846 if (!currentUri.attributes) { 9847 currentUri.attributes = {}; 9848 } 9849 _extends(currentUri.attributes, entry.attributes); 9850 }, 9851 media: function media() { 9852 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 9853 9854 if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) { 9855 this.trigger('warn', { 9856 message: 'ignoring incomplete or missing media group' 9857 }); 9858 return; 9859 } 9860 9861 // find the media group, creating defaults as necessary 9862 var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE]; 9863 9864 mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {}; 9865 mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; 9866 9867 // collect the rendition metadata 9868 rendition = { 9869 'default': /yes/i.test(entry.attributes.DEFAULT) 9870 }; 9871 if (rendition['default']) { 9872 rendition.autoselect = true; 9873 } else { 9874 rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT); 9875 } 9876 if (entry.attributes.LANGUAGE) { 9877 rendition.language = entry.attributes.LANGUAGE; 9878 } 9879 if (entry.attributes.URI) { 9880 rendition.uri = entry.attributes.URI; 9881 } 9882 if (entry.attributes['INSTREAM-ID']) { 9883 rendition.instreamId = entry.attributes['INSTREAM-ID']; 9884 } 9885 if (entry.attributes.CHARACTERISTICS) { 9886 rendition.characteristics = entry.attributes.CHARACTERISTICS; 9887 } 9888 if (entry.attributes.FORCED) { 9889 rendition.forced = /yes/i.test(entry.attributes.FORCED); 9890 } 9891 9892 // insert the new rendition 9893 mediaGroup[entry.attributes.NAME] = rendition; 9894 }, 9895 discontinuity: function discontinuity() { 9896 currentTimeline += 1; 9897 currentUri.discontinuity = true; 9898 this.manifest.discontinuityStarts.push(uris.length); 9899 }, 9900 'program-date-time': function programDateTime() { 9901 this.manifest.dateTimeString = entry.dateTimeString; 9902 this.manifest.dateTimeObject = entry.dateTimeObject; 9903 }, 9904 targetduration: function targetduration() { 9905 if (!isFinite(entry.duration) || entry.duration < 0) { 9906 this.trigger('warn', { 9907 message: 'ignoring invalid target duration: ' + entry.duration 9908 }); 9909 return; 9910 } 9911 this.manifest.targetDuration = entry.duration; 9912 }, 9913 totalduration: function totalduration() { 9914 if (!isFinite(entry.duration) || entry.duration < 0) { 9915 this.trigger('warn', { 9916 message: 'ignoring invalid total duration: ' + entry.duration 9917 }); 9918 return; 9919 } 9920 this.manifest.totalDuration = entry.duration; 9921 }, 9922 'cue-out': function cueOut() { 9923 currentUri.cueOut = entry.data; 9924 }, 9925 'cue-out-cont': function cueOutCont() { 9926 currentUri.cueOutCont = entry.data; 9927 }, 9928 'cue-in': function cueIn() { 9929 currentUri.cueIn = entry.data; 9930 } 9931 })[entry.tagType] || noop).call(self); 9932 }, 9933 uri: function uri() { 9934 currentUri.uri = entry.uri; 9935 uris.push(currentUri); 9936 9937 // if no explicit duration was declared, use the target duration 9938 if (this.manifest.targetDuration && !('duration' in currentUri)) { 9939 this.trigger('warn', { 9940 message: 'defaulting segment duration to the target duration' 9941 }); 9942 currentUri.duration = this.manifest.targetDuration; 9943 } 9944 // annotate with encryption information, if necessary 9945 if (_key) { 9946 currentUri.key = _key; 9947 } 9948 currentUri.timeline = currentTimeline; 9949 // annotate with initialization segment information, if necessary 9950 if (currentMap) { 9951 currentUri.map = currentMap; 9952 } 9953 9954 // prepare for the next URI 9955 currentUri = {}; 9956 }, 9957 comment: function comment() { 9958 // comments are not important for playback 9959 } 9960 })[entry.type].call(self); 9961 }); 9962 9963 return _this; 9964 } 9965 9966 /** 9967 * Parse the input string and update the manifest object. 9968 * 9969 * @param {String} chunk a potentially incomplete portion of the manifest 9970 */ 9971 9972 9973 _createClass(Parser, [{ 9974 key: 'push', 9975 value: function push(chunk) { 9976 this.lineStream.push(chunk); 9977 } 9978 9979 /** 9980 * Flush any remaining input. This can be handy if the last line of an M3U8 9981 * manifest did not contain a trailing newline but the file has been 9982 * completely received. 9983 */ 9984 9985 }, { 9986 key: 'end', 9987 value: function end() { 9988 // flush any buffered input 9989 this.lineStream.push('\n'); 9990 } 9991 }]); 9992 9993 return Parser; 9994}(_stream2['default']); 9995 9996exports['default'] = Parser; 9997},{"./line-stream":34,"./parse-stream":35,"./stream":37}],37:[function(require,module,exports){ 9998'use strict'; 9999 10000Object.defineProperty(exports, "__esModule", { 10001 value: true 10002}); 10003 10004var _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; }; }(); 10005 10006function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 10007 10008/** 10009 * @file stream.js 10010 */ 10011/** 10012 * A lightweight readable stream implemention that handles event dispatching. 10013 * 10014 * @class Stream 10015 */ 10016var Stream = function () { 10017 function Stream() { 10018 _classCallCheck(this, Stream); 10019 10020 this.listeners = {}; 10021 } 10022 10023 /** 10024 * Add a listener for a specified event type. 10025 * 10026 * @param {String} type the event name 10027 * @param {Function} listener the callback to be invoked when an event of 10028 * the specified type occurs 10029 */ 10030 10031 10032 _createClass(Stream, [{ 10033 key: 'on', 10034 value: function on(type, listener) { 10035 if (!this.listeners[type]) { 10036 this.listeners[type] = []; 10037 } 10038 this.listeners[type].push(listener); 10039 } 10040 10041 /** 10042 * Remove a listener for a specified event type. 10043 * 10044 * @param {String} type the event name 10045 * @param {Function} listener a function previously registered for this 10046 * type of event through `on` 10047 * @return {Boolean} if we could turn it off or not 10048 */ 10049 10050 }, { 10051 key: 'off', 10052 value: function off(type, listener) { 10053 if (!this.listeners[type]) { 10054 return false; 10055 } 10056 10057 var index = this.listeners[type].indexOf(listener); 10058 10059 this.listeners[type].splice(index, 1); 10060 return index > -1; 10061 } 10062 10063 /** 10064 * Trigger an event of the specified type on this stream. Any additional 10065 * arguments to this function are passed as parameters to event listeners. 10066 * 10067 * @param {String} type the event name 10068 */ 10069 10070 }, { 10071 key: 'trigger', 10072 value: function trigger(type) { 10073 var callbacks = this.listeners[type]; 10074 var i = void 0; 10075 var length = void 0; 10076 var args = void 0; 10077 10078 if (!callbacks) { 10079 return; 10080 } 10081 // Slicing the arguments on every invocation of this method 10082 // can add a significant amount of overhead. Avoid the 10083 // intermediate object creation for the common case of a 10084 // single callback argument 10085 if (arguments.length === 2) { 10086 length = callbacks.length; 10087 for (i = 0; i < length; ++i) { 10088 callbacks[i].call(this, arguments[1]); 10089 } 10090 } else { 10091 args = Array.prototype.slice.call(arguments, 1); 10092 length = callbacks.length; 10093 for (i = 0; i < length; ++i) { 10094 callbacks[i].apply(this, args); 10095 } 10096 } 10097 } 10098 10099 /** 10100 * Destroys the stream and cleans up. 10101 */ 10102 10103 }, { 10104 key: 'dispose', 10105 value: function dispose() { 10106 this.listeners = {}; 10107 } 10108 /** 10109 * Forwards all `data` events on this stream to the destination stream. The 10110 * destination stream should provide a method `push` to receive the data 10111 * events as they arrive. 10112 * 10113 * @param {Stream} destination the stream that will receive all `data` events 10114 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 10115 */ 10116 10117 }, { 10118 key: 'pipe', 10119 value: function pipe(destination) { 10120 this.on('data', function (data) { 10121 destination.push(data); 10122 }); 10123 } 10124 }]); 10125 10126 return Stream; 10127}(); 10128 10129exports['default'] = Stream; 10130},{}],38:[function(require,module,exports){ 10131/** 10132 * mux.js 10133 * 10134 * Copyright (c) 2016 Brightcove 10135 * All rights reserved. 10136 * 10137 * A stream-based aac to mp4 converter. This utility can be used to 10138 * deliver mp4s to a SourceBuffer on platforms that support native 10139 * Media Source Extensions. 10140 */ 10141'use strict'; 10142var Stream = require('../utils/stream.js'); 10143 10144// Constants 10145var AacStream; 10146 10147/** 10148 * Splits an incoming stream of binary data into ADTS and ID3 Frames. 10149 */ 10150 10151AacStream = function() { 10152 var 10153 everything = new Uint8Array(), 10154 timeStamp = 0; 10155 10156 AacStream.prototype.init.call(this); 10157 10158 this.setTimestamp = function(timestamp) { 10159 timeStamp = timestamp; 10160 }; 10161 10162 this.parseId3TagSize = function(header, byteIndex) { 10163 var 10164 returnSize = (header[byteIndex + 6] << 21) | 10165 (header[byteIndex + 7] << 14) | 10166 (header[byteIndex + 8] << 7) | 10167 (header[byteIndex + 9]), 10168 flags = header[byteIndex + 5], 10169 footerPresent = (flags & 16) >> 4; 10170 10171 if (footerPresent) { 10172 return returnSize + 20; 10173 } 10174 return returnSize + 10; 10175 }; 10176 10177 this.parseAdtsSize = function(header, byteIndex) { 10178 var 10179 lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 10180 middle = header[byteIndex + 4] << 3, 10181 highTwo = header[byteIndex + 3] & 0x3 << 11; 10182 10183 return (highTwo | middle) | lowThree; 10184 }; 10185 10186 this.push = function(bytes) { 10187 var 10188 frameSize = 0, 10189 byteIndex = 0, 10190 bytesLeft, 10191 chunk, 10192 packet, 10193 tempLength; 10194 10195 // If there are bytes remaining from the last segment, prepend them to the 10196 // bytes that were pushed in 10197 if (everything.length) { 10198 tempLength = everything.length; 10199 everything = new Uint8Array(bytes.byteLength + tempLength); 10200 everything.set(everything.subarray(0, tempLength)); 10201 everything.set(bytes, tempLength); 10202 } else { 10203 everything = bytes; 10204 } 10205 10206 while (everything.length - byteIndex >= 3) { 10207 if ((everything[byteIndex] === 'I'.charCodeAt(0)) && 10208 (everything[byteIndex + 1] === 'D'.charCodeAt(0)) && 10209 (everything[byteIndex + 2] === '3'.charCodeAt(0))) { 10210 10211 // Exit early because we don't have enough to parse 10212 // the ID3 tag header 10213 if (everything.length - byteIndex < 10) { 10214 break; 10215 } 10216 10217 // check framesize 10218 frameSize = this.parseId3TagSize(everything, byteIndex); 10219 10220 // Exit early if we don't have enough in the buffer 10221 // to emit a full packet 10222 if (frameSize > everything.length) { 10223 break; 10224 } 10225 chunk = { 10226 type: 'timed-metadata', 10227 data: everything.subarray(byteIndex, byteIndex + frameSize) 10228 }; 10229 this.trigger('data', chunk); 10230 byteIndex += frameSize; 10231 continue; 10232 } else if ((everything[byteIndex] & 0xff === 0xff) && 10233 ((everything[byteIndex + 1] & 0xf0) === 0xf0)) { 10234 10235 // Exit early because we don't have enough to parse 10236 // the ADTS frame header 10237 if (everything.length - byteIndex < 7) { 10238 break; 10239 } 10240 10241 frameSize = this.parseAdtsSize(everything, byteIndex); 10242 10243 // Exit early if we don't have enough in the buffer 10244 // to emit a full packet 10245 if (frameSize > everything.length) { 10246 break; 10247 } 10248 10249 packet = { 10250 type: 'audio', 10251 data: everything.subarray(byteIndex, byteIndex + frameSize), 10252 pts: timeStamp, 10253 dts: timeStamp 10254 }; 10255 this.trigger('data', packet); 10256 byteIndex += frameSize; 10257 continue; 10258 } 10259 byteIndex++; 10260 } 10261 bytesLeft = everything.length - byteIndex; 10262 10263 if (bytesLeft > 0) { 10264 everything = everything.subarray(byteIndex); 10265 } else { 10266 everything = new Uint8Array(); 10267 } 10268 }; 10269}; 10270 10271AacStream.prototype = new Stream(); 10272 10273module.exports = AacStream; 10274 10275},{"../utils/stream.js":62}],39:[function(require,module,exports){ 10276/** 10277 * mux.js 10278 * 10279 * Copyright (c) 2016 Brightcove 10280 * All rights reserved. 10281 * 10282 * Utilities to detect basic properties and metadata about Aac data. 10283 */ 10284'use strict'; 10285 10286var ADTS_SAMPLING_FREQUENCIES = [ 10287 96000, 10288 88200, 10289 64000, 10290 48000, 10291 44100, 10292 32000, 10293 24000, 10294 22050, 10295 16000, 10296 12000, 10297 11025, 10298 8000, 10299 7350 10300]; 10301 10302var parseSyncSafeInteger = function(data) { 10303 return (data[0] << 21) | 10304 (data[1] << 14) | 10305 (data[2] << 7) | 10306 (data[3]); 10307}; 10308 10309// return a percent-encoded representation of the specified byte range 10310// @see http://en.wikipedia.org/wiki/Percent-encoding 10311var percentEncode = function(bytes, start, end) { 10312 var i, result = ''; 10313 for (i = start; i < end; i++) { 10314 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 10315 } 10316 return result; 10317}; 10318 10319// return the string representation of the specified byte range, 10320// interpreted as ISO-8859-1. 10321var parseIso88591 = function(bytes, start, end) { 10322 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 10323}; 10324 10325var parseId3TagSize = function(header, byteIndex) { 10326 var 10327 returnSize = (header[byteIndex + 6] << 21) | 10328 (header[byteIndex + 7] << 14) | 10329 (header[byteIndex + 8] << 7) | 10330 (header[byteIndex + 9]), 10331 flags = header[byteIndex + 5], 10332 footerPresent = (flags & 16) >> 4; 10333 10334 if (footerPresent) { 10335 return returnSize + 20; 10336 } 10337 return returnSize + 10; 10338}; 10339 10340var parseAdtsSize = function(header, byteIndex) { 10341 var 10342 lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 10343 middle = header[byteIndex + 4] << 3, 10344 highTwo = header[byteIndex + 3] & 0x3 << 11; 10345 10346 return (highTwo | middle) | lowThree; 10347}; 10348 10349var parseType = function(header, byteIndex) { 10350 if ((header[byteIndex] === 'I'.charCodeAt(0)) && 10351 (header[byteIndex + 1] === 'D'.charCodeAt(0)) && 10352 (header[byteIndex + 2] === '3'.charCodeAt(0))) { 10353 return 'timed-metadata'; 10354 } else if ((header[byteIndex] & 0xff === 0xff) && 10355 ((header[byteIndex + 1] & 0xf0) === 0xf0)) { 10356 return 'audio'; 10357 } 10358 return null; 10359}; 10360 10361var parseSampleRate = function(packet) { 10362 var i = 0; 10363 10364 while (i + 5 < packet.length) { 10365 if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) { 10366 // If a valid header was not found, jump one forward and attempt to 10367 // find a valid ADTS header starting at the next byte 10368 i++; 10369 continue; 10370 } 10371 return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2]; 10372 } 10373 10374 return null; 10375}; 10376 10377var parseAacTimestamp = function(packet) { 10378 var frameStart, frameSize, frame, frameHeader; 10379 10380 // find the start of the first frame and the end of the tag 10381 frameStart = 10; 10382 if (packet[5] & 0x40) { 10383 // advance the frame start past the extended header 10384 frameStart += 4; // header size field 10385 frameStart += parseSyncSafeInteger(packet.subarray(10, 14)); 10386 } 10387 10388 // parse one or more ID3 frames 10389 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 10390 do { 10391 // determine the number of bytes in this frame 10392 frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8)); 10393 if (frameSize < 1) { 10394 return null; 10395 } 10396 frameHeader = String.fromCharCode(packet[frameStart], 10397 packet[frameStart + 1], 10398 packet[frameStart + 2], 10399 packet[frameStart + 3]); 10400 10401 if (frameHeader === 'PRIV') { 10402 frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10); 10403 10404 for (var i = 0; i < frame.byteLength; i++) { 10405 if (frame[i] === 0) { 10406 var owner = parseIso88591(frame, 0, i); 10407 if (owner === 'com.apple.streaming.transportStreamTimestamp') { 10408 var d = frame.subarray(i + 1); 10409 var size = ((d[3] & 0x01) << 30) | 10410 (d[4] << 22) | 10411 (d[5] << 14) | 10412 (d[6] << 6) | 10413 (d[7] >>> 2); 10414 size *= 4; 10415 size += d[7] & 0x03; 10416 10417 return size; 10418 } 10419 break; 10420 } 10421 } 10422 } 10423 10424 frameStart += 10; // advance past the frame header 10425 frameStart += frameSize; // advance past the frame body 10426 } while (frameStart < packet.byteLength); 10427 return null; 10428}; 10429 10430module.exports = { 10431 parseId3TagSize: parseId3TagSize, 10432 parseAdtsSize: parseAdtsSize, 10433 parseType: parseType, 10434 parseSampleRate: parseSampleRate, 10435 parseAacTimestamp: parseAacTimestamp 10436}; 10437 10438},{}],40:[function(require,module,exports){ 10439'use strict'; 10440 10441var Stream = require('../utils/stream.js'); 10442 10443var AdtsStream; 10444 10445var 10446 ADTS_SAMPLING_FREQUENCIES = [ 10447 96000, 10448 88200, 10449 64000, 10450 48000, 10451 44100, 10452 32000, 10453 24000, 10454 22050, 10455 16000, 10456 12000, 10457 11025, 10458 8000, 10459 7350 10460 ]; 10461 10462/* 10463 * Accepts a ElementaryStream and emits data events with parsed 10464 * AAC Audio Frames of the individual packets. Input audio in ADTS 10465 * format is unpacked and re-emitted as AAC frames. 10466 * 10467 * @see http://wiki.multimedia.cx/index.php?title=ADTS 10468 * @see http://wiki.multimedia.cx/?title=Understanding_AAC 10469 */ 10470AdtsStream = function() { 10471 var buffer; 10472 10473 AdtsStream.prototype.init.call(this); 10474 10475 this.push = function(packet) { 10476 var 10477 i = 0, 10478 frameNum = 0, 10479 frameLength, 10480 protectionSkipBytes, 10481 frameEnd, 10482 oldBuffer, 10483 sampleCount, 10484 adtsFrameDuration; 10485 10486 if (packet.type !== 'audio') { 10487 // ignore non-audio data 10488 return; 10489 } 10490 10491 // Prepend any data in the buffer to the input data so that we can parse 10492 // aac frames the cross a PES packet boundary 10493 if (buffer) { 10494 oldBuffer = buffer; 10495 buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength); 10496 buffer.set(oldBuffer); 10497 buffer.set(packet.data, oldBuffer.byteLength); 10498 } else { 10499 buffer = packet.data; 10500 } 10501 10502 // unpack any ADTS frames which have been fully received 10503 // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS 10504 while (i + 5 < buffer.length) { 10505 10506 // Loook for the start of an ADTS header.. 10507 if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) { 10508 // If a valid header was not found, jump one forward and attempt to 10509 // find a valid ADTS header starting at the next byte 10510 i++; 10511 continue; 10512 } 10513 10514 // The protection skip bit tells us if we have 2 bytes of CRC data at the 10515 // end of the ADTS header 10516 protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; 10517 10518 // Frame length is a 13 bit integer starting 16 bits from the 10519 // end of the sync sequence 10520 frameLength = ((buffer[i + 3] & 0x03) << 11) | 10521 (buffer[i + 4] << 3) | 10522 ((buffer[i + 5] & 0xe0) >> 5); 10523 10524 sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024; 10525 adtsFrameDuration = (sampleCount * 90000) / 10526 ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2]; 10527 10528 frameEnd = i + frameLength; 10529 10530 // If we don't have enough data to actually finish this ADTS frame, return 10531 // and wait for more data 10532 if (buffer.byteLength < frameEnd) { 10533 return; 10534 } 10535 10536 // Otherwise, deliver the complete AAC frame 10537 this.trigger('data', { 10538 pts: packet.pts + (frameNum * adtsFrameDuration), 10539 dts: packet.dts + (frameNum * adtsFrameDuration), 10540 sampleCount: sampleCount, 10541 audioobjecttype: ((buffer[i + 2] >>> 6) & 0x03) + 1, 10542 channelcount: ((buffer[i + 2] & 1) << 2) | 10543 ((buffer[i + 3] & 0xc0) >>> 6), 10544 samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2], 10545 samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2, 10546 // assume ISO/IEC 14496-12 AudioSampleEntry default of 16 10547 samplesize: 16, 10548 data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd) 10549 }); 10550 10551 // If the buffer is empty, clear it and return 10552 if (buffer.byteLength === frameEnd) { 10553 buffer = undefined; 10554 return; 10555 } 10556 10557 frameNum++; 10558 10559 // Remove the finished frame from the buffer and start the process again 10560 buffer = buffer.subarray(frameEnd); 10561 } 10562 }; 10563 this.flush = function() { 10564 this.trigger('done'); 10565 }; 10566}; 10567 10568AdtsStream.prototype = new Stream(); 10569 10570module.exports = AdtsStream; 10571 10572},{"../utils/stream.js":62}],41:[function(require,module,exports){ 10573'use strict'; 10574 10575var Stream = require('../utils/stream.js'); 10576var ExpGolomb = require('../utils/exp-golomb.js'); 10577 10578var H264Stream, NalByteStream; 10579var PROFILES_WITH_OPTIONAL_SPS_DATA; 10580 10581/** 10582 * Accepts a NAL unit byte stream and unpacks the embedded NAL units. 10583 */ 10584NalByteStream = function() { 10585 var 10586 syncPoint = 0, 10587 i, 10588 buffer; 10589 NalByteStream.prototype.init.call(this); 10590 10591 this.push = function(data) { 10592 var swapBuffer; 10593 10594 if (!buffer) { 10595 buffer = data.data; 10596 } else { 10597 swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength); 10598 swapBuffer.set(buffer); 10599 swapBuffer.set(data.data, buffer.byteLength); 10600 buffer = swapBuffer; 10601 } 10602 10603 // Rec. ITU-T H.264, Annex B 10604 // scan for NAL unit boundaries 10605 10606 // a match looks like this: 10607 // 0 0 1 .. NAL .. 0 0 1 10608 // ^ sync point ^ i 10609 // or this: 10610 // 0 0 1 .. NAL .. 0 0 0 10611 // ^ sync point ^ i 10612 10613 // advance the sync point to a NAL start, if necessary 10614 for (; syncPoint < buffer.byteLength - 3; syncPoint++) { 10615 if (buffer[syncPoint + 2] === 1) { 10616 // the sync point is properly aligned 10617 i = syncPoint + 5; 10618 break; 10619 } 10620 } 10621 10622 while (i < buffer.byteLength) { 10623 // look at the current byte to determine if we've hit the end of 10624 // a NAL unit boundary 10625 switch (buffer[i]) { 10626 case 0: 10627 // skip past non-sync sequences 10628 if (buffer[i - 1] !== 0) { 10629 i += 2; 10630 break; 10631 } else if (buffer[i - 2] !== 0) { 10632 i++; 10633 break; 10634 } 10635 10636 // deliver the NAL unit if it isn't empty 10637 if (syncPoint + 3 !== i - 2) { 10638 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 10639 } 10640 10641 // drop trailing zeroes 10642 do { 10643 i++; 10644 } while (buffer[i] !== 1 && i < buffer.length); 10645 syncPoint = i - 2; 10646 i += 3; 10647 break; 10648 case 1: 10649 // skip past non-sync sequences 10650 if (buffer[i - 1] !== 0 || 10651 buffer[i - 2] !== 0) { 10652 i += 3; 10653 break; 10654 } 10655 10656 // deliver the NAL unit 10657 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 10658 syncPoint = i - 2; 10659 i += 3; 10660 break; 10661 default: 10662 // the current byte isn't a one or zero, so it cannot be part 10663 // of a sync sequence 10664 i += 3; 10665 break; 10666 } 10667 } 10668 // filter out the NAL units that were delivered 10669 buffer = buffer.subarray(syncPoint); 10670 i -= syncPoint; 10671 syncPoint = 0; 10672 }; 10673 10674 this.flush = function() { 10675 // deliver the last buffered NAL unit 10676 if (buffer && buffer.byteLength > 3) { 10677 this.trigger('data', buffer.subarray(syncPoint + 3)); 10678 } 10679 // reset the stream state 10680 buffer = null; 10681 syncPoint = 0; 10682 this.trigger('done'); 10683 }; 10684}; 10685NalByteStream.prototype = new Stream(); 10686 10687// values of profile_idc that indicate additional fields are included in the SPS 10688// see Recommendation ITU-T H.264 (4/2013), 10689// 7.3.2.1.1 Sequence parameter set data syntax 10690PROFILES_WITH_OPTIONAL_SPS_DATA = { 10691 100: true, 10692 110: true, 10693 122: true, 10694 244: true, 10695 44: true, 10696 83: true, 10697 86: true, 10698 118: true, 10699 128: true, 10700 138: true, 10701 139: true, 10702 134: true 10703}; 10704 10705/** 10706 * Accepts input from a ElementaryStream and produces H.264 NAL unit data 10707 * events. 10708 */ 10709H264Stream = function() { 10710 var 10711 nalByteStream = new NalByteStream(), 10712 self, 10713 trackId, 10714 currentPts, 10715 currentDts, 10716 10717 discardEmulationPreventionBytes, 10718 readSequenceParameterSet, 10719 skipScalingList; 10720 10721 H264Stream.prototype.init.call(this); 10722 self = this; 10723 10724 this.push = function(packet) { 10725 if (packet.type !== 'video') { 10726 return; 10727 } 10728 trackId = packet.trackId; 10729 currentPts = packet.pts; 10730 currentDts = packet.dts; 10731 10732 nalByteStream.push(packet); 10733 }; 10734 10735 nalByteStream.on('data', function(data) { 10736 var 10737 event = { 10738 trackId: trackId, 10739 pts: currentPts, 10740 dts: currentDts, 10741 data: data 10742 }; 10743 10744 switch (data[0] & 0x1f) { 10745 case 0x05: 10746 event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr'; 10747 break; 10748 case 0x06: 10749 event.nalUnitType = 'sei_rbsp'; 10750 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 10751 break; 10752 case 0x07: 10753 event.nalUnitType = 'seq_parameter_set_rbsp'; 10754 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 10755 event.config = readSequenceParameterSet(event.escapedRBSP); 10756 break; 10757 case 0x08: 10758 event.nalUnitType = 'pic_parameter_set_rbsp'; 10759 break; 10760 case 0x09: 10761 event.nalUnitType = 'access_unit_delimiter_rbsp'; 10762 break; 10763 10764 default: 10765 break; 10766 } 10767 self.trigger('data', event); 10768 }); 10769 nalByteStream.on('done', function() { 10770 self.trigger('done'); 10771 }); 10772 10773 this.flush = function() { 10774 nalByteStream.flush(); 10775 }; 10776 10777 /** 10778 * Advance the ExpGolomb decoder past a scaling list. The scaling 10779 * list is optionally transmitted as part of a sequence parameter 10780 * set and is not relevant to transmuxing. 10781 * @param count {number} the number of entries in this scaling list 10782 * @param expGolombDecoder {object} an ExpGolomb pointed to the 10783 * start of a scaling list 10784 * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 10785 */ 10786 skipScalingList = function(count, expGolombDecoder) { 10787 var 10788 lastScale = 8, 10789 nextScale = 8, 10790 j, 10791 deltaScale; 10792 10793 for (j = 0; j < count; j++) { 10794 if (nextScale !== 0) { 10795 deltaScale = expGolombDecoder.readExpGolomb(); 10796 nextScale = (lastScale + deltaScale + 256) % 256; 10797 } 10798 10799 lastScale = (nextScale === 0) ? lastScale : nextScale; 10800 } 10801 }; 10802 10803 /** 10804 * Expunge any "Emulation Prevention" bytes from a "Raw Byte 10805 * Sequence Payload" 10806 * @param data {Uint8Array} the bytes of a RBSP from a NAL 10807 * unit 10808 * @return {Uint8Array} the RBSP without any Emulation 10809 * Prevention Bytes 10810 */ 10811 discardEmulationPreventionBytes = function(data) { 10812 var 10813 length = data.byteLength, 10814 emulationPreventionBytesPositions = [], 10815 i = 1, 10816 newLength, newData; 10817 10818 // Find all `Emulation Prevention Bytes` 10819 while (i < length - 2) { 10820 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 10821 emulationPreventionBytesPositions.push(i + 2); 10822 i += 2; 10823 } else { 10824 i++; 10825 } 10826 } 10827 10828 // If no Emulation Prevention Bytes were found just return the original 10829 // array 10830 if (emulationPreventionBytesPositions.length === 0) { 10831 return data; 10832 } 10833 10834 // Create a new array to hold the NAL unit data 10835 newLength = length - emulationPreventionBytesPositions.length; 10836 newData = new Uint8Array(newLength); 10837 var sourceIndex = 0; 10838 10839 for (i = 0; i < newLength; sourceIndex++, i++) { 10840 if (sourceIndex === emulationPreventionBytesPositions[0]) { 10841 // Skip this byte 10842 sourceIndex++; 10843 // Remove this position index 10844 emulationPreventionBytesPositions.shift(); 10845 } 10846 newData[i] = data[sourceIndex]; 10847 } 10848 10849 return newData; 10850 }; 10851 10852 /** 10853 * Read a sequence parameter set and return some interesting video 10854 * properties. A sequence parameter set is the H264 metadata that 10855 * describes the properties of upcoming video frames. 10856 * @param data {Uint8Array} the bytes of a sequence parameter set 10857 * @return {object} an object with configuration parsed from the 10858 * sequence parameter set, including the dimensions of the 10859 * associated video frames. 10860 */ 10861 readSequenceParameterSet = function(data) { 10862 var 10863 frameCropLeftOffset = 0, 10864 frameCropRightOffset = 0, 10865 frameCropTopOffset = 0, 10866 frameCropBottomOffset = 0, 10867 sarScale = 1, 10868 expGolombDecoder, profileIdc, levelIdc, profileCompatibility, 10869 chromaFormatIdc, picOrderCntType, 10870 numRefFramesInPicOrderCntCycle, picWidthInMbsMinus1, 10871 picHeightInMapUnitsMinus1, 10872 frameMbsOnlyFlag, 10873 scalingListCount, 10874 sarRatio, 10875 aspectRatioIdc, 10876 i; 10877 10878 expGolombDecoder = new ExpGolomb(data); 10879 profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc 10880 profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag 10881 levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8) 10882 expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id 10883 10884 // some profiles have more optional data we don't need 10885 if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) { 10886 chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb(); 10887 if (chromaFormatIdc === 3) { 10888 expGolombDecoder.skipBits(1); // separate_colour_plane_flag 10889 } 10890 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8 10891 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8 10892 expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag 10893 if (expGolombDecoder.readBoolean()) { // seq_scaling_matrix_present_flag 10894 scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12; 10895 for (i = 0; i < scalingListCount; i++) { 10896 if (expGolombDecoder.readBoolean()) { // seq_scaling_list_present_flag[ i ] 10897 if (i < 6) { 10898 skipScalingList(16, expGolombDecoder); 10899 } else { 10900 skipScalingList(64, expGolombDecoder); 10901 } 10902 } 10903 } 10904 } 10905 } 10906 10907 expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4 10908 picOrderCntType = expGolombDecoder.readUnsignedExpGolomb(); 10909 10910 if (picOrderCntType === 0) { 10911 expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4 10912 } else if (picOrderCntType === 1) { 10913 expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag 10914 expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic 10915 expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field 10916 numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb(); 10917 for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) { 10918 expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ] 10919 } 10920 } 10921 10922 expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames 10923 expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag 10924 10925 picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 10926 picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 10927 10928 frameMbsOnlyFlag = expGolombDecoder.readBits(1); 10929 if (frameMbsOnlyFlag === 0) { 10930 expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag 10931 } 10932 10933 expGolombDecoder.skipBits(1); // direct_8x8_inference_flag 10934 if (expGolombDecoder.readBoolean()) { // frame_cropping_flag 10935 frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb(); 10936 frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb(); 10937 frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb(); 10938 frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb(); 10939 } 10940 if (expGolombDecoder.readBoolean()) { 10941 // vui_parameters_present_flag 10942 if (expGolombDecoder.readBoolean()) { 10943 // aspect_ratio_info_present_flag 10944 aspectRatioIdc = expGolombDecoder.readUnsignedByte(); 10945 switch (aspectRatioIdc) { 10946 case 1: sarRatio = [1, 1]; break; 10947 case 2: sarRatio = [12, 11]; break; 10948 case 3: sarRatio = [10, 11]; break; 10949 case 4: sarRatio = [16, 11]; break; 10950 case 5: sarRatio = [40, 33]; break; 10951 case 6: sarRatio = [24, 11]; break; 10952 case 7: sarRatio = [20, 11]; break; 10953 case 8: sarRatio = [32, 11]; break; 10954 case 9: sarRatio = [80, 33]; break; 10955 case 10: sarRatio = [18, 11]; break; 10956 case 11: sarRatio = [15, 11]; break; 10957 case 12: sarRatio = [64, 33]; break; 10958 case 13: sarRatio = [160, 99]; break; 10959 case 14: sarRatio = [4, 3]; break; 10960 case 15: sarRatio = [3, 2]; break; 10961 case 16: sarRatio = [2, 1]; break; 10962 case 255: { 10963 sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | 10964 expGolombDecoder.readUnsignedByte(), 10965 expGolombDecoder.readUnsignedByte() << 8 | 10966 expGolombDecoder.readUnsignedByte() ]; 10967 break; 10968 } 10969 } 10970 if (sarRatio) { 10971 sarScale = sarRatio[0] / sarRatio[1]; 10972 } 10973 } 10974 } 10975 return { 10976 profileIdc: profileIdc, 10977 levelIdc: levelIdc, 10978 profileCompatibility: profileCompatibility, 10979 width: Math.ceil((((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale), 10980 height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - (frameCropTopOffset * 2) - (frameCropBottomOffset * 2) 10981 }; 10982 }; 10983 10984}; 10985H264Stream.prototype = new Stream(); 10986 10987module.exports = { 10988 H264Stream: H264Stream, 10989 NalByteStream: NalByteStream 10990}; 10991 10992},{"../utils/exp-golomb.js":61,"../utils/stream.js":62}],42:[function(require,module,exports){ 10993var highPrefix = [33, 16, 5, 32, 164, 27]; 10994var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252]; 10995var zeroFill = function(count) { 10996 var a = []; 10997 while (count--) { 10998 a.push(0); 10999 } 11000 return a; 11001}; 11002 11003var makeTable = function(metaTable) { 11004 return Object.keys(metaTable).reduce(function(obj, key) { 11005 obj[key] = new Uint8Array(metaTable[key].reduce(function(arr, part) { 11006 return arr.concat(part); 11007 }, [])); 11008 return obj; 11009 }, {}); 11010}; 11011 11012// Frames-of-silence to use for filling in missing AAC frames
vendor: 4,575 bytes, lines 11013-11141
11013var coneOfSilence = { 11014 96000: [highPrefix, [227, 64], zeroFill(154), [56]], 11015 88200: [highPrefix, [231], zeroFill(170), [56]], 11016 64000: [highPrefix, [248, 192], zeroFill(240), [56]], 11017 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]], 11018 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]], 11019 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]], 11020 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]], 11021 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]], 11022 12000: [lowPrefix, zeroFill(268), [3, 127, 248], zeroFill(268), [6, 255, 240], zeroFill(268), [13, 255, 224], zeroFill(268), [27, 253, 128], zeroFill(259), [56]], 11023 11025: [lowPrefix, zeroFill(268), [3, 127, 248], zeroFill(268), [6, 255, 240], zeroFill(268), [13, 255, 224], zeroFill(268), [27, 255, 192], zeroFill(268), [55, 175, 128], zeroFill(108), [112]], 11024 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]] 11025}; 11026 11027module.exports = makeTable(coneOfSilence); 11028 11029},{}],43:[function(require,module,exports){ 11030'use strict'; 11031 11032var Stream = require('../utils/stream.js'); 11033 11034/** 11035 * The final stage of the transmuxer that emits the flv tags 11036 * for audio, video, and metadata. Also tranlates in time and 11037 * outputs caption data and id3 cues. 11038 */ 11039var CoalesceStream = function(options) { 11040 // Number of Tracks per output segment 11041 // If greater than 1, we combine multiple 11042 // tracks into a single segment 11043 this.numberOfTracks = 0; 11044 this.metadataStream = options.metadataStream; 11045 11046 this.videoTags = []; 11047 this.audioTags = []; 11048 this.videoTrack = null; 11049 this.audioTrack = null; 11050 this.pendingCaptions = []; 11051 this.pendingMetadata = []; 11052 this.pendingTracks = 0; 11053 this.processedTracks = 0; 11054 11055 CoalesceStream.prototype.init.call(this); 11056 11057 // Take output from multiple 11058 this.push = function(output) { 11059 // buffer incoming captions until the associated video segment 11060 // finishes 11061 if (output.text) { 11062 return this.pendingCaptions.push(output); 11063 } 11064 // buffer incoming id3 tags until the final flush 11065 if (output.frames) { 11066 return this.pendingMetadata.push(output); 11067 } 11068 11069 if (output.track.type === 'video') { 11070 this.videoTrack = output.track; 11071 this.videoTags = output.tags; 11072 this.pendingTracks++; 11073 } 11074 if (output.track.type === 'audio') { 11075 this.audioTrack = output.track; 11076 this.audioTags = output.tags; 11077 this.pendingTracks++; 11078 } 11079 }; 11080}; 11081 11082CoalesceStream.prototype = new Stream(); 11083CoalesceStream.prototype.flush = function(flushSource) { 11084 var 11085 id3, 11086 caption, 11087 i, 11088 timelineStartPts, 11089 event = { 11090 tags: {}, 11091 captions: [], 11092 captionStreams: {}, 11093 metadata: [] 11094 }; 11095 11096 if (this.pendingTracks < this.numberOfTracks) { 11097 if (flushSource !== 'VideoSegmentStream' && 11098 flushSource !== 'AudioSegmentStream') { 11099 // Return because we haven't received a flush from a data-generating 11100 // portion of the segment (meaning that we have only recieved meta-data 11101 // or captions.) 11102 return; 11103 } else if (this.pendingTracks === 0) { 11104 // In the case where we receive a flush without any data having been 11105 // received we consider it an emitted track for the purposes of coalescing 11106 // `done` events. 11107 // We do this for the case where there is an audio and video track in the 11108 // segment but no audio data. (seen in several playlists with alternate 11109 // audio tracks and no audio present in the main TS segments.) 11110 this.processedTracks++; 11111 11112 if (this.processedTracks < this.numberOfTracks) { 11113 return; 11114 } 11115 } 11116 } 11117 11118 this.processedTracks += this.pendingTracks; 11119 this.pendingTracks = 0; 11120 11121 if (this.processedTracks < this.numberOfTracks) { 11122 return; 11123 } 11124 11125 if (this.videoTrack) { 11126 timelineStartPts = this.videoTrack.timelineStartInfo.pts; 11127 } else if (this.audioTrack) { 11128 timelineStartPts = this.audioTrack.timelineStartInfo.pts; 11129 } 11130 11131 event.tags.videoTags = this.videoTags; 11132 event.tags.audioTags = this.audioTags; 11133 11134 // Translate caption PTS times into second offsets into the 11135 // video timeline for the segment, and add track info 11136 for (i = 0; i < this.pendingCaptions.length; i++) { 11137 caption = this.pendingCaptions[i]; 11138 caption.startTime = caption.startPts - timelineStartPts; 11139 caption.startTime /= 90e3; 11140 caption.endTime = caption.endPts - timelineStartPts; 11141 caption.endTime /= 90e3;
vendor: 28,064 bytes, lines 11142-12104
11142 event.captionStreams[caption.stream] = true; 11143 event.captions.push(caption); 11144 } 11145 11146 // Translate ID3 frame PTS times into second offsets into the 11147 // video timeline for the segment 11148 for (i = 0; i < this.pendingMetadata.length; i++) { 11149 id3 = this.pendingMetadata[i]; 11150 id3.cueTime = id3.pts - timelineStartPts; 11151 id3.cueTime /= 90e3; 11152 event.metadata.push(id3); 11153 } 11154 // We add this to every single emitted segment even though we only need 11155 // it for the first 11156 event.metadata.dispatchType = this.metadataStream.dispatchType; 11157 11158 // Reset stream state 11159 this.videoTrack = null; 11160 this.audioTrack = null; 11161 this.videoTags = []; 11162 this.audioTags = []; 11163 this.pendingCaptions.length = 0; 11164 this.pendingMetadata.length = 0; 11165 this.pendingTracks = 0; 11166 this.processedTracks = 0; 11167 11168 // Emit the final segment 11169 this.trigger('data', event); 11170 11171 this.trigger('done'); 11172}; 11173 11174module.exports = CoalesceStream; 11175 11176},{"../utils/stream.js":62}],44:[function(require,module,exports){ 11177'use strict'; 11178 11179var FlvTag = require('./flv-tag.js'); 11180 11181// For information on the FLV format, see 11182// http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf. 11183// Technically, this function returns the header and a metadata FLV tag 11184// if duration is greater than zero 11185// duration in seconds 11186// @return {object} the bytes of the FLV header as a Uint8Array 11187var getFlvHeader = function(duration, audio, video) { // :ByteArray { 11188 var 11189 headBytes = new Uint8Array(3 + 1 + 1 + 4), 11190 head = new DataView(headBytes.buffer), 11191 metadata, 11192 result, 11193 metadataLength; 11194 11195 // default arguments 11196 duration = duration || 0; 11197 audio = audio === undefined ? true : audio; 11198 video = video === undefined ? true : video; 11199 11200 // signature 11201 head.setUint8(0, 0x46); // 'F' 11202 head.setUint8(1, 0x4c); // 'L' 11203 head.setUint8(2, 0x56); // 'V' 11204 11205 // version 11206 head.setUint8(3, 0x01); 11207 11208 // flags 11209 head.setUint8(4, (audio ? 0x04 : 0x00) | (video ? 0x01 : 0x00)); 11210 11211 // data offset, should be 9 for FLV v1 11212 head.setUint32(5, headBytes.byteLength); 11213 11214 // init the first FLV tag 11215 if (duration <= 0) { 11216 // no duration available so just write the first field of the first 11217 // FLV tag 11218 result = new Uint8Array(headBytes.byteLength + 4); 11219 result.set(headBytes); 11220 result.set([0, 0, 0, 0], headBytes.byteLength); 11221 return result; 11222 } 11223 11224 // write out the duration metadata tag 11225 metadata = new FlvTag(FlvTag.METADATA_TAG); 11226 metadata.pts = metadata.dts = 0; 11227 metadata.writeMetaDataDouble('duration', duration); 11228 metadataLength = metadata.finalize().length; 11229 result = new Uint8Array(headBytes.byteLength + metadataLength); 11230 result.set(headBytes); 11231 result.set(head.byteLength, metadataLength); 11232 11233 return result; 11234}; 11235 11236module.exports = getFlvHeader; 11237 11238},{"./flv-tag.js":45}],45:[function(require,module,exports){ 11239/** 11240 * An object that stores the bytes of an FLV tag and methods for 11241 * querying and manipulating that data. 11242 * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf 11243 */ 11244'use strict'; 11245 11246var FlvTag; 11247 11248// (type:uint, extraData:Boolean = false) extends ByteArray 11249FlvTag = function(type, extraData) { 11250 var 11251 // Counter if this is a metadata tag, nal start marker if this is a video 11252 // tag. unused if this is an audio tag 11253 adHoc = 0, // :uint 11254 11255 // The default size is 16kb but this is not enough to hold iframe 11256 // data and the resizing algorithm costs a bit so we create a larger 11257 // starting buffer for video tags 11258 bufferStartSize = 16384, 11259 11260 // checks whether the FLV tag has enough capacity to accept the proposed 11261 // write and re-allocates the internal buffers if necessary 11262 prepareWrite = function(flv, count) { 11263 var 11264 bytes, 11265 minLength = flv.position + count; 11266 if (minLength < flv.bytes.byteLength) { 11267 // there's enough capacity so do nothing 11268 return; 11269 } 11270 11271 // allocate a new buffer and copy over the data that will not be modified 11272 bytes = new Uint8Array(minLength * 2); 11273 bytes.set(flv.bytes.subarray(0, flv.position), 0); 11274 flv.bytes = bytes; 11275 flv.view = new DataView(flv.bytes.buffer); 11276 }, 11277 11278 // commonly used metadata properties 11279 widthBytes = FlvTag.widthBytes || new Uint8Array('width'.length), 11280 heightBytes = FlvTag.heightBytes || new Uint8Array('height'.length), 11281 videocodecidBytes = FlvTag.videocodecidBytes || new Uint8Array('videocodecid'.length), 11282 i; 11283 11284 if (!FlvTag.widthBytes) { 11285 // calculating the bytes of common metadata names ahead of time makes the 11286 // corresponding writes faster because we don't have to loop over the 11287 // characters 11288 // re-test with test/perf.html if you're planning on changing this 11289 for (i = 0; i < 'width'.length; i++) { 11290 widthBytes[i] = 'width'.charCodeAt(i); 11291 } 11292 for (i = 0; i < 'height'.length; i++) { 11293 heightBytes[i] = 'height'.charCodeAt(i); 11294 } 11295 for (i = 0; i < 'videocodecid'.length; i++) { 11296 videocodecidBytes[i] = 'videocodecid'.charCodeAt(i); 11297 } 11298 11299 FlvTag.widthBytes = widthBytes; 11300 FlvTag.heightBytes = heightBytes; 11301 FlvTag.videocodecidBytes = videocodecidBytes; 11302 } 11303 11304 this.keyFrame = false; // :Boolean 11305 11306 switch (type) { 11307 case FlvTag.VIDEO_TAG: 11308 this.length = 16; 11309 // Start the buffer at 256k 11310 bufferStartSize *= 6; 11311 break; 11312 case FlvTag.AUDIO_TAG: 11313 this.length = 13; 11314 this.keyFrame = true; 11315 break; 11316 case FlvTag.METADATA_TAG: 11317 this.length = 29; 11318 this.keyFrame = true; 11319 break; 11320 default: 11321 throw new Error('Unknown FLV tag type'); 11322 } 11323 11324 this.bytes = new Uint8Array(bufferStartSize); 11325 this.view = new DataView(this.bytes.buffer); 11326 this.bytes[0] = type; 11327 this.position = this.length; 11328 this.keyFrame = extraData; // Defaults to false 11329 11330 // presentation timestamp 11331 this.pts = 0; 11332 // decoder timestamp 11333 this.dts = 0; 11334 11335 // ByteArray#writeBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0) 11336 this.writeBytes = function(bytes, offset, length) { 11337 var 11338 start = offset || 0, 11339 end; 11340 length = length || bytes.byteLength; 11341 end = start + length; 11342 11343 prepareWrite(this, length); 11344 this.bytes.set(bytes.subarray(start, end), this.position); 11345 11346 this.position += length; 11347 this.length = Math.max(this.length, this.position); 11348 }; 11349 11350 // ByteArray#writeByte(value:int):void 11351 this.writeByte = function(byte) { 11352 prepareWrite(this, 1); 11353 this.bytes[this.position] = byte; 11354 this.position++; 11355 this.length = Math.max(this.length, this.position); 11356 }; 11357 11358 // ByteArray#writeShort(value:int):void 11359 this.writeShort = function(short) { 11360 prepareWrite(this, 2); 11361 this.view.setUint16(this.position, short); 11362 this.position += 2; 11363 this.length = Math.max(this.length, this.position); 11364 }; 11365 11366 // Negative index into array 11367 // (pos:uint):int 11368 this.negIndex = function(pos) { 11369 return this.bytes[this.length - pos]; 11370 }; 11371 11372 // The functions below ONLY work when this[0] == VIDEO_TAG. 11373 // We are not going to check for that because we dont want the overhead 11374 // (nal:ByteArray = null):int 11375 this.nalUnitSize = function() { 11376 if (adHoc === 0) { 11377 return 0; 11378 } 11379 11380 return this.length - (adHoc + 4); 11381 }; 11382 11383 this.startNalUnit = function() { 11384 // remember position and add 4 bytes 11385 if (adHoc > 0) { 11386 throw new Error('Attempted to create new NAL wihout closing the old one'); 11387 } 11388 11389 // reserve 4 bytes for nal unit size 11390 adHoc = this.length; 11391 this.length += 4; 11392 this.position = this.length; 11393 }; 11394 11395 // (nal:ByteArray = null):void 11396 this.endNalUnit = function(nalContainer) { 11397 var 11398 nalStart, // :uint 11399 nalLength; // :uint 11400 11401 // Rewind to the marker and write the size 11402 if (this.length === adHoc + 4) { 11403 // we started a nal unit, but didnt write one, so roll back the 4 byte size value 11404 this.length -= 4; 11405 } else if (adHoc > 0) { 11406 nalStart = adHoc + 4; 11407 nalLength = this.length - nalStart; 11408 11409 this.position = adHoc; 11410 this.view.setUint32(this.position, nalLength); 11411 this.position = this.length; 11412 11413 if (nalContainer) { 11414 // Add the tag to the NAL unit 11415 nalContainer.push(this.bytes.subarray(nalStart, nalStart + nalLength)); 11416 } 11417 } 11418 11419 adHoc = 0; 11420 }; 11421 11422 /** 11423 * Write out a 64-bit floating point valued metadata property. This method is 11424 * called frequently during a typical parse and needs to be fast. 11425 */ 11426 // (key:String, val:Number):void 11427 this.writeMetaDataDouble = function(key, val) { 11428 var i; 11429 prepareWrite(this, 2 + key.length + 9); 11430 11431 // write size of property name 11432 this.view.setUint16(this.position, key.length); 11433 this.position += 2; 11434 11435 // this next part looks terrible but it improves parser throughput by 11436 // 10kB/s in my testing 11437 11438 // write property name 11439 if (key === 'width') { 11440 this.bytes.set(widthBytes, this.position); 11441 this.position += 5; 11442 } else if (key === 'height') { 11443 this.bytes.set(heightBytes, this.position); 11444 this.position += 6; 11445 } else if (key === 'videocodecid') { 11446 this.bytes.set(videocodecidBytes, this.position); 11447 this.position += 12; 11448 } else { 11449 for (i = 0; i < key.length; i++) { 11450 this.bytes[this.position] = key.charCodeAt(i); 11451 this.position++; 11452 } 11453 } 11454 11455 // skip null byte 11456 this.position++; 11457 11458 // write property value 11459 this.view.setFloat64(this.position, val); 11460 this.position += 8; 11461 11462 // update flv tag length 11463 this.length = Math.max(this.length, this.position); 11464 ++adHoc; 11465 }; 11466 11467 // (key:String, val:Boolean):void 11468 this.writeMetaDataBoolean = function(key, val) { 11469 var i; 11470 prepareWrite(this, 2); 11471 this.view.setUint16(this.position, key.length); 11472 this.position += 2; 11473 for (i = 0; i < key.length; i++) { 11474 // if key.charCodeAt(i) >= 255, handle error 11475 prepareWrite(this, 1); 11476 this.bytes[this.position] = key.charCodeAt(i); 11477 this.position++; 11478 } 11479 prepareWrite(this, 2); 11480 this.view.setUint8(this.position, 0x01); 11481 this.position++; 11482 this.view.setUint8(this.position, val ? 0x01 : 0x00); 11483 this.position++; 11484 this.length = Math.max(this.length, this.position); 11485 ++adHoc; 11486 }; 11487 11488 // ():ByteArray 11489 this.finalize = function() { 11490 var 11491 dtsDelta, // :int 11492 len; // :int 11493 11494 switch (this.bytes[0]) { 11495 // Video Data 11496 case FlvTag.VIDEO_TAG: 11497 // We only support AVC, 1 = key frame (for AVC, a seekable 11498 // frame), 2 = inter frame (for AVC, a non-seekable frame) 11499 this.bytes[11] = ((this.keyFrame || extraData) ? 0x10 : 0x20) | 0x07; 11500 this.bytes[12] = extraData ? 0x00 : 0x01; 11501 11502 dtsDelta = this.pts - this.dts; 11503 this.bytes[13] = (dtsDelta & 0x00FF0000) >>> 16; 11504 this.bytes[14] = (dtsDelta & 0x0000FF00) >>> 8; 11505 this.bytes[15] = (dtsDelta & 0x000000FF) >>> 0; 11506 break; 11507 11508 case FlvTag.AUDIO_TAG: 11509 this.bytes[11] = 0xAF; // 44 kHz, 16-bit stereo 11510 this.bytes[12] = extraData ? 0x00 : 0x01; 11511 break; 11512 11513 case FlvTag.METADATA_TAG: 11514 this.position = 11; 11515 this.view.setUint8(this.position, 0x02); // String type 11516 this.position++; 11517 this.view.setUint16(this.position, 0x0A); // 10 Bytes 11518 this.position += 2; 11519 // set "onMetaData" 11520 this.bytes.set([0x6f, 0x6e, 0x4d, 0x65, 11521 0x74, 0x61, 0x44, 0x61, 11522 0x74, 0x61], this.position); 11523 this.position += 10; 11524 this.bytes[this.position] = 0x08; // Array type 11525 this.position++; 11526 this.view.setUint32(this.position, adHoc); 11527 this.position = this.length; 11528 this.bytes.set([0, 0, 9], this.position); 11529 this.position += 3; // End Data Tag 11530 this.length = this.position; 11531 break; 11532 } 11533 11534 len = this.length - 11; 11535 11536 // write the DataSize field 11537 this.bytes[ 1] = (len & 0x00FF0000) >>> 16; 11538 this.bytes[ 2] = (len & 0x0000FF00) >>> 8; 11539 this.bytes[ 3] = (len & 0x000000FF) >>> 0; 11540 // write the Timestamp 11541 this.bytes[ 4] = (this.dts & 0x00FF0000) >>> 16; 11542 this.bytes[ 5] = (this.dts & 0x0000FF00) >>> 8; 11543 this.bytes[ 6] = (this.dts & 0x000000FF) >>> 0; 11544 this.bytes[ 7] = (this.dts & 0xFF000000) >>> 24; 11545 // write the StreamID 11546 this.bytes[ 8] = 0; 11547 this.bytes[ 9] = 0; 11548 this.bytes[10] = 0; 11549 11550 // Sometimes we're at the end of the view and have one slot to write a 11551 // uint32, so, prepareWrite of count 4, since, view is uint8 11552 prepareWrite(this, 4); 11553 this.view.setUint32(this.length, this.length); 11554 this.length += 4; 11555 this.position += 4; 11556 11557 // trim down the byte buffer to what is actually being used 11558 this.bytes = this.bytes.subarray(0, this.length); 11559 this.frameTime = FlvTag.frameTime(this.bytes); 11560 // if bytes.bytelength isn't equal to this.length, handle error 11561 return this; 11562 }; 11563}; 11564 11565FlvTag.AUDIO_TAG = 0x08; // == 8, :uint 11566FlvTag.VIDEO_TAG = 0x09; // == 9, :uint 11567FlvTag.METADATA_TAG = 0x12; // == 18, :uint 11568 11569// (tag:ByteArray):Boolean { 11570FlvTag.isAudioFrame = function(tag) { 11571 return FlvTag.AUDIO_TAG === tag[0]; 11572}; 11573 11574// (tag:ByteArray):Boolean { 11575FlvTag.isVideoFrame = function(tag) { 11576 return FlvTag.VIDEO_TAG === tag[0]; 11577}; 11578 11579// (tag:ByteArray):Boolean { 11580FlvTag.isMetaData = function(tag) { 11581 return FlvTag.METADATA_TAG === tag[0]; 11582}; 11583 11584// (tag:ByteArray):Boolean { 11585FlvTag.isKeyFrame = function(tag) { 11586 if (FlvTag.isVideoFrame(tag)) { 11587 return tag[11] === 0x17; 11588 } 11589 11590 if (FlvTag.isAudioFrame(tag)) { 11591 return true; 11592 } 11593 11594 if (FlvTag.isMetaData(tag)) { 11595 return true; 11596 } 11597 11598 return false; 11599}; 11600 11601// (tag:ByteArray):uint { 11602FlvTag.frameTime = function(tag) { 11603 var pts = tag[ 4] << 16; // :uint 11604 pts |= tag[ 5] << 8; 11605 pts |= tag[ 6] << 0; 11606 pts |= tag[ 7] << 24; 11607 return pts; 11608}; 11609 11610module.exports = FlvTag; 11611 11612},{}],46:[function(require,module,exports){ 11613module.exports = { 11614 tag: require('./flv-tag'), 11615 Transmuxer: require('./transmuxer'), 11616 getFlvHeader: require('./flv-header') 11617}; 11618 11619},{"./flv-header":44,"./flv-tag":45,"./transmuxer":48}],47:[function(require,module,exports){ 11620'use strict'; 11621 11622var TagList = function() { 11623 var self = this; 11624 11625 this.list = []; 11626 11627 this.push = function(tag) { 11628 this.list.push({ 11629 bytes: tag.bytes, 11630 dts: tag.dts, 11631 pts: tag.pts, 11632 keyFrame: tag.keyFrame, 11633 metaDataTag: tag.metaDataTag 11634 }); 11635 }; 11636 11637 Object.defineProperty(this, 'length', { 11638 get: function() { 11639 return self.list.length; 11640 } 11641 }); 11642}; 11643 11644module.exports = TagList; 11645 11646},{}],48:[function(require,module,exports){ 11647'use strict'; 11648 11649var Stream = require('../utils/stream.js'); 11650var FlvTag = require('./flv-tag.js'); 11651var m2ts = require('../m2ts/m2ts.js'); 11652var AdtsStream = require('../codecs/adts.js'); 11653var H264Stream = require('../codecs/h264').H264Stream; 11654var CoalesceStream = require('./coalesce-stream.js'); 11655var TagList = require('./tag-list.js'); 11656 11657var 11658 Transmuxer, 11659 VideoSegmentStream, 11660 AudioSegmentStream, 11661 collectTimelineInfo, 11662 metaDataTag, 11663 extraDataTag; 11664 11665/** 11666 * Store information about the start and end of the tracka and the 11667 * duration for each frame/sample we process in order to calculate 11668 * the baseMediaDecodeTime 11669 */ 11670collectTimelineInfo = function(track, data) { 11671 if (typeof data.pts === 'number') { 11672 if (track.timelineStartInfo.pts === undefined) { 11673 track.timelineStartInfo.pts = data.pts; 11674 } else { 11675 track.timelineStartInfo.pts = 11676 Math.min(track.timelineStartInfo.pts, data.pts); 11677 } 11678 } 11679 11680 if (typeof data.dts === 'number') { 11681 if (track.timelineStartInfo.dts === undefined) { 11682 track.timelineStartInfo.dts = data.dts; 11683 } else { 11684 track.timelineStartInfo.dts = 11685 Math.min(track.timelineStartInfo.dts, data.dts); 11686 } 11687 } 11688}; 11689 11690metaDataTag = function(track, pts) { 11691 var 11692 tag = new FlvTag(FlvTag.METADATA_TAG); // :FlvTag 11693 11694 tag.dts = pts; 11695 tag.pts = pts; 11696 11697 tag.writeMetaDataDouble('videocodecid', 7); 11698 tag.writeMetaDataDouble('width', track.width); 11699 tag.writeMetaDataDouble('height', track.height); 11700 11701 return tag; 11702}; 11703 11704extraDataTag = function(track, pts) { 11705 var 11706 i, 11707 tag = new FlvTag(FlvTag.VIDEO_TAG, true); 11708 11709 tag.dts = pts; 11710 tag.pts = pts; 11711 11712 tag.writeByte(0x01);// version 11713 tag.writeByte(track.profileIdc);// profile 11714 tag.writeByte(track.profileCompatibility);// compatibility 11715 tag.writeByte(track.levelIdc);// level 11716 tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits) 11717 tag.writeByte(0xE0 | 0x01); // reserved (3 bits), num of SPS (5 bits) 11718 tag.writeShort(track.sps[0].length); // data of SPS 11719 tag.writeBytes(track.sps[0]); // SPS 11720 11721 tag.writeByte(track.pps.length); // num of PPS (will there ever be more that 1 PPS?) 11722 for (i = 0; i < track.pps.length; ++i) { 11723 tag.writeShort(track.pps[i].length); // 2 bytes for length of PPS 11724 tag.writeBytes(track.pps[i]); // data of PPS 11725 } 11726 11727 return tag; 11728}; 11729 11730/** 11731 * Constructs a single-track, media segment from AAC data 11732 * events. The output of this stream can be fed to flash. 11733 */ 11734AudioSegmentStream = function(track) { 11735 var 11736 adtsFrames = [], 11737 videoKeyFrames = [], 11738 oldExtraData; 11739 11740 AudioSegmentStream.prototype.init.call(this); 11741 11742 this.push = function(data) { 11743 collectTimelineInfo(track, data); 11744 11745 if (track) { 11746 track.audioobjecttype = data.audioobjecttype; 11747 track.channelcount = data.channelcount; 11748 track.samplerate = data.samplerate; 11749 track.samplingfrequencyindex = data.samplingfrequencyindex; 11750 track.samplesize = data.samplesize; 11751 track.extraData = (track.audioobjecttype << 11) | 11752 (track.samplingfrequencyindex << 7) | 11753 (track.channelcount << 3); 11754 } 11755 11756 data.pts = Math.round(data.pts / 90); 11757 data.dts = Math.round(data.dts / 90); 11758 11759 // buffer audio data until end() is called 11760 adtsFrames.push(data); 11761 }; 11762 11763 this.flush = function() { 11764 var currentFrame, adtsFrame, lastMetaPts, tags = new TagList(); 11765 // return early if no audio data has been observed 11766 if (adtsFrames.length === 0) { 11767 this.trigger('done', 'AudioSegmentStream'); 11768 return; 11769 } 11770 11771 lastMetaPts = -Infinity; 11772 11773 while (adtsFrames.length) { 11774 currentFrame = adtsFrames.shift(); 11775 11776 // write out a metadata frame at every video key frame 11777 if (videoKeyFrames.length && currentFrame.pts >= videoKeyFrames[0]) { 11778 lastMetaPts = videoKeyFrames.shift(); 11779 this.writeMetaDataTags(tags, lastMetaPts); 11780 } 11781 11782 // also write out metadata tags every 1 second so that the decoder 11783 // is re-initialized quickly after seeking into a different 11784 // audio configuration. 11785 if (track.extraData !== oldExtraData || currentFrame.pts - lastMetaPts >= 1000) { 11786 this.writeMetaDataTags(tags, currentFrame.pts); 11787 oldExtraData = track.extraData; 11788 lastMetaPts = currentFrame.pts; 11789 } 11790 11791 adtsFrame = new FlvTag(FlvTag.AUDIO_TAG); 11792 adtsFrame.pts = currentFrame.pts; 11793 adtsFrame.dts = currentFrame.dts; 11794 11795 adtsFrame.writeBytes(currentFrame.data); 11796 11797 tags.push(adtsFrame.finalize()); 11798 } 11799 11800 videoKeyFrames.length = 0; 11801 oldExtraData = null; 11802 this.trigger('data', {track: track, tags: tags.list}); 11803 11804 this.trigger('done', 'AudioSegmentStream'); 11805 }; 11806 11807 this.writeMetaDataTags = function(tags, pts) { 11808 var adtsFrame; 11809 11810 adtsFrame = new FlvTag(FlvTag.METADATA_TAG); 11811 // For audio, DTS is always the same as PTS. We want to set the DTS 11812 // however so we can compare with video DTS to determine approximate 11813 // packet order 11814 adtsFrame.pts = pts; 11815 adtsFrame.dts = pts; 11816 11817 // AAC is always 10 11818 adtsFrame.writeMetaDataDouble('audiocodecid', 10); 11819 adtsFrame.writeMetaDataBoolean('stereo', track.channelcount === 2); 11820 adtsFrame.writeMetaDataDouble('audiosamplerate', track.samplerate); 11821 // Is AAC always 16 bit? 11822 adtsFrame.writeMetaDataDouble('audiosamplesize', 16); 11823 11824 tags.push(adtsFrame.finalize()); 11825 11826 adtsFrame = new FlvTag(FlvTag.AUDIO_TAG, true); 11827 // For audio, DTS is always the same as PTS. We want to set the DTS 11828 // however so we can compare with video DTS to determine approximate 11829 // packet order 11830 adtsFrame.pts = pts; 11831 adtsFrame.dts = pts; 11832 11833 adtsFrame.view.setUint16(adtsFrame.position, track.extraData); 11834 adtsFrame.position += 2; 11835 adtsFrame.length = Math.max(adtsFrame.length, adtsFrame.position); 11836 11837 tags.push(adtsFrame.finalize()); 11838 }; 11839 11840 this.onVideoKeyFrame = function(pts) { 11841 videoKeyFrames.push(pts); 11842 }; 11843}; 11844AudioSegmentStream.prototype = new Stream(); 11845 11846/** 11847 * Store FlvTags for the h264 stream 11848 * @param track {object} track metadata configuration 11849 */ 11850VideoSegmentStream = function(track) { 11851 var 11852 nalUnits = [], 11853 config, 11854 h264Frame; 11855 VideoSegmentStream.prototype.init.call(this); 11856 11857 this.finishFrame = function(tags, frame) { 11858 if (!frame) { 11859 return; 11860 } 11861 // Check if keyframe and the length of tags. 11862 // This makes sure we write metadata on the first frame of a segment. 11863 if (config && track && track.newMetadata && 11864 (frame.keyFrame || tags.length === 0)) { 11865 // Push extra data on every IDR frame in case we did a stream change + seek 11866 var metaTag = metaDataTag(config, frame.dts).finalize(); 11867 var extraTag = extraDataTag(track, frame.dts).finalize(); 11868 11869 metaTag.metaDataTag = extraTag.metaDataTag = true; 11870 11871 tags.push(metaTag); 11872 tags.push(extraTag); 11873 track.newMetadata = false; 11874 11875 this.trigger('keyframe', frame.dts); 11876 } 11877 11878 frame.endNalUnit(); 11879 tags.push(frame.finalize()); 11880 h264Frame = null; 11881 }; 11882 11883 this.push = function(data) { 11884 collectTimelineInfo(track, data); 11885 11886 data.pts = Math.round(data.pts / 90); 11887 data.dts = Math.round(data.dts / 90); 11888 11889 // buffer video until flush() is called 11890 nalUnits.push(data); 11891 }; 11892 11893 this.flush = function() { 11894 var 11895 currentNal, 11896 tags = new TagList(); 11897 11898 // Throw away nalUnits at the start of the byte stream until we find 11899 // the first AUD 11900 while (nalUnits.length) { 11901 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { 11902 break; 11903 } 11904 nalUnits.shift(); 11905 } 11906 11907 // return early if no video data has been observed 11908 if (nalUnits.length === 0) { 11909 this.trigger('done', 'VideoSegmentStream'); 11910 return; 11911 } 11912 11913 while (nalUnits.length) { 11914 currentNal = nalUnits.shift(); 11915 11916 // record the track config 11917 if (currentNal.nalUnitType === 'seq_parameter_set_rbsp') { 11918 track.newMetadata = true; 11919 config = currentNal.config; 11920 track.width = config.width; 11921 track.height = config.height; 11922 track.sps = [currentNal.data]; 11923 track.profileIdc = config.profileIdc; 11924 track.levelIdc = config.levelIdc; 11925 track.profileCompatibility = config.profileCompatibility; 11926 h264Frame.endNalUnit(); 11927 } else if (currentNal.nalUnitType === 'pic_parameter_set_rbsp') { 11928 track.newMetadata = true; 11929 track.pps = [currentNal.data]; 11930 h264Frame.endNalUnit(); 11931 } else if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { 11932 if (h264Frame) { 11933 this.finishFrame(tags, h264Frame); 11934 } 11935 h264Frame = new FlvTag(FlvTag.VIDEO_TAG); 11936 h264Frame.pts = currentNal.pts; 11937 h264Frame.dts = currentNal.dts; 11938 } else { 11939 if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { 11940 // the current sample is a key frame 11941 h264Frame.keyFrame = true; 11942 } 11943 h264Frame.endNalUnit(); 11944 } 11945 h264Frame.startNalUnit(); 11946 h264Frame.writeBytes(currentNal.data); 11947 } 11948 if (h264Frame) { 11949 this.finishFrame(tags, h264Frame); 11950 } 11951 11952 this.trigger('data', {track: track, tags: tags.list}); 11953 11954 // Continue with the flush process now 11955 this.trigger('done', 'VideoSegmentStream'); 11956 }; 11957}; 11958 11959VideoSegmentStream.prototype = new Stream(); 11960 11961/** 11962 * An object that incrementally transmuxes MPEG2 Trasport Stream 11963 * chunks into an FLV. 11964 */ 11965Transmuxer = function(options) { 11966 var 11967 self = this, 11968 11969 packetStream, parseStream, elementaryStream, 11970 videoTimestampRolloverStream, audioTimestampRolloverStream, 11971 timedMetadataTimestampRolloverStream, 11972 adtsStream, h264Stream, 11973 videoSegmentStream, audioSegmentStream, captionStream, 11974 coalesceStream; 11975 11976 Transmuxer.prototype.init.call(this); 11977 11978 options = options || {}; 11979 11980 // expose the metadata stream 11981 this.metadataStream = new m2ts.MetadataStream(); 11982 11983 options.metadataStream = this.metadataStream; 11984 11985 // set up the parsing pipeline 11986 packetStream = new m2ts.TransportPacketStream(); 11987 parseStream = new m2ts.TransportParseStream(); 11988 elementaryStream = new m2ts.ElementaryStream(); 11989 videoTimestampRolloverStream = new m2ts.TimestampRolloverStream('video'); 11990 audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio'); 11991 timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata'); 11992 11993 adtsStream = new AdtsStream(); 11994 h264Stream = new H264Stream(); 11995 coalesceStream = new CoalesceStream(options); 11996 11997 // disassemble MPEG2-TS packets into elementary streams 11998 packetStream 11999 .pipe(parseStream) 12000 .pipe(elementaryStream); 12001 12002 // !!THIS ORDER IS IMPORTANT!! 12003 // demux the streams 12004 elementaryStream 12005 .pipe(videoTimestampRolloverStream) 12006 .pipe(h264Stream); 12007 elementaryStream 12008 .pipe(audioTimestampRolloverStream) 12009 .pipe(adtsStream); 12010 12011 elementaryStream 12012 .pipe(timedMetadataTimestampRolloverStream) 12013 .pipe(this.metadataStream) 12014 .pipe(coalesceStream); 12015 // if CEA-708 parsing is available, hook up a caption stream 12016 captionStream = new m2ts.CaptionStream(); 12017 h264Stream.pipe(captionStream) 12018 .pipe(coalesceStream); 12019 12020 // hook up the segment streams once track metadata is delivered 12021 elementaryStream.on('data', function(data) { 12022 var i, videoTrack, audioTrack; 12023 12024 if (data.type === 'metadata') { 12025 i = data.tracks.length; 12026 12027 // scan the tracks listed in the metadata 12028 while (i--) { 12029 if (data.tracks[i].type === 'video') { 12030 videoTrack = data.tracks[i]; 12031 } else if (data.tracks[i].type === 'audio') { 12032 audioTrack = data.tracks[i]; 12033 } 12034 } 12035 12036 // hook up the video segment stream to the first track with h264 data 12037 if (videoTrack && !videoSegmentStream) { 12038 coalesceStream.numberOfTracks++; 12039 videoSegmentStream = new VideoSegmentStream(videoTrack); 12040 12041 // Set up the final part of the video pipeline 12042 h264Stream 12043 .pipe(videoSegmentStream) 12044 .pipe(coalesceStream); 12045 } 12046 12047 if (audioTrack && !audioSegmentStream) { 12048 // hook up the audio segment stream to the first track with aac data 12049 coalesceStream.numberOfTracks++; 12050 audioSegmentStream = new AudioSegmentStream(audioTrack); 12051 12052 // Set up the final part of the audio pipeline 12053 adtsStream 12054 .pipe(audioSegmentStream) 12055 .pipe(coalesceStream); 12056 12057 if (videoSegmentStream) { 12058 videoSegmentStream.on('keyframe', audioSegmentStream.onVideoKeyFrame); 12059 } 12060 } 12061 } 12062 }); 12063 12064 // feed incoming data to the front of the parsing pipeline 12065 this.push = function(data) { 12066 packetStream.push(data); 12067 }; 12068 12069 // flush any buffered data 12070 this.flush = function() { 12071 // Start at the top of the pipeline and flush all pending work 12072 packetStream.flush(); 12073 }; 12074 12075 // Caption data has to be reset when seeking outside buffered range 12076 this.resetCaptions = function() { 12077 captionStream.reset(); 12078 }; 12079 12080 // Re-emit any data coming from the coalesce stream to the outside world 12081 coalesceStream.on('data', function(event) { 12082 self.trigger('data', event); 12083 }); 12084 12085 // Let the consumer know we have finished flushing the entire pipeline 12086 coalesceStream.on('done', function() { 12087 self.trigger('done'); 12088 }); 12089}; 12090Transmuxer.prototype = new Stream(); 12091 12092// forward compatibility 12093module.exports = Transmuxer; 12094 12095},{"../codecs/adts.js":40,"../codecs/h264":41,"../m2ts/m2ts.js":50,"../utils/stream.js":62,"./coalesce-stream.js":43,"./flv-tag.js":45,"./tag-list.js":47}],49:[function(require,module,exports){ 12096/** 12097 * mux.js 12098 * 12099 * Copyright (c) 2015 Brightcove 12100 * All rights reserved. 12101 * 12102 * Reads in-band caption information from a video elementary 12103 * stream. Captions must follow the CEA-708 standard for injection 12104 * into an MPEG-2 transport streams.
vendor: 7,004 bytes, lines 12105-12353
12105 * @see https://en.wikipedia.org/wiki/CEA-708 12106 * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf 12107 */ 12108 12109'use strict'; 12110 12111// ----------------- 12112// Link To Transport 12113// ----------------- 12114 12115// Supplemental enhancement information (SEI) NAL units have a 12116// payload type field to indicate how they are to be 12117// interpreted. CEAS-708 caption content is always transmitted with 12118// payload type 0x04. 12119var USER_DATA_REGISTERED_ITU_T_T35 = 4, 12120 RBSP_TRAILING_BITS = 128, 12121 Stream = require('../utils/stream'); 12122 12123/** 12124 * Parse a supplemental enhancement information (SEI) NAL unit. 12125 * Stops parsing once a message of type ITU T T35 has been found. 12126 * 12127 * @param bytes {Uint8Array} the bytes of a SEI NAL unit 12128 * @return {object} the parsed SEI payload 12129 * @see Rec. ITU-T H.264, 7.3.2.3.1 12130 */ 12131var parseSei = function(bytes) { 12132 var 12133 i = 0, 12134 result = { 12135 payloadType: -1, 12136 payloadSize: 0 12137 }, 12138 payloadType = 0, 12139 payloadSize = 0; 12140 12141 // go through the sei_rbsp parsing each each individual sei_message 12142 while (i < bytes.byteLength) { 12143 // stop once we have hit the end of the sei_rbsp 12144 if (bytes[i] === RBSP_TRAILING_BITS) { 12145 break; 12146 } 12147 12148 // Parse payload type 12149 while (bytes[i] === 0xFF) { 12150 payloadType += 255; 12151 i++; 12152 } 12153 payloadType += bytes[i++]; 12154 12155 // Parse payload size 12156 while (bytes[i] === 0xFF) { 12157 payloadSize += 255; 12158 i++; 12159 } 12160 payloadSize += bytes[i++]; 12161 12162 // this sei_message is a 608/708 caption so save it and break 12163 // there can only ever be one caption message in a frame's sei 12164 if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) { 12165 result.payloadType = payloadType; 12166 result.payloadSize = payloadSize; 12167 result.payload = bytes.subarray(i, i + payloadSize); 12168 break; 12169 } 12170 12171 // skip the payload and parse the next message 12172 i += payloadSize; 12173 payloadType = 0; 12174 payloadSize = 0; 12175 } 12176 12177 return result; 12178}; 12179 12180// see ANSI/SCTE 128-1 (2013), section 8.1 12181var parseUserData = function(sei) { 12182 // itu_t_t35_contry_code must be 181 (United States) for 12183 // captions 12184 if (sei.payload[0] !== 181) { 12185 return null; 12186 } 12187 12188 // itu_t_t35_provider_code should be 49 (ATSC) for captions 12189 if (((sei.payload[1] << 8) | sei.payload[2]) !== 49) { 12190 return null; 12191 } 12192 12193 // the user_identifier should be "GA94" to indicate ATSC1 data 12194 if (String.fromCharCode(sei.payload[3], 12195 sei.payload[4], 12196 sei.payload[5], 12197 sei.payload[6]) !== 'GA94') { 12198 return null; 12199 } 12200 12201 // finally, user_data_type_code should be 0x03 for caption data 12202 if (sei.payload[7] !== 0x03) { 12203 return null; 12204 } 12205 12206 // return the user_data_type_structure and strip the trailing 12207 // marker bits 12208 return sei.payload.subarray(8, sei.payload.length - 1); 12209}; 12210 12211// see CEA-708-D, section 4.4 12212var parseCaptionPackets = function(pts, userData) { 12213 var results = [], i, count, offset, data; 12214 12215 // if this is just filler, return immediately 12216 if (!(userData[0] & 0x40)) { 12217 return results; 12218 } 12219 12220 // parse out the cc_data_1 and cc_data_2 fields 12221 count = userData[0] & 0x1f; 12222 for (i = 0; i < count; i++) { 12223 offset = i * 3; 12224 data = { 12225 type: userData[offset + 2] & 0x03, 12226 pts: pts 12227 }; 12228 12229 // capture cc data when cc_valid is 1 12230 if (userData[offset + 2] & 0x04) { 12231 data.ccData = (userData[offset + 3] << 8) | userData[offset + 4]; 12232 results.push(data); 12233 } 12234 } 12235 return results; 12236}; 12237 12238var packetDropper = { 12239 push: function() {} 12240}; 12241 12242var CaptionStream = function() { 12243 12244 CaptionStream.prototype.init.call(this); 12245 12246 this.captionPackets_ = []; 12247 12248 this.ccStreams_ = [ 12249 new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define 12250 new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define 12251 new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define 12252 new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define 12253 ]; 12254 12255 this.reset(); 12256 12257 // forward data and done events from CCs to this CaptionStream 12258 this.ccStreams_.forEach(function(cc) { 12259 cc.on('data', this.trigger.bind(this, 'data')); 12260 cc.on('done', this.trigger.bind(this, 'done')); 12261 }, this); 12262 12263}; 12264 12265CaptionStream.prototype = new Stream(); 12266CaptionStream.prototype.push = function(event) { 12267 var sei, userData; 12268 12269 // only examine SEI NALs 12270 if (event.nalUnitType !== 'sei_rbsp') { 12271 return; 12272 } 12273 12274 // parse the sei 12275 sei = parseSei(event.escapedRBSP); 12276 12277 // ignore everything but user_data_registered_itu_t_t35 12278 if (sei.payloadType !== USER_DATA_REGISTERED_ITU_T_T35) { 12279 return; 12280 } 12281 12282 // parse out the user data payload 12283 userData = parseUserData(sei); 12284 12285 // ignore unrecognized userData 12286 if (!userData) { 12287 return; 12288 } 12289 12290 // Sometimes, the same segment # will be downloaded twice. To stop the 12291 // caption data from being processed twice, we track the latest dts we've 12292 // received and ignore everything with a dts before that. However, since 12293 // data for a specific dts can be split across 2 packets on either side of 12294 // a segment boundary, we need to make sure we *don't* ignore the second 12295 // dts packet we receive that has dts === this.latestDts_. And thus, the 12296 // ignoreNextEqualDts_ flag was born. 12297 if (event.dts < this.latestDts_) { 12298 // We've started getting older data, so set the flag. 12299 this.ignoreNextEqualDts_ = true; 12300 return; 12301 } else if ((event.dts === this.latestDts_) && (this.ignoreNextEqualDts_)) { 12302 // We've received the last duplicate packet, time to start processing again 12303 this.ignoreNextEqualDts_ = false; 12304 return; 12305 } 12306 12307 // parse out CC data packets and save them for later 12308 this.captionPackets_ = this.captionPackets_.concat(parseCaptionPackets(event.pts, userData)); 12309 this.latestDts_ = event.dts; 12310}; 12311 12312CaptionStream.prototype.flush = function() { 12313 // make sure we actually parsed captions before proceeding 12314 if (!this.captionPackets_.length) { 12315 this.ccStreams_.forEach(function(cc) { 12316 cc.flush(); 12317 }, this); 12318 return; 12319 } 12320 12321 // In Chrome, the Array#sort function is not stable so add a 12322 // presortIndex that we can use to ensure we get a stable-sort 12323 this.captionPackets_.forEach(function(elem, idx) { 12324 elem.presortIndex = idx; 12325 }); 12326 12327 // sort caption byte-pairs based on their PTS values 12328 this.captionPackets_.sort(function(a, b) { 12329 if (a.pts === b.pts) { 12330 return a.presortIndex - b.presortIndex; 12331 } 12332 return a.pts - b.pts; 12333 }); 12334 12335 this.captionPackets_.forEach(function(packet) { 12336 if (packet.type < 2) { 12337 // Dispatch packet to the right Cea608Stream 12338 this.dispatchCea608Packet(packet); 12339 } 12340 // this is where an 'else' would go for a dispatching packets 12341 // to a theoretical Cea708Stream that handles SERVICEn data 12342 }, this); 12343 12344 this.captionPackets_.length = 0; 12345 this.ccStreams_.forEach(function(cc) { 12346 cc.flush(); 12347 }, this); 12348 return; 12349}; 12350 12351CaptionStream.prototype.reset = function() { 12352 this.latestDts_ = null; 12353 this.ignoreNextEqualDts_ = false;
12354 this.activeCea608Channel_ = null; 12355 // Since we don't know which channel is active until we get a control 12356 // code that sets it, we start off with CEA608 handlers that just drop 12357 // all the packets. 12358 this.activeCea608Streams_ = [ 12359 packetDropper, 12360 packetDropper 12361 ]; 12362 this.ccStreams_.forEach(function(ccStream) { 12363 ccStream.reset(); 12364 }); 12365}; 12366 12367CaptionStream.prototype.dispatchCea608Packet = function(packet) { 12368 if (this.setsChannel1Active(packet) && this.activeCea608Channel_ !== 1) { 12369 this.activeCea608Channel_ = 1; 12370 this.activeCea608Streams_ = [this.ccStreams_[0], this.ccStreams_[2]]; // CC1, CC3 12371 } else if (this.setsChannel2Active(packet) && this.activeCea608Channel_ !== 2) { 12372 this.activeCea608Channel_ = 2; 12373 this.activeCea608Streams_ = [this.ccStreams_[1], this.ccStreams_[3]]; // CC2, CC4 12374 } 12375 // If we haven't set the active streams yet, this next call just returns 12376 // immediately. 12377 this.activeCea608Streams_[packet.type].push(packet); 12378}; 12379 12380CaptionStream.prototype.setsChannel1Active = function(packet) { 12381 return ((packet.ccData & 0x7800) === 0x1000); 12382}; 12383CaptionStream.prototype.setsChannel2Active = function(packet) { 12384 return ((packet.ccData & 0x7800) === 0x1800); 12385}; 12386 12387// ---------------------- 12388// Session to Application 12389// ---------------------- 12390 12391var CHARACTER_TRANSLATION = { 12392 0x2a: 0xe1, // á 12393 0x5c: 0xe9, // é 12394 0x5e: 0xed, // à 12395 0x5f: 0xf3, // ó 12396 0x60: 0xfa, // ú 12397 0x7b: 0xe7, // ç 12398 0x7c: 0xf7, // ÷ 12399 0x7d: 0xd1, // à 12400 0x7e: 0xf1, // ñ 12401 0x7f: 0x2588, // â 12402 0x0130: 0xae, // ® 12403 0x0131: 0xb0, // ° 12404 0x0132: 0xbd, // ½ 12405 0x0133: 0xbf, // ¿ 12406 0x0134: 0x2122, // ⢠12407 0x0135: 0xa2, // ¢ 12408 0x0136: 0xa3, // £ 12409 0x0137: 0x266a, // ⪠12410 0x0138: 0xe0, // à 12411 0x0139: 0xa0, // 12412 0x013a: 0xe8, // è 12413 0x013b: 0xe2, // â 12414 0x013c: 0xea, // ê 12415 0x013d: 0xee, // î 12416 0x013e: 0xf4, // ô 12417 0x013f: 0xfb, // û 12418 0x0220: 0xc1, // à 12419 0x0221: 0xc9, // à 12420 0x0222: 0xd3, // à 12421 0x0223: 0xda, // à 12422 0x0224: 0xdc, // à 12423 0x0225: 0xfc, // ü 12424 0x0226: 0x2018, // â 12425 0x0227: 0xa1, // ¡ 12426 0x0228: 0x2a, // * 12427 0x0229: 0x27, // ' 12428 0x022a: 0x2014, // â 12429 0x022b: 0xa9, // © 12430 0x022c: 0x2120, // â 12431 0x022d: 0x2022, // ⢠12432 0x022e: 0x201c, // â 12433 0x022f: 0x201d, // â 12434 0x0230: 0xc0, // à 12435 0x0231: 0xc2, // à 12436 0x0232: 0xc7, // à 12437 0x0233: 0xc8, // à 12438 0x0234: 0xca, // à 12439 0x0235: 0xcb, // à 12440 0x0236: 0xeb, // ë 12441 0x0237: 0xce, // à 12442 0x0238: 0xcf, // à 12443 0x0239: 0xef, // ï 12444 0x023a: 0xd4, // à 12445 0x023b: 0xd9, // à 12446 0x023c: 0xf9, // ù 12447 0x023d: 0xdb, // à 12448 0x023e: 0xab, // « 12449 0x023f: 0xbb, // » 12450 0x0320: 0xc3, // à 12451 0x0321: 0xe3, // ã 12452 0x0322: 0xcd, // à 12453 0x0323: 0xcc, // à 12454 0x0324: 0xec, // ì 12455 0x0325: 0xd2, // à 12456 0x0326: 0xf2, // ò 12457 0x0327: 0xd5, // à 12458 0x0328: 0xf5, // õ 12459 0x0329: 0x7b, // { 12460 0x032a: 0x7d, // } 12461 0x032b: 0x5c, // \ 12462 0x032c: 0x5e, // ^ 12463 0x032d: 0x5f, // _ 12464 0x032e: 0x7c, // | 12465 0x032f: 0x7e, // ~ 12466 0x0330: 0xc4, // à 12467 0x0331: 0xe4, // ä 12468 0x0332: 0xd6, // à 12469 0x0333: 0xf6, // ö 12470 0x0334: 0xdf, // à 12471 0x0335: 0xa5, // Â¥ 12472 0x0336: 0xa4, // ¤ 12473 0x0337: 0x2502, // â 12474 0x0338: 0xc5, // à 12475 0x0339: 0xe5, // Ã¥ 12476 0x033a: 0xd8, // à 12477 0x033b: 0xf8, // ø 12478 0x033c: 0x250c, // â 12479 0x033d: 0x2510, // â 12480 0x033e: 0x2514, // â 12481 0x033f: 0x2518 // â 12482}; 12483 12484var getCharFromCode = function(code) { 12485 if (code === null) { 12486 return ''; 12487 } 12488 code = CHARACTER_TRANSLATION[code] || code; 12489 return String.fromCharCode(code); 12490}; 12491 12492// the index of the last row in a CEA-608 display buffer 12493var BOTTOM_ROW = 14; 12494 12495// This array is used for mapping PACs -> row #, since there's no way of 12496// getting it through bit logic. 12497var ROWS = [0x1100, 0x1120, 0x1200, 0x1220, 0x1500, 0x1520, 0x1600, 0x1620, 12498 0x1700, 0x1720, 0x1000, 0x1300, 0x1320, 0x1400, 0x1420]; 12499 12500// CEA-608 captions are rendered onto a 34x15 matrix of character 12501// cells. The "bottom" row is the last element in the outer array. 12502var createDisplayBuffer = function() { 12503 var result = [], i = BOTTOM_ROW + 1; 12504 while (i--) { 12505 result.push(''); 12506 } 12507 return result; 12508}; 12509 12510var Cea608Stream = function(field, dataChannel) { 12511 Cea608Stream.prototype.init.call(this); 12512 12513 this.field_ = field || 0; 12514 this.dataChannel_ = dataChannel || 0; 12515 12516 this.name_ = 'CC' + (((this.field_ << 1) | this.dataChannel_) + 1); 12517 12518 this.setConstants(); 12519 this.reset(); 12520 12521 this.push = function(packet) { 12522 var data, swap, char0, char1, text; 12523 // remove the parity bits 12524 data = packet.ccData & 0x7f7f; 12525 12526 // ignore duplicate control codes; the spec demands they're sent twice 12527 if (data === this.lastControlCode_) { 12528 this.lastControlCode_ = null; 12529 return; 12530 } 12531 12532 // Store control codes 12533 if ((data & 0xf000) === 0x1000) { 12534 this.lastControlCode_ = data; 12535 } else if (data !== this.PADDING_) { 12536 this.lastControlCode_ = null; 12537 } 12538 12539 char0 = data >>> 8; 12540 char1 = data & 0xff;
vendor: 4,062 bytes, lines 12541-12647
12541 12542 if (data === this.PADDING_) { 12543 return; 12544 12545 } else if (data === this.RESUME_CAPTION_LOADING_) { 12546 this.mode_ = 'popOn'; 12547 12548 } else if (data === this.END_OF_CAPTION_) { 12549 this.clearFormatting(packet.pts); 12550 // if a caption was being displayed, it's gone now 12551 this.flushDisplayed(packet.pts); 12552 12553 // flip memory 12554 swap = this.displayed_; 12555 this.displayed_ = this.nonDisplayed_; 12556 this.nonDisplayed_ = swap; 12557 12558 // start measuring the time to display the caption 12559 this.startPts_ = packet.pts; 12560 12561 } else if (data === this.ROLL_UP_2_ROWS_) { 12562 this.topRow_ = BOTTOM_ROW - 1; 12563 this.mode_ = 'rollUp'; 12564 } else if (data === this.ROLL_UP_3_ROWS_) { 12565 this.topRow_ = BOTTOM_ROW - 2; 12566 this.mode_ = 'rollUp'; 12567 } else if (data === this.ROLL_UP_4_ROWS_) { 12568 this.topRow_ = BOTTOM_ROW - 3; 12569 this.mode_ = 'rollUp'; 12570 } else if (data === this.CARRIAGE_RETURN_) { 12571 this.clearFormatting(packet.pts); 12572 this.flushDisplayed(packet.pts); 12573 this.shiftRowsUp_(); 12574 this.startPts_ = packet.pts; 12575 12576 } else if (data === this.BACKSPACE_) { 12577 if (this.mode_ === 'popOn') { 12578 this.nonDisplayed_[BOTTOM_ROW] = this.nonDisplayed_[BOTTOM_ROW].slice(0, -1); 12579 } else { 12580 this.displayed_[BOTTOM_ROW] = this.displayed_[BOTTOM_ROW].slice(0, -1); 12581 } 12582 } else if (data === this.ERASE_DISPLAYED_MEMORY_) { 12583 this.flushDisplayed(packet.pts); 12584 this.displayed_ = createDisplayBuffer(); 12585 } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) { 12586 this.nonDisplayed_ = createDisplayBuffer(); 12587 12588 } else if (data === this.RESUME_DIRECT_CAPTIONING_) { 12589 this.mode_ = 'paintOn'; 12590 12591 // Append special characters to caption text 12592 } else if (this.isSpecialCharacter(char0, char1)) { 12593 // Bitmask char0 so that we can apply character transformations 12594 // regardless of field and data channel. 12595 // Then byte-shift to the left and OR with char1 so we can pass the 12596 // entire character code to `getCharFromCode`. 12597 char0 = (char0 & 0x03) << 8; 12598 text = getCharFromCode(char0 | char1); 12599 this[this.mode_](packet.pts, text); 12600 this.column_++; 12601 12602 // Append extended characters to caption text 12603 } else if (this.isExtCharacter(char0, char1)) { 12604 // Extended characters always follow their "non-extended" equivalents. 12605 // IE if a "è" is desired, you'll always receive "eè"; non-compliant 12606 // decoders are supposed to drop the "è", while compliant decoders 12607 // backspace the "e" and insert "è". 12608 12609 // Delete the previous character 12610 if (this.mode_ === 'popOn') { 12611 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1); 12612 } else { 12613 this.displayed_[BOTTOM_ROW] = this.displayed_[BOTTOM_ROW].slice(0, -1); 12614 } 12615 12616 // Bitmask char0 so that we can apply character transformations 12617 // regardless of field and data channel. 12618 // Then byte-shift to the left and OR with char1 so we can pass the 12619 // entire character code to `getCharFromCode`. 12620 char0 = (char0 & 0x03) << 8; 12621 text = getCharFromCode(char0 | char1); 12622 this[this.mode_](packet.pts, text); 12623 this.column_++; 12624 12625 // Process mid-row codes 12626 } else if (this.isMidRowCode(char0, char1)) { 12627 // Attributes are not additive, so clear all formatting 12628 this.clearFormatting(packet.pts); 12629 12630 // According to the standard, mid-row codes 12631 // should be replaced with spaces, so add one now 12632 this[this.mode_](packet.pts, ' '); 12633 this.column_++; 12634 12635 if ((char1 & 0xe) === 0xe) { 12636 this.addFormatting(packet.pts, ['i']); 12637 } 12638 12639 if ((char1 & 0x1) === 0x1) { 12640 this.addFormatting(packet.pts, ['u']); 12641 } 12642 12643 // Detect offset control codes and adjust cursor 12644 } else if (this.isOffsetControlCode(char0, char1)) { 12645 // Cursor position is set by indent PAC (see below) in 4-column 12646 // increments, with an additional offset code of 1-3 to reach any 12647 // of the 32 columns specified by CEA-608. So all we need to do
vendor: 46,419 bytes, lines 12648-14093
12648 // here is increment the column cursor by the given offset. 12649 this.column_ += (char1 & 0x03); 12650 12651 // Detect PACs (Preamble Address Codes) 12652 } else if (this.isPAC(char0, char1)) { 12653 12654 // There's no logic for PAC -> row mapping, so we have to just 12655 // find the row code in an array and use its index :( 12656 var row = ROWS.indexOf(data & 0x1f20); 12657 12658 if (row !== this.row_) { 12659 // formatting is only persistent for current row 12660 this.clearFormatting(packet.pts); 12661 this.row_ = row; 12662 } 12663 // All PACs can apply underline, so detect and apply 12664 // (All odd-numbered second bytes set underline) 12665 if ((char1 & 0x1) && (this.formatting_.indexOf('u') === -1)) { 12666 this.addFormatting(packet.pts, ['u']); 12667 } 12668 12669 if ((data & 0x10) === 0x10) { 12670 // We've got an indent level code. Each successive even number 12671 // increments the column cursor by 4, so we can get the desired 12672 // column position by bit-shifting to the right (to get n/2) 12673 // and multiplying by 4. 12674 this.column_ = ((data & 0xe) >> 1) * 4; 12675 } 12676 12677 if (this.isColorPAC(char1)) { 12678 // it's a color code, though we only support white, which 12679 // can be either normal or italicized. white italics can be 12680 // either 0x4e or 0x6e depending on the row, so we just 12681 // bitwise-and with 0xe to see if italics should be turned on 12682 if ((char1 & 0xe) === 0xe) { 12683 this.addFormatting(packet.pts, ['i']); 12684 } 12685 } 12686 12687 // We have a normal character in char0, and possibly one in char1 12688 } else if (this.isNormalChar(char0)) { 12689 if (char1 === 0x00) { 12690 char1 = null; 12691 } 12692 text = getCharFromCode(char0); 12693 text += getCharFromCode(char1); 12694 this[this.mode_](packet.pts, text); 12695 this.column_ += text.length; 12696 12697 } // finish data processing 12698 12699 }; 12700}; 12701Cea608Stream.prototype = new Stream(); 12702// Trigger a cue point that captures the current state of the 12703// display buffer 12704Cea608Stream.prototype.flushDisplayed = function(pts) { 12705 var content = this.displayed_ 12706 // remove spaces from the start and end of the string 12707 .map(function(row) { 12708 return row.trim(); 12709 }) 12710 // combine all text rows to display in one cue 12711 .join('\n') 12712 // and remove blank rows from the start and end, but not the middle 12713 .replace(/^\n+|\n+$/g, ''); 12714 12715 if (content.length) { 12716 this.trigger('data', { 12717 startPts: this.startPts_, 12718 endPts: pts, 12719 text: content, 12720 stream: this.name_ 12721 }); 12722 } 12723}; 12724 12725/** 12726 * Zero out the data, used for startup and on seek 12727 */ 12728Cea608Stream.prototype.reset = function() { 12729 this.mode_ = 'popOn'; 12730 // When in roll-up mode, the index of the last row that will 12731 // actually display captions. If a caption is shifted to a row 12732 // with a lower index than this, it is cleared from the display 12733 // buffer 12734 this.topRow_ = 0; 12735 this.startPts_ = 0; 12736 this.displayed_ = createDisplayBuffer(); 12737 this.nonDisplayed_ = createDisplayBuffer(); 12738 this.lastControlCode_ = null; 12739 12740 // Track row and column for proper line-breaking and spacing 12741 this.column_ = 0; 12742 this.row_ = BOTTOM_ROW; 12743 12744 // This variable holds currently-applied formatting 12745 this.formatting_ = []; 12746}; 12747 12748/** 12749 * Sets up control code and related constants for this instance 12750 */ 12751Cea608Stream.prototype.setConstants = function() { 12752 // The following attributes have these uses: 12753 // ext_ : char0 for mid-row codes, and the base for extended 12754 // chars (ext_+0, ext_+1, and ext_+2 are char0s for 12755 // extended codes) 12756 // control_: char0 for control codes, except byte-shifted to the 12757 // left so that we can do this.control_ | CONTROL_CODE 12758 // offset_: char0 for tab offset codes 12759 // 12760 // It's also worth noting that control codes, and _only_ control codes, 12761 // differ between field 1 and field2. Field 2 control codes are always 12762 // their field 1 value plus 1. That's why there's the "| field" on the 12763 // control value. 12764 if (this.dataChannel_ === 0) { 12765 this.BASE_ = 0x10; 12766 this.EXT_ = 0x11; 12767 this.CONTROL_ = (0x14 | this.field_) << 8; 12768 this.OFFSET_ = 0x17; 12769 } else if (this.dataChannel_ === 1) { 12770 this.BASE_ = 0x18; 12771 this.EXT_ = 0x19; 12772 this.CONTROL_ = (0x1c | this.field_) << 8; 12773 this.OFFSET_ = 0x1f; 12774 } 12775 12776 // Constants for the LSByte command codes recognized by Cea608Stream. This 12777 // list is not exhaustive. For a more comprehensive listing and semantics see 12778 // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf 12779 // Padding 12780 this.PADDING_ = 0x0000; 12781 // Pop-on Mode 12782 this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20; 12783 this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f; 12784 // Roll-up Mode 12785 this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25; 12786 this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26; 12787 this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27; 12788 this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d; 12789 // paint-on mode (not supported) 12790 this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29; 12791 // Erasure 12792 this.BACKSPACE_ = this.CONTROL_ | 0x21; 12793 this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c; 12794 this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e; 12795}; 12796 12797/** 12798 * Detects if the 2-byte packet data is a special character 12799 * 12800 * Special characters have a second byte in the range 0x30 to 0x3f, 12801 * with the first byte being 0x11 (for data channel 1) or 0x19 (for 12802 * data channel 2). 12803 * 12804 * @param {Integer} char0 The first byte 12805 * @param {Integer} char1 The second byte 12806 * @return {Boolean} Whether the 2 bytes are an special character 12807 */ 12808Cea608Stream.prototype.isSpecialCharacter = function(char0, char1) { 12809 return (char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f); 12810}; 12811 12812/** 12813 * Detects if the 2-byte packet data is an extended character 12814 * 12815 * Extended characters have a second byte in the range 0x20 to 0x3f, 12816 * with the first byte being 0x12 or 0x13 (for data channel 1) or 12817 * 0x1a or 0x1b (for data channel 2). 12818 * 12819 * @param {Integer} char0 The first byte 12820 * @param {Integer} char1 The second byte 12821 * @return {Boolean} Whether the 2 bytes are an extended character 12822 */ 12823Cea608Stream.prototype.isExtCharacter = function(char0, char1) { 12824 return ((char0 === (this.EXT_ + 1) || char0 === (this.EXT_ + 2)) && 12825 (char1 >= 0x20 && char1 <= 0x3f)); 12826}; 12827 12828/** 12829 * Detects if the 2-byte packet is a mid-row code 12830 * 12831 * Mid-row codes have a second byte in the range 0x20 to 0x2f, with 12832 * the first byte being 0x11 (for data channel 1) or 0x19 (for data 12833 * channel 2). 12834 * 12835 * @param {Integer} char0 The first byte 12836 * @param {Integer} char1 The second byte 12837 * @return {Boolean} Whether the 2 bytes are a mid-row code 12838 */ 12839Cea608Stream.prototype.isMidRowCode = function(char0, char1) { 12840 return (char0 === this.EXT_ && (char1 >= 0x20 && char1 <= 0x2f)); 12841}; 12842 12843/** 12844 * Detects if the 2-byte packet is an offset control code 12845 * 12846 * Offset control codes have a second byte in the range 0x21 to 0x23, 12847 * with the first byte being 0x17 (for data channel 1) or 0x1f (for 12848 * data channel 2). 12849 * 12850 * @param {Integer} char0 The first byte 12851 * @param {Integer} char1 The second byte 12852 * @return {Boolean} Whether the 2 bytes are an offset control code 12853 */ 12854Cea608Stream.prototype.isOffsetControlCode = function(char0, char1) { 12855 return (char0 === this.OFFSET_ && (char1 >= 0x21 && char1 <= 0x23)); 12856}; 12857 12858/** 12859 * Detects if the 2-byte packet is a Preamble Address Code 12860 * 12861 * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1) 12862 * or 0x18 to 0x1f (for data channel 2), with the second byte in the 12863 * range 0x40 to 0x7f. 12864 * 12865 * @param {Integer} char0 The first byte 12866 * @param {Integer} char1 The second byte 12867 * @return {Boolean} Whether the 2 bytes are a PAC 12868 */ 12869Cea608Stream.prototype.isPAC = function(char0, char1) { 12870 return (char0 >= this.BASE_ && char0 < (this.BASE_ + 8) && 12871 (char1 >= 0x40 && char1 <= 0x7f)); 12872}; 12873 12874/** 12875 * Detects if a packet's second byte is in the range of a PAC color code 12876 * 12877 * PAC color codes have the second byte be in the range 0x40 to 0x4f, or 12878 * 0x60 to 0x6f. 12879 * 12880 * @param {Integer} char1 The second byte 12881 * @return {Boolean} Whether the byte is a color PAC 12882 */ 12883Cea608Stream.prototype.isColorPAC = function(char1) { 12884 return ((char1 >= 0x40 && char1 <= 0x4f) || (char1 >= 0x60 && char1 <= 0x7f)); 12885}; 12886 12887/** 12888 * Detects if a single byte is in the range of a normal character 12889 * 12890 * Normal text bytes are in the range 0x20 to 0x7f. 12891 * 12892 * @param {Integer} char The byte 12893 * @return {Boolean} Whether the byte is a normal character 12894 */ 12895Cea608Stream.prototype.isNormalChar = function(char) { 12896 return (char >= 0x20 && char <= 0x7f); 12897}; 12898 12899// Adds the opening HTML tag for the passed character to the caption text, 12900// and keeps track of it for later closing 12901Cea608Stream.prototype.addFormatting = function(pts, format) { 12902 this.formatting_ = this.formatting_.concat(format); 12903 var text = format.reduce(function(text, format) { 12904 return text + '<' + format + '>'; 12905 }, ''); 12906 this[this.mode_](pts, text); 12907}; 12908 12909// Adds HTML closing tags for current formatting to caption text and 12910// clears remembered formatting 12911Cea608Stream.prototype.clearFormatting = function(pts) { 12912 if (!this.formatting_.length) { 12913 return; 12914 } 12915 var text = this.formatting_.reverse().reduce(function(text, format) { 12916 return text + '</' + format + '>'; 12917 }, ''); 12918 this.formatting_ = []; 12919 this[this.mode_](pts, text); 12920}; 12921 12922// Mode Implementations 12923Cea608Stream.prototype.popOn = function(pts, text) { 12924 var baseRow = this.nonDisplayed_[this.row_]; 12925 12926 // buffer characters 12927 baseRow += text; 12928 this.nonDisplayed_[this.row_] = baseRow; 12929}; 12930 12931Cea608Stream.prototype.rollUp = function(pts, text) { 12932 var baseRow = this.displayed_[BOTTOM_ROW]; 12933 12934 baseRow += text; 12935 this.displayed_[BOTTOM_ROW] = baseRow; 12936 12937}; 12938 12939Cea608Stream.prototype.shiftRowsUp_ = function() { 12940 var i; 12941 // clear out inactive rows 12942 for (i = 0; i < this.topRow_; i++) { 12943 this.displayed_[i] = ''; 12944 } 12945 // shift displayed rows up 12946 for (i = this.topRow_; i < BOTTOM_ROW; i++) { 12947 this.displayed_[i] = this.displayed_[i + 1]; 12948 } 12949 // clear out the bottom row 12950 this.displayed_[BOTTOM_ROW] = ''; 12951}; 12952 12953// paintOn mode is not implemented 12954Cea608Stream.prototype.paintOn = function() {}; 12955 12956// exports 12957module.exports = { 12958 CaptionStream: CaptionStream, 12959 Cea608Stream: Cea608Stream 12960}; 12961 12962},{"../utils/stream":62}],50:[function(require,module,exports){ 12963/** 12964 * mux.js 12965 * 12966 * Copyright (c) 2015 Brightcove 12967 * All rights reserved. 12968 * 12969 * A stream-based mp2t to mp4 converter. This utility can be used to 12970 * deliver mp4s to a SourceBuffer on platforms that support native 12971 * Media Source Extensions. 12972 */ 12973'use strict'; 12974var Stream = require('../utils/stream.js'), 12975 CaptionStream = require('./caption-stream'), 12976 StreamTypes = require('./stream-types'), 12977 TimestampRolloverStream = require('./timestamp-rollover-stream').TimestampRolloverStream; 12978 12979var m2tsStreamTypes = require('./stream-types.js'); 12980 12981// object types 12982var TransportPacketStream, TransportParseStream, ElementaryStream; 12983 12984// constants 12985var 12986 MP2T_PACKET_LENGTH = 188, // bytes 12987 SYNC_BYTE = 0x47; 12988 12989/** 12990 * Splits an incoming stream of binary data into MPEG-2 Transport 12991 * Stream packets. 12992 */ 12993TransportPacketStream = function() { 12994 var 12995 buffer = new Uint8Array(MP2T_PACKET_LENGTH), 12996 bytesInBuffer = 0; 12997 12998 TransportPacketStream.prototype.init.call(this); 12999 13000 // Deliver new bytes to the stream. 13001 13002 this.push = function(bytes) { 13003 var 13004 startIndex = 0, 13005 endIndex = MP2T_PACKET_LENGTH, 13006 everything; 13007 13008 // If there are bytes remaining from the last segment, prepend them to the 13009 // bytes that were pushed in 13010 if (bytesInBuffer) { 13011 everything = new Uint8Array(bytes.byteLength + bytesInBuffer); 13012 everything.set(buffer.subarray(0, bytesInBuffer)); 13013 everything.set(bytes, bytesInBuffer); 13014 bytesInBuffer = 0; 13015 } else { 13016 everything = bytes; 13017 } 13018 13019 // While we have enough data for a packet 13020 while (endIndex < everything.byteLength) { 13021 // Look for a pair of start and end sync bytes in the data.. 13022 if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) { 13023 // We found a packet so emit it and jump one whole packet forward in 13024 // the stream 13025 this.trigger('data', everything.subarray(startIndex, endIndex)); 13026 startIndex += MP2T_PACKET_LENGTH; 13027 endIndex += MP2T_PACKET_LENGTH; 13028 continue; 13029 } 13030 // If we get here, we have somehow become de-synchronized and we need to step 13031 // forward one byte at a time until we find a pair of sync bytes that denote 13032 // a packet 13033 startIndex++; 13034 endIndex++; 13035 } 13036 13037 // If there was some data left over at the end of the segment that couldn't 13038 // possibly be a whole packet, keep it because it might be the start of a packet 13039 // that continues in the next segment 13040 if (startIndex < everything.byteLength) { 13041 buffer.set(everything.subarray(startIndex), 0); 13042 bytesInBuffer = everything.byteLength - startIndex; 13043 } 13044 }; 13045 13046 this.flush = function() { 13047 // If the buffer contains a whole packet when we are being flushed, emit it 13048 // and empty the buffer. Otherwise hold onto the data because it may be 13049 // important for decoding the next segment 13050 if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) { 13051 this.trigger('data', buffer); 13052 bytesInBuffer = 0; 13053 } 13054 this.trigger('done'); 13055 }; 13056}; 13057TransportPacketStream.prototype = new Stream(); 13058 13059/** 13060 * Accepts an MP2T TransportPacketStream and emits data events with parsed 13061 * forms of the individual transport stream packets. 13062 */ 13063TransportParseStream = function() { 13064 var parsePsi, parsePat, parsePmt, self; 13065 TransportParseStream.prototype.init.call(this); 13066 self = this; 13067 13068 this.packetsWaitingForPmt = []; 13069 this.programMapTable = undefined; 13070 13071 parsePsi = function(payload, psi) { 13072 var offset = 0; 13073 13074 // PSI packets may be split into multiple sections and those 13075 // sections may be split into multiple packets. If a PSI 13076 // section starts in this packet, the payload_unit_start_indicator 13077 // will be true and the first byte of the payload will indicate 13078 // the offset from the current position to the start of the 13079 // section. 13080 if (psi.payloadUnitStartIndicator) { 13081 offset += payload[offset] + 1; 13082 } 13083 13084 if (psi.type === 'pat') { 13085 parsePat(payload.subarray(offset), psi); 13086 } else { 13087 parsePmt(payload.subarray(offset), psi); 13088 } 13089 }; 13090 13091 parsePat = function(payload, pat) { 13092 pat.section_number = payload[7]; // eslint-disable-line camelcase 13093 pat.last_section_number = payload[8]; // eslint-disable-line camelcase 13094 13095 // skip the PSI header and parse the first PMT entry 13096 self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11]; 13097 pat.pmtPid = self.pmtPid; 13098 }; 13099 13100 /** 13101 * Parse out the relevant fields of a Program Map Table (PMT). 13102 * @param payload {Uint8Array} the PMT-specific portion of an MP2T 13103 * packet. The first byte in this array should be the table_id 13104 * field. 13105 * @param pmt {object} the object that should be decorated with 13106 * fields parsed from the PMT. 13107 */ 13108 parsePmt = function(payload, pmt) { 13109 var sectionLength, tableEnd, programInfoLength, offset; 13110 13111 // PMTs can be sent ahead of the time when they should actually 13112 // take effect. We don't believe this should ever be the case 13113 // for HLS but we'll ignore "forward" PMT declarations if we see 13114 // them. Future PMT declarations have the current_next_indicator 13115 // set to zero. 13116 if (!(payload[5] & 0x01)) { 13117 return; 13118 } 13119 13120 // overwrite any existing program map table 13121 self.programMapTable = {}; 13122 13123 // the mapping table ends at the end of the current section 13124 sectionLength = (payload[1] & 0x0f) << 8 | payload[2]; 13125 tableEnd = 3 + sectionLength - 4; 13126 13127 // to determine where the table is, we have to figure out how 13128 // long the program info descriptors are 13129 programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; 13130 13131 // advance the offset to the first entry in the mapping table 13132 offset = 12 + programInfoLength; 13133 while (offset < tableEnd) { 13134 // add an entry that maps the elementary_pid to the stream_type 13135 self.programMapTable[(payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]] = payload[offset]; 13136 13137 // move to the next table entry 13138 // skip past the elementary stream descriptors, if present 13139 offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5; 13140 } 13141 13142 // record the map on the packet as well 13143 pmt.programMapTable = self.programMapTable; 13144 13145 // if there are any packets waiting for a PMT to be found, process them now 13146 while (self.packetsWaitingForPmt.length) { 13147 self.processPes_.apply(self, self.packetsWaitingForPmt.shift()); 13148 } 13149 }; 13150 13151 /** 13152 * Deliver a new MP2T packet to the stream. 13153 */ 13154 this.push = function(packet) { 13155 var 13156 result = {}, 13157 offset = 4; 13158 13159 result.payloadUnitStartIndicator = !!(packet[1] & 0x40); 13160 13161 // pid is a 13-bit field starting at the last bit of packet[1] 13162 result.pid = packet[1] & 0x1f; 13163 result.pid <<= 8; 13164 result.pid |= packet[2]; 13165 13166 // if an adaption field is present, its length is specified by the 13167 // fifth byte of the TS packet header. The adaptation field is 13168 // used to add stuffing to PES packets that don't fill a complete 13169 // TS packet, and to specify some forms of timing and control data 13170 // that we do not currently use. 13171 if (((packet[3] & 0x30) >>> 4) > 0x01) { 13172 offset += packet[offset] + 1; 13173 } 13174 13175 // parse the rest of the packet based on the type 13176 if (result.pid === 0) { 13177 result.type = 'pat'; 13178 parsePsi(packet.subarray(offset), result); 13179 this.trigger('data', result); 13180 } else if (result.pid === this.pmtPid) { 13181 result.type = 'pmt'; 13182 parsePsi(packet.subarray(offset), result); 13183 this.trigger('data', result); 13184 } else if (this.programMapTable === undefined) { 13185 // When we have not seen a PMT yet, defer further processing of 13186 // PES packets until one has been parsed 13187 this.packetsWaitingForPmt.push([packet, offset, result]); 13188 } else { 13189 this.processPes_(packet, offset, result); 13190 } 13191 }; 13192 13193 this.processPes_ = function(packet, offset, result) { 13194 result.streamType = this.programMapTable[result.pid]; 13195 result.type = 'pes'; 13196 result.data = packet.subarray(offset); 13197 13198 this.trigger('data', result); 13199 }; 13200 13201}; 13202TransportParseStream.prototype = new Stream(); 13203TransportParseStream.STREAM_TYPES = { 13204 h264: 0x1b, 13205 adts: 0x0f 13206}; 13207 13208/** 13209 * Reconsistutes program elementary stream (PES) packets from parsed 13210 * transport stream packets. That is, if you pipe an 13211 * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output 13212 * events will be events which capture the bytes for individual PES 13213 * packets plus relevant metadata that has been extracted from the 13214 * container. 13215 */ 13216ElementaryStream = function() { 13217 var 13218 self = this, 13219 // PES packet fragments 13220 video = { 13221 data: [], 13222 size: 0 13223 }, 13224 audio = { 13225 data: [], 13226 size: 0 13227 }, 13228 timedMetadata = { 13229 data: [], 13230 size: 0 13231 }, 13232 parsePes = function(payload, pes) { 13233 var ptsDtsFlags; 13234 13235 // get the packet length, this will be 0 for video 13236 pes.packetLength = 6 + ((payload[4] << 8) | payload[5]); 13237 13238 // find out if this packets starts a new keyframe 13239 pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; 13240 // PES packets may be annotated with a PTS value, or a PTS value 13241 // and a DTS value. Determine what combination of values is 13242 // available to work with. 13243 ptsDtsFlags = payload[7]; 13244 13245 // PTS and DTS are normally stored as a 33-bit number. Javascript 13246 // performs all bitwise operations on 32-bit integers but javascript 13247 // supports a much greater range (52-bits) of integer using standard 13248 // mathematical operations. 13249 // We construct a 31-bit value using bitwise operators over the 31 13250 // most significant bits and then multiply by 4 (equal to a left-shift 13251 // of 2) before we add the final 2 least significant bits of the 13252 // timestamp (equal to an OR.) 13253 if (ptsDtsFlags & 0xC0) { 13254 // the PTS and DTS are not written out directly. For information 13255 // on how they are encoded, see 13256 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 13257 pes.pts = (payload[9] & 0x0E) << 27 | 13258 (payload[10] & 0xFF) << 20 | 13259 (payload[11] & 0xFE) << 12 | 13260 (payload[12] & 0xFF) << 5 | 13261 (payload[13] & 0xFE) >>> 3; 13262 pes.pts *= 4; // Left shift by 2 13263 pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs 13264 pes.dts = pes.pts; 13265 if (ptsDtsFlags & 0x40) { 13266 pes.dts = (payload[14] & 0x0E) << 27 | 13267 (payload[15] & 0xFF) << 20 | 13268 (payload[16] & 0xFE) << 12 | 13269 (payload[17] & 0xFF) << 5 | 13270 (payload[18] & 0xFE) >>> 3; 13271 pes.dts *= 4; // Left shift by 2 13272 pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs 13273 } 13274 } 13275 // the data section starts immediately after the PES header. 13276 // pes_header_data_length specifies the number of header bytes 13277 // that follow the last byte of the field. 13278 pes.data = payload.subarray(9 + payload[8]); 13279 }, 13280 flushStream = function(stream, type, forceFlush) { 13281 var 13282 packetData = new Uint8Array(stream.size), 13283 event = { 13284 type: type 13285 }, 13286 i = 0, 13287 offset = 0, 13288 packetFlushable = false, 13289 fragment; 13290 13291 // do nothing if there is not enough buffered data for a complete 13292 // PES header 13293 if (!stream.data.length || stream.size < 9) { 13294 return; 13295 } 13296 event.trackId = stream.data[0].pid; 13297 13298 // reassemble the packet 13299 for (i = 0; i < stream.data.length; i++) { 13300 fragment = stream.data[i]; 13301 13302 packetData.set(fragment.data, offset); 13303 offset += fragment.data.byteLength; 13304 } 13305 13306 // parse assembled packet's PES header 13307 parsePes(packetData, event); 13308 13309 // non-video PES packets MUST have a non-zero PES_packet_length 13310 // check that they match before we do a flush 13311 packetFlushable = type === 'video' || event.packetLength === stream.size; 13312 13313 // flush pending packets if the conditions are right 13314 if (forceFlush || packetFlushable) { 13315 stream.size = 0; 13316 stream.data.length = 0; 13317 } 13318 13319 // only emit packets that are complete. this is to avoid assembling 13320 // incomplete PES packets due to poor segmentation 13321 if (packetFlushable) { 13322 self.trigger('data', event); 13323 } 13324 }; 13325 13326 ElementaryStream.prototype.init.call(this); 13327 13328 this.push = function(data) { 13329 ({ 13330 pat: function() { 13331 // we have to wait for the PMT to arrive as well before we 13332 // have any meaningful metadata 13333 }, 13334 pes: function() { 13335 var stream, streamType; 13336 13337 switch (data.streamType) { 13338 case StreamTypes.H264_STREAM_TYPE: 13339 case m2tsStreamTypes.H264_STREAM_TYPE: 13340 stream = video; 13341 streamType = 'video'; 13342 break; 13343 case StreamTypes.ADTS_STREAM_TYPE: 13344 stream = audio; 13345 streamType = 'audio'; 13346 break; 13347 case StreamTypes.METADATA_STREAM_TYPE: 13348 stream = timedMetadata; 13349 streamType = 'timed-metadata'; 13350 break; 13351 default: 13352 // ignore unknown stream types 13353 return; 13354 } 13355 13356 // if a new packet is starting, we can flush the completed 13357 // packet 13358 if (data.payloadUnitStartIndicator) { 13359 flushStream(stream, streamType, true); 13360 } 13361 13362 // buffer this fragment until we are sure we've received the 13363 // complete payload 13364 stream.data.push(data); 13365 stream.size += data.data.byteLength; 13366 }, 13367 pmt: function() { 13368 var 13369 event = { 13370 type: 'metadata', 13371 tracks: [] 13372 }, 13373 programMapTable = data.programMapTable, 13374 k, 13375 track; 13376 13377 // translate streams to tracks 13378 for (k in programMapTable) { 13379 if (programMapTable.hasOwnProperty(k)) { 13380 track = { 13381 timelineStartInfo: { 13382 baseMediaDecodeTime: 0 13383 } 13384 }; 13385 track.id = +k; 13386 if (programMapTable[k] === m2tsStreamTypes.H264_STREAM_TYPE) { 13387 track.codec = 'avc'; 13388 track.type = 'video'; 13389 } else if (programMapTable[k] === m2tsStreamTypes.ADTS_STREAM_TYPE) { 13390 track.codec = 'adts'; 13391 track.type = 'audio'; 13392 } 13393 event.tracks.push(track); 13394 } 13395 } 13396 self.trigger('data', event); 13397 } 13398 })[data.type](); 13399 }; 13400 13401 /** 13402 * Flush any remaining input. Video PES packets may be of variable 13403 * length. Normally, the start of a new video packet can trigger the 13404 * finalization of the previous packet. That is not possible if no 13405 * more video is forthcoming, however. In that case, some other 13406 * mechanism (like the end of the file) has to be employed. When it is 13407 * clear that no additional data is forthcoming, calling this method 13408 * will flush the buffered packets. 13409 */ 13410 this.flush = function() { 13411 // !!THIS ORDER IS IMPORTANT!! 13412 // video first then audio 13413 flushStream(video, 'video'); 13414 flushStream(audio, 'audio'); 13415 flushStream(timedMetadata, 'timed-metadata'); 13416 this.trigger('done'); 13417 }; 13418}; 13419ElementaryStream.prototype = new Stream(); 13420 13421var m2ts = { 13422 PAT_PID: 0x0000, 13423 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH, 13424 TransportPacketStream: TransportPacketStream, 13425 TransportParseStream: TransportParseStream, 13426 ElementaryStream: ElementaryStream, 13427 TimestampRolloverStream: TimestampRolloverStream, 13428 CaptionStream: CaptionStream.CaptionStream, 13429 Cea608Stream: CaptionStream.Cea608Stream, 13430 MetadataStream: require('./metadata-stream') 13431}; 13432 13433for (var type in StreamTypes) { 13434 if (StreamTypes.hasOwnProperty(type)) { 13435 m2ts[type] = StreamTypes[type]; 13436 } 13437} 13438 13439module.exports = m2ts; 13440 13441},{"../utils/stream.js":62,"./caption-stream":49,"./metadata-stream":51,"./stream-types":53,"./stream-types.js":53,"./timestamp-rollover-stream":54}],51:[function(require,module,exports){ 13442/** 13443 * Accepts program elementary stream (PES) data events and parses out 13444 * ID3 metadata from them, if present. 13445 * @see http://id3.org/id3v2.3.0 13446 */ 13447'use strict'; 13448var 13449 Stream = require('../utils/stream'), 13450 StreamTypes = require('./stream-types'), 13451 // return a percent-encoded representation of the specified byte range 13452 // @see http://en.wikipedia.org/wiki/Percent-encoding 13453 percentEncode = function(bytes, start, end) { 13454 var i, result = ''; 13455 for (i = start; i < end; i++) { 13456 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 13457 } 13458 return result; 13459 }, 13460 // return the string representation of the specified byte range, 13461 // interpreted as UTf-8. 13462 parseUtf8 = function(bytes, start, end) { 13463 return decodeURIComponent(percentEncode(bytes, start, end)); 13464 }, 13465 // return the string representation of the specified byte range, 13466 // interpreted as ISO-8859-1. 13467 parseIso88591 = function(bytes, start, end) { 13468 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 13469 }, 13470 parseSyncSafeInteger = function(data) { 13471 return (data[0] << 21) | 13472 (data[1] << 14) | 13473 (data[2] << 7) | 13474 (data[3]); 13475 }, 13476 tagParsers = { 13477 TXXX: function(tag) { 13478 var i; 13479 if (tag.data[0] !== 3) { 13480 // ignore frames with unrecognized character encodings 13481 return; 13482 } 13483 13484 for (i = 1; i < tag.data.length; i++) { 13485 if (tag.data[i] === 0) { 13486 // parse the text fields 13487 tag.description = parseUtf8(tag.data, 1, i); 13488 // do not include the null terminator in the tag value 13489 tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, ''); 13490 break; 13491 } 13492 } 13493 tag.data = tag.value; 13494 }, 13495 WXXX: function(tag) { 13496 var i; 13497 if (tag.data[0] !== 3) { 13498 // ignore frames with unrecognized character encodings 13499 return; 13500 } 13501 13502 for (i = 1; i < tag.data.length; i++) { 13503 if (tag.data[i] === 0) { 13504 // parse the description and URL fields 13505 tag.description = parseUtf8(tag.data, 1, i); 13506 tag.url = parseUtf8(tag.data, i + 1, tag.data.length); 13507 break; 13508 } 13509 } 13510 }, 13511 PRIV: function(tag) { 13512 var i; 13513 13514 for (i = 0; i < tag.data.length; i++) { 13515 if (tag.data[i] === 0) { 13516 // parse the description and URL fields 13517 tag.owner = parseIso88591(tag.data, 0, i); 13518 break; 13519 } 13520 } 13521 tag.privateData = tag.data.subarray(i + 1); 13522 tag.data = tag.privateData; 13523 } 13524 }, 13525 MetadataStream; 13526 13527MetadataStream = function(options) { 13528 var 13529 settings = { 13530 debug: !!(options && options.debug), 13531 13532 // the bytes of the program-level descriptor field in MP2T 13533 // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and 13534 // program element descriptors" 13535 descriptor: options && options.descriptor 13536 }, 13537 // the total size in bytes of the ID3 tag being parsed 13538 tagSize = 0, 13539 // tag data that is not complete enough to be parsed 13540 buffer = [], 13541 // the total number of bytes currently in the buffer 13542 bufferSize = 0, 13543 i; 13544 13545 MetadataStream.prototype.init.call(this); 13546 13547 // calculate the text track in-band metadata track dispatch type 13548 // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track 13549 this.dispatchType = StreamTypes.METADATA_STREAM_TYPE.toString(16); 13550 if (settings.descriptor) { 13551 for (i = 0; i < settings.descriptor.length; i++) { 13552 this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2); 13553 } 13554 } 13555 13556 this.push = function(chunk) { 13557 var tag, frameStart, frameSize, frame, i, frameHeader; 13558 if (chunk.type !== 'timed-metadata') { 13559 return; 13560 } 13561 13562 // if data_alignment_indicator is set in the PES header, 13563 // we must have the start of a new ID3 tag. Assume anything 13564 // remaining in the buffer was malformed and throw it out 13565 if (chunk.dataAlignmentIndicator) { 13566 bufferSize = 0; 13567 buffer.length = 0; 13568 } 13569 13570 // ignore events that don't look like ID3 data 13571 if (buffer.length === 0 && 13572 (chunk.data.length < 10 || 13573 chunk.data[0] !== 'I'.charCodeAt(0) || 13574 chunk.data[1] !== 'D'.charCodeAt(0) || 13575 chunk.data[2] !== '3'.charCodeAt(0))) { 13576 if (settings.debug) { 13577 // eslint-disable-next-line no-console 13578 console.log('Skipping unrecognized metadata packet'); 13579 } 13580 return; 13581 } 13582 13583 // add this chunk to the data we've collected so far 13584 13585 buffer.push(chunk); 13586 bufferSize += chunk.data.byteLength; 13587 13588 // grab the size of the entire frame from the ID3 header 13589 if (buffer.length === 1) { 13590 // the frame size is transmitted as a 28-bit integer in the 13591 // last four bytes of the ID3 header. 13592 // The most significant bit of each byte is dropped and the 13593 // results concatenated to recover the actual value. 13594 tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); 13595 13596 // ID3 reports the tag size excluding the header but it's more 13597 // convenient for our comparisons to include it 13598 tagSize += 10; 13599 } 13600 13601 // if the entire frame has not arrived, wait for more data 13602 if (bufferSize < tagSize) { 13603 return; 13604 } 13605 13606 // collect the entire frame so it can be parsed 13607 tag = { 13608 data: new Uint8Array(tagSize), 13609 frames: [], 13610 pts: buffer[0].pts, 13611 dts: buffer[0].dts 13612 }; 13613 for (i = 0; i < tagSize;) { 13614 tag.data.set(buffer[0].data.subarray(0, tagSize - i), i); 13615 i += buffer[0].data.byteLength; 13616 bufferSize -= buffer[0].data.byteLength; 13617 buffer.shift(); 13618 } 13619 13620 // find the start of the first frame and the end of the tag 13621 frameStart = 10; 13622 if (tag.data[5] & 0x40) { 13623 // advance the frame start past the extended header 13624 frameStart += 4; // header size field 13625 frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); 13626 13627 // clip any padding off the end 13628 tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20)); 13629 } 13630 13631 // parse one or more ID3 frames 13632 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 13633 do { 13634 // determine the number of bytes in this frame 13635 frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8)); 13636 if (frameSize < 1) { 13637 // eslint-disable-next-line no-console 13638 return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.'); 13639 } 13640 frameHeader = String.fromCharCode(tag.data[frameStart], 13641 tag.data[frameStart + 1], 13642 tag.data[frameStart + 2], 13643 tag.data[frameStart + 3]); 13644 13645 13646 frame = { 13647 id: frameHeader, 13648 data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10) 13649 }; 13650 frame.key = frame.id; 13651 if (tagParsers[frame.id]) { 13652 tagParsers[frame.id](frame); 13653 13654 // handle the special PRIV frame used to indicate the start 13655 // time for raw AAC data 13656 if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') { 13657 var 13658 d = frame.data, 13659 size = ((d[3] & 0x01) << 30) | 13660 (d[4] << 22) | 13661 (d[5] << 14) | 13662 (d[6] << 6) | 13663 (d[7] >>> 2); 13664 13665 size *= 4; 13666 size += d[7] & 0x03; 13667 frame.timeStamp = size; 13668 // in raw AAC, all subsequent data will be timestamped based 13669 // on the value of this frame 13670 // we couldn't have known the appropriate pts and dts before 13671 // parsing this ID3 tag so set those values now 13672 if (tag.pts === undefined && tag.dts === undefined) { 13673 tag.pts = frame.timeStamp; 13674 tag.dts = frame.timeStamp; 13675 } 13676 this.trigger('timestamp', frame); 13677 } 13678 } 13679 tag.frames.push(frame); 13680 13681 frameStart += 10; // advance past the frame header 13682 frameStart += frameSize; // advance past the frame body 13683 } while (frameStart < tagSize); 13684 this.trigger('data', tag); 13685 }; 13686}; 13687MetadataStream.prototype = new Stream(); 13688 13689module.exports = MetadataStream; 13690 13691},{"../utils/stream":62,"./stream-types":53}],52:[function(require,module,exports){ 13692/** 13693 * mux.js 13694 * 13695 * Copyright (c) 2016 Brightcove 13696 * All rights reserved. 13697 * 13698 * Utilities to detect basic properties and metadata about TS Segments. 13699 */ 13700'use strict'; 13701 13702var StreamTypes = require('./stream-types.js'); 13703 13704var parsePid = function(packet) { 13705 var pid = packet[1] & 0x1f; 13706 pid <<= 8; 13707 pid |= packet[2]; 13708 return pid; 13709}; 13710 13711var parsePayloadUnitStartIndicator = function(packet) { 13712 return !!(packet[1] & 0x40); 13713}; 13714 13715var parseAdaptionField = function(packet) { 13716 var offset = 0; 13717 // if an adaption field is present, its length is specified by the 13718 // fifth byte of the TS packet header. The adaptation field is 13719 // used to add stuffing to PES packets that don't fill a complete 13720 // TS packet, and to specify some forms of timing and control data 13721 // that we do not currently use. 13722 if (((packet[3] & 0x30) >>> 4) > 0x01) { 13723 offset += packet[4] + 1; 13724 } 13725 return offset; 13726}; 13727 13728var parseType = function(packet, pmtPid) { 13729 var pid = parsePid(packet); 13730 if (pid === 0) { 13731 return 'pat'; 13732 } else if (pid === pmtPid) { 13733 return 'pmt'; 13734 } else if (pmtPid) { 13735 return 'pes'; 13736 } 13737 return null; 13738}; 13739 13740var parsePat = function(packet) { 13741 var pusi = parsePayloadUnitStartIndicator(packet); 13742 var offset = 4 + parseAdaptionField(packet); 13743 13744 if (pusi) { 13745 offset += packet[offset] + 1; 13746 } 13747 13748 return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11]; 13749}; 13750 13751var parsePmt = function(packet) { 13752 var programMapTable = {}; 13753 var pusi = parsePayloadUnitStartIndicator(packet); 13754 var payloadOffset = 4 + parseAdaptionField(packet); 13755 13756 if (pusi) { 13757 payloadOffset += packet[payloadOffset] + 1; 13758 } 13759 13760 // PMTs can be sent ahead of the time when they should actually 13761 // take effect. We don't believe this should ever be the case 13762 // for HLS but we'll ignore "forward" PMT declarations if we see 13763 // them. Future PMT declarations have the current_next_indicator 13764 // set to zero. 13765 if (!(packet[payloadOffset + 5] & 0x01)) { 13766 return; 13767 } 13768 13769 var sectionLength, tableEnd, programInfoLength; 13770 // the mapping table ends at the end of the current section 13771 sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2]; 13772 tableEnd = 3 + sectionLength - 4; 13773 13774 // to determine where the table is, we have to figure out how 13775 // long the program info descriptors are 13776 programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; 13777 13778 // advance the offset to the first entry in the mapping table 13779 var offset = 12 + programInfoLength; 13780 while (offset < tableEnd) { 13781 var i = payloadOffset + offset; 13782 // add an entry that maps the elementary_pid to the stream_type 13783 programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; 13784 13785 // move to the next table entry 13786 // skip past the elementary stream descriptors, if present 13787 offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5; 13788 } 13789 return programMapTable; 13790}; 13791 13792var parsePesType = function(packet, programMapTable) { 13793 var pid = parsePid(packet); 13794 var type = programMapTable[pid]; 13795 switch (type) { 13796 case StreamTypes.H264_STREAM_TYPE: 13797 return 'video'; 13798 case StreamTypes.ADTS_STREAM_TYPE: 13799 return 'audio'; 13800 case StreamTypes.METADATA_STREAM_TYPE: 13801 return 'timed-metadata'; 13802 default: 13803 return null; 13804 } 13805}; 13806 13807var parsePesTime = function(packet) { 13808 var pusi = parsePayloadUnitStartIndicator(packet); 13809 if (!pusi) { 13810 return null; 13811 } 13812 13813 var offset = 4 + parseAdaptionField(packet); 13814 13815 if (offset >= packet.byteLength) { 13816 // From the H 222.0 MPEG-TS spec 13817 // "For transport stream packets carrying PES packets, stuffing is needed when there 13818 // is insufficient PES packet data to completely fill the transport stream packet 13819 // payload bytes. Stuffing is accomplished by defining an adaptation field longer than 13820 // the sum of the lengths of the data elements in it, so that the payload bytes 13821 // remaining after the adaptation field exactly accommodates the available PES packet 13822 // data." 13823 // 13824 // If the offset is >= the length of the packet, then the packet contains no data 13825 // and instead is just adaption field stuffing bytes 13826 return null; 13827 } 13828 13829 var pes = null; 13830 var ptsDtsFlags; 13831 13832 // PES packets may be annotated with a PTS value, or a PTS value 13833 // and a DTS value. Determine what combination of values is 13834 // available to work with. 13835 ptsDtsFlags = packet[offset + 7]; 13836 13837 // PTS and DTS are normally stored as a 33-bit number. Javascript 13838 // performs all bitwise operations on 32-bit integers but javascript 13839 // supports a much greater range (52-bits) of integer using standard 13840 // mathematical operations. 13841 // We construct a 31-bit value using bitwise operators over the 31 13842 // most significant bits and then multiply by 4 (equal to a left-shift 13843 // of 2) before we add the final 2 least significant bits of the 13844 // timestamp (equal to an OR.) 13845 if (ptsDtsFlags & 0xC0) { 13846 pes = {}; 13847 // the PTS and DTS are not written out directly. For information 13848 // on how they are encoded, see 13849 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 13850 pes.pts = (packet[offset + 9] & 0x0E) << 27 | 13851 (packet[offset + 10] & 0xFF) << 20 | 13852 (packet[offset + 11] & 0xFE) << 12 | 13853 (packet[offset + 12] & 0xFF) << 5 | 13854 (packet[offset + 13] & 0xFE) >>> 3; 13855 pes.pts *= 4; // Left shift by 2 13856 pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs 13857 pes.dts = pes.pts; 13858 if (ptsDtsFlags & 0x40) { 13859 pes.dts = (packet[offset + 14] & 0x0E) << 27 | 13860 (packet[offset + 15] & 0xFF) << 20 | 13861 (packet[offset + 16] & 0xFE) << 12 | 13862 (packet[offset + 17] & 0xFF) << 5 | 13863 (packet[offset + 18] & 0xFE) >>> 3; 13864 pes.dts *= 4; // Left shift by 2 13865 pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs 13866 } 13867 } 13868 return pes; 13869}; 13870 13871var parseNalUnitType = function(type) { 13872 switch (type) { 13873 case 0x05: 13874 return 'slice_layer_without_partitioning_rbsp_idr'; 13875 case 0x06: 13876 return 'sei_rbsp'; 13877 case 0x07: 13878 return 'seq_parameter_set_rbsp'; 13879 case 0x08: 13880 return 'pic_parameter_set_rbsp'; 13881 case 0x09: 13882 return 'access_unit_delimiter_rbsp'; 13883 default: 13884 return null; 13885 } 13886}; 13887 13888var videoPacketContainsKeyFrame = function(packet) { 13889 var offset = 4 + parseAdaptionField(packet); 13890 var frameBuffer = packet.subarray(offset); 13891 var frameI = 0; 13892 var frameSyncPoint = 0; 13893 var foundKeyFrame = false; 13894 var nalType; 13895 13896 // advance the sync point to a NAL start, if necessary 13897 for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) { 13898 if (frameBuffer[frameSyncPoint + 2] === 1) { 13899 // the sync point is properly aligned 13900 frameI = frameSyncPoint + 5; 13901 break; 13902 } 13903 } 13904 13905 while (frameI < frameBuffer.byteLength) { 13906 // look at the current byte to determine if we've hit the end of 13907 // a NAL unit boundary 13908 switch (frameBuffer[frameI]) { 13909 case 0: 13910 // skip past non-sync sequences 13911 if (frameBuffer[frameI - 1] !== 0) { 13912 frameI += 2; 13913 break; 13914 } else if (frameBuffer[frameI - 2] !== 0) { 13915 frameI++; 13916 break; 13917 } 13918 13919 if (frameSyncPoint + 3 !== frameI - 2) { 13920 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 13921 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 13922 foundKeyFrame = true; 13923 } 13924 } 13925 13926 // drop trailing zeroes 13927 do { 13928 frameI++; 13929 } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length); 13930 frameSyncPoint = frameI - 2; 13931 frameI += 3; 13932 break; 13933 case 1: 13934 // skip past non-sync sequences 13935 if (frameBuffer[frameI - 1] !== 0 || 13936 frameBuffer[frameI - 2] !== 0) { 13937 frameI += 3; 13938 break; 13939 } 13940 13941 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 13942 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 13943 foundKeyFrame = true; 13944 } 13945 frameSyncPoint = frameI - 2; 13946 frameI += 3; 13947 break; 13948 default: 13949 // the current byte isn't a one or zero, so it cannot be part 13950 // of a sync sequence 13951 frameI += 3; 13952 break; 13953 } 13954 } 13955 frameBuffer = frameBuffer.subarray(frameSyncPoint); 13956 frameI -= frameSyncPoint; 13957 frameSyncPoint = 0; 13958 // parse the final nal 13959 if (frameBuffer && frameBuffer.byteLength > 3) { 13960 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 13961 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 13962 foundKeyFrame = true; 13963 } 13964 } 13965 13966 return foundKeyFrame; 13967}; 13968 13969 13970module.exports = { 13971 parseType: parseType, 13972 parsePat: parsePat, 13973 parsePmt: parsePmt, 13974 parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator, 13975 parsePesType: parsePesType, 13976 parsePesTime: parsePesTime, 13977 videoPacketContainsKeyFrame: videoPacketContainsKeyFrame 13978}; 13979 13980},{"./stream-types.js":53}],53:[function(require,module,exports){ 13981'use strict'; 13982 13983module.exports = { 13984 H264_STREAM_TYPE: 0x1B, 13985 ADTS_STREAM_TYPE: 0x0F, 13986 METADATA_STREAM_TYPE: 0x15 13987}; 13988 13989},{}],54:[function(require,module,exports){ 13990/** 13991 * mux.js 13992 * 13993 * Copyright (c) 2016 Brightcove 13994 * All rights reserved. 13995 * 13996 * Accepts program elementary stream (PES) data events and corrects 13997 * decode and presentation time stamps to account for a rollover 13998 * of the 33 bit value. 13999 */ 14000 14001'use strict'; 14002 14003var Stream = require('../utils/stream'); 14004 14005var MAX_TS = 8589934592; 14006 14007var RO_THRESH = 4294967296; 14008 14009var handleRollover = function(value, reference) { 14010 var direction = 1; 14011 14012 if (value > reference) { 14013 // If the current timestamp value is greater than our reference timestamp and we detect a 14014 // timestamp rollover, this means the roll over is happening in the opposite direction. 14015 // Example scenario: Enter a long stream/video just after a rollover occurred. The reference 14016 // point will be set to a small number, e.g. 1. The user then seeks backwards over the 14017 // rollover point. In loading this segment, the timestamp values will be very large, 14018 // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust 14019 // the time stamp to be `value - 2^33`. 14020 direction = -1; 14021 } 14022 14023 // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will 14024 // cause an incorrect adjustment. 14025 while (Math.abs(reference - value) > RO_THRESH) { 14026 value += (direction * MAX_TS); 14027 } 14028 14029 return value; 14030}; 14031 14032var TimestampRolloverStream = function(type) { 14033 var lastDTS, referenceDTS; 14034 14035 TimestampRolloverStream.prototype.init.call(this); 14036 14037 this.type_ = type; 14038 14039 this.push = function(data) { 14040 if (data.type !== this.type_) { 14041 return; 14042 } 14043 14044 if (referenceDTS === undefined) { 14045 referenceDTS = data.dts; 14046 } 14047 14048 data.dts = handleRollover(data.dts, referenceDTS); 14049 data.pts = handleRollover(data.pts, referenceDTS); 14050 14051 lastDTS = data.dts; 14052 14053 this.trigger('data', data); 14054 }; 14055 14056 this.flush = function() { 14057 referenceDTS = lastDTS; 14058 this.trigger('done'); 14059 }; 14060 14061 this.discontinuity = function() { 14062 referenceDTS = void 0; 14063 lastDTS = void 0; 14064 }; 14065 14066}; 14067 14068TimestampRolloverStream.prototype = new Stream(); 14069 14070module.exports = { 14071 TimestampRolloverStream: TimestampRolloverStream, 14072 handleRollover: handleRollover 14073}; 14074 14075},{"../utils/stream":62}],55:[function(require,module,exports){ 14076module.exports = { 14077 generator: require('./mp4-generator'), 14078 Transmuxer: require('./transmuxer').Transmuxer, 14079 AudioSegmentStream: require('./transmuxer').AudioSegmentStream, 14080 VideoSegmentStream: require('./transmuxer').VideoSegmentStream 14081}; 14082 14083},{"./mp4-generator":56,"./transmuxer":58}],56:[function(require,module,exports){ 14084/** 14085 * mux.js 14086 * 14087 * Copyright (c) 2015 Brightcove 14088 * All rights reserved. 14089 * 14090 * Functions that generate fragmented MP4s suitable for use with Media 14091 * Source Extensions. 14092 */ 14093'use strict';
vendor: 21,889 bytes, lines 14093-14854
14093 14094 14095var UINT32_MAX = Math.pow(2, 32) - 1; 14096 14097var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, 14098 trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, 14099 trun, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, 14100 AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; 14101 14102// pre-calculate constants 14103(function() { 14104 var i; 14105 types = { 14106 avc1: [], // codingname 14107 avcC: [], 14108 btrt: [], 14109 dinf: [], 14110 dref: [], 14111 esds: [], 14112 ftyp: [], 14113 hdlr: [], 14114 mdat: [], 14115 mdhd: [], 14116 mdia: [], 14117 mfhd: [], 14118 minf: [], 14119 moof: [], 14120 moov: [], 14121 mp4a: [], // codingname 14122 mvex: [], 14123 mvhd: [], 14124 sdtp: [], 14125 smhd: [], 14126 stbl: [], 14127 stco: [], 14128 stsc: [], 14129 stsd: [], 14130 stsz: [], 14131 stts: [], 14132 styp: [], 14133 tfdt: [], 14134 tfhd: [], 14135 traf: [], 14136 trak: [], 14137 trun: [], 14138 trex: [], 14139 tkhd: [], 14140 vmhd: [] 14141 }; 14142 14143 // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we 14144 // don't throw an error 14145 if (typeof Uint8Array === 'undefined') { 14146 return; 14147 } 14148 14149 for (i in types) { 14150 if (types.hasOwnProperty(i)) { 14151 types[i] = [ 14152 i.charCodeAt(0), 14153 i.charCodeAt(1), 14154 i.charCodeAt(2), 14155 i.charCodeAt(3) 14156 ]; 14157 } 14158 } 14159 14160 MAJOR_BRAND = new Uint8Array([ 14161 'i'.charCodeAt(0), 14162 's'.charCodeAt(0), 14163 'o'.charCodeAt(0), 14164 'm'.charCodeAt(0) 14165 ]); 14166 AVC1_BRAND = new Uint8Array([ 14167 'a'.charCodeAt(0), 14168 'v'.charCodeAt(0), 14169 'c'.charCodeAt(0), 14170 '1'.charCodeAt(0) 14171 ]); 14172 MINOR_VERSION = new Uint8Array([0, 0, 0, 1]); 14173 VIDEO_HDLR = new Uint8Array([ 14174 0x00, // version 0 14175 0x00, 0x00, 0x00, // flags 14176 0x00, 0x00, 0x00, 0x00, // pre_defined 14177 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' 14178 0x00, 0x00, 0x00, 0x00, // reserved 14179 0x00, 0x00, 0x00, 0x00, // reserved 14180 0x00, 0x00, 0x00, 0x00, // reserved 14181 0x56, 0x69, 0x64, 0x65, 14182 0x6f, 0x48, 0x61, 0x6e, 14183 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' 14184 ]); 14185 AUDIO_HDLR = new Uint8Array([ 14186 0x00, // version 0 14187 0x00, 0x00, 0x00, // flags 14188 0x00, 0x00, 0x00, 0x00, // pre_defined 14189 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun' 14190 0x00, 0x00, 0x00, 0x00, // reserved 14191 0x00, 0x00, 0x00, 0x00, // reserved 14192 0x00, 0x00, 0x00, 0x00, // reserved 14193 0x53, 0x6f, 0x75, 0x6e, 14194 0x64, 0x48, 0x61, 0x6e, 14195 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' 14196 ]); 14197 HDLR_TYPES = { 14198 video: VIDEO_HDLR, 14199 audio: AUDIO_HDLR 14200 }; 14201 DREF = new Uint8Array([ 14202 0x00, // version 0 14203 0x00, 0x00, 0x00, // flags 14204 0x00, 0x00, 0x00, 0x01, // entry_count 14205 0x00, 0x00, 0x00, 0x0c, // entry_size 14206 0x75, 0x72, 0x6c, 0x20, // 'url' type 14207 0x00, // version 0 14208 0x00, 0x00, 0x01 // entry_flags 14209 ]); 14210 SMHD = new Uint8Array([ 14211 0x00, // version 14212 0x00, 0x00, 0x00, // flags 14213 0x00, 0x00, // balance, 0 means centered 14214 0x00, 0x00 // reserved 14215 ]); 14216 STCO = new Uint8Array([ 14217 0x00, // version 14218 0x00, 0x00, 0x00, // flags 14219 0x00, 0x00, 0x00, 0x00 // entry_count 14220 ]); 14221 STSC = STCO; 14222 STSZ = new Uint8Array([ 14223 0x00, // version 14224 0x00, 0x00, 0x00, // flags 14225 0x00, 0x00, 0x00, 0x00, // sample_size 14226 0x00, 0x00, 0x00, 0x00 // sample_count 14227 ]); 14228 STTS = STCO; 14229 VMHD = new Uint8Array([ 14230 0x00, // version 14231 0x00, 0x00, 0x01, // flags 14232 0x00, 0x00, // graphicsmode 14233 0x00, 0x00, 14234 0x00, 0x00, 14235 0x00, 0x00 // opcolor 14236 ]); 14237}()); 14238 14239box = function(type) { 14240 var 14241 payload = [], 14242 size = 0, 14243 i, 14244 result, 14245 view; 14246 14247 for (i = 1; i < arguments.length; i++) { 14248 payload.push(arguments[i]); 14249 } 14250 14251 i = payload.length; 14252 14253 // calculate the total size we need to allocate 14254 while (i--) { 14255 size += payload[i].byteLength; 14256 } 14257 result = new Uint8Array(size + 8); 14258 view = new DataView(result.buffer, result.byteOffset, result.byteLength); 14259 view.setUint32(0, result.byteLength); 14260 result.set(type, 4); 14261 14262 // copy the payload into the result 14263 for (i = 0, size = 8; i < payload.length; i++) { 14264 result.set(payload[i], size); 14265 size += payload[i].byteLength; 14266 } 14267 return result; 14268}; 14269 14270dinf = function() { 14271 return box(types.dinf, box(types.dref, DREF)); 14272}; 14273 14274esds = function(track) { 14275 return box(types.esds, new Uint8Array([ 14276 0x00, // version 14277 0x00, 0x00, 0x00, // flags 14278 14279 // ES_Descriptor 14280 0x03, // tag, ES_DescrTag 14281 0x19, // length 14282 0x00, 0x00, // ES_ID 14283 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority 14284 14285 // DecoderConfigDescriptor 14286 0x04, // tag, DecoderConfigDescrTag 14287 0x11, // length 14288 0x40, // object type 14289 0x15, // streamType 14290 0x00, 0x06, 0x00, // bufferSizeDB 14291 0x00, 0x00, 0xda, 0xc0, // maxBitrate 14292 0x00, 0x00, 0xda, 0xc0, // avgBitrate 14293 14294 // DecoderSpecificInfo 14295 0x05, // tag, DecoderSpecificInfoTag 14296 0x02, // length 14297 // ISO/IEC 14496-3, AudioSpecificConfig 14298 // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35 14299 (track.audioobjecttype << 3) | (track.samplingfrequencyindex >>> 1), 14300 (track.samplingfrequencyindex << 7) | (track.channelcount << 3), 14301 0x06, 0x01, 0x02 // GASpecificConfig 14302 ])); 14303}; 14304 14305ftyp = function() { 14306 return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND); 14307}; 14308 14309hdlr = function(type) { 14310 return box(types.hdlr, HDLR_TYPES[type]); 14311}; 14312mdat = function(data) { 14313 return box(types.mdat, data); 14314}; 14315mdhd = function(track) { 14316 var result = new Uint8Array([ 14317 0x00, // version 0 14318 0x00, 0x00, 0x00, // flags 14319 0x00, 0x00, 0x00, 0x02, // creation_time 14320 0x00, 0x00, 0x00, 0x03, // modification_time 14321 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 14322 14323 (track.duration >>> 24) & 0xFF, 14324 (track.duration >>> 16) & 0xFF, 14325 (track.duration >>> 8) & 0xFF, 14326 track.duration & 0xFF, // duration 14327 0x55, 0xc4, // 'und' language (undetermined) 14328 0x00, 0x00 14329 ]); 14330 14331 // Use the sample rate from the track metadata, when it is 14332 // defined. The sample rate can be parsed out of an ADTS header, for 14333 // instance. 14334 if (track.samplerate) { 14335 result[12] = (track.samplerate >>> 24) & 0xFF; 14336 result[13] = (track.samplerate >>> 16) & 0xFF; 14337 result[14] = (track.samplerate >>> 8) & 0xFF; 14338 result[15] = (track.samplerate) & 0xFF; 14339 } 14340 14341 return box(types.mdhd, result); 14342}; 14343mdia = function(track) { 14344 return box(types.mdia, mdhd(track), hdlr(track.type), minf(track)); 14345}; 14346mfhd = function(sequenceNumber) { 14347 return box(types.mfhd, new Uint8Array([ 14348 0x00, 14349 0x00, 0x00, 0x00, // flags 14350 (sequenceNumber & 0xFF000000) >> 24, 14351 (sequenceNumber & 0xFF0000) >> 16, 14352 (sequenceNumber & 0xFF00) >> 8, 14353 sequenceNumber & 0xFF // sequence_number 14354 ])); 14355}; 14356minf = function(track) { 14357 return box(types.minf, 14358 track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), 14359 dinf(), 14360 stbl(track)); 14361}; 14362moof = function(sequenceNumber, tracks) { 14363 var 14364 trackFragments = [], 14365 i = tracks.length; 14366 // build traf boxes for each track fragment 14367 while (i--) { 14368 trackFragments[i] = traf(tracks[i]); 14369 } 14370 return box.apply(null, [ 14371 types.moof, 14372 mfhd(sequenceNumber) 14373 ].concat(trackFragments)); 14374}; 14375/** 14376 * Returns a movie box. 14377 * @param tracks {array} the tracks associated with this movie 14378 * @see ISO/IEC 14496-12:2012(E), section 8.2.1 14379 */ 14380moov = function(tracks) { 14381 var 14382 i = tracks.length, 14383 boxes = []; 14384 14385 while (i--) { 14386 boxes[i] = trak(tracks[i]); 14387 } 14388 14389 return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks))); 14390}; 14391mvex = function(tracks) { 14392 var 14393 i = tracks.length, 14394 boxes = []; 14395 14396 while (i--) { 14397 boxes[i] = trex(tracks[i]); 14398 } 14399 return box.apply(null, [types.mvex].concat(boxes)); 14400}; 14401mvhd = function(duration) { 14402 var 14403 bytes = new Uint8Array([ 14404 0x00, // version 0 14405 0x00, 0x00, 0x00, // flags 14406 0x00, 0x00, 0x00, 0x01, // creation_time 14407 0x00, 0x00, 0x00, 0x02, // modification_time 14408 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 14409 (duration & 0xFF000000) >> 24, 14410 (duration & 0xFF0000) >> 16, 14411 (duration & 0xFF00) >> 8, 14412 duration & 0xFF, // duration 14413 0x00, 0x01, 0x00, 0x00, // 1.0 rate 14414 0x01, 0x00, // 1.0 volume 14415 0x00, 0x00, // reserved 14416 0x00, 0x00, 0x00, 0x00, // reserved 14417 0x00, 0x00, 0x00, 0x00, // reserved 14418 0x00, 0x01, 0x00, 0x00, 14419 0x00, 0x00, 0x00, 0x00, 14420 0x00, 0x00, 0x00, 0x00, 14421 0x00, 0x00, 0x00, 0x00, 14422 0x00, 0x01, 0x00, 0x00, 14423 0x00, 0x00, 0x00, 0x00, 14424 0x00, 0x00, 0x00, 0x00, 14425 0x00, 0x00, 0x00, 0x00, 14426 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix 14427 0x00, 0x00, 0x00, 0x00, 14428 0x00, 0x00, 0x00, 0x00, 14429 0x00, 0x00, 0x00, 0x00, 14430 0x00, 0x00, 0x00, 0x00, 14431 0x00, 0x00, 0x00, 0x00, 14432 0x00, 0x00, 0x00, 0x00, // pre_defined 14433 0xff, 0xff, 0xff, 0xff // next_track_ID 14434 ]); 14435 return box(types.mvhd, bytes); 14436}; 14437 14438sdtp = function(track) { 14439 var 14440 samples = track.samples || [], 14441 bytes = new Uint8Array(4 + samples.length), 14442 flags, 14443 i; 14444 14445 // leave the full box header (4 bytes) all zero 14446 14447 // write the sample table 14448 for (i = 0; i < samples.length; i++) { 14449 flags = samples[i].flags; 14450 14451 bytes[i + 4] = (flags.dependsOn << 4) | 14452 (flags.isDependedOn << 2) | 14453 (flags.hasRedundancy); 14454 } 14455 14456 return box(types.sdtp, 14457 bytes); 14458}; 14459 14460stbl = function(track) { 14461 return box(types.stbl, 14462 stsd(track), 14463 box(types.stts, STTS), 14464 box(types.stsc, STSC), 14465 box(types.stsz, STSZ), 14466 box(types.stco, STCO)); 14467}; 14468 14469(function() { 14470 var videoSample, audioSample; 14471 14472 stsd = function(track) { 14473 14474 return box(types.stsd, new Uint8Array([ 14475 0x00, // version 0 14476 0x00, 0x00, 0x00, // flags 14477 0x00, 0x00, 0x00, 0x01 14478 ]), track.type === 'video' ? videoSample(track) : audioSample(track)); 14479 }; 14480 14481 videoSample = function(track) { 14482 var 14483 sps = track.sps || [], 14484 pps = track.pps || [], 14485 sequenceParameterSets = [], 14486 pictureParameterSets = [], 14487 i; 14488 14489 // assemble the SPSs 14490 for (i = 0; i < sps.length; i++) { 14491 sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8); 14492 sequenceParameterSets.push((sps[i].byteLength & 0xFF)); // sequenceParameterSetLength 14493 sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS 14494 } 14495 14496 // assemble the PPSs 14497 for (i = 0; i < pps.length; i++) { 14498 pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8); 14499 pictureParameterSets.push((pps[i].byteLength & 0xFF)); 14500 pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i])); 14501 } 14502 14503 return box(types.avc1, new Uint8Array([ 14504 0x00, 0x00, 0x00, 14505 0x00, 0x00, 0x00, // reserved 14506 0x00, 0x01, // data_reference_index 14507 0x00, 0x00, // pre_defined 14508 0x00, 0x00, // reserved 14509 0x00, 0x00, 0x00, 0x00, 14510 0x00, 0x00, 0x00, 0x00, 14511 0x00, 0x00, 0x00, 0x00, // pre_defined 14512 (track.width & 0xff00) >> 8, 14513 track.width & 0xff, // width 14514 (track.height & 0xff00) >> 8, 14515 track.height & 0xff, // height 14516 0x00, 0x48, 0x00, 0x00, // horizresolution 14517 0x00, 0x48, 0x00, 0x00, // vertresolution 14518 0x00, 0x00, 0x00, 0x00, // reserved 14519 0x00, 0x01, // frame_count 14520 0x13, 14521 0x76, 0x69, 0x64, 0x65, 14522 0x6f, 0x6a, 0x73, 0x2d, 14523 0x63, 0x6f, 0x6e, 0x74, 14524 0x72, 0x69, 0x62, 0x2d, 14525 0x68, 0x6c, 0x73, 0x00, 14526 0x00, 0x00, 0x00, 0x00, 14527 0x00, 0x00, 0x00, 0x00, 14528 0x00, 0x00, 0x00, // compressorname 14529 0x00, 0x18, // depth = 24 14530 0x11, 0x11 // pre_defined = -1 14531 ]), box(types.avcC, new Uint8Array([ 14532 0x01, // configurationVersion 14533 track.profileIdc, // AVCProfileIndication 14534 track.profileCompatibility, // profile_compatibility 14535 track.levelIdc, // AVCLevelIndication 14536 0xff // lengthSizeMinusOne, hard-coded to 4 bytes 14537 ].concat([ 14538 sps.length // numOfSequenceParameterSets 14539 ]).concat(sequenceParameterSets).concat([ 14540 pps.length // numOfPictureParameterSets 14541 ]).concat(pictureParameterSets))), // "PPS" 14542 box(types.btrt, new Uint8Array([ 14543 0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB 14544 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate 14545 0x00, 0x2d, 0xc6, 0xc0 14546 ])) // avgBitrate 14547 ); 14548 }; 14549 14550 audioSample = function(track) { 14551 return box(types.mp4a, new Uint8Array([ 14552 14553 // SampleEntry, ISO/IEC 14496-12 14554 0x00, 0x00, 0x00, 14555 0x00, 0x00, 0x00, // reserved 14556 0x00, 0x01, // data_reference_index 14557 14558 // AudioSampleEntry, ISO/IEC 14496-12 14559 0x00, 0x00, 0x00, 0x00, // reserved 14560 0x00, 0x00, 0x00, 0x00, // reserved 14561 (track.channelcount & 0xff00) >> 8, 14562 (track.channelcount & 0xff), // channelcount 14563 14564 (track.samplesize & 0xff00) >> 8, 14565 (track.samplesize & 0xff), // samplesize 14566 0x00, 0x00, // pre_defined 14567 0x00, 0x00, // reserved 14568 14569 (track.samplerate & 0xff00) >> 8, 14570 (track.samplerate & 0xff), 14571 0x00, 0x00 // samplerate, 16.16 14572 14573 // MP4AudioSampleEntry, ISO/IEC 14496-14 14574 ]), esds(track)); 14575 }; 14576}()); 14577 14578tkhd = function(track) { 14579 var result = new Uint8Array([ 14580 0x00, // version 0 14581 0x00, 0x00, 0x07, // flags 14582 0x00, 0x00, 0x00, 0x00, // creation_time 14583 0x00, 0x00, 0x00, 0x00, // modification_time 14584 (track.id & 0xFF000000) >> 24, 14585 (track.id & 0xFF0000) >> 16, 14586 (track.id & 0xFF00) >> 8, 14587 track.id & 0xFF, // track_ID 14588 0x00, 0x00, 0x00, 0x00, // reserved 14589 (track.duration & 0xFF000000) >> 24, 14590 (track.duration & 0xFF0000) >> 16, 14591 (track.duration & 0xFF00) >> 8, 14592 track.duration & 0xFF, // duration 14593 0x00, 0x00, 0x00, 0x00, 14594 0x00, 0x00, 0x00, 0x00, // reserved 14595 0x00, 0x00, // layer 14596 0x00, 0x00, // alternate_group 14597 0x01, 0x00, // non-audio track volume 14598 0x00, 0x00, // reserved 14599 0x00, 0x01, 0x00, 0x00, 14600 0x00, 0x00, 0x00, 0x00, 14601 0x00, 0x00, 0x00, 0x00, 14602 0x00, 0x00, 0x00, 0x00, 14603 0x00, 0x01, 0x00, 0x00, 14604 0x00, 0x00, 0x00, 0x00, 14605 0x00, 0x00, 0x00, 0x00, 14606 0x00, 0x00, 0x00, 0x00, 14607 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix 14608 (track.width & 0xFF00) >> 8, 14609 track.width & 0xFF, 14610 0x00, 0x00, // width 14611 (track.height & 0xFF00) >> 8, 14612 track.height & 0xFF, 14613 0x00, 0x00 // height 14614 ]); 14615 14616 return box(types.tkhd, result); 14617}; 14618 14619/** 14620 * Generate a track fragment (traf) box. A traf box collects metadata 14621 * about tracks in a movie fragment (moof) box. 14622 */ 14623traf = function(track) { 14624 var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, 14625 sampleDependencyTable, dataOffset, 14626 upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime; 14627 14628 trackFragmentHeader = box(types.tfhd, new Uint8Array([ 14629 0x00, // version 0 14630 0x00, 0x00, 0x3a, // flags 14631 (track.id & 0xFF000000) >> 24, 14632 (track.id & 0xFF0000) >> 16, 14633 (track.id & 0xFF00) >> 8, 14634 (track.id & 0xFF), // track_ID 14635 0x00, 0x00, 0x00, 0x01, // sample_description_index 14636 0x00, 0x00, 0x00, 0x00, // default_sample_duration 14637 0x00, 0x00, 0x00, 0x00, // default_sample_size 14638 0x00, 0x00, 0x00, 0x00 // default_sample_flags 14639 ])); 14640 14641 upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1)); 14642 lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1)); 14643 14644 trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([ 14645 0x01, // version 1 14646 0x00, 0x00, 0x00, // flags 14647 // baseMediaDecodeTime 14648 (upperWordBaseMediaDecodeTime >>> 24) & 0xFF, 14649 (upperWordBaseMediaDecodeTime >>> 16) & 0xFF, 14650 (upperWordBaseMediaDecodeTime >>> 8) & 0xFF, 14651 upperWordBaseMediaDecodeTime & 0xFF, 14652 (lowerWordBaseMediaDecodeTime >>> 24) & 0xFF, 14653 (lowerWordBaseMediaDecodeTime >>> 16) & 0xFF, 14654 (lowerWordBaseMediaDecodeTime >>> 8) & 0xFF, 14655 lowerWordBaseMediaDecodeTime & 0xFF 14656 ])); 14657 14658 // the data offset specifies the number of bytes from the start of 14659 // the containing moof to the first payload byte of the associated 14660 // mdat 14661 dataOffset = (32 + // tfhd 14662 20 + // tfdt 14663 8 + // traf header 14664 16 + // mfhd 14665 8 + // moof header 14666 8); // mdat header 14667 14668 // audio tracks require less metadata 14669 if (track.type === 'audio') { 14670 trackFragmentRun = trun(track, dataOffset); 14671 return box(types.traf, 14672 trackFragmentHeader, 14673 trackFragmentDecodeTime, 14674 trackFragmentRun); 14675 } 14676 14677 // video tracks should contain an independent and disposable samples 14678 // box (sdtp) 14679 // generate one and adjust offsets to match 14680 sampleDependencyTable = sdtp(track); 14681 trackFragmentRun = trun(track, 14682 sampleDependencyTable.length + dataOffset); 14683 return box(types.traf, 14684 trackFragmentHeader, 14685 trackFragmentDecodeTime, 14686 trackFragmentRun, 14687 sampleDependencyTable); 14688}; 14689 14690/** 14691 * Generate a track box. 14692 * @param track {object} a track definition 14693 * @return {Uint8Array} the track box 14694 */ 14695trak = function(track) { 14696 track.duration = track.duration || 0xffffffff; 14697 return box(types.trak, 14698 tkhd(track), 14699 mdia(track)); 14700}; 14701 14702trex = function(track) { 14703 var result = new Uint8Array([ 14704 0x00, // version 0 14705 0x00, 0x00, 0x00, // flags 14706 (track.id & 0xFF000000) >> 24, 14707 (track.id & 0xFF0000) >> 16, 14708 (track.id & 0xFF00) >> 8, 14709 (track.id & 0xFF), // track_ID 14710 0x00, 0x00, 0x00, 0x01, // default_sample_description_index 14711 0x00, 0x00, 0x00, 0x00, // default_sample_duration 14712 0x00, 0x00, 0x00, 0x00, // default_sample_size 14713 0x00, 0x01, 0x00, 0x01 // default_sample_flags 14714 ]); 14715 // the last two bytes of default_sample_flags is the sample 14716 // degradation priority, a hint about the importance of this sample 14717 // relative to others. Lower the degradation priority for all sample 14718 // types other than video. 14719 if (track.type !== 'video') { 14720 result[result.length - 1] = 0x00; 14721 } 14722 14723 return box(types.trex, result); 14724}; 14725 14726(function() { 14727 var audioTrun, videoTrun, trunHeader; 14728 14729 // This method assumes all samples are uniform. That is, if a 14730 // duration is present for the first sample, it will be present for 14731 // all subsequent samples. 14732 // see ISO/IEC 14496-12:2012, Section 8.8.8.1 14733 trunHeader = function(samples, offset) { 14734 var durationPresent = 0, sizePresent = 0, 14735 flagsPresent = 0, compositionTimeOffset = 0; 14736 14737 // trun flag constants 14738 if (samples.length) { 14739 if (samples[0].duration !== undefined) { 14740 durationPresent = 0x1; 14741 } 14742 if (samples[0].size !== undefined) { 14743 sizePresent = 0x2; 14744 } 14745 if (samples[0].flags !== undefined) { 14746 flagsPresent = 0x4; 14747 } 14748 if (samples[0].compositionTimeOffset !== undefined) { 14749 compositionTimeOffset = 0x8; 14750 } 14751 } 14752 14753 return [ 14754 0x00, // version 0 14755 0x00, 14756 durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 14757 0x01, // flags 14758 (samples.length & 0xFF000000) >>> 24, 14759 (samples.length & 0xFF0000) >>> 16, 14760 (samples.length & 0xFF00) >>> 8, 14761 samples.length & 0xFF, // sample_count 14762 (offset & 0xFF000000) >>> 24, 14763 (offset & 0xFF0000) >>> 16, 14764 (offset & 0xFF00) >>> 8, 14765 offset & 0xFF // data_offset 14766 ]; 14767 }; 14768 14769 videoTrun = function(track, offset) { 14770 var bytes, samples, sample, i; 14771 14772 samples = track.samples || []; 14773 offset += 8 + 12 + (16 * samples.length); 14774 14775 bytes = trunHeader(samples, offset); 14776 14777 for (i = 0; i < samples.length; i++) { 14778 sample = samples[i]; 14779 bytes = bytes.concat([ 14780 (sample.duration & 0xFF000000) >>> 24, 14781 (sample.duration & 0xFF0000) >>> 16, 14782 (sample.duration & 0xFF00) >>> 8, 14783 sample.duration & 0xFF, // sample_duration 14784 (sample.size & 0xFF000000) >>> 24, 14785 (sample.size & 0xFF0000) >>> 16, 14786 (sample.size & 0xFF00) >>> 8, 14787 sample.size & 0xFF, // sample_size 14788 (sample.flags.isLeading << 2) | sample.flags.dependsOn, 14789 (sample.flags.isDependedOn << 6) | 14790 (sample.flags.hasRedundancy << 4) | 14791 (sample.flags.paddingValue << 1) | 14792 sample.flags.isNonSyncSample, 14793 sample.flags.degradationPriority & 0xF0 << 8, 14794 sample.flags.degradationPriority & 0x0F, // sample_flags 14795 (sample.compositionTimeOffset & 0xFF000000) >>> 24, 14796 (sample.compositionTimeOffset & 0xFF0000) >>> 16, 14797 (sample.compositionTimeOffset & 0xFF00) >>> 8, 14798 sample.compositionTimeOffset & 0xFF // sample_composition_time_offset 14799 ]); 14800 } 14801 return box(types.trun, new Uint8Array(bytes)); 14802 }; 14803 14804 audioTrun = function(track, offset) { 14805 var bytes, samples, sample, i; 14806 14807 samples = track.samples || []; 14808 offset += 8 + 12 + (8 * samples.length); 14809 14810 bytes = trunHeader(samples, offset); 14811 14812 for (i = 0; i < samples.length; i++) { 14813 sample = samples[i]; 14814 bytes = bytes.concat([ 14815 (sample.duration & 0xFF000000) >>> 24, 14816 (sample.duration & 0xFF0000) >>> 16, 14817 (sample.duration & 0xFF00) >>> 8, 14818 sample.duration & 0xFF, // sample_duration 14819 (sample.size & 0xFF000000) >>> 24, 14820 (sample.size & 0xFF0000) >>> 16, 14821 (sample.size & 0xFF00) >>> 8, 14822 sample.size & 0xFF]); // sample_size 14823 } 14824 14825 return box(types.trun, new Uint8Array(bytes)); 14826 }; 14827 14828 trun = function(track, offset) { 14829 if (track.type === 'audio') { 14830 return audioTrun(track, offset); 14831 } 14832 14833 return videoTrun(track, offset); 14834 }; 14835}()); 14836 14837module.exports = { 14838 ftyp: ftyp, 14839 mdat: mdat, 14840 moof: moof, 14841 moov: moov, 14842 initSegment: function(tracks) { 14843 var 14844 fileType = ftyp(), 14845 movie = moov(tracks), 14846 result; 14847 14848 result = new Uint8Array(fileType.byteLength + movie.byteLength); 14849 result.set(fileType); 14850 result.set(movie, fileType.byteLength); 14851 return result; 14852 } 14853}; 14854
14855},{}],57:[function(require,module,exports){ 14856/** 14857 * mux.js 14858 * 14859 * Copyright (c) 2015 Brightcove 14860 * All rights reserved. 14861 * 14862 * Utilities to detect basic properties and metadata about MP4s. 14863 */ 14864'use strict'; 14865 14866var findBox, parseType, timescale, startTime; 14867 14868// Find the data for a box specified by its path 14869findBox = function(data, path) { 14870 var results = [], 14871 i, size, type, end, subresults; 14872 14873 if (!path.length) { 14874 // short-circuit the search for empty paths 14875 return null; 14876 } 14877 14878 for (i = 0; i < data.byteLength;) { 14879 size = data[i] << 24; 14880 size |= data[i + 1] << 16; 14881 size |= data[i + 2] << 8; 14882 size |= data[i + 3]; 14883 14884 type = parseType(data.subarray(i + 4, i + 8)); 14885 14886 end = size > 1 ? i + size : data.byteLength; 14887 14888 if (type === path[0]) { 14889 if (path.length === 1) { 14890 // this is the end of the path and we've found the box we were 14891 // looking for 14892 results.push(data.subarray(i + 8, end)); 14893 } else { 14894 // recursively search for the next box along the path 14895 subresults = findBox(data.subarray(i + 8, end), path.slice(1)); 14896 if (subresults.length) { 14897 results = results.concat(subresults); 14898 } 14899 } 14900 } 14901 i = end; 14902 } 14903 14904 // we've finished searching all of data 14905 return results; 14906}; 14907 14908/** 14909 * Returns the string representation of an ASCII encoded four byte buffer. 14910 * @param buffer {Uint8Array} a four-byte buffer to translate 14911 * @return {string} the corresponding string 14912 */ 14913parseType = function(buffer) { 14914 var result = ''; 14915 result += String.fromCharCode(buffer[0]); 14916 result += String.fromCharCode(buffer[1]); 14917 result += String.fromCharCode(buffer[2]); 14918 result += String.fromCharCode(buffer[3]); 14919 return result; 14920}; 14921 14922/** 14923 * Parses an MP4 initialization segment and extracts the timescale 14924 * values for any declared tracks. Timescale values indicate the 14925 * number of clock ticks per second to assume for time-based values 14926 * elsewhere in the MP4. 14927 * 14928 * To determine the start time of an MP4, you need two pieces of 14929 * information: the timescale unit and the earliest base media decode 14930 * time. Multiple timescales can be specified within an MP4 but the 14931 * base media decode time is always expressed in the timescale from 14932 * the media header box for the track: 14933 * ``` 14934 * moov > trak > mdia > mdhd.timescale 14935 * ``` 14936 * @param init {Uint8Array} the bytes of the init segment 14937 * @return {object} a hash of track ids to timescale values or null if 14938 * the init segment is malformed. 14939 */ 14940timescale = function(init) { 14941 var 14942 result = {}, 14943 traks = findBox(init, ['moov', 'trak']); 14944 14945 // mdhd timescale 14946 return traks.reduce(function(result, trak) { 14947 var tkhd, version, index, id, mdhd; 14948 14949 tkhd = findBox(trak, ['tkhd'])[0]; 14950 if (!tkhd) { 14951 return null; 14952 } 14953 version = tkhd[0]; 14954 index = version === 0 ? 12 : 20; 14955 id = tkhd[index] << 24 | 14956 tkhd[index + 1] << 16 | 14957 tkhd[index + 2] << 8 | 14958 tkhd[index + 3]; 14959 14960 mdhd = findBox(trak, ['mdia', 'mdhd'])[0]; 14961 if (!mdhd) { 14962 return null; 14963 } 14964 version = mdhd[0]; 14965 index = version === 0 ? 12 : 20; 14966 result[id] = mdhd[index] << 24 | 14967 mdhd[index + 1] << 16 | 14968 mdhd[index + 2] << 8 | 14969 mdhd[index + 3]; 14970 return result; 14971 }, result); 14972}; 14973 14974/** 14975 * Determine the base media decode start time, in seconds, for an MP4 14976 * fragment. If multiple fragments are specified, the earliest time is 14977 * returned. 14978 * 14979 * The base media decode time can be parsed from track fragment 14980 * metadata: 14981 * ``` 14982 * moof > traf > tfdt.baseMediaDecodeTime 14983 * ``` 14984 * It requires the timescale value from the mdhd to interpret. 14985 * 14986 * @param timescale {object} a hash of track ids to timescale values. 14987 * @return {number} the earliest base media decode start time for the 14988 * fragment, in seconds 14989 */ 14990startTime = function(timescale, fragment) { 14991 var trafs, baseTimes, result; 14992 14993 // we need info from two childrend of each track fragment box 14994 trafs = findBox(fragment, ['moof', 'traf']); 14995 14996 // determine the start times for each track 14997 baseTimes = [].concat.apply([], trafs.map(function(traf) { 14998 return findBox(traf, ['tfhd']).map(function(tfhd) { 14999 var id, scale, baseTime; 15000 15001 // get the track id from the tfhd 15002 id = tfhd[4] << 24 | 15003 tfhd[5] << 16 | 15004 tfhd[6] << 8 | 15005 tfhd[7]; 15006 // assume a 90kHz clock if no timescale was specified 15007 scale = timescale[id] || 90e3; 15008 15009 // get the base media decode time from the tfdt 15010 baseTime = findBox(traf, ['tfdt']).map(function(tfdt) { 15011 var version, result; 15012 15013 version = tfdt[0]; 15014 result = tfdt[4] << 24 | 15015 tfdt[5] << 16 | 15016 tfdt[6] << 8 | 15017 tfdt[7]; 15018 if (version === 1) { 15019 result *= Math.pow(2, 32); 15020 result += tfdt[8] << 24 | 15021 tfdt[9] << 16 | 15022 tfdt[10] << 8 | 15023 tfdt[11]; 15024 } 15025 return result; 15026 })[0]; 15027 baseTime = baseTime || Infinity; 15028 15029 // convert base time to seconds 15030 return baseTime / scale; 15031 }); 15032 })); 15033 15034 // return the minimum 15035 result = Math.min.apply(null, baseTimes); 15036 return isFinite(result) ? result : 0; 15037}; 15038 15039module.exports = { 15040 parseType: parseType, 15041 timescale: timescale, 15042 startTime: startTime 15043}; 15044 15045},{}],58:[function(require,module,exports){ 15046/** 15047 * mux.js 15048 * 15049 * Copyright (c) 2015 Brightcove 15050 * All rights reserved. 15051 * 15052 * A stream-based mp2t to mp4 converter. This utility can be used to 15053 * deliver mp4s to a SourceBuffer on platforms that support native 15054 * Media Source Extensions. 15055 */ 15056'use strict'; 15057 15058var Stream = require('../utils/stream.js'); 15059var mp4 = require('./mp4-generator.js'); 15060var m2ts = require('../m2ts/m2ts.js'); 15061var AdtsStream = require('../codecs/adts.js'); 15062var H264Stream = require('../codecs/h264').H264Stream; 15063var AacStream = require('../aac'); 15064var coneOfSilence = require('../data/silence'); 15065var clock = require('../utils/clock'); 15066 15067// constants 15068var AUDIO_PROPERTIES = [ 15069 'audioobjecttype', 15070 'channelcount', 15071 'samplerate', 15072 'samplingfrequencyindex', 15073 'samplesize' 15074]; 15075 15076var VIDEO_PROPERTIES = [ 15077 'width', 15078 'height', 15079 'profileIdc', 15080 'levelIdc', 15081 'profileCompatibility' 15082]; 15083 15084var ONE_SECOND_IN_TS = 90000; // 90kHz clock 15085 15086// object types 15087var VideoSegmentStream, AudioSegmentStream, Transmuxer, CoalesceStream; 15088 15089// Helper functions 15090var 15091 createDefaultSample, 15092 isLikelyAacData, 15093 collectDtsInfo, 15094 clearDtsInfo, 15095 calculateTrackBaseMediaDecodeTime, 15096 arrayEquals, 15097 sumFrameByteLengths; 15098 15099/** 15100 * Default sample object 15101 * see ISO/IEC 14496-12:2012, section 8.6.4.3 15102 */ 15103createDefaultSample = function() { 15104 return { 15105 size: 0, 15106 flags: { 15107 isLeading: 0, 15108 dependsOn: 1, 15109 isDependedOn: 0, 15110 hasRedundancy: 0, 15111 degradationPriority: 0 15112 } 15113 }; 15114}; 15115 15116isLikelyAacData = function(data) { 15117 if ((data[0] === 'I'.charCodeAt(0)) && 15118 (data[1] === 'D'.charCodeAt(0)) && 15119 (data[2] === '3'.charCodeAt(0))) { 15120 return true; 15121 } 15122 return false;
vendor: 58,390 bytes, lines 15123-17058
15123}; 15124 15125/** 15126 * Compare two arrays (even typed) for same-ness 15127 */ 15128arrayEquals = function(a, b) { 15129 var 15130 i; 15131 15132 if (a.length !== b.length) { 15133 return false; 15134 } 15135 15136 // compare the value of each element in the array 15137 for (i = 0; i < a.length; i++) { 15138 if (a[i] !== b[i]) { 15139 return false; 15140 } 15141 } 15142 15143 return true; 15144}; 15145 15146/** 15147 * Sum the `byteLength` properties of the data in each AAC frame 15148 */ 15149sumFrameByteLengths = function(array) { 15150 var 15151 i, 15152 currentObj, 15153 sum = 0; 15154 15155 // sum the byteLength's all each nal unit in the frame 15156 for (i = 0; i < array.length; i++) { 15157 currentObj = array[i]; 15158 sum += currentObj.data.byteLength; 15159 } 15160 15161 return sum; 15162}; 15163 15164/** 15165 * Constructs a single-track, ISO BMFF media segment from AAC data 15166 * events. The output of this stream can be fed to a SourceBuffer 15167 * configured with a suitable initialization segment. 15168 */ 15169AudioSegmentStream = function(track) { 15170 var 15171 adtsFrames = [], 15172 sequenceNumber = 0, 15173 earliestAllowedDts = 0, 15174 audioAppendStartTs = 0, 15175 videoBaseMediaDecodeTime = Infinity; 15176 15177 AudioSegmentStream.prototype.init.call(this); 15178 15179 this.push = function(data) { 15180 collectDtsInfo(track, data); 15181 15182 if (track) { 15183 AUDIO_PROPERTIES.forEach(function(prop) { 15184 track[prop] = data[prop]; 15185 }); 15186 } 15187 15188 // buffer audio data until end() is called 15189 adtsFrames.push(data); 15190 }; 15191 15192 this.setEarliestDts = function(earliestDts) { 15193 earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime; 15194 }; 15195 15196 this.setVideoBaseMediaDecodeTime = function(baseMediaDecodeTime) { 15197 videoBaseMediaDecodeTime = baseMediaDecodeTime; 15198 }; 15199 15200 this.setAudioAppendStart = function(timestamp) { 15201 audioAppendStartTs = timestamp; 15202 }; 15203 15204 this.flush = function() { 15205 var 15206 frames, 15207 moof, 15208 mdat, 15209 boxes; 15210 15211 // return early if no audio data has been observed 15212 if (adtsFrames.length === 0) { 15213 this.trigger('done', 'AudioSegmentStream'); 15214 return; 15215 } 15216 15217 frames = this.trimAdtsFramesByEarliestDts_(adtsFrames); 15218 track.baseMediaDecodeTime = calculateTrackBaseMediaDecodeTime(track); 15219 15220 this.prefixWithSilence_(track, frames); 15221 15222 // we have to build the index from byte locations to 15223 // samples (that is, adts frames) in the audio data 15224 track.samples = this.generateSampleTable_(frames); 15225 15226 // concatenate the audio data to constuct the mdat 15227 mdat = mp4.mdat(this.concatenateFrameData_(frames)); 15228 15229 adtsFrames = []; 15230 15231 moof = mp4.moof(sequenceNumber, [track]); 15232 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); 15233 15234 // bump the sequence number for next time 15235 sequenceNumber++; 15236 15237 boxes.set(moof); 15238 boxes.set(mdat, moof.byteLength); 15239 15240 clearDtsInfo(track); 15241 15242 this.trigger('data', {track: track, boxes: boxes}); 15243 this.trigger('done', 'AudioSegmentStream'); 15244 }; 15245 15246 // Possibly pad (prefix) the audio track with silence if appending this track 15247 // would lead to the introduction of a gap in the audio buffer 15248 this.prefixWithSilence_ = function(track, frames) { 15249 var 15250 baseMediaDecodeTimeTs, 15251 frameDuration = 0, 15252 audioGapDuration = 0, 15253 audioFillFrameCount = 0, 15254 audioFillDuration = 0, 15255 silentFrame, 15256 i; 15257 15258 if (!frames.length) { 15259 return; 15260 } 15261 15262 baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); 15263 // determine frame clock duration based on sample rate, round up to avoid overfills 15264 frameDuration = Math.ceil(ONE_SECOND_IN_TS / (track.samplerate / 1024)); 15265 15266 if (audioAppendStartTs && videoBaseMediaDecodeTime) { 15267 // insert the shortest possible amount (audio gap or audio to video gap) 15268 audioGapDuration = 15269 baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); 15270 // number of full frames in the audio gap 15271 audioFillFrameCount = Math.floor(audioGapDuration / frameDuration); 15272 audioFillDuration = audioFillFrameCount * frameDuration; 15273 } 15274 15275 // don't attempt to fill gaps smaller than a single frame or larger 15276 // than a half second 15277 if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS / 2) { 15278 return; 15279 } 15280 15281 silentFrame = coneOfSilence[track.samplerate]; 15282 15283 if (!silentFrame) { 15284 // we don't have a silent frame pregenerated for the sample rate, so use a frame 15285 // from the content instead 15286 silentFrame = frames[0].data; 15287 } 15288 15289 for (i = 0; i < audioFillFrameCount; i++) { 15290 frames.splice(i, 0, { 15291 data: silentFrame 15292 }); 15293 } 15294 15295 track.baseMediaDecodeTime -= 15296 Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate)); 15297 }; 15298 15299 // If the audio segment extends before the earliest allowed dts 15300 // value, remove AAC frames until starts at or after the earliest 15301 // allowed DTS so that we don't end up with a negative baseMedia- 15302 // DecodeTime for the audio track 15303 this.trimAdtsFramesByEarliestDts_ = function(adtsFrames) { 15304 if (track.minSegmentDts >= earliestAllowedDts) { 15305 return adtsFrames; 15306 } 15307 15308 // We will need to recalculate the earliest segment Dts 15309 track.minSegmentDts = Infinity; 15310 15311 return adtsFrames.filter(function(currentFrame) { 15312 // If this is an allowed frame, keep it and record it's Dts 15313 if (currentFrame.dts >= earliestAllowedDts) { 15314 track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts); 15315 track.minSegmentPts = track.minSegmentDts; 15316 return true; 15317 } 15318 // Otherwise, discard it 15319 return false; 15320 }); 15321 }; 15322 15323 // generate the track's raw mdat data from an array of frames 15324 this.generateSampleTable_ = function(frames) { 15325 var 15326 i, 15327 currentFrame, 15328 samples = []; 15329 15330 for (i = 0; i < frames.length; i++) { 15331 currentFrame = frames[i]; 15332 samples.push({ 15333 size: currentFrame.data.byteLength, 15334 duration: 1024 // For AAC audio, all samples contain 1024 samples 15335 }); 15336 } 15337 return samples; 15338 }; 15339 15340 // generate the track's sample table from an array of frames 15341 this.concatenateFrameData_ = function(frames) { 15342 var 15343 i, 15344 currentFrame, 15345 dataOffset = 0, 15346 data = new Uint8Array(sumFrameByteLengths(frames)); 15347 15348 for (i = 0; i < frames.length; i++) { 15349 currentFrame = frames[i]; 15350 15351 data.set(currentFrame.data, dataOffset); 15352 dataOffset += currentFrame.data.byteLength; 15353 } 15354 return data; 15355 }; 15356}; 15357 15358AudioSegmentStream.prototype = new Stream(); 15359 15360/** 15361 * Constructs a single-track, ISO BMFF media segment from H264 data 15362 * events. The output of this stream can be fed to a SourceBuffer 15363 * configured with a suitable initialization segment. 15364 * @param track {object} track metadata configuration 15365 */ 15366VideoSegmentStream = function(track) { 15367 var 15368 sequenceNumber = 0, 15369 nalUnits = [], 15370 config, 15371 pps; 15372 15373 VideoSegmentStream.prototype.init.call(this); 15374 15375 delete track.minPTS; 15376 15377 this.gopCache_ = []; 15378 15379 this.push = function(nalUnit) { 15380 collectDtsInfo(track, nalUnit); 15381 15382 // record the track config 15383 if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) { 15384 config = nalUnit.config; 15385 track.sps = [nalUnit.data]; 15386 15387 VIDEO_PROPERTIES.forEach(function(prop) { 15388 track[prop] = config[prop]; 15389 }, this); 15390 } 15391 15392 if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && 15393 !pps) { 15394 pps = nalUnit.data; 15395 track.pps = [nalUnit.data]; 15396 } 15397 15398 // buffer video until flush() is called 15399 nalUnits.push(nalUnit); 15400 }; 15401 15402 this.flush = function() { 15403 var 15404 frames, 15405 gopForFusion, 15406 gops, 15407 moof, 15408 mdat, 15409 boxes; 15410 15411 // Throw away nalUnits at the start of the byte stream until 15412 // we find the first AUD 15413 while (nalUnits.length) { 15414 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { 15415 break; 15416 } 15417 nalUnits.shift(); 15418 } 15419 15420 // Return early if no video data has been observed 15421 if (nalUnits.length === 0) { 15422 this.resetStream_(); 15423 this.trigger('done', 'VideoSegmentStream'); 15424 return; 15425 } 15426 15427 // Organize the raw nal-units into arrays that represent 15428 // higher-level constructs such as frames and gops 15429 // (group-of-pictures) 15430 frames = this.groupNalsIntoFrames_(nalUnits); 15431 gops = this.groupFramesIntoGops_(frames); 15432 15433 // If the first frame of this fragment is not a keyframe we have 15434 // a problem since MSE (on Chrome) requires a leading keyframe. 15435 // 15436 // We have two approaches to repairing this situation: 15437 // 1) GOP-FUSION: 15438 // This is where we keep track of the GOPS (group-of-pictures) 15439 // from previous fragments and attempt to find one that we can 15440 // prepend to the current fragment in order to create a valid 15441 // fragment. 15442 // 2) KEYFRAME-PULLING: 15443 // Here we search for the first keyframe in the fragment and 15444 // throw away all the frames between the start of the fragment 15445 // and that keyframe. We then extend the duration and pull the 15446 // PTS of the keyframe forward so that it covers the time range 15447 // of the frames that were disposed of. 15448 // 15449 // #1 is far prefereable over #2 which can cause "stuttering" but 15450 // requires more things to be just right. 15451 if (!gops[0][0].keyFrame) { 15452 // Search for a gop for fusion from our gopCache 15453 gopForFusion = this.getGopForFusion_(nalUnits[0], track); 15454 15455 if (gopForFusion) { 15456 gops.unshift(gopForFusion); 15457 // Adjust Gops' metadata to account for the inclusion of the 15458 // new gop at the beginning 15459 gops.byteLength += gopForFusion.byteLength; 15460 gops.nalCount += gopForFusion.nalCount; 15461 gops.pts = gopForFusion.pts; 15462 gops.dts = gopForFusion.dts; 15463 gops.duration += gopForFusion.duration; 15464 } else { 15465 // If we didn't find a candidate gop fall back to keyrame-pulling 15466 gops = this.extendFirstKeyFrame_(gops); 15467 } 15468 } 15469 collectDtsInfo(track, gops); 15470 15471 // First, we have to build the index from byte locations to 15472 // samples (that is, frames) in the video data 15473 track.samples = this.generateSampleTable_(gops); 15474 15475 // Concatenate the video data and construct the mdat 15476 mdat = mp4.mdat(this.concatenateNalData_(gops)); 15477 15478 // save all the nals in the last GOP into the gop cache 15479 this.gopCache_.unshift({ 15480 gop: gops.pop(), 15481 pps: track.pps, 15482 sps: track.sps 15483 }); 15484 15485 // Keep a maximum of 6 GOPs in the cache 15486 this.gopCache_.length = Math.min(6, this.gopCache_.length); 15487 15488 // Clear nalUnits 15489 nalUnits = []; 15490 15491 track.baseMediaDecodeTime = calculateTrackBaseMediaDecodeTime(track); 15492 15493 this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime); 15494 this.trigger('timelineStartInfo', track.timelineStartInfo); 15495 15496 moof = mp4.moof(sequenceNumber, [track]); 15497 15498 // it would be great to allocate this array up front instead of 15499 // throwing away hundreds of media segment fragments 15500 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); 15501 15502 // Bump the sequence number for next time 15503 sequenceNumber++; 15504 15505 boxes.set(moof); 15506 boxes.set(mdat, moof.byteLength); 15507 15508 this.trigger('data', {track: track, boxes: boxes}); 15509 15510 this.resetStream_(); 15511 15512 // Continue with the flush process now 15513 this.trigger('done', 'VideoSegmentStream'); 15514 }; 15515 15516 this.resetStream_ = function() { 15517 clearDtsInfo(track); 15518 15519 // reset config and pps because they may differ across segments 15520 // for instance, when we are rendition switching 15521 config = undefined; 15522 pps = undefined; 15523 }; 15524 15525 // Search for a candidate Gop for gop-fusion from the gop cache and 15526 // return it or return null if no good candidate was found 15527 this.getGopForFusion_ = function(nalUnit) { 15528 var 15529 halfSecond = 45000, // Half-a-second in a 90khz clock 15530 allowableOverlap = 10000, // About 3 frames @ 30fps 15531 nearestDistance = Infinity, 15532 dtsDistance, 15533 nearestGopObj, 15534 currentGop, 15535 currentGopObj, 15536 i; 15537 15538 // Search for the GOP nearest to the beginning of this nal unit 15539 for (i = 0; i < this.gopCache_.length; i++) { 15540 currentGopObj = this.gopCache_[i]; 15541 currentGop = currentGopObj.gop; 15542 15543 // Reject Gops with different SPS or PPS 15544 if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || 15545 !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) { 15546 continue; 15547 } 15548 15549 // Reject Gops that would require a negative baseMediaDecodeTime 15550 if (currentGop.dts < track.timelineStartInfo.dts) { 15551 continue; 15552 } 15553 15554 // The distance between the end of the gop and the start of the nalUnit 15555 dtsDistance = (nalUnit.dts - currentGop.dts) - currentGop.duration; 15556 15557 // Only consider GOPS that start before the nal unit and end within 15558 // a half-second of the nal unit 15559 if (dtsDistance >= -allowableOverlap && 15560 dtsDistance <= halfSecond) { 15561 15562 // Always use the closest GOP we found if there is more than 15563 // one candidate 15564 if (!nearestGopObj || 15565 nearestDistance > dtsDistance) { 15566 nearestGopObj = currentGopObj; 15567 nearestDistance = dtsDistance; 15568 } 15569 } 15570 } 15571 15572 if (nearestGopObj) { 15573 return nearestGopObj.gop; 15574 } 15575 return null; 15576 }; 15577 15578 this.extendFirstKeyFrame_ = function(gops) { 15579 var currentGop; 15580 15581 if (!gops[0][0].keyFrame && gops.length > 1) { 15582 // Remove the first GOP 15583 currentGop = gops.shift(); 15584 15585 gops.byteLength -= currentGop.byteLength; 15586 gops.nalCount -= currentGop.nalCount; 15587 15588 // Extend the first frame of what is now the 15589 // first gop to cover the time period of the 15590 // frames we just removed 15591 gops[0][0].dts = currentGop.dts; 15592 gops[0][0].pts = currentGop.pts; 15593 gops[0][0].duration += currentGop.duration; 15594 } 15595 15596 return gops; 15597 }; 15598 15599 // Convert an array of nal units into an array of frames with each frame being 15600 // composed of the nal units that make up that frame 15601 // Also keep track of cummulative data about the frame from the nal units such 15602 // as the frame duration, starting pts, etc. 15603 this.groupNalsIntoFrames_ = function(nalUnits) { 15604 var 15605 i, 15606 currentNal, 15607 currentFrame = [], 15608 frames = []; 15609 15610 currentFrame.byteLength = 0; 15611 15612 for (i = 0; i < nalUnits.length; i++) { 15613 currentNal = nalUnits[i]; 15614 15615 // Split on 'aud'-type nal units 15616 if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { 15617 // Since the very first nal unit is expected to be an AUD 15618 // only push to the frames array when currentFrame is not empty 15619 if (currentFrame.length) { 15620 currentFrame.duration = currentNal.dts - currentFrame.dts; 15621 frames.push(currentFrame); 15622 } 15623 currentFrame = [currentNal]; 15624 currentFrame.byteLength = currentNal.data.byteLength; 15625 currentFrame.pts = currentNal.pts; 15626 currentFrame.dts = currentNal.dts; 15627 } else { 15628 // Specifically flag key frames for ease of use later 15629 if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { 15630 currentFrame.keyFrame = true; 15631 } 15632 currentFrame.duration = currentNal.dts - currentFrame.dts; 15633 currentFrame.byteLength += currentNal.data.byteLength; 15634 currentFrame.push(currentNal); 15635 } 15636 } 15637 15638 // For the last frame, use the duration of the previous frame if we 15639 // have nothing better to go on 15640 if (frames.length && 15641 (!currentFrame.duration || 15642 currentFrame.duration <= 0)) { 15643 currentFrame.duration = frames[frames.length - 1].duration; 15644 } 15645 15646 // Push the final frame 15647 frames.push(currentFrame); 15648 return frames; 15649 }; 15650 15651 // Convert an array of frames into an array of Gop with each Gop being composed 15652 // of the frames that make up that Gop 15653 // Also keep track of cummulative data about the Gop from the frames such as the 15654 // Gop duration, starting pts, etc. 15655 this.groupFramesIntoGops_ = function(frames) { 15656 var 15657 i, 15658 currentFrame, 15659 currentGop = [], 15660 gops = []; 15661 15662 // We must pre-set some of the values on the Gop since we 15663 // keep running totals of these values 15664 currentGop.byteLength = 0; 15665 currentGop.nalCount = 0; 15666 currentGop.duration = 0; 15667 currentGop.pts = frames[0].pts; 15668 currentGop.dts = frames[0].dts; 15669 15670 // store some metadata about all the Gops 15671 gops.byteLength = 0; 15672 gops.nalCount = 0; 15673 gops.duration = 0; 15674 gops.pts = frames[0].pts; 15675 gops.dts = frames[0].dts; 15676 15677 for (i = 0; i < frames.length; i++) { 15678 currentFrame = frames[i]; 15679 15680 if (currentFrame.keyFrame) { 15681 // Since the very first frame is expected to be an keyframe 15682 // only push to the gops array when currentGop is not empty 15683 if (currentGop.length) { 15684 gops.push(currentGop); 15685 gops.byteLength += currentGop.byteLength; 15686 gops.nalCount += currentGop.nalCount; 15687 gops.duration += currentGop.duration; 15688 } 15689 15690 currentGop = [currentFrame]; 15691 currentGop.nalCount = currentFrame.length; 15692 currentGop.byteLength = currentFrame.byteLength; 15693 currentGop.pts = currentFrame.pts; 15694 currentGop.dts = currentFrame.dts; 15695 currentGop.duration = currentFrame.duration; 15696 } else { 15697 currentGop.duration += currentFrame.duration; 15698 currentGop.nalCount += currentFrame.length; 15699 currentGop.byteLength += currentFrame.byteLength; 15700 currentGop.push(currentFrame); 15701 } 15702 } 15703 15704 if (gops.length && currentGop.duration <= 0) { 15705 currentGop.duration = gops[gops.length - 1].duration; 15706 } 15707 gops.byteLength += currentGop.byteLength; 15708 gops.nalCount += currentGop.nalCount; 15709 gops.duration += currentGop.duration; 15710 15711 // push the final Gop 15712 gops.push(currentGop); 15713 return gops; 15714 }; 15715 15716 // generate the track's sample table from an array of gops 15717 this.generateSampleTable_ = function(gops, baseDataOffset) { 15718 var 15719 h, i, 15720 sample, 15721 currentGop, 15722 currentFrame, 15723 dataOffset = baseDataOffset || 0, 15724 samples = []; 15725 15726 for (h = 0; h < gops.length; h++) { 15727 currentGop = gops[h]; 15728 15729 for (i = 0; i < currentGop.length; i++) { 15730 currentFrame = currentGop[i]; 15731 15732 sample = createDefaultSample(); 15733 15734 sample.dataOffset = dataOffset; 15735 sample.compositionTimeOffset = currentFrame.pts - currentFrame.dts; 15736 sample.duration = currentFrame.duration; 15737 sample.size = 4 * currentFrame.length; // Space for nal unit size 15738 sample.size += currentFrame.byteLength; 15739 15740 if (currentFrame.keyFrame) { 15741 sample.flags.dependsOn = 2; 15742 } 15743 15744 dataOffset += sample.size; 15745 15746 samples.push(sample); 15747 } 15748 } 15749 return samples; 15750 }; 15751 15752 // generate the track's raw mdat data from an array of gops 15753 this.concatenateNalData_ = function(gops) { 15754 var 15755 h, i, j, 15756 currentGop, 15757 currentFrame, 15758 currentNal, 15759 dataOffset = 0, 15760 nalsByteLength = gops.byteLength, 15761 numberOfNals = gops.nalCount, 15762 totalByteLength = nalsByteLength + 4 * numberOfNals, 15763 data = new Uint8Array(totalByteLength), 15764 view = new DataView(data.buffer); 15765 15766 // For each Gop.. 15767 for (h = 0; h < gops.length; h++) { 15768 currentGop = gops[h]; 15769 15770 // For each Frame.. 15771 for (i = 0; i < currentGop.length; i++) { 15772 currentFrame = currentGop[i]; 15773 15774 // For each NAL.. 15775 for (j = 0; j < currentFrame.length; j++) { 15776 currentNal = currentFrame[j]; 15777 15778 view.setUint32(dataOffset, currentNal.data.byteLength); 15779 dataOffset += 4; 15780 data.set(currentNal.data, dataOffset); 15781 dataOffset += currentNal.data.byteLength; 15782 } 15783 } 15784 } 15785 return data; 15786 }; 15787}; 15788 15789VideoSegmentStream.prototype = new Stream(); 15790 15791/** 15792 * Store information about the start and end of the track and the 15793 * duration for each frame/sample we process in order to calculate 15794 * the baseMediaDecodeTime 15795 */ 15796collectDtsInfo = function(track, data) { 15797 if (typeof data.pts === 'number') { 15798 if (track.timelineStartInfo.pts === undefined) { 15799 track.timelineStartInfo.pts = data.pts; 15800 } 15801 15802 if (track.minSegmentPts === undefined) { 15803 track.minSegmentPts = data.pts; 15804 } else { 15805 track.minSegmentPts = Math.min(track.minSegmentPts, data.pts); 15806 } 15807 15808 if (track.maxSegmentPts === undefined) { 15809 track.maxSegmentPts = data.pts; 15810 } else { 15811 track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts); 15812 } 15813 } 15814 15815 if (typeof data.dts === 'number') { 15816 if (track.timelineStartInfo.dts === undefined) { 15817 track.timelineStartInfo.dts = data.dts; 15818 } 15819 15820 if (track.minSegmentDts === undefined) { 15821 track.minSegmentDts = data.dts; 15822 } else { 15823 track.minSegmentDts = Math.min(track.minSegmentDts, data.dts); 15824 } 15825 15826 if (track.maxSegmentDts === undefined) { 15827 track.maxSegmentDts = data.dts; 15828 } else { 15829 track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts); 15830 } 15831 } 15832}; 15833 15834/** 15835 * Clear values used to calculate the baseMediaDecodeTime between 15836 * tracks 15837 */ 15838clearDtsInfo = function(track) { 15839 delete track.minSegmentDts; 15840 delete track.maxSegmentDts; 15841 delete track.minSegmentPts; 15842 delete track.maxSegmentPts; 15843}; 15844 15845/** 15846 * Calculate the track's baseMediaDecodeTime based on the earliest 15847 * DTS the transmuxer has ever seen and the minimum DTS for the 15848 * current track 15849 */ 15850calculateTrackBaseMediaDecodeTime = function(track) { 15851 var 15852 baseMediaDecodeTime, 15853 scale, 15854 // Calculate the distance, in time, that this segment starts from the start 15855 // of the timeline (earliest time seen since the transmuxer initialized) 15856 timeSinceStartOfTimeline = track.minSegmentDts - track.timelineStartInfo.dts; 15857 15858 // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where 15859 // we want the start of the first segment to be placed 15860 baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; 15861 15862 // Add to that the distance this segment is from the very first 15863 baseMediaDecodeTime += timeSinceStartOfTimeline; 15864 15865 // baseMediaDecodeTime must not become negative 15866 baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime); 15867 15868 if (track.type === 'audio') { 15869 // Audio has a different clock equal to the sampling_rate so we need to 15870 // scale the PTS values into the clock rate of the track 15871 scale = track.samplerate / ONE_SECOND_IN_TS; 15872 baseMediaDecodeTime *= scale; 15873 baseMediaDecodeTime = Math.floor(baseMediaDecodeTime); 15874 } 15875 15876 return baseMediaDecodeTime; 15877}; 15878 15879/** 15880 * A Stream that can combine multiple streams (ie. audio & video) 15881 * into a single output segment for MSE. Also supports audio-only 15882 * and video-only streams. 15883 */ 15884CoalesceStream = function(options, metadataStream) { 15885 // Number of Tracks per output segment 15886 // If greater than 1, we combine multiple 15887 // tracks into a single segment 15888 this.numberOfTracks = 0; 15889 this.metadataStream = metadataStream; 15890 15891 if (typeof options.remux !== 'undefined') { 15892 this.remuxTracks = !!options.remux; 15893 } else { 15894 this.remuxTracks = true; 15895 } 15896 15897 this.pendingTracks = []; 15898 this.videoTrack = null; 15899 this.pendingBoxes = []; 15900 this.pendingCaptions = []; 15901 this.pendingMetadata = []; 15902 this.pendingBytes = 0; 15903 this.emittedTracks = 0; 15904 15905 CoalesceStream.prototype.init.call(this); 15906 15907 // Take output from multiple 15908 this.push = function(output) { 15909 // buffer incoming captions until the associated video segment 15910 // finishes 15911 if (output.text) { 15912 return this.pendingCaptions.push(output); 15913 } 15914 // buffer incoming id3 tags until the final flush 15915 if (output.frames) { 15916 return this.pendingMetadata.push(output); 15917 } 15918 15919 // Add this track to the list of pending tracks and store 15920 // important information required for the construction of 15921 // the final segment 15922 this.pendingTracks.push(output.track); 15923 this.pendingBoxes.push(output.boxes); 15924 this.pendingBytes += output.boxes.byteLength; 15925 15926 if (output.track.type === 'video') { 15927 this.videoTrack = output.track; 15928 } 15929 if (output.track.type === 'audio') { 15930 this.audioTrack = output.track; 15931 } 15932 }; 15933}; 15934 15935CoalesceStream.prototype = new Stream(); 15936CoalesceStream.prototype.flush = function(flushSource) { 15937 var 15938 offset = 0, 15939 event = { 15940 captions: [], 15941 captionStreams: {}, 15942 metadata: [], 15943 info: {} 15944 }, 15945 caption, 15946 id3, 15947 initSegment, 15948 timelineStartPts = 0, 15949 i; 15950 15951 if (this.pendingTracks.length < this.numberOfTracks) { 15952 if (flushSource !== 'VideoSegmentStream' && 15953 flushSource !== 'AudioSegmentStream') { 15954 // Return because we haven't received a flush from a data-generating 15955 // portion of the segment (meaning that we have only recieved meta-data 15956 // or captions.) 15957 return; 15958 } else if (this.remuxTracks) { 15959 // Return until we have enough tracks from the pipeline to remux (if we 15960 // are remuxing audio and video into a single MP4) 15961 return; 15962 } else if (this.pendingTracks.length === 0) { 15963 // In the case where we receive a flush without any data having been 15964 // received we consider it an emitted track for the purposes of coalescing 15965 // `done` events. 15966 // We do this for the case where there is an audio and video track in the 15967 // segment but no audio data. (seen in several playlists with alternate 15968 // audio tracks and no audio present in the main TS segments.) 15969 this.emittedTracks++; 15970 15971 if (this.emittedTracks >= this.numberOfTracks) { 15972 this.trigger('done'); 15973 this.emittedTracks = 0; 15974 } 15975 return; 15976 } 15977 } 15978 15979 if (this.videoTrack) { 15980 timelineStartPts = this.videoTrack.timelineStartInfo.pts; 15981 VIDEO_PROPERTIES.forEach(function(prop) { 15982 event.info[prop] = this.videoTrack[prop]; 15983 }, this); 15984 } else if (this.audioTrack) { 15985 timelineStartPts = this.audioTrack.timelineStartInfo.pts; 15986 AUDIO_PROPERTIES.forEach(function(prop) { 15987 event.info[prop] = this.audioTrack[prop]; 15988 }, this); 15989 } 15990 15991 if (this.pendingTracks.length === 1) { 15992 event.type = this.pendingTracks[0].type; 15993 } else { 15994 event.type = 'combined'; 15995 } 15996 15997 this.emittedTracks += this.pendingTracks.length; 15998 15999 initSegment = mp4.initSegment(this.pendingTracks); 16000 16001 // Create a new typed array to hold the init segment 16002 event.initSegment = new Uint8Array(initSegment.byteLength); 16003 16004 // Create an init segment containing a moov 16005 // and track definitions 16006 event.initSegment.set(initSegment); 16007 16008 // Create a new typed array to hold the moof+mdats 16009 event.data = new Uint8Array(this.pendingBytes); 16010 16011 // Append each moof+mdat (one per track) together 16012 for (i = 0; i < this.pendingBoxes.length; i++) { 16013 event.data.set(this.pendingBoxes[i], offset); 16014 offset += this.pendingBoxes[i].byteLength; 16015 } 16016 16017 // Translate caption PTS times into second offsets into the 16018 // video timeline for the segment, and add track info 16019 for (i = 0; i < this.pendingCaptions.length; i++) { 16020 caption = this.pendingCaptions[i]; 16021 caption.startTime = (caption.startPts - timelineStartPts); 16022 caption.startTime /= 90e3; 16023 caption.endTime = (caption.endPts - timelineStartPts); 16024 caption.endTime /= 90e3; 16025 event.captionStreams[caption.stream] = true; 16026 event.captions.push(caption); 16027 } 16028 16029 // Translate ID3 frame PTS times into second offsets into the 16030 // video timeline for the segment 16031 for (i = 0; i < this.pendingMetadata.length; i++) { 16032 id3 = this.pendingMetadata[i]; 16033 id3.cueTime = (id3.pts - timelineStartPts); 16034 id3.cueTime /= 90e3; 16035 event.metadata.push(id3); 16036 } 16037 // We add this to every single emitted segment even though we only need 16038 // it for the first 16039 event.metadata.dispatchType = this.metadataStream.dispatchType; 16040 16041 // Reset stream state 16042 this.pendingTracks.length = 0; 16043 this.videoTrack = null; 16044 this.pendingBoxes.length = 0; 16045 this.pendingCaptions.length = 0; 16046 this.pendingBytes = 0; 16047 this.pendingMetadata.length = 0; 16048 16049 // Emit the built segment 16050 this.trigger('data', event); 16051 16052 // Only emit `done` if all tracks have been flushed and emitted 16053 if (this.emittedTracks >= this.numberOfTracks) { 16054 this.trigger('done'); 16055 this.emittedTracks = 0; 16056 } 16057}; 16058/** 16059 * A Stream that expects MP2T binary data as input and produces 16060 * corresponding media segments, suitable for use with Media Source 16061 * Extension (MSE) implementations that support the ISO BMFF byte 16062 * stream format, like Chrome. 16063 */ 16064Transmuxer = function(options) { 16065 var 16066 self = this, 16067 hasFlushed = true, 16068 videoTrack, 16069 audioTrack; 16070 16071 Transmuxer.prototype.init.call(this); 16072 16073 options = options || {}; 16074 this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0; 16075 this.transmuxPipeline_ = {}; 16076 16077 this.setupAacPipeline = function() { 16078 var pipeline = {}; 16079 this.transmuxPipeline_ = pipeline; 16080 16081 pipeline.type = 'aac'; 16082 pipeline.metadataStream = new m2ts.MetadataStream(); 16083 16084 // set up the parsing pipeline 16085 pipeline.aacStream = new AacStream(); 16086 pipeline.audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio'); 16087 pipeline.timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata'); 16088 pipeline.adtsStream = new AdtsStream(); 16089 pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream); 16090 pipeline.headOfPipeline = pipeline.aacStream; 16091 16092 pipeline.aacStream 16093 .pipe(pipeline.audioTimestampRolloverStream) 16094 .pipe(pipeline.adtsStream); 16095 pipeline.aacStream 16096 .pipe(pipeline.timedMetadataTimestampRolloverStream) 16097 .pipe(pipeline.metadataStream) 16098 .pipe(pipeline.coalesceStream); 16099 16100 pipeline.metadataStream.on('timestamp', function(frame) { 16101 pipeline.aacStream.setTimestamp(frame.timeStamp); 16102 }); 16103 16104 pipeline.aacStream.on('data', function(data) { 16105 if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) { 16106 audioTrack = audioTrack || { 16107 timelineStartInfo: { 16108 baseMediaDecodeTime: self.baseMediaDecodeTime 16109 }, 16110 codec: 'adts', 16111 type: 'audio' 16112 }; 16113 // hook up the audio segment stream to the first track with aac data 16114 pipeline.coalesceStream.numberOfTracks++; 16115 pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack); 16116 // Set up the final part of the audio pipeline 16117 pipeline.adtsStream 16118 .pipe(pipeline.audioSegmentStream) 16119 .pipe(pipeline.coalesceStream); 16120 } 16121 }); 16122 16123 // Re-emit any data coming from the coalesce stream to the outside world 16124 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); 16125 // Let the consumer know we have finished flushing the entire pipeline 16126 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 16127 }; 16128 16129 this.setupTsPipeline = function() { 16130 var pipeline = {}; 16131 this.transmuxPipeline_ = pipeline; 16132 16133 pipeline.type = 'ts'; 16134 pipeline.metadataStream = new m2ts.MetadataStream(); 16135 16136 // set up the parsing pipeline 16137 pipeline.packetStream = new m2ts.TransportPacketStream(); 16138 pipeline.parseStream = new m2ts.TransportParseStream(); 16139 pipeline.elementaryStream = new m2ts.ElementaryStream(); 16140 pipeline.videoTimestampRolloverStream = new m2ts.TimestampRolloverStream('video'); 16141 pipeline.audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio'); 16142 pipeline.timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata'); 16143 pipeline.adtsStream = new AdtsStream(); 16144 pipeline.h264Stream = new H264Stream(); 16145 pipeline.captionStream = new m2ts.CaptionStream(); 16146 pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream); 16147 pipeline.headOfPipeline = pipeline.packetStream; 16148 16149 // disassemble MPEG2-TS packets into elementary streams 16150 pipeline.packetStream 16151 .pipe(pipeline.parseStream) 16152 .pipe(pipeline.elementaryStream); 16153 16154 // !!THIS ORDER IS IMPORTANT!! 16155 // demux the streams 16156 pipeline.elementaryStream 16157 .pipe(pipeline.videoTimestampRolloverStream) 16158 .pipe(pipeline.h264Stream); 16159 pipeline.elementaryStream 16160 .pipe(pipeline.audioTimestampRolloverStream) 16161 .pipe(pipeline.adtsStream); 16162 16163 pipeline.elementaryStream 16164 .pipe(pipeline.timedMetadataTimestampRolloverStream) 16165 .pipe(pipeline.metadataStream) 16166 .pipe(pipeline.coalesceStream); 16167 16168 // Hook up CEA-608/708 caption stream 16169 pipeline.h264Stream.pipe(pipeline.captionStream) 16170 .pipe(pipeline.coalesceStream); 16171 16172 pipeline.elementaryStream.on('data', function(data) { 16173 var i; 16174 16175 if (data.type === 'metadata') { 16176 i = data.tracks.length; 16177 16178 // scan the tracks listed in the metadata 16179 while (i--) { 16180 if (!videoTrack && data.tracks[i].type === 'video') { 16181 videoTrack = data.tracks[i]; 16182 videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 16183 } else if (!audioTrack && data.tracks[i].type === 'audio') { 16184 audioTrack = data.tracks[i]; 16185 audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 16186 } 16187 } 16188 16189 // hook up the video segment stream to the first track with h264 data 16190 if (videoTrack && !pipeline.videoSegmentStream) { 16191 pipeline.coalesceStream.numberOfTracks++; 16192 pipeline.videoSegmentStream = new VideoSegmentStream(videoTrack); 16193 16194 pipeline.videoSegmentStream.on('timelineStartInfo', function(timelineStartInfo) { 16195 // When video emits timelineStartInfo data after a flush, we forward that 16196 // info to the AudioSegmentStream, if it exists, because video timeline 16197 // data takes precedence. 16198 if (audioTrack) { 16199 audioTrack.timelineStartInfo = timelineStartInfo; 16200 // On the first segment we trim AAC frames that exist before the 16201 // very earliest DTS we have seen in video because Chrome will 16202 // interpret any video track with a baseMediaDecodeTime that is 16203 // non-zero as a gap. 16204 pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts); 16205 } 16206 }); 16207 16208 pipeline.videoSegmentStream.on('baseMediaDecodeTime', function(baseMediaDecodeTime) { 16209 if (audioTrack) { 16210 pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime); 16211 } 16212 }); 16213 16214 // Set up the final part of the video pipeline 16215 pipeline.h264Stream 16216 .pipe(pipeline.videoSegmentStream) 16217 .pipe(pipeline.coalesceStream); 16218 } 16219 16220 if (audioTrack && !pipeline.audioSegmentStream) { 16221 // hook up the audio segment stream to the first track with aac data 16222 pipeline.coalesceStream.numberOfTracks++; 16223 pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack); 16224 16225 // Set up the final part of the audio pipeline 16226 pipeline.adtsStream 16227 .pipe(pipeline.audioSegmentStream) 16228 .pipe(pipeline.coalesceStream); 16229 } 16230 } 16231 }); 16232 16233 // Re-emit any data coming from the coalesce stream to the outside world 16234 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); 16235 // Let the consumer know we have finished flushing the entire pipeline 16236 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 16237 }; 16238 16239 // hook up the segment streams once track metadata is delivered 16240 this.setBaseMediaDecodeTime = function(baseMediaDecodeTime) { 16241 var pipeline = this.transmuxPipeline_; 16242 16243 this.baseMediaDecodeTime = baseMediaDecodeTime; 16244 if (audioTrack) { 16245 audioTrack.timelineStartInfo.dts = undefined; 16246 audioTrack.timelineStartInfo.pts = undefined; 16247 clearDtsInfo(audioTrack); 16248 audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 16249 if (pipeline.audioTimestampRolloverStream) { 16250 pipeline.audioTimestampRolloverStream.discontinuity(); 16251 } 16252 } 16253 if (videoTrack) { 16254 if (pipeline.videoSegmentStream) { 16255 pipeline.videoSegmentStream.gopCache_ = []; 16256 pipeline.videoTimestampRolloverStream.discontinuity(); 16257 } 16258 videoTrack.timelineStartInfo.dts = undefined; 16259 videoTrack.timelineStartInfo.pts = undefined; 16260 clearDtsInfo(videoTrack); 16261 pipeline.captionStream.reset(); 16262 videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 16263 } 16264 16265 if (pipeline.timedMetadataTimestampRolloverStream) { 16266 pipeline.timedMetadataTimestampRolloverStream.discontinuity(); 16267 } 16268 }; 16269 16270 this.setAudioAppendStart = function(timestamp) { 16271 if (audioTrack) { 16272 this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp); 16273 } 16274 }; 16275 16276 // feed incoming data to the front of the parsing pipeline 16277 this.push = function(data) { 16278 if (hasFlushed) { 16279 var isAac = isLikelyAacData(data); 16280 16281 if (isAac && this.transmuxPipeline_.type !== 'aac') { 16282 this.setupAacPipeline(); 16283 } else if (!isAac && this.transmuxPipeline_.type !== 'ts') { 16284 this.setupTsPipeline(); 16285 } 16286 hasFlushed = false; 16287 } 16288 this.transmuxPipeline_.headOfPipeline.push(data); 16289 }; 16290 16291 // flush any buffered data 16292 this.flush = function() { 16293 hasFlushed = true; 16294 // Start at the top of the pipeline and flush all pending work 16295 this.transmuxPipeline_.headOfPipeline.flush(); 16296 }; 16297 16298 // Caption data has to be reset when seeking outside buffered range 16299 this.resetCaptions = function() { 16300 if (this.transmuxPipeline_.captionStream) { 16301 this.transmuxPipeline_.captionStream.reset(); 16302 } 16303 }; 16304 16305}; 16306Transmuxer.prototype = new Stream(); 16307 16308module.exports = { 16309 Transmuxer: Transmuxer, 16310 VideoSegmentStream: VideoSegmentStream, 16311 AudioSegmentStream: AudioSegmentStream, 16312 AUDIO_PROPERTIES: AUDIO_PROPERTIES, 16313 VIDEO_PROPERTIES: VIDEO_PROPERTIES 16314}; 16315 16316},{"../aac":38,"../codecs/adts.js":40,"../codecs/h264":41,"../data/silence":42,"../m2ts/m2ts.js":50,"../utils/clock":60,"../utils/stream.js":62,"./mp4-generator.js":56}],59:[function(require,module,exports){ 16317/** 16318 * mux.js 16319 * 16320 * Copyright (c) 2016 Brightcove 16321 * All rights reserved. 16322 * 16323 * Parse mpeg2 transport stream packets to extract basic timing information 16324 */ 16325'use strict'; 16326 16327var StreamTypes = require('../m2ts/stream-types.js'); 16328var handleRollover = require('../m2ts/timestamp-rollover-stream.js').handleRollover; 16329var probe = {}; 16330probe.ts = require('../m2ts/probe.js'); 16331probe.aac = require('../aac/probe.js'); 16332 16333 16334var 16335 PES_TIMESCALE = 90000, 16336 MP2T_PACKET_LENGTH = 188, // bytes 16337 SYNC_BYTE = 0x47; 16338 16339var isLikelyAacData = function(data) { 16340 if ((data[0] === 'I'.charCodeAt(0)) && 16341 (data[1] === 'D'.charCodeAt(0)) && 16342 (data[2] === '3'.charCodeAt(0))) { 16343 return true; 16344 } 16345 return false; 16346}; 16347 16348/** 16349 * walks through segment data looking for pat and pmt packets to parse out 16350 * program map table information 16351 */ 16352var parsePsi_ = function(bytes, pmt) { 16353 var 16354 startIndex = 0, 16355 endIndex = MP2T_PACKET_LENGTH, 16356 packet, type; 16357 16358 while (endIndex < bytes.byteLength) { 16359 // Look for a pair of start and end sync bytes in the data.. 16360 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 16361 // We found a packet 16362 packet = bytes.subarray(startIndex, endIndex); 16363 type = probe.ts.parseType(packet, pmt.pid); 16364 16365 switch (type) { 16366 case 'pat': 16367 if (!pmt.pid) { 16368 pmt.pid = probe.ts.parsePat(packet); 16369 } 16370 break; 16371 case 'pmt': 16372 if (!pmt.table) { 16373 pmt.table = probe.ts.parsePmt(packet); 16374 } 16375 break; 16376 default: 16377 break; 16378 } 16379 16380 // Found the pat and pmt, we can stop walking the segment 16381 if (pmt.pid && pmt.table) { 16382 return; 16383 } 16384 16385 startIndex += MP2T_PACKET_LENGTH; 16386 endIndex += MP2T_PACKET_LENGTH; 16387 continue; 16388 } 16389 16390 // If we get here, we have somehow become de-synchronized and we need to step 16391 // forward one byte at a time until we find a pair of sync bytes that denote 16392 // a packet 16393 startIndex++; 16394 endIndex++; 16395 } 16396}; 16397 16398/** 16399 * walks through the segment data from the start and end to get timing information 16400 * for the first and last audio pes packets 16401 */ 16402var parseAudioPes_ = function(bytes, pmt, result) { 16403 var 16404 startIndex = 0, 16405 endIndex = MP2T_PACKET_LENGTH, 16406 packet, type, pesType, pusi, parsed; 16407 16408 var endLoop = false; 16409 16410 // Start walking from start of segment to get first audio packet 16411 while (endIndex < bytes.byteLength) { 16412 // Look for a pair of start and end sync bytes in the data.. 16413 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 16414 // We found a packet 16415 packet = bytes.subarray(startIndex, endIndex); 16416 type = probe.ts.parseType(packet, pmt.pid); 16417 16418 switch (type) { 16419 case 'pes': 16420 pesType = probe.ts.parsePesType(packet, pmt.table); 16421 pusi = probe.ts.parsePayloadUnitStartIndicator(packet); 16422 if (pesType === 'audio' && pusi) { 16423 parsed = probe.ts.parsePesTime(packet); 16424 if (parsed) { 16425 parsed.type = 'audio'; 16426 result.audio.push(parsed); 16427 endLoop = true; 16428 } 16429 } 16430 break; 16431 default: 16432 break; 16433 } 16434 16435 if (endLoop) { 16436 break; 16437 } 16438 16439 startIndex += MP2T_PACKET_LENGTH; 16440 endIndex += MP2T_PACKET_LENGTH; 16441 continue; 16442 } 16443 16444 // If we get here, we have somehow become de-synchronized and we need to step 16445 // forward one byte at a time until we find a pair of sync bytes that denote 16446 // a packet 16447 startIndex++; 16448 endIndex++; 16449 } 16450 16451 // Start walking from end of segment to get last audio packet 16452 endIndex = bytes.byteLength; 16453 startIndex = endIndex - MP2T_PACKET_LENGTH; 16454 endLoop = false; 16455 while (startIndex >= 0) { 16456 // Look for a pair of start and end sync bytes in the data.. 16457 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 16458 // We found a packet 16459 packet = bytes.subarray(startIndex, endIndex); 16460 type = probe.ts.parseType(packet, pmt.pid); 16461 16462 switch (type) { 16463 case 'pes': 16464 pesType = probe.ts.parsePesType(packet, pmt.table); 16465 pusi = probe.ts.parsePayloadUnitStartIndicator(packet); 16466 if (pesType === 'audio' && pusi) { 16467 parsed = probe.ts.parsePesTime(packet); 16468 if (parsed) { 16469 parsed.type = 'audio'; 16470 result.audio.push(parsed); 16471 endLoop = true; 16472 } 16473 } 16474 break; 16475 default: 16476 break; 16477 } 16478 16479 if (endLoop) { 16480 break; 16481 } 16482 16483 startIndex -= MP2T_PACKET_LENGTH; 16484 endIndex -= MP2T_PACKET_LENGTH; 16485 continue; 16486 } 16487 16488 // If we get here, we have somehow become de-synchronized and we need to step 16489 // forward one byte at a time until we find a pair of sync bytes that denote 16490 // a packet 16491 startIndex--; 16492 endIndex--; 16493 } 16494}; 16495 16496/** 16497 * walks through the segment data from the start and end to get timing information 16498 * for the first and last video pes packets as well as timing information for the first 16499 * key frame. 16500 */ 16501var parseVideoPes_ = function(bytes, pmt, result) { 16502 var 16503 startIndex = 0, 16504 endIndex = MP2T_PACKET_LENGTH, 16505 packet, type, pesType, pusi, parsed, frame, i, pes; 16506 16507 var endLoop = false; 16508 16509 var currentFrame = { 16510 data: [], 16511 size: 0 16512 }; 16513 16514 // Start walking from start of segment to get first video packet 16515 while (endIndex < bytes.byteLength) { 16516 // Look for a pair of start and end sync bytes in the data.. 16517 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 16518 // We found a packet 16519 packet = bytes.subarray(startIndex, endIndex); 16520 type = probe.ts.parseType(packet, pmt.pid); 16521 16522 switch (type) { 16523 case 'pes': 16524 pesType = probe.ts.parsePesType(packet, pmt.table); 16525 pusi = probe.ts.parsePayloadUnitStartIndicator(packet); 16526 if (pesType === 'video') { 16527 if (pusi && !endLoop) { 16528 parsed = probe.ts.parsePesTime(packet); 16529 if (parsed) { 16530 parsed.type = 'video'; 16531 result.video.push(parsed); 16532 endLoop = true; 16533 } 16534 } 16535 if (!result.firstKeyFrame) { 16536 if (pusi) { 16537 if (currentFrame.size !== 0) { 16538 frame = new Uint8Array(currentFrame.size); 16539 i = 0; 16540 while (currentFrame.data.length) { 16541 pes = currentFrame.data.shift(); 16542 frame.set(pes, i); 16543 i += pes.byteLength; 16544 } 16545 if (probe.ts.videoPacketContainsKeyFrame(frame)) { 16546 result.firstKeyFrame = probe.ts.parsePesTime(frame); 16547 result.firstKeyFrame.type = 'video'; 16548 } 16549 currentFrame.size = 0; 16550 } 16551 } 16552 currentFrame.data.push(packet); 16553 currentFrame.size += packet.byteLength; 16554 } 16555 } 16556 break; 16557 default: 16558 break; 16559 } 16560 16561 if (endLoop && result.firstKeyFrame) { 16562 break; 16563 } 16564 16565 startIndex += MP2T_PACKET_LENGTH; 16566 endIndex += MP2T_PACKET_LENGTH; 16567 continue; 16568 } 16569 16570 // If we get here, we have somehow become de-synchronized and we need to step 16571 // forward one byte at a time until we find a pair of sync bytes that denote 16572 // a packet 16573 startIndex++; 16574 endIndex++; 16575 } 16576 16577 // Start walking from end of segment to get last video packet 16578 endIndex = bytes.byteLength; 16579 startIndex = endIndex - MP2T_PACKET_LENGTH; 16580 endLoop = false; 16581 while (startIndex >= 0) { 16582 // Look for a pair of start and end sync bytes in the data.. 16583 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 16584 // We found a packet 16585 packet = bytes.subarray(startIndex, endIndex); 16586 type = probe.ts.parseType(packet, pmt.pid); 16587 16588 switch (type) { 16589 case 'pes': 16590 pesType = probe.ts.parsePesType(packet, pmt.table); 16591 pusi = probe.ts.parsePayloadUnitStartIndicator(packet); 16592 if (pesType === 'video' && pusi) { 16593 parsed = probe.ts.parsePesTime(packet); 16594 if (parsed) { 16595 parsed.type = 'video'; 16596 result.video.push(parsed); 16597 endLoop = true; 16598 } 16599 } 16600 break; 16601 default: 16602 break; 16603 } 16604 16605 if (endLoop) { 16606 break; 16607 } 16608 16609 startIndex -= MP2T_PACKET_LENGTH; 16610 endIndex -= MP2T_PACKET_LENGTH; 16611 continue; 16612 } 16613 16614 // If we get here, we have somehow become de-synchronized and we need to step 16615 // forward one byte at a time until we find a pair of sync bytes that denote 16616 // a packet 16617 startIndex--; 16618 endIndex--; 16619 } 16620}; 16621 16622/** 16623 * Adjusts the timestamp information for the segment to account for 16624 * rollover and convert to seconds based on pes packet timescale (90khz clock) 16625 */ 16626var adjustTimestamp_ = function(segmentInfo, baseTimestamp) { 16627 if (segmentInfo.audio && segmentInfo.audio.length) { 16628 var audioBaseTimestamp = baseTimestamp; 16629 if (typeof audioBaseTimestamp === 'undefined') { 16630 audioBaseTimestamp = segmentInfo.audio[0].dts; 16631 } 16632 segmentInfo.audio.forEach(function(info) { 16633 info.dts = handleRollover(info.dts, audioBaseTimestamp); 16634 info.pts = handleRollover(info.pts, audioBaseTimestamp); 16635 // time in seconds 16636 info.dtsTime = info.dts / PES_TIMESCALE; 16637 info.ptsTime = info.pts / PES_TIMESCALE; 16638 }); 16639 } 16640 16641 if (segmentInfo.video && segmentInfo.video.length) { 16642 var videoBaseTimestamp = baseTimestamp; 16643 if (typeof videoBaseTimestamp === 'undefined') { 16644 videoBaseTimestamp = segmentInfo.video[0].dts; 16645 } 16646 segmentInfo.video.forEach(function(info) { 16647 info.dts = handleRollover(info.dts, videoBaseTimestamp); 16648 info.pts = handleRollover(info.pts, videoBaseTimestamp); 16649 // time in seconds 16650 info.dtsTime = info.dts / PES_TIMESCALE; 16651 info.ptsTime = info.pts / PES_TIMESCALE; 16652 }); 16653 if (segmentInfo.firstKeyFrame) { 16654 var frame = segmentInfo.firstKeyFrame; 16655 frame.dts = handleRollover(frame.dts, videoBaseTimestamp); 16656 frame.pts = handleRollover(frame.pts, videoBaseTimestamp); 16657 // time in seconds 16658 frame.dtsTime = frame.dts / PES_TIMESCALE; 16659 frame.ptsTime = frame.dts / PES_TIMESCALE; 16660 } 16661 } 16662}; 16663 16664/** 16665 * inspects the aac data stream for start and end time information 16666 */ 16667var inspectAac_ = function(bytes) { 16668 var 16669 endLoop = false, 16670 audioCount = 0, 16671 sampleRate = null, 16672 timestamp = null, 16673 frameSize = 0, 16674 byteIndex = 0, 16675 packet; 16676 16677 while (bytes.length - byteIndex >= 3) { 16678 var type = probe.aac.parseType(bytes, byteIndex); 16679 switch (type) { 16680 case 'timed-metadata': 16681 // Exit early because we don't have enough to parse 16682 // the ID3 tag header 16683 if (bytes.length - byteIndex < 10) { 16684 endLoop = true; 16685 break; 16686 } 16687 16688 frameSize = probe.aac.parseId3TagSize(bytes, byteIndex); 16689 16690 // Exit early if we don't have enough in the buffer 16691 // to emit a full packet 16692 if (frameSize > bytes.length) { 16693 endLoop = true; 16694 break; 16695 } 16696 if (timestamp === null) { 16697 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 16698 timestamp = probe.aac.parseAacTimestamp(packet); 16699 } 16700 byteIndex += frameSize; 16701 break; 16702 case 'audio': 16703 // Exit early because we don't have enough to parse 16704 // the ADTS frame header 16705 if (bytes.length - byteIndex < 7) { 16706 endLoop = true; 16707 break; 16708 } 16709 16710 frameSize = probe.aac.parseAdtsSize(bytes, byteIndex); 16711 16712 // Exit early if we don't have enough in the buffer 16713 // to emit a full packet 16714 if (frameSize > bytes.length) { 16715 endLoop = true; 16716 break; 16717 } 16718 if (sampleRate === null) { 16719 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 16720 sampleRate = probe.aac.parseSampleRate(packet); 16721 } 16722 audioCount++; 16723 byteIndex += frameSize; 16724 break; 16725 default: 16726 byteIndex++; 16727 break; 16728 } 16729 if (endLoop) { 16730 return null; 16731 } 16732 } 16733 if (sampleRate === null || timestamp === null) { 16734 return null; 16735 } 16736 16737 var audioTimescale = PES_TIMESCALE / sampleRate; 16738 16739 var result = { 16740 audio: [ 16741 { 16742 type: 'audio', 16743 dts: timestamp, 16744 pts: timestamp 16745 }, 16746 { 16747 type: 'audio', 16748 dts: timestamp + (audioCount * 1024 * audioTimescale), 16749 pts: timestamp + (audioCount * 1024 * audioTimescale) 16750 } 16751 ] 16752 }; 16753 16754 return result; 16755}; 16756 16757/** 16758 * inspects the transport stream segment data for start and end time information 16759 * of the audio and video tracks (when present) as well as the first key frame's 16760 * start time. 16761 */ 16762var inspectTs_ = function(bytes) { 16763 var pmt = { 16764 pid: null, 16765 table: null 16766 }; 16767 16768 var result = {}; 16769 16770 parsePsi_(bytes, pmt); 16771 16772 for (var pid in pmt.table) { 16773 if (pmt.table.hasOwnProperty(pid)) { 16774 var type = pmt.table[pid]; 16775 switch (type) { 16776 case StreamTypes.H264_STREAM_TYPE: 16777 result.video = []; 16778 parseVideoPes_(bytes, pmt, result); 16779 if (result.video.length === 0) { 16780 delete result.video; 16781 } 16782 break; 16783 case StreamTypes.ADTS_STREAM_TYPE: 16784 result.audio = []; 16785 parseAudioPes_(bytes, pmt, result); 16786 if (result.audio.length === 0) { 16787 delete result.audio; 16788 } 16789 break; 16790 default: 16791 break; 16792 } 16793 } 16794 } 16795 return result; 16796}; 16797 16798/** 16799 * Inspects segment byte data and returns an object with start and end timing information 16800 * 16801 * @param {Uint8Array} bytes The segment byte data 16802 * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame 16803 * timestamps for rollover. This value must be in 90khz clock. 16804 * @return {Object} Object containing start and end frame timing info of segment. 16805 */ 16806var inspect = function(bytes, baseTimestamp) { 16807 var isAacData = isLikelyAacData(bytes); 16808 16809 var result; 16810 16811 if (isAacData) { 16812 result = inspectAac_(bytes); 16813 } else { 16814 result = inspectTs_(bytes); 16815 } 16816 16817 if (!result || (!result.audio && !result.video)) { 16818 return null; 16819 } 16820 16821 adjustTimestamp_(result, baseTimestamp); 16822 16823 return result; 16824}; 16825 16826module.exports = { 16827 inspect: inspect 16828}; 16829 16830},{"../aac/probe.js":39,"../m2ts/probe.js":52,"../m2ts/stream-types.js":53,"../m2ts/timestamp-rollover-stream.js":54}],60:[function(require,module,exports){ 16831var 16832 ONE_SECOND_IN_TS = 90000, // 90kHz clock 16833 secondsToVideoTs, 16834 secondsToAudioTs, 16835 videoTsToSeconds, 16836 audioTsToSeconds, 16837 audioTsToVideoTs, 16838 videoTsToAudioTs; 16839 16840secondsToVideoTs = function(seconds) { 16841 return seconds * ONE_SECOND_IN_TS; 16842}; 16843 16844secondsToAudioTs = function(seconds, sampleRate) { 16845 return seconds * sampleRate; 16846}; 16847 16848videoTsToSeconds = function(timestamp) { 16849 return timestamp / ONE_SECOND_IN_TS; 16850}; 16851 16852audioTsToSeconds = function(timestamp, sampleRate) { 16853 return timestamp / sampleRate; 16854}; 16855 16856audioTsToVideoTs = function(timestamp, sampleRate) { 16857 return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate)); 16858}; 16859 16860videoTsToAudioTs = function(timestamp, sampleRate) { 16861 return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate); 16862}; 16863 16864module.exports = { 16865 secondsToVideoTs: secondsToVideoTs, 16866 secondsToAudioTs: secondsToAudioTs, 16867 videoTsToSeconds: videoTsToSeconds, 16868 audioTsToSeconds: audioTsToSeconds, 16869 audioTsToVideoTs: audioTsToVideoTs, 16870 videoTsToAudioTs: videoTsToAudioTs 16871}; 16872 16873},{}],61:[function(require,module,exports){ 16874'use strict'; 16875 16876var ExpGolomb; 16877 16878/** 16879 * Parser for exponential Golomb codes, a variable-bitwidth number encoding 16880 * scheme used by h264. 16881 */ 16882ExpGolomb = function(workingData) { 16883 var 16884 // the number of bytes left to examine in workingData 16885 workingBytesAvailable = workingData.byteLength, 16886 16887 // the current word being examined 16888 workingWord = 0, // :uint 16889 16890 // the number of bits left to examine in the current word 16891 workingBitsAvailable = 0; // :uint; 16892 16893 // ():uint 16894 this.length = function() { 16895 return (8 * workingBytesAvailable); 16896 }; 16897 16898 // ():uint 16899 this.bitsAvailable = function() { 16900 return (8 * workingBytesAvailable) + workingBitsAvailable; 16901 }; 16902 16903 // ():void 16904 this.loadWord = function() { 16905 var 16906 position = workingData.byteLength - workingBytesAvailable, 16907 workingBytes = new Uint8Array(4), 16908 availableBytes = Math.min(4, workingBytesAvailable); 16909 16910 if (availableBytes === 0) { 16911 throw new Error('no bytes available'); 16912 } 16913 16914 workingBytes.set(workingData.subarray(position, 16915 position + availableBytes)); 16916 workingWord = new DataView(workingBytes.buffer).getUint32(0); 16917 16918 // track the amount of workingData that has been processed 16919 workingBitsAvailable = availableBytes * 8; 16920 workingBytesAvailable -= availableBytes; 16921 }; 16922 16923 // (count:int):void 16924 this.skipBits = function(count) { 16925 var skipBytes; // :int 16926 if (workingBitsAvailable > count) { 16927 workingWord <<= count; 16928 workingBitsAvailable -= count; 16929 } else { 16930 count -= workingBitsAvailable; 16931 skipBytes = Math.floor(count / 8); 16932 16933 count -= (skipBytes * 8); 16934 workingBytesAvailable -= skipBytes; 16935 16936 this.loadWord(); 16937 16938 workingWord <<= count; 16939 workingBitsAvailable -= count; 16940 } 16941 }; 16942 16943 // (size:int):uint 16944 this.readBits = function(size) { 16945 var 16946 bits = Math.min(workingBitsAvailable, size), // :uint 16947 valu = workingWord >>> (32 - bits); // :uint 16948 // if size > 31, handle error 16949 workingBitsAvailable -= bits; 16950 if (workingBitsAvailable > 0) { 16951 workingWord <<= bits; 16952 } else if (workingBytesAvailable > 0) { 16953 this.loadWord(); 16954 } 16955 16956 bits = size - bits; 16957 if (bits > 0) { 16958 return valu << bits | this.readBits(bits); 16959 } 16960 return valu; 16961 }; 16962 16963 // ():uint 16964 this.skipLeadingZeros = function() { 16965 var leadingZeroCount; // :uint 16966 for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) { 16967 if ((workingWord & (0x80000000 >>> leadingZeroCount)) !== 0) { 16968 // the first bit of working word is 1 16969 workingWord <<= leadingZeroCount; 16970 workingBitsAvailable -= leadingZeroCount; 16971 return leadingZeroCount; 16972 } 16973 } 16974 16975 // we exhausted workingWord and still have not found a 1 16976 this.loadWord(); 16977 return leadingZeroCount + this.skipLeadingZeros(); 16978 }; 16979 16980 // ():void 16981 this.skipUnsignedExpGolomb = function() { 16982 this.skipBits(1 + this.skipLeadingZeros()); 16983 }; 16984 16985 // ():void 16986 this.skipExpGolomb = function() { 16987 this.skipBits(1 + this.skipLeadingZeros()); 16988 }; 16989 16990 // ():uint 16991 this.readUnsignedExpGolomb = function() { 16992 var clz = this.skipLeadingZeros(); // :uint 16993 return this.readBits(clz + 1) - 1; 16994 }; 16995 16996 // ():int 16997 this.readExpGolomb = function() { 16998 var valu = this.readUnsignedExpGolomb(); // :int 16999 if (0x01 & valu) { 17000 // the number is odd if the low order bit is set 17001 return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2 17002 } 17003 return -1 * (valu >>> 1); // divide by two then make it negative 17004 }; 17005 17006 // Some convenience functions 17007 // :Boolean 17008 this.readBoolean = function() { 17009 return this.readBits(1) === 1; 17010 }; 17011 17012 // ():int 17013 this.readUnsignedByte = function() { 17014 return this.readBits(8); 17015 }; 17016 17017 this.loadWord(); 17018}; 17019 17020module.exports = ExpGolomb; 17021 17022},{}],62:[function(require,module,exports){ 17023/** 17024 * mux.js 17025 * 17026 * Copyright (c) 2014 Brightcove 17027 * All rights reserved. 17028 * 17029 * A lightweight readable stream implemention that handles event dispatching. 17030 * Objects that inherit from streams should call init in their constructors. 17031 */ 17032'use strict'; 17033 17034var Stream = function() { 17035 this.init = function() { 17036 var listeners = {}; 17037 /** 17038 * Add a listener for a specified event type. 17039 * @param type {string} the event name 17040 * @param listener {function} the callback to be invoked when an event of 17041 * the specified type occurs 17042 */ 17043 this.on = function(type, listener) { 17044 if (!listeners[type]) { 17045 listeners[type] = []; 17046 } 17047 listeners[type] = listeners[type].concat(listener); 17048 }; 17049 /** 17050 * Remove a listener for a specified event type. 17051 * @param type {string} the event name 17052 * @param listener {function} a function previously registered for this 17053 * type of event through `on` 17054 */ 17055 this.off = function(type, listener) { 17056 var index; 17057 if (!listeners[type]) { 17058 return false;
17059 } 17060 index = listeners[type].indexOf(listener); 17061 listeners[type] = listeners[type].slice(); 17062 listeners[type].splice(index, 1); 17063 return index > -1; 17064 }; 17065 /** 17066 * Trigger an event of the specified type on this stream. Any additional 17067 * arguments to this function are passed as parameters to event listeners. 17068 * @param type {string} the event name 17069 */ 17070 this.trigger = function(type) { 17071 var callbacks, i, length, args; 17072 callbacks = listeners[type]; 17073 if (!callbacks) { 17074 return; 17075 } 17076 // Slicing the arguments on every invocation of this method 17077 // can add a significant amount of overhead. Avoid the 17078 // intermediate object creation for the common case of a 17079 // single callback argument 17080 if (arguments.length === 2) { 17081 length = callbacks.length; 17082 for (i = 0; i < length; ++i) { 17083 callbacks[i].call(this, arguments[1]); 17084 } 17085 } else { 17086 args = []; 17087 i = arguments.length; 17088 for (i = 1; i < arguments.length; ++i) { 17089 args.push(arguments[i]); 17090 } 17091 length = callbacks.length; 17092 for (i = 0; i < length; ++i) { 17093 callbacks[i].apply(this, args); 17094 } 17095 } 17096 }; 17097 /** 17098 * Destroys the stream and cleans up. 17099 */ 17100 this.dispose = function() { 17101 listeners = {}; 17102 }; 17103 }; 17104}; 17105 17106/** 17107 * Forwards all `data` events on this stream to the destination stream. The 17108 * destination stream should provide a method `push` to receive the data 17109 * events as they arrive. 17110 * @param destination {stream} the stream that will receive all `data` events 17111 * @param autoFlush {boolean} if false, we will not call `flush` on the destination 17112 * when the current stream emits a 'done' event 17113 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 17114 */ 17115Stream.prototype.pipe = function(destination) { 17116 this.on('data', function(data) { 17117 destination.push(data); 17118 }); 17119 17120 this.on('done', function(flushSource) { 17121 destination.flush(flushSource); 17122 }); 17123 17124 return destination; 17125}; 17126 17127// Default stream functions that are expected to be overridden to perform 17128// actual work. These are provided by the prototype as a sort of no-op 17129// implementation so that we don't have to check for their existence in the 17130// `pipe` function above. 17131Stream.prototype.push = function(data) { 17132 this.trigger('data', data); 17133}; 17134 17135Stream.prototype.flush = function(flushSource) { 17136 this.trigger('done', flushSource); 17137}; 17138 17139module.exports = Stream; 17140 17141},{}],63:[function(require,module,exports){ 17142/* jshint ignore:start */ 17143(function(root) { 17144/* jshint ignore:end */ 17145 var URLToolkit = { 17146 // build an absolute URL from a relative one using the provided baseURL 17147 // if relativeURL is an absolute URL it will be returned as is. 17148 buildAbsoluteURL: function(baseURL, relativeURL) { 17149 // remove any remaining space and CRLF 17150 relativeURL = relativeURL.trim(); 17151 if (/^[a-z]+:/i.test(relativeURL)) { 17152 // complete url, not relative 17153 return relativeURL; 17154 } 17155 17156 var relativeURLQuery = null; 17157 var relativeURLHash = null; 17158 17159 var relativeURLHashSplit = /^([^#]*)(.*)$/.exec(relativeURL); 17160 if (relativeURLHashSplit) { 17161 relativeURLHash = relativeURLHashSplit[2]; 17162 relativeURL = relativeURLHashSplit[1]; 17163 } 17164 var relativeURLQuerySplit = /^([^\?]*)(.*)$/.exec(relativeURL); 17165 if (relativeURLQuerySplit) { 17166 relativeURLQuery = relativeURLQuerySplit[2]; 17167 relativeURL = relativeURLQuerySplit[1]; 17168 } 17169 17170 var baseURLHashSplit = /^([^#]*)(.*)$/.exec(baseURL); 17171 if (baseURLHashSplit) { 17172 baseURL = baseURLHashSplit[1]; 17173 } 17174 var baseURLQuerySplit = /^([^\?]*)(.*)$/.exec(baseURL); 17175 if (baseURLQuerySplit) { 17176 baseURL = baseURLQuerySplit[1]; 17177 } 17178 17179 var baseURLDomainSplit = /^(([a-z]+:)?\/\/[^:\/]+(:[0-9]+)?)?(\/?.*)$/i.exec(baseURL); 17180 if (!baseURLDomainSplit) { 17181 throw new Error('Error trying to parse base URL.'); 17182 } 17183 17184 // e.g. 'http:', 'https:', '' 17185 var baseURLProtocol = baseURLDomainSplit[2] || ''; 17186 // e.g. 'http://example.com', '//example.com', '' 17187 var baseURLProtocolDomain = baseURLDomainSplit[1] || ''; 17188 // e.g. '/a/b/c/playlist.m3u8', 'a/b/c/playlist.m3u8' 17189 var baseURLPath = baseURLDomainSplit[4]; 17190 if (baseURLPath.indexOf('/') !== 0 && baseURLProtocolDomain !== '') { 17191 // this handles a base url of http://example.com (missing last slash) 17192 baseURLPath = '/'+baseURLPath; 17193 } 17194 17195 var builtURL = null; 17196 if (/^\/\//.test(relativeURL)) { 17197 // relative url starts wth '//' so copy protocol (which may be '' if baseUrl didn't provide one) 17198 builtURL = baseURLProtocol+'//'+URLToolkit.buildAbsolutePath('', relativeURL.substring(2)); 17199 } 17200 else if (/^\//.test(relativeURL)) { 17201 // relative url starts with '/' so start from root of domain 17202 builtURL = baseURLProtocolDomain+'/'+URLToolkit.buildAbsolutePath('', relativeURL.substring(1)); 17203 } 17204 else { 17205 builtURL = URLToolkit.buildAbsolutePath(baseURLProtocolDomain+baseURLPath, relativeURL); 17206 } 17207 17208 // put the query and hash parts back 17209 if (relativeURLQuery) { 17210 builtURL += relativeURLQuery; 17211 } 17212 if (relativeURLHash) { 17213 builtURL += relativeURLHash; 17214 } 17215 return builtURL; 17216 }, 17217 17218 // build an absolute path using the provided basePath 17219 // adapted from https://developer.mozilla.org/en-US/docs/Web/API/document/cookie#Using_relative_URLs_in_the_path_parameter 17220 // this does not handle the case where relativePath is "/" or "//". These
17220cases should be handled outside this. 17221 buildAbsolutePath: function(basePath, relativePath) { 17222 var sRelPath = relativePath; 17223 var nUpLn, sDir = '', sPath = basePath.replace(/[^\/]*$/, sRelPath.replace(/(\/|^)(?:\.?\/+)+/g, '$1')); 17224 for (var nEnd, nStart = 0; nEnd = sPath.indexOf('/../', nStart), nEnd > -1; nStart = nEnd + nUpLn) { 17225 nUpLn = /^\/(?:\.\.\/)*/.exec(sPath.slice(nEnd))[0].length; 17226 sDir = (sDir + sPath.substring(nStart, nEnd)).replace(new RegExp('(?:\\\/+[^\\\/]*){0,' + ((nUpLn - 1) / 3) + '}$'), '/'); 17227 } 17228 return sDir + sPath.substr(nStart); 17229 } 17230 }; 17231 17232/* jshint ignore:start */ 17233 if(typeof exports === 'object' && typeof module === 'object') 17234 module.exports = URLToolkit; 17235 else if(typeof define === 'function' && define.amd) 17236 define([], function() { return URLToolkit; }); 17237 else if(typeof exports === 'object') 17238 exports["URLToolkit"] = URLToolkit; 17239 else 17240 root["URLToolkit"] = URLToolkit; 17241})(this); 17242/* jshint ignore:end */ 17243 17244},{}],64:[function(require,module,exports){ 17245(function (global){ 17246/** 17247 * @file add-text-track-data.js 17248 */ 17249'use strict'; 17250 17251Object.defineProperty(exports, '__esModule', { 17252 value: true 17253}); 17254 17255function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 17256 17257var _globalWindow = require('global/window'); 17258 17259var _globalWindow2 = _interopRequireDefault(_globalWindow); 17260 17261var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 17262 17263var _videoJs2 = _interopRequireDefault(_videoJs); 17264 17265/** 17266 * Define properties on a cue for backwards compatability, 17267 * but warn the user that the way that they are using it 17268 * is depricated and will be removed at a later date. 17269 * 17270 * @param {Cue} cue the cue to add the properties on 17271 * @private 17272 */ 17273var deprecateOldCue = function deprecateOldCue(cue) { 17274 Object.defineProperties(cue.frame, { 17275 id: { 17276 get: function get() { 17277 _videoJs2['default'].log.warn('cue.frame.id is deprecated. Use cue.value.key instead.'); 17278 return cue.value.key; 17279 } 17280 }, 17281 value: { 17282 get: function get() { 17283 _videoJs2['default'].log.warn('cue.frame.value is deprecated. Use cue.value.data instead.'); 17284 return cue.value.data; 17285 } 17286 }, 17287 privateData: { 17288 get: function get() { 17289 _videoJs2['default'].log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.'); 17290 return cue.value.data; 17291 } 17292 } 17293 }); 17294}; 17295 17296var durationOfVideo = function durationOfVideo(duration) { 17297 var dur = undefined; 17298 17299 if (isNaN(duration) || Math.abs(duration) === Infinity) { 17300 dur = Number.MAX_VALUE; 17301 } else { 17302 dur = duration; 17303 } 17304 return dur; 17305}; 17306/** 17307 * Add text track data to a source handler given the captions and 17308 * metadata from the buffer. 17309 * 17310 * @param {Object} sourceHandler the flash or virtual source buffer 17311 * @param {Array} captionArray an array of caption data 17312 * @param {Array} metadataArray an array of meta data 17313 * @private 17314 */ 17315var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) { 17316 var Cue = _globalWindow2['default'].WebKitDataCue || _globalWindow2['default'].VTTCue; 17317 17318 if (captionArray) { 17319 captionArray.forEach(function (caption) { 17320 var track = caption.stream; 17321 17322 this.inbandTextTracks_[track].addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text)); 17323 }, sourceHandler); 17324 } 17325 17326 if (metadataArray) { 17327 (function () { 17328 var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration); 17329 17330 metadataArray.forEach(function (metadata) { 17331 var time = metadata.cueTime + this.timestampOffset; 17332 17333 metadata.frames.forEach(function (frame) { 17334 var cue = new Cue(time, time, frame.value || frame.url || frame.data || ''); 17335 17336 cue.frame = frame; 17337 cue.value = frame; 17338 deprecateOldCue(cue); 17339 17340 this.metadataTrack_.addCue(cue); 17341 }, this); 17342 }, sourceHandler); 17343 17344 // Updating the metadeta cues so that 17345 // the endTime of each cue is the startTime of the next cue 17346 // the endTime of last cue is the duration of the video 17347 if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) { 17348 (function () { 17349 var cues = sourceHandler.metadataTrack_.cues; 17350 var cuesArray = []; 17351 17352 // Create a copy of the TextTrackCueList... 17353 // ...disregarding cues with a falsey value 17354 for (var i = 0; i < cues.length; i++) { 17355 if (cues[i]) { 17356 cuesArray.push(cues[i]); 17357 } 17358 } 17359 17360 // Group cues by their startTime value 17361 var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) { 17362 var timeSlot = obj[cue.startTime] || []; 17363 17364 timeSlot.push(cue); 17365 obj[cue.startTime] = timeSlot; 17366 17367 return obj; 17368 }, {}); 17369 17370 // Sort startTimes by ascending order 17371 var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) { 17372 return Number(a) - Number(b); 17373 }); 17374 17375 // Map each cue group's endTime to the next group's startTime
17376 sortedStartTimes.forEach(function (startTime, idx) { 17377 var cueGroup = cuesGroupedByStartTime[startTime]; 17378 var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; 17379 17380 // Map each cue's endTime the next group's startTime 17381 cueGroup.forEach(function (cue) { 17382 cue.endTime = nextTime; 17383 }); 17384 }); 17385 })(); 17386 } 17387 })(); 17388 } 17389}; 17390 17391exports['default'] = { 17392 addTextTrackData: addTextTrackData, 17393 durationOfVideo: durationOfVideo 17394}; 17395module.exports = exports['default']; 17396}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 17397},{"global/window":32}],65:[function(require,module,exports){ 17398/** 17399 * @file codec-utils.js 17400 */ 17401 17402/** 17403 * Check if a codec string refers to an audio codec. 17404 * 17405 * @param {String} codec codec string to check 17406 * @return {Boolean} if this is an audio codec 17407 * @private 17408 */ 17409'use strict'; 17410 17411Object.defineProperty(exports, '__esModule', { 17412 value: true 17413}); 17414var isAudioCodec = function isAudioCodec(codec) { 17415 return (/mp4a\.\d+.\d+/i.test(codec) 17416 ); 17417}; 17418 17419/** 17420 * Check if a codec string refers to a video codec. 17421 * 17422 * @param {String} codec codec string to check 17423 * @return {Boolean} if this is a video codec 17424 * @private 17425 */ 17426var isVideoCodec = function isVideoCodec(codec) { 17427 return (/avc1\.[\da-f]+/i.test(codec) 17428 ); 17429}; 17430 17431/** 17432 * Parse a content type header into a type and parameters 17433 * object 17434 * 17435 * @param {String} type the content type header 17436 * @return {Object} the parsed content-type 17437 * @private 17438 */ 17439var parseContentType = function parseContentType(type) { 17440 var object = { type: '', parameters: {} }; 17441 var parameters = type.trim().split(';'); 17442 17443 // first parameter should always be content-type 17444 object.type = parameters.shift().trim(); 17445 parameters.forEach(function (parameter) { 17446 var pair = parameter.trim().split('='); 17447 17448 if (pair.length > 1) { 17449 var _name = pair[0].replace(/"/g, '').trim(); 17450 var value = pair[1].replace(/"/g, '').trim(); 17451 17452 object.parameters[_name] = value; 17453 } 17454 }); 17455 17456 return object; 17457}; 17458 17459/** 17460 * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard 17461 * `avc1.<hhhhhh>` 17462 * 17463 * @param {Array} codecs an array of codec strings to fix 17464 * @return {Array} the translated codec array 17465 * @private 17466 */ 17467var translateLegacyCodecs = function translateLegacyCodecs(codecs) { 17468 return codecs.map(function (codec) { 17469 return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) { 17470 var profileHex = ('00' + Number(profile).toString(16)).slice(-2); 17471 var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2); 17472 17473 return 'avc1.' + profileHex + '00' + avcLevelHex; 17474 }); 17475 }); 17476}; 17477 17478exports['default'] = { 17479 isAudioCodec: isAudioCodec, 17480 parseContentType: parseContentType, 17481 isVideoCodec: isVideoCodec, 17482 translateLegacyCodecs: translateLegacyCodecs 17483}; 17484module.exports = exports['default']; 17485},{}],66:[function(require,module,exports){ 17486/** 17487 * @file create-text-tracks-if-necessary.js 17488 */ 17489 17490/** 17491 * Create text tracks on video.js if they exist on a segment. 17492 * 17493 * @param {Object} sourceBuffer the VSB or FSB 17494 * @param {Object} mediaSource the HTML or Flash media source 17495 * @param {Object} segment the segment that may contain the text track 17496 * @private 17497 */ 17498'use strict'; 17499 17500Object.defineProperty(exports, '__esModule', { 17501 value: true 17502}); 17503var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) { 17504 var player = mediaSource.player_; 17505 17506 // create an in-band caption track if one is present in the segment 17507 if (segment.captions && segment.captions.length) { 17508 if (!sourceBuffer.inbandTextTracks_) { 17509 sourceBuffer.inbandTextTracks_ = {}; 17510 } 17511 17512 for (var trackId in segment.captionStreams) { 17513 if (!sourceBuffer.inbandTextTracks_[trackId]) { 17514 player.tech_.trigger({ type: 'usage', name: 'hls-608' }); 17515 var track = player.textTracks().getTrackById(trackId); 17516 17517 if (track) { 17518 // Resuse an existing track with a CC# id because this was 17519 // very likely created by videojs-contrib-hls from information 17520 // in the m3u8 for us to use 17521 sourceBuffer.inbandTextTracks_[trackId] = track; 17522 } else { 17523 // Otherwise, create a track with the default `CC#` label and 17524 // without a language 17525 sourceBuffer.inbandTextTracks_[trackId] = player.addRemoteTextTrack({ 17526 kind: 'captions', 17527 id: trackId, 17528 label: trackId 17529 }, false).track; 17530 } 17531 } 17532 } 17533 } 17534 17535 if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) { 17536 sourceBuffer.metadataTrack_ = player.addRemoteTextTrack({ 17537 kind: 'metadata', 17538 label: 'Timed Metadata' 17539 }, false).track; 17540 sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType; 17541 } 17542}; 17543 17544exports['default'] = createTextTracksIfNecessary; 17545module.exports = exports['default']; 17546},{}],67:[function(require,module,exports){ 17547/** 17548 * @file flash-constants.js 17549 */ 17550/** 17551 * The maximum size in bytes for append operations to the video.js 17552 * SWF. Calling through to Flash blocks and can be expensive so 17553 * we chunk data and pass through 4KB at a time, yielding to the 17554 * browser between chunks. This gives a theoretical maximum rate of 17555 * 1MB/s into Flash. Any higher and we begin to drop frames and UI 17556 * responsiveness suffers. 17557 * 17558 * @private 17559 */ 17560"use strict"; 17561 17562Object.defineProperty(exports, "__esModule", { 17563 value: true 17564}); 17565var flashConstants = { 17566 // times in milliseconds 17567 TIME_BETWEEN_CHUNKS: 1, 17568 BYTES_PER_CHUNK: 1024 * 32 17569}; 17570 17571exports["default"] = flashConstants; 17572module.exports = exports["default"]; 17573},{}],68:[function(require,module,exports){ 17574(function (global){ 17575/** 17576 * @file flash-media-source.js 17577 */ 17578'use strict'; 17579 17580Object.defineProperty(exports, '__esModule', { 17581 value: true 17582}); 17583 17584var _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; }; })(); 17585 17586var _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); } } }; 17587 17588function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 17589 17590function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 17591 17592function _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, { c
17592onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 17593 17594var _globalDocument = require('global/document'); 17595 17596var _globalDocument2 = _interopRequireDefault(_globalDocument); 17597 17598var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 17599 17600var _videoJs2 = _interopRequireDefault(_videoJs); 17601 17602var _flashSourceBuffer = require('./flash-source-buffer'); 17603 17604var _flashSourceBuffer2 = _interopRequireDefault(_flashSourceBuffer); 17605 17606var _flashConstants = require('./flash-constants'); 17607 17608var _flashConstants2 = _interopRequireDefault(_flashConstants); 17609 17610var _codecUtils = require('./codec-utils'); 17611 17612/** 17613 * A flash implmentation of HTML MediaSources and a polyfill 17614 * for browsers that don't support native or HTML MediaSources.. 17615 * 17616 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource 17617 * @class FlashMediaSource 17618 * @extends videojs.EventTarget 17619 */ 17620 17621var FlashMediaSource = (function (_videojs$EventTarget) { 17622 _inherits(FlashMediaSource, _videojs$EventTarget); 17623 17624 function FlashMediaSource() { 17625 var _this = this; 17626 17627 _classCallCheck(this, FlashMediaSource); 17628 17629 _get(Object.getPrototypeOf(FlashMediaSource.prototype), 'constructor', this).call(this); 17630 this.sourceBuffers = []; 17631 this.readyState = 'closed'; 17632 17633 this.on(['sourceopen', 'webkitsourceopen'], function (event) { 17634 // find the swf where we will push media data 17635 _this.swfObj = _globalDocument2['default'].getElementById(event.swfId); 17636 _this.player_ = (0, _videoJs2['default'])(_this.swfObj.parentNode); 17637 _this.tech_ = _this.swfObj.tech; 17638 _this.readyState = 'open'; 17639 17640 _this.tech_.on('seeking', function () { 17641 var i = _this.sourceBuffers.length; 17642 17643 while (i--) { 17644 _this.sourceBuffers[i].abort(); 17645 } 17646 }); 17647 17648 // trigger load events 17649 if (_this.swfObj) { 17650 _this.swfObj.vjs_load(); 17651 } 17652 }); 17653 } 17654 17655 /** 17656 * Set or return the presentation duration. 17657 * 17658 * @param {Double} value the duration of the media in seconds 17659 * @param {Double} the current presentation duration 17660 * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration 17661 */ 17662 17663 /** 17664 * We have this function so that the html and flash interfaces 17665 * are the same. 17666 * 17667 * @private 17668 */ 17669 17670 _createClass(FlashMediaSource, [{ 17671 key: 'addSeekableRange_', 17672 value: function addSeekableRange_() {} 17673 // intentional no-op 17674 17675 /** 17676 * Create a new flash source buffer and add it to our flash media source. 17677 * 17678 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer 17679 * @param {String} type the content-type of the source 17680 * @return {Object} the flash source buffer 17681 */ 17682 17683 }, { 17684 key: 'addSourceBuffer', 17685 value: function addSourceBuffer(type) { 17686 var parsedType = (0, _codecUtils.parseContentType)(type); 17687 var sourceBuffer = undefined; 17688 17689 // if this is an FLV type, we'll push data to flash 17690 if (parsedType.type === 'video/mp2t' || parsedType.type === 'audio/mp2t') { 17691 // Flash source buffers 17692 sourceBuffer = new _flashSourceBuffer2['default'](this); 17693 } else { 17694 throw new Error('NotSupportedError (Video.js)'); 17695 } 17696 17697 this.sourceBuffers.push(sourceBuffer); 17698 return sourceBuffer; 17699 } 17700 17701 /** 17702 * Signals the end of the stream. 17703 * 17704 * @link https://w3c.github.io/media-source/#widl-MediaSource-endOfStream-void-EndOfStreamError-error 17705 * @param {String=} error Signals that a playback error 17706 * has occurred. If specified, it must be either "network" or 17707 * "decode". 17708 */ 17709 }, { 17710 key: 'endOfStream', 17711 value: function endOfStream(error) { 17712 if (error === 'network') { 17713 // MEDIA_ERR_NETWORK 17714 this.tech_.error(2); 17715 } else if (error === 'decode') { 17716 // MEDIA_ERR_DECODE 17717 this.tech_.error(3); 17718 } 17719 if (this.readyState !== 'ended') { 17720 this.readyState = 'ended'; 17721 this.swfObj.vjs_endOfStream(); 17722 } 17723 } 17724 }]); 17725 17726 return FlashMediaSource; 17727})(_videoJs2['default'].EventTarget); 17728 17729exports['default'] = FlashMediaSource; 17730try { 17731 Object.defineProperty(FlashMediaSource.prototype, 'duration', { 17732 /** 17733 * Return the presentation duration. 17734 * 17735 * @return {Double} the duration of the media in seconds 17736 * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration 17737 */ 17738 get: function get() { 17739 if (!this.swfObj) { 17740 return NaN; 17741 } 17742 // get the current duration from the SWF 17743 return this.swfObj.vjs_getProperty('duration'); 17744 }, 17745 /** 17746 * Set the presentation duration. 17747 * 17748 * @param {Double} value the duration of the media in seconds 17749 * @return {Double} the duration of the media in seconds 17750 * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration 17751 */ 17752 set: function set(value) { 17753 var i = undefined; 17754 var oldDuration = this.swfObj.vjs_getProperty('duration'); 17755 17756 this.swfObj.vjs_setProperty('duration', value); 17757 17758 if (value < oldDuration) { 17759 // In MSE, this triggers the range removal algorithm which causes 17760 // an update to occur 17761 for (i = 0; i < this.sourceBuffers.length; i++) { 17762 this.sourceBuffers[i].remove(value, oldDuration); 17763 } 17764 } 17765 17766 return value; 17767 } 17768 }); 17769} catch (e) { 17770 // IE8 throws if defineProperty is called on a non-DOM node. We 17771 // don't support IE8 but we shouldn't throw an error if loaded 17772 // there. 17773 FlashMediaSource.prototype.duration = NaN; 17774} 17775 17776for (var property in _flashConstants2['default']) { 17777 FlashMediaSource[property] = _flashConstants2['default'][property]; 17778} 17779module.exports = exports['default']; 17780}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 17781},{"./codec-utils":65,"./flash-constants":67,"./flash-source-buffer":69,"global/document":31}],69:[function(require,module,exports){ 17782(function (global){ 17783/** 17784 * @file flash-source-buffer.js 17785 */ 17786'use strict'; 17787 17788Object.defineProperty(exports, '__esModule', { 17789 value: true 17790}); 17791 17792var _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; }; })(); 17793 17794var _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); } } }; 17795 17796function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 17797 17798function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 17799 17800function _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, { c
17800onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 17801 17802var _globalWindow = require('global/window'); 17803 17804var _globalWindow2 = _interopRequireDefault(_globalWindow); 17805 17806var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 17807 17808var _videoJs2 = _interopRequireDefault(_videoJs); 17809 17810var _muxJsLibFlv = require('mux.js/lib/flv'); 17811 17812var _muxJsLibFlv2 = _interopRequireDefault(_muxJsLibFlv); 17813 17814var _removeCuesFromTrack = require('./remove-cues-from-track'); 17815 17816var _removeCuesFromTrack2 = _interopRequireDefault(_removeCuesFromTrack); 17817 17818var _createTextTracksIfNecessary = require('./create-text-tracks-if-necessary'); 17819 17820var _createTextTracksIfNecessary2 = _interopRequireDefault(_createTextTracksIfNecessary); 17821 17822var _addTextTrackData = require('./add-text-track-data'); 17823 17824var _flashTransmuxerWorker = require('./flash-transmuxer-worker'); 17825 17826var _flashTransmuxerWorker2 = _interopRequireDefault(_flashTransmuxerWorker); 17827 17828var _webworkify = require('webworkify'); 17829 17830var _webworkify2 = _interopRequireDefault(_webworkify); 17831 17832var _flashConstants = require('./flash-constants'); 17833 17834var _flashConstants2 = _interopRequireDefault(_flashConstants); 17835 17836/** 17837 * A wrapper around the setTimeout function that uses 17838 * the flash constant time between ticks value. 17839 * 17840 * @param {Function} func the function callback to run 17841 * @private 17842 */ 17843var scheduleTick = function scheduleTick(func) { 17844 // Chrome doesn't invoke requestAnimationFrame callbacks 17845 // in background tabs, so use setTimeout. 17846 _globalWindow2['default'].setTimeout(func, _flashConstants2['default'].TIME_BETWEEN_CHUNKS); 17847}; 17848 17849/** 17850 * Generates a random string of max length 6 17851 * 17852 * @return {String} the randomly generated string 17853 * @function generateRandomString 17854 * @private 17855 */ 17856var generateRandomString = function generateRandomString() { 17857 return Math.random().toString(36).slice(2, 8); 17858}; 17859 17860/** 17861 * Round a number to a specified number of places much like 17862 * toFixed but return a number instead of a string representation. 17863 * 17864 * @param {Number} num A number 17865 * @param {Number} places The number of decimal places which to 17866 * round 17867 * @private 17868 */ 17869var toDecimalPlaces = function toDecimalPlaces(num, places) { 17870 if (typeof places !== 'number' || places < 0) { 17871 places = 0; 17872 } 17873 17874 var scale = Math.pow(10, places); 17875 17876 return Math.round(num * scale) / scale; 17877}; 17878 17879/** 17880 * A SourceBuffer implementation for Flash rather than HTML. 17881 * 17882 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource 17883 * @param {Object} mediaSource the flash media source 17884 * @class FlashSourceBuffer 17885 * @extends videojs.EventTarget 17886 */ 17887 17888var FlashSourceBuffer = (function (_videojs$EventTarget) { 17889 _inherits(FlashSourceBuffer, _videojs$EventTarget); 17890 17891 function FlashSourceBuffer(mediaSource) { 17892 var _this = this; 17893 17894 _classCallCheck(this, FlashSourceBuffer); 17895 17896 _get(Object.getPrototypeOf(FlashSourceBuffer.prototype), 'constructor', this).call(this); 17897 var encodedHeader = undefined; 17898 17899 // Start off using the globally defined value but refine 17900 // as we append data into flash 17901 this.chunkSize_ = _flashConstants2['default'].BYTES_PER_CHUNK; 17902 17903 // byte arrays queued to be appended 17904 this.buffer_ = []; 17905 17906 // the total number of queued bytes 17907 this.bufferSize_ = 0; 17908 17909 // to be able to determine the correct position to seek to, we 17910 // need to retain information about the mapping between the 17911 // media timeline and PTS values 17912 this.basePtsOffset_ = NaN; 17913 17914 this.mediaSource_ = mediaSource; 17915 17916 this.audioBufferEnd_ = NaN; 17917 this.videoBufferEnd_ = NaN; 17918 17919 // indicates whether the asynchronous continuation of an operation 17920 // is still being processed 17921 // see https://w3c.github.io/media-source/#widl-SourceBuffer-updating 17922 this.updating = false; 17923 this.timestampOffset_ = 0; 17924 17925 encodedHeader = _globalWindow2['default'].btoa(String.fromCharCode.apply(null, Array.prototype.slice.call(_muxJsLibFlv2['default'].getFlvHeader()))); 17926 17927 // create function names with added randomness for the global callbacks flash will use 17928 // to get data from javascript into the swf. Random strings are added as a safety 17929 // measure for pages with multiple players since these functions will be global 17930 // instead of per instance. When making a call to the swf, the browser generates a 17931 // try catch code snippet, but just takes the function name and writes out an unquoted 17932 // call to that function. If the player id has any special characters, this will result 17933 // in an error, so safePlayerId replaces all special characters to '_' 17934 var safePlayerId = this.mediaSource_.player_.id().replace(/[^a-zA-Z0-9]/g, '_'); 17935 17936 this.flashEncodedHeaderName_ = 'vjs_flashEncodedHeader_' + s
17936afePlayerId + generateRandomString(); 17937 this.flashEncodedDataName_ = 'vjs_flashEncodedData_' + safePlayerId + generateRandomString(); 17938 17939 _globalWindow2['default'][this.flashEncodedHeaderName_] = function () { 17940 delete _globalWindow2['default'][_this.flashEncodedHeaderName_]; 17941 return encodedHeader; 17942 }; 17943 17944 this.mediaSource_.swfObj.vjs_appendChunkReady(this.flashEncodedHeaderName_); 17945 17946 this.transmuxer_ = (0, _webworkify2['default'])(_flashTransmuxerWorker2['default']); 17947 this.transmuxer_.postMessage({ action: 'init', options: {} }); 17948 this.transmuxer_.onmessage = function (event) { 17949 if (event.data.action === 'data') { 17950 _this.receiveBuffer_(event.data.segment); 17951 } 17952 }; 17953 17954 this.one('updateend', function () { 17955 _this.mediaSource_.tech_.trigger('loadedmetadata'); 17956 }); 17957 17958 Object.defineProperty(this, 'timestampOffset', { 17959 get: function get() { 17960 return this.timestampOffset_; 17961 }, 17962 set: function set(val) { 17963 if (typeof val === 'number' && val >= 0) { 17964 this.timestampOffset_ = val; 17965 // We have to tell flash to expect a discontinuity 17966 this.mediaSource_.swfObj.vjs_discontinuity(); 17967 // the media <-> PTS mapping must be re-established after 17968 // the discontinuity 17969 this.basePtsOffset_ = NaN; 17970 this.audioBufferEnd_ = NaN; 17971 this.videoBufferEnd_ = NaN; 17972 17973 this.transmuxer_.postMessage({ action: 'reset' }); 17974 } 17975 } 17976 }); 17977 17978 Object.defineProperty(this, 'buffered', { 17979 get: function get() { 17980 if (!this.mediaSource_ || !this.mediaSource_.swfObj || !('vjs_getProperty' in this.mediaSource_.swfObj)) { 17981 return _videoJs2['default'].createTimeRange(); 17982 } 17983 17984 var buffered = this.mediaSource_.swfObj.vjs_getProperty('buffered'); 17985 17986 if (buffered && buffered.length) { 17987 buffered[0][0] = toDecimalPlaces(buffered[0][0], 3); 17988 buffered[0][1] = toDecimalPlaces(buffered[0][1], 3); 17989 } 17990 return _videoJs2['default'].createTimeRanges(buffered); 17991 } 17992 }); 17993 17994 // On a seek we remove all text track data since flash has no concept 17995 // of a buffered-range and everything else is reset on seek 17996 this.mediaSource_.player_.on('seeked', function () { 17997 (0, _removeCuesFromTrack2['default'])(0, Infinity, _this.metadataTrack_); 17998 if (_this.inbandTextTracks_) { 17999 for (var track in _this.inbandTextTracks_) { 18000 (0, _removeCuesFromTrack2['default'])(0, Infinity, _this.inbandTextTracks_[track]); 18001 } 18002 } 18003 }); 18004 18005 var onHlsReset = this.onHlsReset_.bind(this); 18006 18007 this.mediaSource_.player_.tech_.on('hls-reset', onHlsReset); 18008 18009 this.mediaSource_.player_.tech_.hls.on('dispose', function () { 18010 _this.transmuxer_.terminate(); 18011 _this.mediaSource_.player_.tech_.off('hls-reset', onHlsReset); 18012 }); 18013 } 18014 18015 /** 18016 * Append bytes to the sourcebuffers buffer, in this case we 18017 * have to append it to swf object. 18018 * 18019 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer 18020 * @param {Array} bytes 18021 */ 18022 18023 _createClass(FlashSourceBuffer, [{ 18024 key: 'appendBuffer', 18025 value: function appendBuffer(bytes) { 18026 var error = undefined; 18027 18028 if (this.updating) { 18029 error = new Error('SourceBuffer.append() cannot be called ' + 'while an update is in progress'); 18030 error.name = 'InvalidStateError'; 18031 error.code = 11; 18032 throw error; 18033 } 18034 this.updating = true; 18035 this.mediaSource_.readyState = 'open'; 18036 this.trigger({ type: 'update' }); 18037 18038 this.transmuxer_.postMessage({ 18039 action: 'push', 18040 data: bytes.buffer, 18041 byteOffset: bytes.byteOffset, 18042 byteLength: bytes.byteLength 18043 }, [bytes.buffer]); 18044 this.transmuxer_.postMessage({ action: 'flush' }); 18045 } 18046 18047 /** 18048 * Reset the parser and remove any data queued to be sent to the SWF. 18049 * 18050 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort 18051 */ 18052 }, { 18053 key: 'abort', 18054 value: function abort() { 18055 this.buffer_ = []; 18056 this.bufferSize_ = 0; 18057 this.mediaSource_.swfObj.vjs_abort(); 18058 18059 // report any outstanding updates have ended 18060 if (this.updating) { 18061 this.updating = false;
18062 this.trigger({ type: 'updateend' }); 18063 } 18064 } 18065 18066 /** 18067 * Flash cannot remove ranges already buffered in the NetStream 18068 * but seeking clears the buffer entirely. For most purposes, 18069 * having this operation act as a no-op is acceptable. 18070 * 18071 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove 18072 * @param {Double} start start of the section to remove 18073 * @param {Double} end end of the section to remove 18074 */ 18075 }, { 18076 key: 'remove', 18077 value: function remove(start, end) { 18078 (0, _removeCuesFromTrack2['default'])(start, end, this.metadataTrack_); 18079 if (this.inbandTextTracks_) { 18080 for (var track in this.inbandTextTracks_) { 18081 (0, _removeCuesFromTrack2['default'])(start, end, this.inbandTextTracks_[track]); 18082 } 18083 } 18084 this.trigger({ type: 'update' }); 18085 this.trigger({ type: 'updateend' }); 18086 } 18087 18088 /** 18089 * Receive a buffer from the flv. 18090 * 18091 * @param {Object} segment 18092 * @private 18093 */ 18094 }, { 18095 key: 'receiveBuffer_', 18096 value: function receiveBuffer_(segment) { 18097 var _this2 = this; 18098 18099 // create an in-band caption track if one is present in the segment 18100 (0, _createTextTracksIfNecessary2['default'])(this, this.mediaSource_, segment); 18101 (0, _addTextTrackData.addTextTrackData)(this, segment.captions, segment.metadata); 18102 18103 // Do this asynchronously since convertTagsToData_ can be time consuming 18104 scheduleTick(function () { 18105 var flvBytes = _this2.convertTagsToData_(segment); 18106 18107 if (_this2.buffer_.length === 0) { 18108 scheduleTick(_this2.processBuffer_.bind(_this2)); 18109 } 18110 18111 if (flvBytes) { 18112 _this2.buffer_.push(flvBytes); 18113 _this2.bufferSize_ += flvBytes.byteLength; 18114 } 18115 }); 18116 } 18117 18118 /** 18119 * Append a portion of the current buffer to the SWF. 18120 * 18121 * @private 18122 */ 18123 }, { 18124 key: 'processBuffer_', 18125 value: function processBuffer_() { 18126 var _this3 = this; 18127 18128 var chunkSize = _flashConstants2['default'].BYTES_PER_CHUNK; 18129 18130 if (!this.buffer_.length) { 18131 if (this.updating !== false) { 18132 this.updating = false; 18133 this.trigger({ type: 'updateend' }); 18134 } 18135 // do nothing if the buffer is empty 18136 return; 18137 } 18138 18139 // concatenate appends up to the max append size 18140 var chunk = this.buffer_[0].subarray(0, chunkSize); 18141 18142 // requeue any bytes that won't make it this round 18143 if (chunk.byteLength < chunkSize || this.buffer_[0].byteLength === chunkSize) { 18144 this.buffer_.shift(); 18145 } else { 18146 this.buffer_[0] = this.buffer_[0].subarray(chunkSize); 18147 } 18148 18149 this.bufferSize_ -= chunk.byteLength; 18150 18151 // base64 encode the bytes 18152 var binary = []; 18153 var length = chunk.byteLength; 18154 18155 for (var i = 0; i < length; i++) { 18156 binary.push(String.fromCharCode(chunk[i])); 18157 } 18158 var b64str = _globalWindow2['default'].btoa(binary.join('')); 18159 18160 _globalWindow2['default'][this.flashEncodedDataName_] = function () { 18161 // schedule another processBuffer to process any left over data or to 18162 // trigger updateend 18163 scheduleTick(_this3.processBuffer_.bind(_this3)); 18164 delete _globalWindow2['default'][_this3.flashEncodedDataName_]; 18165 return b64str; 18166 }; 18167 18168 // Notify the swf that segment data is ready to be appended 18169 this.mediaSource_.swfObj.vjs_appendChunkReady(this.flashEncodedDataName_); 18170 } 18171 18172 /** 18173 * Turns an array of flv tags into a Uint8Array representing the 18174 * flv data. Also removes any tags that are before the current 18175 * time so that playback begins at or slightly after the right 18176 * place on a seek 18177 * 18178 * @private 18179 * @param {Object} segmentData object of segment data 18180 */ 18181 }, { 18182 key: 'convertTagsToData_', 18183 value: function convertTagsToData_(segmentData) { 18184 var segmentByteLength = 0; 18185 var tech = this.mediaSource_.tech_; 18186 var videoTargetPts = 0; 18187 var segment = undefined; 18188 var videoTags = segmentData.tags.videoTags; 18189 var audioTags = segmentData.tags.audioTags; 18190 18191 // Establish the media timeline to PTS translation if we don't 18192 // have one already 18193 if (isNaN(this.basePtsOffset_) && (videoTags.length || audioTags.length)) { 18194 // We know there is at least one video or audio tag, but since we may
18194not have both, 18195 // we use pts: Infinity for the missing tag. The will force the following Math.min 18196 // call will to use the proper pts value since it will always be less than Infinity 18197 var firstVideoTag = videoTags[0] || { pts: Infinity }; 18198 var firstAudioTag = audioTags[0] || { pts: Infinity }; 18199 18200 this.basePtsOffset_ = Math.min(firstAudioTag.pts, firstVideoTag.pts); 18201 } 18202 18203 if (tech.seeking()) { 18204 // Do not use previously saved buffer end values while seeking since buffer 18205 // is cleared on all seeks 18206 this.videoBufferEnd_ = NaN; 18207 this.audioBufferEnd_ = NaN; 18208 } 18209 18210 if (isNaN(this.videoBufferEnd_)) { 18211 if (tech.buffered().length) { 18212 videoTargetPts = tech.buffered().end(0) - this.timestampOffset; 18213 } 18214 18215 // Trim to currentTime if seeking 18216 if (tech.seeking()) { 18217 videoTargetPts = Math.max(videoTargetPts, tech.currentTime() - this.timestampOffset); 18218 } 18219 18220 // PTS values are represented in milliseconds 18221 videoTargetPts *= 1e3; 18222 videoTargetPts += this.basePtsOffset_; 18223 } else { 18224 // Add a fudge factor of 0.1 to the last video pts appended since a rendition change 18225 // could append an overlapping segment, in which case there is a high likelyhood 18226 // a tag could have a matching pts to videoBufferEnd_, which would cause 18227 // that tag to get appended by the tag.pts >= targetPts check below even though it 18228 // is a duplicate of what was previously appended 18229 videoTargetPts = this.videoBufferEnd_ + 0.1; 18230 } 18231 18232 // filter complete GOPs with a presentation time less than the seek target/end of buffer 18233 var currentIndex = videoTags.length; 18234 18235 // if the last tag is beyond videoTargetPts, then do not search the list for a GOP 18236 // since our videoTargetPts lies in a future segment 18237 if (currentIndex && videoTags[currentIndex - 1].pts >= videoTargetPts) { 18238 // Start by walking backwards from the end of the list until we reach a tag that 18239 // is equal to or less than videoTargetPts 18240 while (--currentIndex) { 18241 var currentTag = videoTags[currentIndex]; 18242 18243 if (currentTag.pts > videoTargetPts) { 18244 continue; 18245 } 18246 18247 // if we see a keyFrame or metadata tag once we've gone below videoTargetPts, 18248 // exit the loop as this is the start of the GOP that we want to append 18249 if (currentTag.keyFrame || currentTag.metaDataTag) { 18250 break; 18251 } 18252 } 18253 18254 // We need to check if there are any metadata tags that come before currentIndex 18255 // as those will be metadata tags associated with the GOP we are appending 18256 // There could be 0 to 2 metadata tags that come before the currentIndex depending 18257 // on what videoTargetPts is and whether the transmuxer prepended metadata tags to this 18258 // key frame 18259 while (currentIndex) { 18260 var nextTag = videoTags[currentIndex - 1]; 18261 18262 if (!nextTag.metaDataTag) { 18263 break; 18264 } 18265 18266 currentIndex--; 18267 } 18268 } 18269 18270 var filteredVideoTags = videoTags.slice(currentIndex); 18271 18272 var audioTargetPts = undefined; 18273 18274 if (isNaN(this.audioBufferEnd_)) { 18275 audioTargetPts = videoTargetPts; 18276 } else { 18277 // Add a fudge factor of 0.1 to the last video pts appended since a rendition change 18278 // could append an overlapping segment, in which case there is a high likelyhood 18279 // a tag could have a matching pts to videoBufferEnd_, which would cause 18280 // that tag to get appended by the tag.pts >= targetPts check below even though it 18281 // is a duplicate of what was previously appended 18282 audioTargetPts = this.audioBufferEnd_ + 0.1; 18283 } 18284 18285 if (filteredVideoTags.length) { 18286 // If targetPts intersects a GOP and we appended the tags for the GOP that came 18287 // before targetPts, we want to make sure to trim audio tags at the pts 18288 // of the first video tag to avoid brief moments of silence 18289 audioTargetPts = Math.min(audioTargetPts, filteredVideoTags[0].pts); 18290 } 18291 18292 // skip tags with a presentation time less than the seek target/end of buffer 18293 currentIndex = 0; 18294 18295 while (currentIndex < audioTags.length) { 18296 if (audioTags[currentIndex].pts >= audioTargetPts) { 18297 break; 18298 } 18299 18300 currentIndex++; 18301 } 18302 18303 var filteredAudioTags = audioTags.slice(currentIndex); 18304 18305 // update the audio and video buffer ends 18306 if (filteredAudioTags.length) { 18307 this.audioBufferEnd_ = filteredAudioTags[filteredAudioTags.length - 1].pts; 18308 } 18309 if (filteredVideoTags.length) { 18310 this.videoBufferEnd_ = filteredVideoTags[filteredVideoTags.length - 1].pts; 18311 } 18312 18313 var tags = this.getOrderedTags_(filteredVideoTags, filteredAudioTags); 18314 18315 if (tags.length === 0) { 18316 return; 18317 } 18318 18319 // If we are appending data that comes before our target pts, we want to tell 18320 // the swf to adjust its notion of current time to account for the extra tags 18321 // we are appending to complete the GOP that intersects with targetPts 18322 if (tags[0].pts < videoTargetPts && tech.seeking()) { 18323 var fudgeFactor = 1 / 30; 18324 var currentTime = tech.currentTime(); 18325 var diff = (videoTargetPts - tags[0].pts) / 1e3; 18326 var adjustedTime = currentTime - diff; 18327 18328 if (adjustedTime < fudgeFactor) { 18329 adjustedTime = 0; 18330 } 18331 18332 try { 18333 this.mediaSource_.swfObj.vjs_adjustCurrentTime(adjustedTime); 18334 } catch (e) { 18335 // no-op for backwards compatability of swf. If adjustCurrentTime fails, 18336 // the swf may incorrectly report currentTime and buffered ranges 18337 // but should not affect playback over than the time displayed on the 18338 // progress bar is inaccurate 18339 } 18340 } 18341 18342 // concatenate the bytes into a single segment 18343 for (var i = 0; i < tags.length; i++) { 18344 segmentByteLength += tags[i].bytes.byteLength; 18345 } 18346 segment = new Uint8Array(segmentByteLength); 18347 for (var i = 0, j = 0; i < tags.length; i++) { 18348 segment.set(tags[i].bytes, j); 18349 j += tags[i].bytes.byteLength; 18350 } 18351 18352 return segment; 18353 } 18354 18355 /** 18356 * Assemble the FLV tags in decoder order. 18357 * 18358 * @private 18359 * @param {Array} videoTags list of video tags 18360 * @param {Array} audioTags list of audio tags 18361 */ 18362 }, { 18363 key: 'getOrderedTags_', 18364 value: function getOrderedTags_(videoTags, audioTags) { 18365 var tag = undefined; 18366 var tags = []; 18367 18368 while (videoTags.length || audioTags.length) { 18369 if (!videoTags.length) { 18370 // only audio tags remain 18371 tag = audioTags.shift(); 18372 } else if (!audioTags.length) { 18373 // only video tags remain 18374 tag = videoTags.shift(); 18375 } else if (audioTags[0].dts < videoTags[0].dts) { 18376 // audio should be decoded next 18377 tag = audioTags.shift(); 18378 } else { 18379 // video should be decoded next 18380 tag = videoTags.shift(); 18381 } 18382 18383 tags.push(tag); 18384 } 18385 18386 return tags; 18387 } 18388 }, { 18389 key: 'onHlsReset_', 18390 value: function onHlsReset_() { 18391 this.transmuxer_.postMessage({ action: 'resetCaptions' }); 18392 } 18393 }]); 18394 18395 return FlashSourceBuffer; 18396})(_videoJs2['default'].EventTarget); 18397 18398exports['default'] = FlashSourceBuffer; 18399module.exports = exports['default']; 18400}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 18401},{"./add-text-track-data":64,"./create-text-tracks-if-necessary":66,"./flash-constants":67,"./flash-transmuxer-worker":70,"./remove-cues-from-track":72,"global/window":32,"mux.js/lib/flv":46,"webworkify":76}],70:[function(require,module,exports){ 18402/** 18403 * @file flash-transmuxer-worker.js 18404 */ 18405'use strict'; 18406 18407Object.defineProperty(exports, '__esModule', { 18408 value: true 18409}); 18410 18411var _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; }; })(); 18412 18413function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 18414 18415function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 18416 18417var _globalWindow = require('global/window'); 18418 18419var _globalWindow2 = _interopRequireDefault(_globalWindow); 18420 18421var _muxJsLibFlv = require('mux.js/lib/flv'); 18422 18423var _muxJsLibFlv2 = _interopRequireDefault(_muxJsLibFlv); 18424 18425/**
18426 * Re-emits transmuxer events by converting them into messages to the 18427 * world outside the worker. 18428 * 18429 * @param {Object} transmuxer the transmuxer to wire events on 18430 * @private 18431 */ 18432var wireTransmuxerEvents = function wireTransmuxerEvents(transmuxer) { 18433 transmuxer.on('data', function (segment) { 18434 _globalWindow2['default'].postMessage({ 18435 action: 'data', 18436 segment: segment 18437 }); 18438 }); 18439 18440 transmuxer.on('done', function (data) { 18441 _globalWindow2['default'].postMessage({ action: 'done' }); 18442 }); 18443}; 18444 18445/** 18446 * All incoming messages route through this hash. If no function exists 18447 * to handle an incoming message, then we ignore the message. 18448 * 18449 * @class MessageHandlers 18450 * @param {Object} options the options to initialize with 18451 */ 18452 18453var MessageHandlers = (function () { 18454 function MessageHandlers(options) { 18455 _classCallCheck(this, MessageHandlers); 18456 18457 this.options = options || {}; 18458 this.init(); 18459 } 18460 18461 /** 18462 * Our web wroker interface so that things can talk to mux.js 18463 * that will be running in a web worker. The scope is passed to this by 18464 * webworkify. 18465 * 18466 * @param {Object} self the scope for the web worker 18467 */ 18468 18469 /** 18470 * initialize our web worker and wire all the events. 18471 */ 18472 18473 _createClass(MessageHandlers, [{ 18474 key: 'init', 18475 value: function init() { 18476 if (this.transmuxer) { 18477 this.transmuxer.dispose(); 18478 } 18479 this.transmuxer = new _muxJsLibFlv2['default'].Transmuxer(this.options); 18480 wireTransmuxerEvents(this.transmuxer); 18481 } 18482 18483 /** 18484 * Adds data (a ts segment) to the start of the transmuxer pipeline for 18485 * processing. 18486 * 18487 * @param {ArrayBuffer} data data to push into the muxer 18488 */ 18489 }, { 18490 key: 'push', 18491 value: function push(data) { 18492 // Cast array buffer to correct type for transmuxer 18493 var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); 18494 18495 this.transmuxer.push(segment); 18496 } 18497 18498 /** 18499 * Recreate the transmuxer so that the next segment added via `push` 18500 * start with a fresh transmuxer. 18501 */ 18502 }, { 18503 key: 'reset', 18504 value: function reset() { 18505 this.init(); 18506 } 18507 18508 /** 18509 * Forces the pipeline to finish processing the last segment and emit its 18510 * results. 18511 */ 18512 }, { 18513 key: 'flush', 18514 value: function flush() { 18515 this.transmuxer.flush(); 18516 } 18517 }, { 18518 key: 'resetCaptions', 18519 value: function resetCaptions() { 18520 this.transmuxer.resetCaptions(); 18521 } 18522 }]); 18523 18524 return MessageHandlers; 18525})(); 18526 18527var FlashTransmuxerWorker = function FlashTransmuxerWorker(self) { 18528 self.onmessage = function (event) { 18529 if (event.data.action === 'init' && event.data.options) { 18530 this.messageHandlers = new MessageHandlers(event.data.options); 18531 return; 18532 } 18533 18534 if (!this.messageHandlers) { 18535 this.messageHandlers = new MessageHandlers(); 18536 } 18537 18538 if (event.data && event.data.action && event.data.action !== 'init') { 18539 if (this.messageHandlers[event.data.action]) { 18540 this.messageHandlers[event.data.action](event.data); 18541 } 18542 } 18543 }; 18544}; 18545 18546exports['default'] = function (self) { 18547 return new FlashTransmuxerWorker(self); 18548}; 18549 18550module.exports = exports['default']; 18551},{"global/window":32,"mux.js/lib/flv":46}],71:[function(require,module,exports){ 18552(function (global){ 18553/** 18554 * @file html-media-source.js 18555 */ 18556'use strict'; 18557 18558Object.defineProperty(exports, '__esModule', { 18559 value: true 18560}); 18561 18562var _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; }; })(); 18563 18564var _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); } } }; 18565 18566function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 18567 18568function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 18569 18570function _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, { c
18570onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 18571 18572var _globalWindow = require('global/window'); 18573 18574var _globalWindow2 = _interopRequireDefault(_globalWindow); 18575 18576var _globalDocument = require('global/document'); 18577 18578var _globalDocument2 = _interopRequireDefault(_globalDocument); 18579 18580var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 18581 18582var _videoJs2 = _interopRequireDefault(_videoJs); 18583 18584var _virtualSourceBuffer = require('./virtual-source-buffer'); 18585 18586var _virtualSourceBuffer2 = _interopRequireDefault(_virtualSourceBuffer); 18587 18588var _addTextTrackData = require('./add-text-track-data'); 18589 18590var _codecUtils = require('./codec-utils'); 18591 18592/** 18593 * Our MediaSource implementation in HTML, mimics native 18594 * MediaSource where/if possible. 18595 * 18596 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource 18597 * @class HtmlMediaSource 18598 * @extends videojs.EventTarget 18599 */ 18600 18601var HtmlMediaSource = (function (_videojs$EventTarget) { 18602 _inherits(HtmlMediaSource, _videojs$EventTarget); 18603 18604 function HtmlMediaSource() { 18605 var _this = this; 18606 18607 _classCallCheck(this, HtmlMediaSource); 18608 18609 _get(Object.getPrototypeOf(HtmlMediaSource.prototype), 'constructor', this).call(this); 18610 var property = undefined; 18611 18612 this.nativeMediaSource_ = new _globalWindow2['default'].MediaSource(); 18613 // delegate to the native MediaSource's methods by default 18614 for (property in this.nativeMediaSource_) { 18615 if (!(property in HtmlMediaSource.prototype) && typeof this.nativeMediaSource_[property] === 'function') { 18616 this[property] = this.nativeMediaSource_[property].bind(this.nativeMediaSource_); 18617 } 18618 } 18619 18620 // emulate `duration` and `seekable` until seeking can be 18621 // handled uniformly for live streams 18622 // see https://github.com/w3c/media-source/issues/5 18623 this.duration_ = NaN; 18624 Object.defineProperty(this, 'duration', { 18625 get: function get() { 18626 if (this.duration_ === Infinity) { 18627 return this.duration_; 18628 } 18629 return this.nativeMediaSource_.duration; 18630 }, 18631 set: function set(duration) { 18632 this.duration_ = duration; 18633 if (duration !== Infinity) { 18634 this.nativeMediaSource_.duration = duration; 18635 return; 18636 } 18637 } 18638 }); 18639 Object.defineProperty(this, 'seekable', { 18640 get: function get() { 18641 if (this.duration_ === Infinity) { 18642 return _videoJs2['default'].createTimeRanges([[0, this.nativeMediaSource_.duration]]); 18643 } 18644 return this.nativeMediaSource_.seekable; 18645 } 18646 }); 18647 18648 Object.defineProperty(this, 'readyState', { 18649 get: function get() { 18650 return this.nativeMediaSource_.readyState; 18651 } 18652 }); 18653 18654 Object.defineProperty(this, 'activeSourceBuffers', { 18655 get: function get() { 18656 return this.activeSourceBuffers_; 18657 } 18658 }); 18659 18660 // the list of virtual and native SourceBuffers created by this 18661 // MediaSource 18662 this.sourceBuffers = []; 18663 18664 this.activeSourceBuffers_ = []; 18665 18666 /** 18667 * update the list of active source buffers based upon various 18668 * imformation from HLS and video.js 18669 * 18670 * @private 18671 */ 18672 this.updateActiveSourceBuffers_ = function () { 18673 // Retain the reference but empty the array 18674 _this.activeSourceBuffers_.length = 0; 18675 18676 // If there is only one source buffer, then it will always be active and audio will 18677 // be disabled based on the codec of the source buffer 18678 if (_this.sourceBuffers.length === 1) { 18679 var sourceBuffer = _this.sourceBuffers[0]; 18680 18681 sourceBuffer.appendAudioInitSegment_ = true; 18682 sourceBuffer.audioDisabled_ = !sourceBuffer.audioCodec_; 18683 _this.activeSourceBuffers_.push(sourceBuffer); 18684 return; 18685 } 18686 18687 // There are 2 source buffers, a combined (possibly video only) source buffer and 18688 // and an audio only source buffer. 18689 // By default, the audio in the combined virtual source buffer is enabled 18690 // and the audio-only source buffer (if it exists) is disabled. 18691 var disableCombined = false;
18692 var disableAudioOnly = true; 18693 18694 // TODO: maybe we can store the sourcebuffers on the track objects? 18695 // safari may do something like this 18696 for (var i = 0; i < _this.player_.audioTracks().length; i++) { 18697 var track = _this.player_.audioTracks()[i]; 18698 18699 if (track.enabled && track.kind !== 'main') { 18700 // The enabled track is an alternate audio track so disable the audio in 18701 // the combined source buffer and enable the audio-only source buffer. 18702 disableCombined = true; 18703 disableAudioOnly = false; 18704 break; 18705 } 18706 } 18707 18708 _this.sourceBuffers.forEach(function (sourceBuffer) { 18709 /* eslinst-disable */ 18710 // TODO once codecs are required, we can switch to using the codecs to determine 18711 // what stream is the video stream, rather than relying on videoTracks 18712 /* eslinst-enable */ 18713 18714 sourceBuffer.appendAudioInitSegment_ = true; 18715 18716 if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { 18717 // combined 18718 sourceBuffer.audioDisabled_ = disableCombined; 18719 } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) { 18720 // If the "combined" source buffer is video only, then we do not want 18721 // disable the audio-only source buffer (this is mostly for demuxed 18722 // audio and video hls) 18723 sourceBuffer.audioDisabled_ = true; 18724 disableAudioOnly = false; 18725 } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { 18726 // audio only 18727 sourceBuffer.audioDisabled_ = disableAudioOnly; 18728 if (disableAudioOnly) { 18729 return; 18730 } 18731 } 18732 18733 _this.activeSourceBuffers_.push(sourceBuffer); 18734 }); 18735 }; 18736 18737 this.onPlayerMediachange_ = function () { 18738 _this.sourceBuffers.forEach(function (sourceBuffer) { 18739 sourceBuffer.appendAudioInitSegment_ = true; 18740 }); 18741 }; 18742 18743 this.onHlsReset_ = function () { 18744 _this.sourceBuffers.forEach(function (sourceBuffer) { 18745 if (sourceBuffer.transmuxer_) { 18746 sourceBuffer.transmuxer_.postMessage({ action: 'resetCaptions' }); 18747 } 18748 }); 18749 }; 18750 18751 // Re-emit MediaSource events on the polyfill 18752 ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) { 18753 this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this)); 18754 }, this); 18755 18756 // capture the associated player when the MediaSource is 18757 // successfully attached 18758 this.on('sourceopen', function (event) { 18759 // Get the player this MediaSource is attached to 18760 var video = _globalDocument2['default'].querySelector('[src="' + _this.url_ + '"]'); 18761 18762 if (!video) { 18763 return; 18764 } 18765 18766 _this.player_ = (0, _videoJs2['default'])(video.parentNode); 18767 18768 _this.player_.tech_.on('hls-reset', _this.onHlsReset_); 18769 18770 if (_this.player_.audioTracks && _this.player_.audioTracks()) { 18771 _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_); 18772 _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_); 18773 _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_); 18774 } 18775 18776 _this.player_.on('mediachange', _this.onPlayerMediachange_); 18777 }); 18778 18779 this.on('sourceended', function (event) { 18780 var duration = (0, _addTextTrackData.durationOfVideo)(_this.duration); 18781 18782 for (var i = 0; i < _this.sourceBuffers.length; i++) { 18783 var sourcebuffer = _this.sourceBuffers[i]; 18784 var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues; 18785 18786 if (cues && cues.length) { 18787 cues[cues.length - 1].endTime = duration; 18788 } 18789 } 18790 }); 18791 18792 // explicitly terminate any WebWorkers that were created 18793 // by SourceHandlers 18794 this.on('sourceclose', function (event) { 18795 this.sourceBuffers.forEach(function (sourceBuffer) { 18796 if (sourceBuffer.transmuxer_) { 18797 sourceBuffer.transmuxer_.terminate(); 18798 } 18799 }); 18800 18801 this.sourceBuffers.length = 0; 18802 if (!this.player_) { 18803 return; 18804 } 18805 18806 if (this.player_.audioTracks && this.player_.audioTracks()) { 18807 this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_); 18808 this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_); 18809 this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_); 18810 } 18811 18812 // We can only change this if the player hasn't been disposed of yet 18813 // because `off` eventually tries to use the el_ property. If it has 18814 // been disposed of, then don't worry about it because there are no 18815 // event handlers left to unbind anyway 18816 if (this.player_.el_) { 18817 this.player_.off('mediachange', this.onPlayerMediachange_); 18818 this.player_.tech_.off('hls-reset', this.onHlsReset_); 18819 } 18820 }); 18821 } 18822 18823 /** 18824 * Add a range that that can now be seeked to. 18825 * 18826 * @param {Double} start where to start the addition 18827 * @param {Double} end where to end the addition 18828 * @private 18829 */ 18830 18831 _createClass(HtmlMediaSource, [{ 18832 key: 'addSeekableRange_', 18833 value: function addSeekableRange_(start, end) { 18834 var error = undefined; 18835 18836 if (this.duration !== Infinity) { 18837 error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity');
18838 error.name = 'InvalidStateError'; 18839 error.code = 11; 18840 throw error; 18841 } 18842 18843 if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) { 18844 this.nativeMediaSource_.duration = end; 18845 } 18846 } 18847 18848 /** 18849 * Add a source buffer to the media source. 18850 * 18851 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer 18852 * @param {String} type the content-type of the content 18853 * @return {Object} the created source buffer 18854 */ 18855 }, { 18856 key: 'addSourceBuffer', 18857 value: function addSourceBuffer(type) { 18858 var buffer = undefined; 18859 var parsedType = (0, _codecUtils.parseContentType)(type); 18860 18861 // Create a VirtualSourceBuffer to transmux MPEG-2 transport 18862 // stream segments into fragmented MP4s 18863 if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) { 18864 var codecs = []; 18865 18866 if (parsedType.parameters && parsedType.parameters.codecs) { 18867 codecs = parsedType.parameters.codecs.split(','); 18868 codecs = (0, _codecUtils.translateLegacyCodecs)(codecs); 18869 codecs = codecs.filter(function (codec) { 18870 return (0, _codecUtils.isAudioCodec)(codec) || (0, _codecUtils.isVideoCodec)(codec); 18871 }); 18872 } 18873 18874 if (codecs.length === 0) { 18875 codecs = ['avc1.4d400d', 'mp4a.40.2']; 18876 } 18877 18878 buffer = new _virtualSourceBuffer2['default'](this, codecs); 18879 18880 if (this.sourceBuffers.length !== 0) { 18881 // If another VirtualSourceBuffer already exists, then we are creating a 18882 // SourceBuffer for an alternate audio track and therefore we know that 18883 // the source has both an audio and video track. 18884 // That means we should trigger the manual creation of the real 18885 // SourceBuffers instead of waiting for the transmuxer to return data 18886 this.sourceBuffers[0].createRealSourceBuffers_(); 18887 buffer.createRealSourceBuffers_(); 18888 18889 // Automatically disable the audio on the first source buffer if 18890 // a second source buffer is ever created 18891 this.sourceBuffers[0].audioDisabled_ = true; 18892 } 18893 } else { 18894 // delegate to the native implementation 18895 buffer = this.nativeMediaSource_.addSourceBuffer(type); 18896 } 18897 18898 this.sourceBuffers.push(buffer); 18899 return buffer; 18900 } 18901 }]); 18902 18903 return HtmlMediaSource; 18904})(_videoJs2['default'].EventTarget); 18905 18906exports['default'] = HtmlMediaSource; 18907module.exports = exports['default']; 18908}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 18909},{"./add-text-track-data":64,"./codec-utils":65,"./virtual-source-buffer":75,"global/document":31,"global/window":32}],72:[function(require,module,exports){ 18910/** 18911 * @file remove-cues-from-track.js 18912 */ 18913 18914/** 18915 * Remove cues from a track on video.js. 18916 * 18917 * @param {Double} start start of where we should remove the cue 18918 * @param {Double} end end of where the we should remove the cue 18919 * @param {Object} track the text track to remove the cues from 18920 * @private 18921 */ 18922"use strict"; 18923 18924Object.defineProperty(exports, "__esModule", { 18925 value: true 18926}); 18927var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) { 18928 var i = undefined; 18929 var cue = undefined; 18930 18931 if (!track) { 18932 return; 18933 } 18934 18935 if (!track.cues) { 18936 return; 18937 } 18938 18939 i = track.cues.length; 18940 18941 while (i--) { 18942 cue = track.cues[i]; 18943 18944 // Remove any overlapping cue 18945 if (cue.startTime <= end && cue.endTime >= start) { 18946 track.removeCue(cue); 18947 } 18948 } 18949}; 18950 18951exports["default"] = removeCuesFromTrack; 18952module.exports = exports["default"]; 18953},{}],73:[function(require,module,exports){ 18954/** 18955 * @file transmuxer-worker.js 18956 */ 18957 18958/** 18959 * videojs-contrib-media-sources 18960 * 18961 * Copyright (c) 2015 Brightcove 18962 * All rights reserved. 18963 * 18964 * Handles communication between the browser-world and the mux.js 18965 * transmuxer running inside of a WebWorker by exposing a simple 18966 * message-based interface to a Transmuxer object. 18967 */ 18968'use strict'; 18969 18970Object.defineProperty(exports, '__esModule', { 18971 value: true 18972}); 18973 18974var _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; }; })(); 18975 18976function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 18977 18978function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 18979 18980var _globalWindow = require('global/window'); 18981 18982var _globalWindow2 = _interopRequireDefault(_globalWindow); 18983 18984var _muxJsLibMp4 = require('mux.js/lib/mp4'); 18985 18986var _muxJsLibMp42 = _interopRequireDefault(_muxJsLibMp4); 18987 18988/**
18989 * Re-emits transmuxer events by converting them into messages to the 18990 * world outside the worker. 18991 * 18992 * @param {Object} transmuxer the transmuxer to wire events on 18993 * @private 18994 */ 18995var wireTransmuxerEvents = function wireTransmuxerEvents(transmuxer) { 18996 transmuxer.on('data', function (segment) { 18997 // transfer ownership of the underlying ArrayBuffer 18998 // instead of doing a copy to save memory 18999 // ArrayBuffers are transferable but generic TypedArrays are not 19000 // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects) 19001 var initArray = segment.initSegment; 19002 19003 segment.initSegment = { 19004 data: initArray.buffer, 19005 byteOffset: initArray.byteOffset, 19006 byteLength: initArray.byteLength 19007 }; 19008 19009 var typedArray = segment.data; 19010 19011 segment.data = typedArray.buffer; 19012 _globalWindow2['default'].postMessage({ 19013 action: 'data', 19014 segment: segment, 19015 byteOffset: typedArray.byteOffset, 19016 byteLength: typedArray.byteLength 19017 }, [segment.data]); 19018 }); 19019 19020 if (transmuxer.captionStream) { 19021 transmuxer.captionStream.on('data', function (caption) { 19022 _globalWindow2['default'].postMessage({ 19023 action: 'caption', 19024 data: caption 19025 }); 19026 }); 19027 } 19028 19029 transmuxer.on('done', function (data) { 19030 _globalWindow2['default'].postMessage({ action: 'done' }); 19031 }); 19032}; 19033 19034/** 19035 * All incoming messages route through this hash. If no function exists 19036 * to handle an incoming message, then we ignore the message. 19037 * 19038 * @class MessageHandlers 19039 * @param {Object} options the options to initialize with 19040 */ 19041 19042var MessageHandlers = (function () { 19043 function MessageHandlers(options) { 19044 _classCallCheck(this, MessageHandlers); 19045 19046 this.options = options || {}; 19047 this.init(); 19048 } 19049 19050 /** 19051 * Our web wroker interface so that things can talk to mux.js 19052 * that will be running in a web worker. the scope is passed to this by 19053 * webworkify. 19054 * 19055 * @param {Object} self the scope for the web worker 19056 */ 19057 19058 /** 19059 * initialize our web worker and wire all the events. 19060 */ 19061 19062 _createClass(MessageHandlers, [{ 19063 key: 'init', 19064 value: function init() { 19065 if (this.transmuxer) { 19066 this.transmuxer.dispose(); 19067 } 19068 this.transmuxer = new _muxJsLibMp42['default'].Transmuxer(this.options); 19069 wireTransmuxerEvents(this.transmuxer); 19070 } 19071 19072 /** 19073 * Adds data (a ts segment) to the start of the transmuxer pipeline for 19074 * processing. 19075 * 19076 * @param {ArrayBuffer} data data to push into the muxer 19077 */ 19078 }, { 19079 key: 'push', 19080 value: function push(data) { 19081 // Cast array buffer to correct type for transmuxer 19082 var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); 19083 19084 this.transmuxer.push(segment); 19085 } 19086 19087 /** 19088 * Recreate the transmuxer so that the next segment added via `push` 19089 * start with a fresh transmuxer. 19090 */ 19091 }, { 19092 key: 'reset', 19093 value: function reset() { 19094 this.init(); 19095 } 19096 19097 /** 19098 * Set the value that will be used as the `baseMediaDecodeTime` time for the 19099 * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime` 19100 * set relative to the first based on the PTS values. 19101 * 19102 * @param {Object} data used to set the timestamp offset in the muxer 19103 */ 19104 }, { 19105 key: 'setTimestampOffset', 19106 value: function setTimestampOffset(data) { 19107 var timestampOffset = data.timestampOffset || 0; 19108 19109 this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000)); 19110 } 19111 }, { 19112 key: 'setAudioAppendStart', 19113 value: function setAudioAppendStart(data) { 19114 this.transmuxer.setAudioAppendStart(Math.ceil(data.appendStart * 90000)); 19115 } 19116 19117 /** 19118 * Forces the pipeline to finish processing the last segment and emit it's 19119 * results. 19120 * 19121 * @param {Object} data event data, not really used 19122 */ 19123 }, { 19124 key: 'flush', 19125 value: function flush(data) { 19126 this.transmuxer.flush(); 19127 } 19128 }, { 19129 key: 'resetCaptions', 19130 value: function resetCaptions() { 19131 this.transmuxer.resetCaptions(); 19132 } 19133 }]); 19134 19135 return MessageHandlers; 19136})(); 19137 19138var TransmuxerWorker = function TransmuxerWorker(self) { 19139 self.onmessage = function (event) {
19140 if (event.data.action === 'init' && event.data.options) { 19141 this.messageHandlers = new MessageHandlers(event.data.options); 19142 return; 19143 } 19144 19145 if (!this.messageHandlers) { 19146 this.messageHandlers = new MessageHandlers(); 19147 } 19148 19149 if (event.data && event.data.action && event.data.action !== 'init') { 19150 if (this.messageHandlers[event.data.action]) { 19151 this.messageHandlers[event.data.action](event.data); 19152 } 19153 } 19154 }; 19155}; 19156 19157exports['default'] = function (self) { 19158 return new TransmuxerWorker(self); 19159}; 19160 19161module.exports = exports['default']; 19162},{"global/window":32,"mux.js/lib/mp4":55}],74:[function(require,module,exports){ 19163(function (global){ 19164/** 19165 * @file videojs-contrib-media-sources.js 19166 */ 19167'use strict'; 19168 19169Object.defineProperty(exports, '__esModule', { 19170 value: true 19171}); 19172 19173function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 19174 19175var _globalWindow = require('global/window'); 19176 19177var _globalWindow2 = _interopRequireDefault(_globalWindow); 19178 19179var _flashMediaSource = require('./flash-media-source'); 19180 19181var _flashMediaSource2 = _interopRequireDefault(_flashMediaSource); 19182 19183var _htmlMediaSource = require('./html-media-source'); 19184 19185var _htmlMediaSource2 = _interopRequireDefault(_htmlMediaSource); 19186 19187var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 19188 19189var _videoJs2 = _interopRequireDefault(_videoJs); 19190 19191var urlCount = 0; 19192 19193// ------------ 19194// Media Source 19195// ------------ 19196 19197var defaults = { 19198 // how to determine the MediaSource implementation to use. There 19199 // are three available modes: 19200 // - auto: use native MediaSources where available and Flash 19201 // everywhere else 19202 // - html5: always use native MediaSources 19203 // - flash: always use the Flash MediaSource polyfill 19204 mode: 'auto' 19205}; 19206 19207// store references to the media sources so they can be connected 19208// to a video element (a swf object) 19209// TODO: can we store this somewhere local to this module? 19210_videoJs2['default'].mediaSources = {}; 19211 19212/** 19213 * Provide a method for a swf object to notify JS that a 19214 * media source is now open. 19215 * 19216 * @param {String} msObjectURL string referencing the MSE Object URL 19217 * @param {String} swfId the swf id 19218 */ 19219var open = function open(msObjectURL, swfId) { 19220 var mediaSource = _videoJs2['default'].mediaSources[msObjectURL]; 19221 19222 if (mediaSource) { 19223 mediaSource.trigger({ type: 'sourceopen', swfId: swfId }); 19224 } else { 19225 throw new Error('Media Source not found (Video.js)'); 19226 } 19227}; 19228 19229/** 19230 * Check to see if the native MediaSource object exists and supports 19231 * an MP4 container with both H.264 video and AAC-LC audio. 19232 * 19233 * @return {Boolean} if native media sources are supported 19234 */ 19235var supportsNativeMediaSources = function supportsNativeMediaSources() { 19236 return !!_globalWindow2['default'].MediaSource && !!_globalWindow2['default'].MediaSource.isTypeSupported && _globalWindow2['default'].MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"'); 19237}; 19238 19239/** 19240 * An emulation of the MediaSource API so that we can support 19241 * native and non-native functionality such as flash and 19242 * video/mp2t videos. returns an instance of HtmlMediaSource or 19243 * FlashMediaSource depending on what is supported and what options 19244 * are passed in. 19245 * 19246 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource 19247 * @param {Object} options options to use during setup. 19248 */ 19249var MediaSource = function MediaSource(options) { 19250 var settings = _videoJs2['default'].mergeOptions(defaults, options); 19251 19252 this.MediaSource = { 19253 open: open, 19254 supportsNativeMediaSources: supportsNativeMediaSources 19255 }; 19256 19257 // determine whether HTML MediaSources should be used 19258 if (settings.mode === 'html5' || settings.mode === 'auto' && supportsNativeMediaSources()) { 19259 return new _htmlMediaSource2['default'](); 19260 } else if (_videoJs2['default'].getTech('Flash')) { 19261 return new _flashMediaSource2['default'](); 19262 } 19263 19264 throw new Error('Cannot use Flash or Html5 to create a MediaSource for this video'); 19265}; 19266 19267exports.MediaSource = MediaSource; 19268MediaSource.open = open; 19269MediaSource.supportsNativeMediaSources = supportsNativeMediaSources; 19270 19271/** 19272 * A wrapper around the native URL for our MSE object 19273 * implementation, this object is exposed under videojs.URL 19274 * 19275 * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL 19276 */ 19277var URL = { 19278 /** 19279 * A wrapper around the native createObjectURL for our objects. 19280 * This function maps a native or emulated mediaSource to a blob 19281 * url so that it can be loaded into video.js 19282 * 19283 * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL 19284 * @param {MediaSource} object the object to create a blob url to 19285 */ 19286 createObjectURL: function createObjectURL(object) { 19287 var objectUrlPrefix = 'blob:vjs-media-source/'; 19288 var url = undefined; 19289 19290 // use the native MediaSource to generate an object URL 19291 if (object instanceof _htmlMediaSource2['default']) { 19292 url = _globalWindow2['default'].URL.createObjectURL(object.nativeMediaSource_); 19293 object.url_ = url; 19294 return url; 19295 } 19296 // if the object isn't an emulated MediaSource, delegate to the 19297 // native implementation 19298 if (!(object instanceof _flashMediaSource2['default'])) { 19299 url = _globalWindow2['default'].URL.createObjectURL(object); 19300 object.url_ = url; 19301 return url; 19302 } 19303 19304 // build a URL that can be used to map back to the emulated 19305 // MediaSource 19306 url = objectUrlPrefix + urlCount; 19307 19308 urlCount++; 19309 19310 // setup the mapping back to object 19311 _videoJs2['default'].mediaSources[url] = object; 19312 19313 return url; 19314 } 19315}; 19316 19317exports.URL = URL; 19318_videoJs2['default'].MediaSource = MediaSource; 19319_videoJs2['default'].URL = URL; 19320}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 19321},{"./flash-media-source":68,"./html-media-source":71,"global/window":32}],75:[function(require,module,exports){ 19322(function (global){ 19323/** 19324 * @file virtual-source-buffer.js 19325 */ 19326'use strict'; 19327 19328Object.defineProperty(exports, '__esModule', { 19329 value: true 19330}); 19331 19332var _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; }; })(); 19333 19334var _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); } } }; 19335 19336function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 19337 19338function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 19339 19340function _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, { c
19340onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 19341 19342var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 19343 19344var _videoJs2 = _interopRequireDefault(_videoJs); 19345 19346var _createTextTracksIfNecessary = require('./create-text-tracks-if-necessary'); 19347 19348var _createTextTracksIfNecessary2 = _interopRequireDefault(_createTextTracksIfNecessary); 19349 19350var _removeCuesFromTrack = require('./remove-cues-from-track'); 19351 19352var _removeCuesFromTrack2 = _interopRequireDefault(_removeCuesFromTrack); 19353 19354var _addTextTrackData = require('./add-text-track-data'); 19355 19356var _webworkify = require('webworkify'); 19357 19358var _webworkify2 = _interopRequireDefault(_webworkify); 19359 19360var _transmuxerWorker = require('./transmuxer-worker'); 19361 19362var _transmuxerWorker2 = _interopRequireDefault(_transmuxerWorker); 19363 19364var _codecUtils = require('./codec-utils'); 19365 19366// We create a wrapper around the SourceBuffer so that we can manage the 19367// state of the `updating` property manually. We have to do this because 19368// Firefox changes `updating` to false long before triggering `updateend` 19369// events and that was causing strange problems in videojs-contrib-hls 19370var makeWrappedSourceBuffer = function makeWrappedSourceBuffer(mediaSource, mimeType) { 19371 var sourceBuffer = mediaSource.addSourceBuffer(mimeType); 19372 var wrapper = Object.create(null); 19373 19374 wrapper.updating = false; 19375 wrapper.realBuffer_ = sourceBuffer; 19376 19377 var _loop = function (key) { 19378 if (typeof sourceBuffer[key] === 'function') { 19379 wrapper[key] = function () { 19380 return sourceBuffer[key].apply(sourceBuffer, arguments); 19381 }; 19382 } else if (typeof wrapper[key] === 'undefined') { 19383 Object.defineProperty(wrapper, key, { 19384 get: function get() { 19385 return sourceBuffer[key]; 19386 }, 19387 set: function set(v) { 19388 return sourceBuffer[key] = v; 19389 } 19390 }); 19391 } 19392 }; 19393 19394 for (var key in sourceBuffer) { 19395 _loop(key); 19396 } 19397 19398 return wrapper; 19399}; 19400 19401/** 19402 * VirtualSourceBuffers exist so that we can transmux non native formats 19403 * into a native format, but keep the same api as a native source buffer. 19404 * It creates a transmuxer, that works in its own thread (a web worker) and 19405 * that transmuxer muxes the data into a native format. VirtualSourceBuffer will 19406 * then send all of that data to the naive sourcebuffer so that it is 19407 * indestinguishable from a natively supported format. 19408 * 19409 * @param {HtmlMediaSource} mediaSource the parent mediaSource 19410 * @param {Array} codecs array of codecs that we will be dealing with 19411 * @class VirtualSourceBuffer 19412 * @extends video.js.EventTarget 19413 */ 19414 19415var VirtualSourceBuffer = (function (_videojs$EventTarget) { 19416 _inherits(VirtualSourceBuffer, _videojs$EventTarget); 19417 19418 function VirtualSourceBuffer(mediaSource, codecs) { 19419 var _this = this; 19420 19421 _classCallCheck(this, VirtualSourceBuffer); 19422 19423 _get(Object.getPrototypeOf(VirtualSourceBuffer.prototype), 'constructor', this).call(this, _videoJs2['default'].EventTarget); 19424 this.timestampOffset_ = 0; 19425 this.pendingBuffers_ = []; 19426 this.bufferUpdating_ = false; 19427 19428 this.mediaSource_ = mediaSource; 19429 this.codecs_ = codecs; 19430 this.audioCodec_ = null; 19431 this.videoCodec_ = null; 19432 this.audioDisabled_ = false; 19433 this.appendAudioInitSegment_ = true; 19434 19435 var options = { 19436 remux: false 19437 }; 19438 19439 this.codecs_.forEach(function (codec) { 19440 if ((0, _codecUtils.isAudioCodec)(codec)) { 19441 _this.audioCodec_ = codec; 19442 } else if ((0, _codecUtils.isVideoCodec)(codec)) { 19443 _this.videoCodec_ = codec; 19444 } 19445 }); 19446 19447 // append muxed segments to their respective native buffers as 19448 // soon as they are available 19449 this.transmuxer_ = (0, _webworkify2['default'])(_transmuxerWorker2['default']); 19450 this.transmuxer_.postMessage({ action: 'init', options: options }); 19451 19452 this.transmuxer_.onmessage = function (event) { 19453 if (event.data.action === 'data') { 19454 return _this.data_(event); 19455 } 19456 19457 if (event.data.action === 'done') { 19458 return _this.done_(event); 19459 } 19460 }; 19461 19462 // this timestampOffset is a property with the side-effect of resetting 19463 // baseMediaDecodeTime in the transmuxer on the setter
19464 Object.defineProperty(this, 'timestampOffset', { 19465 get: function get() { 19466 return this.timestampOffset_; 19467 }, 19468 set: function set(val) { 19469 if (typeof val === 'number' && val >= 0) { 19470 this.timestampOffset_ = val; 19471 this.appendAudioInitSegment_ = true; 19472 19473 // We have to tell the transmuxer to set the baseMediaDecodeTime to 19474 // the desired timestampOffset for the next segment 19475 this.transmuxer_.postMessage({ 19476 action: 'setTimestampOffset', 19477 timestampOffset: val 19478 }); 19479 } 19480 } 19481 }); 19482 19483 // setting the append window affects both source buffers 19484 Object.defineProperty(this, 'appendWindowStart', { 19485 get: function get() { 19486 return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart; 19487 }, 19488 set: function set(start) { 19489 if (this.videoBuffer_) { 19490 this.videoBuffer_.appendWindowStart = start; 19491 } 19492 if (this.audioBuffer_) { 19493 this.audioBuffer_.appendWindowStart = start; 19494 } 19495 } 19496 }); 19497 19498 // this buffer is "updating" if either of its native buffers are 19499 Object.defineProperty(this, 'updating', { 19500 get: function get() { 19501 return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating); 19502 } 19503 }); 19504 19505 // the buffered property is the intersection of the buffered 19506 // ranges of the native source buffers 19507 Object.defineProperty(this, 'buffered', { 19508 get: function get() { 19509 var start = null; 19510 var end = null; 19511 var arity = 0; 19512 var extents = []; 19513 var ranges = []; 19514 19515 // neither buffer has been created yet 19516 if (!this.videoBuffer_ && !this.audioBuffer_) { 19517 return _videoJs2['default'].createTimeRange(); 19518 } 19519 19520 // only one buffer is configured 19521 if (!this.videoBuffer_) { 19522 return this.audioBuffer_.buffered; 19523 } 19524 if (!this.audioBuffer_) { 19525 return this.videoBuffer_.buffered; 19526 } 19527 19528 // both buffers are configured 19529 if (this.audioDisabled_) { 19530 return this.videoBuffer_.buffered; 19531 } 19532 19533 // both buffers are empty 19534 if (this.videoBuffer_.buffered.length === 0 && this.audioBuffer_.buffered.length === 0) { 19535 return _videoJs2['default'].createTimeRange(); 19536 } 19537 19538 // Handle the case where we have both buffers and create an 19539 // intersection of the two 19540 var videoBuffered = this.videoBuffer_.buffered; 19541 var audioBuffered = this.audioBuffer_.buffered; 19542 var count = videoBuffered.length; 19543 19544 // A) Gather up all start and end times 19545 while (count--) { 19546 extents.push({ time: videoBuffered.start(count), type: 'start' }); 19547 extents.push({ time: videoBuffered.end(count), type: 'end' }); 19548 } 19549 count = audioBuffered.length; 19550 while (count--) { 19551 extents.push({ time: audioBuffered.start(count), type: 'start' }); 19552 extents.push({ time: audioBuffered.end(count), type: 'end' }); 19553 } 19554 // B) Sort them by time 19555 extents.sort(function (a, b) { 19556 return a.time - b.time; 19557 }); 19558 19559 // C) Go along one by one incrementing arity for start and decrementing 19560 // arity for ends 19561 for (count = 0; count < extents.length; count++) { 19562 if (extents[count].type === 'start') { 19563 arity++; 19564 19565 // D) If arity is ever incremented to 2 we are entering an 19566 // overlapping range 19567 if (arity === 2) { 19568 start = extents[count].time; 19569 } 19570 } else if (extents[count].type === 'end') { 19571 arity--; 19572 19573 // E) If arity is ever decremented to 1 we leaving an 19574 // overlapping range 19575 if (arity === 1) { 19576 end = extents[count].time; 19577 } 19578 } 19579 19580 // F) Record overlapping ranges 19581 if (start !== null && end !== null) { 19582 ranges.push([start, end]); 19583 start = null; 19584 end = null; 19585 } 19586 } 19587 19588 return _videoJs2['default'].createTimeRanges(ranges); 19589 } 19590 }); 19591 } 19592 19593 /** 19594 * When we get a data event from the transmuxer 19595 * we call this function and handle the data that 19596 * was sent to us 19597 * 19598 * @private 19599 * @param {Event} event the data event from the transmuxer 19600 */ 19601 19602 _createClass(VirtualSourceBuffer, [{ 19603 key: 'data_', 19604 value: function data_(event) { 19605 var segment = event.data.segment; 19606 19607 // Cast ArrayBuffer to TypedArray 19608 segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength); 19609 19610 segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength); 19611 19612 (0, _createTextTracksIfNecessary2['default'])(this, this.mediaSource_, segment); 19613 19614 // Add the segments to the pendingBuffers array 19615 this.pendingBuffers_.push(segment); 19616 return; 19617 } 19618 19619 /** 19620 * When we get a done event from the transmuxer 19621 * we call this function and we process all 19622 * of the pending data that we have been saving in the 19623 * data_ function 19624 * 19625 * @private 19626 * @param {Event} event the done event from the transmuxer 19627 */ 19628 }, { 19629 key: 'done_', 19630 value: function done_(event) { 19631 // Don't process and append data if the mediaSource is closed 19632 if (this.mediaSource_.readyState === 'closed') { 19633 this.pendingBuffers_.length = 0; 19634 return; 19635 } 19636 19637 // All buffers should have been flushed from the muxer 19638 // start processing anything we have received 19639 this.processPendingSegments_(); 19640 return; 19641 } 19642 19643 /** 19644 * Create our internal native audio/video source buffers and add 19645 * event handlers to them with the following conditions: 19646 * 1. they do not already exist on the mediaSource 19647 * 2. this VSB has a codec for them 19648 * 19649 * @private 19650 */
vendor: 3,306 bytes, lines 19650-19739
19650 19651 }, { 19652 key: 'createRealSourceBuffers_', 19653 value: function createRealSourceBuffers_() { 19654 var _this2 = this; 19655 19656 var types = ['audio', 'video']; 19657 19658 types.forEach(function (type) { 19659 // Don't create a SourceBuffer of this type if we don't have a 19660 // codec for it 19661 if (!_this2[type + 'Codec_']) { 19662 return; 19663 } 19664 19665 // Do nothing if a SourceBuffer of this type already exists 19666 if (_this2[type + 'Buffer_']) { 19667 return; 19668 } 19669 19670 var buffer = null; 19671 19672 // If the mediasource already has a SourceBuffer for the codec 19673 // use that 19674 if (_this2.mediaSource_[type + 'Buffer_']) { 19675 buffer = _this2.mediaSource_[type + 'Buffer_']; 19676 // In multiple audio track cases, the audio source buffer is disabled 19677 // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier 19678 // than createRealSourceBuffers_ is called to create the second 19679 // VirtualSourceBuffer because that happens as a side-effect of 19680 // videojs-contrib-hls starting the audioSegmentLoader. As a result, 19681 // the audioBuffer is essentially "ownerless" and no one will toggle 19682 // the `updating` state back to false once the `updateend` event is received 19683 // 19684 // Setting `updating` to false manually will work around this 19685 // situation and allow work to continue 19686 buffer.updating = false; 19687 } else { 19688 var codecProperty = type + 'Codec_'; 19689 var mimeType = type + '/mp4;codecs="' + _this2[codecProperty] + '"'; 19690 19691 buffer = makeWrappedSourceBuffer(_this2.mediaSource_.nativeMediaSource_, mimeType); 19692 19693 _this2.mediaSource_[type + 'Buffer_'] = buffer; 19694 } 19695 19696 _this2[type + 'Buffer_'] = buffer; 19697 19698 // Wire up the events to the SourceBuffer 19699 ['update', 'updatestart', 'updateend'].forEach(function (event) { 19700 buffer.addEventListener(event, function () { 19701 // if audio is disabled 19702 if (type === 'audio' && _this2.audioDisabled_) { 19703 return; 19704 } 19705 19706 if (event === 'updateend') { 19707 _this2[type + 'Buffer_'].updating = false; 19708 } 19709 19710 var shouldTrigger = types.every(function (t) { 19711 // skip checking audio's updating status if audio 19712 // is not enabled 19713 if (t === 'audio' && _this2.audioDisabled_) { 19714 return true; 19715 } 19716 // if the other type if updating we don't trigger 19717 if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) { 19718 return false; 19719 } 19720 return true; 19721 }); 19722 19723 if (shouldTrigger) { 19724 return _this2.trigger(event); 19725 } 19726 }); 19727 }); 19728 }); 19729 } 19730 19731 /** 19732 * Emulate the native mediasource function, but our function will 19733 * send all of the proposed segments to the transmuxer so that we 19734 * can transmux them before we append them to our internal 19735 * native source buffers in the correct format. 19736 * 19737 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer 19738 * @param {Uint8Array} segment the segment to append to the buffer 19739 */
vendor: 4,661 bytes, lines 19740-19880
19740 }, { 19741 key: 'appendBuffer', 19742 value: function appendBuffer(segment) { 19743 // Start the internal "updating" state 19744 this.bufferUpdating_ = true; 19745 19746 if (this.audioBuffer_ && this.audioBuffer_.buffered.length) { 19747 var audioBuffered = this.audioBuffer_.buffered; 19748 19749 this.transmuxer_.postMessage({ 19750 action: 'setAudioAppendStart', 19751 appendStart: audioBuffered.end(audioBuffered.length - 1) 19752 }); 19753 } 19754 19755 this.transmuxer_.postMessage({ 19756 action: 'push', 19757 // Send the typed-array of data as an ArrayBuffer so that 19758 // it can be sent as a "Transferable" and avoid the costly 19759 // memory copy 19760 data: segment.buffer, 19761 19762 // To recreate the original typed-array, we need information 19763 // about what portion of the ArrayBuffer it was a view into 19764 byteOffset: segment.byteOffset, 19765 byteLength: segment.byteLength 19766 }, [segment.buffer]); 19767 this.transmuxer_.postMessage({ action: 'flush' }); 19768 } 19769 19770 /** 19771 * Emulate the native mediasource function and remove parts 19772 * of the buffer from any of our internal buffers that exist 19773 * 19774 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove 19775 * @param {Double} start position to start the remove at 19776 * @param {Double} end position to end the remove at 19777 */ 19778 }, { 19779 key: 'remove', 19780 value: function remove(start, end) { 19781 if (this.videoBuffer_) { 19782 this.videoBuffer_.updating = true; 19783 this.videoBuffer_.remove(start, end); 19784 } 19785 if (!this.audioDisabled_ && this.audioBuffer_) { 19786 this.audioBuffer_.updating = true; 19787 this.audioBuffer_.remove(start, end); 19788 } 19789 19790 // Remove Metadata Cues (id3) 19791 (0, _removeCuesFromTrack2['default'])(start, end, this.metadataTrack_); 19792 19793 // Remove Any Captions 19794 if (this.inbandTextTracks_) { 19795 for (var track in this.inbandTextTracks_) { 19796 (0, _removeCuesFromTrack2['default'])(start, end, this.inbandTextTracks_[track]); 19797 } 19798 } 19799 } 19800 19801 /** 19802 * Process any segments that the muxer has output 19803 * Concatenate segments together based on type and append them into 19804 * their respective sourceBuffers 19805 * 19806 * @private 19807 */ 19808 }, { 19809 key: 'processPendingSegments_', 19810 value: function processPendingSegments_() { 19811 var sortedSegments = { 19812 video: { 19813 segments: [], 19814 bytes: 0 19815 }, 19816 audio: { 19817 segments: [], 19818 bytes: 0 19819 }, 19820 captions: [], 19821 metadata: [] 19822 }; 19823 19824 // Sort segments into separate video/audio arrays and 19825 // keep track of their total byte lengths 19826 sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) { 19827 var type = segment.type; 19828 var data = segment.data; 19829 var initSegment = segment.initSegment; 19830 19831 segmentObj[type].segments.push(data); 19832 segmentObj[type].bytes += data.byteLength; 19833 19834 segmentObj[type].initSegment = initSegment; 19835 19836 // Gather any captions into a single array 19837 if (segment.captions) { 19838 segmentObj.captions = segmentObj.captions.concat(segment.captions); 19839 } 19840 19841 if (segment.info) { 19842 segmentObj[type].info = segment.info; 19843 } 19844 19845 // Gather any metadata into a single array 19846 if (segment.metadata) { 19847 segmentObj.metadata = segmentObj.metadata.concat(segment.metadata); 19848 } 19849 19850 return segmentObj; 19851 }, sortedSegments); 19852 19853 // Create the real source buffers if they don't exist by now since we 19854 // finally are sure what tracks are contained in the source 19855 if (!this.videoBuffer_ && !this.audioBuffer_) { 19856 // Remove any codecs that may have been specified by default but 19857 // are no longer applicable now 19858 if (sortedSegments.video.bytes === 0) { 19859 this.videoCodec_ = null; 19860 } 19861 if (sortedSegments.audio.bytes === 0) { 19862 this.audioCodec_ = null; 19863 } 19864 19865 this.createRealSourceBuffers_(); 19866 } 19867 19868 if (sortedSegments.audio.info) { 19869 this.mediaSource_.trigger({ type: 'audioinfo', info: sortedSegments.audio.info }); 19870 } 19871 if (sortedSegments.video.info) { 19872 this.mediaSource_.trigger({ type: 'videoinfo', info: sortedSegments.video.info }); 19873 } 19874 19875 if (this.appendAudioInitSegment_) { 19876 if (!this.audioDisabled_ && this.audioBuffer_) { 19877 sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment); 19878 sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength; 19879 } 19880 this.appendAudioInitSegment_ = false;
19881 } 19882 19883 // Merge multiple video and audio segments into one and append 19884 if (this.videoBuffer_) { 19885 sortedSegments.video.segments.unshift(sortedSegments.video.initSegment); 19886 sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength; 19887 this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_); 19888 // TODO: are video tracks the only ones with text tracks? 19889 (0, _addTextTrackData.addTextTrackData)(this, sortedSegments.captions, sortedSegments.metadata); 19890 } 19891 19892 if (!this.audioDisabled_ && this.audioBuffer_) { 19893 this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_); 19894 } 19895 19896 this.pendingBuffers_.length = 0; 19897 19898 // We are no longer in the internal "updating" state 19899 this.bufferUpdating_ = false; 19900 } 19901 19902 /** 19903 * Combine all segments into a single Uint8Array and then append them 19904 * to the destination buffer 19905 * 19906 * @param {Object} segmentObj 19907 * @param {SourceBuffer} destinationBuffer native source buffer to append data to 19908 * @private 19909 */ 19910 }, { 19911 key: 'concatAndAppendSegments_', 19912 value: function concatAndAppendSegments_(segmentObj, destinationBuffer) { 19913 var offset = 0; 19914 var tempBuffer = undefined; 19915 19916 if (segmentObj.bytes) { 19917 tempBuffer = new Uint8Array(segmentObj.bytes); 19918 19919 // Combine the individual segments into one large typed-array 19920 segmentObj.segments.forEach(function (segment) { 19921 tempBuffer.set(segment, offset); 19922 offset += segment.byteLength; 19923 }); 19924 19925 try { 19926 destinationBuffer.updating = true; 19927 destinationBuffer.appendBuffer(tempBuffer); 19928 } catch (error) { 19929 if (this.mediaSource_.player_) { 19930 this.mediaSource_.player_.error({ 19931 code: -3, 19932 type: 'APPEND_BUFFER_ERR', 19933 message: error.message, 19934 originalError: error 19935 }); 19936 } 19937 } 19938 } 19939 } 19940 19941 /** 19942 * Emulate the native mediasource function. abort any soureBuffer 19943 * actions and throw out any un-appended data. 19944 * 19945 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort 19946 */ 19947 }, { 19948 key: 'abort', 19949 value: function abort() { 19950 if (this.videoBuffer_) { 19951 this.videoBuffer_.abort(); 19952 } 19953 if (!this.audioDisabled_ && this.audioBuffer_) { 19954 this.audioBuffer_.abort(); 19955 } 19956 if (this.transmuxer_) { 19957 this.transmuxer_.postMessage({ action: 'reset' }); 19958 } 19959 this.pendingBuffers_.length = 0; 19960 this.bufferUpdating_ = false; 19961 } 19962 }]); 19963 19964 return VirtualSourceBuffer; 19965})(_videoJs2['default'].EventTarget); 19966 19967exports['default'] = VirtualSourceBuffer; 19968module.exports = exports['default']; 19969}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 19970},{"./add-text-track-data":64,"./codec-utils":65,"./create-text-tracks-if-necessary":66,"./remove-cues-from-track":72,"./transmuxer-worker":73,"webworkify":76}],76:[function(require,module,exports){ 19971var bundleFn = arguments[3]; 19972var sources = arguments[4]; 19973var cache = arguments[5]; 19974 19975var stringify = JSON.stringify; 19976 19977module.exports = function (fn) { 19978 var keys = []; 19979 var wkey; 19980 var cacheKeys = Object.keys(cache); 19981 19982 for (var i = 0, l = cacheKeys.length; i < l; i++) { 19983 var key = cacheKeys[i]; 19984 if (cache[key].exports === fn) { 19985 wkey = key; 19986 break; 19987 } 19988 } 19989 19990 if (!wkey) { 19991 wkey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16); 19992 var wcache = {}; 19993 for (var i = 0, l = cacheKeys.length; i < l; i++) { 19994 var key = cacheKeys[i]; 19995 wcache[key] = key; 19996 } 19997 sources[wkey] = [ 19998 Function(['require','module','exports'], '(' + fn + ')(self)'), 19999 wcache 20000 ]; 20001 } 20002 var skey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16); 20003 20004 var scache = {}; scache[wkey] = wkey; 20005 sources[skey] = [ 20006 Function(['require'],'require(' + stringify(wkey) + ')(self)'), 20007 scache 20008 ]; 20009 20010 var src = '(' + bundleFn + ')({' 20011 + Object.keys(sources).map(function (key) { 20012 return stringify(key) + ':[' 20013 + sources[key][0] 20014 + ',' + stringify(sources[key][1]) + ']' 20015 ; 20016 }).join(',') 20017 + '},{},[' + stringify(skey) + '])' 20018 ; 20019 20020 var URL = window.URL || window.webkitURL || window.mozURL || window.msURL; 20021 20022 return new Worker(URL.createObjectURL( 20023 new Blob([src], { type: 'text/javascript' }) 20024 )); 20025}; 20026 20027},{}],77:[function(require,module,exports){ 20028(function (global){ 20029/** 20030 * @file videojs-contrib-hls.js 20031 * 20032 * The main file for the HLS project. 20033 * License: https://github.com/videojs/videojs-contrib-hls/blob/master/LICENSE 20034 */ 20035'use strict'; 20036 20037var _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; }; })(); 20038 20039var _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); } } }; 20040 20041function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 20042 20043function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 20044 20045function _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, { c
20045onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 20046 20047var _globalDocument = require('global/document'); 20048 20049var _globalDocument2 = _interopRequireDefault(_globalDocument); 20050 20051var _playlistLoader = require('./playlist-loader'); 20052 20053var _playlistLoader2 = _interopRequireDefault(_playlistLoader); 20054 20055var _playlist = require('./playlist'); 20056 20057var _playlist2 = _interopRequireDefault(_playlist); 20058 20059var _xhr = require('./xhr'); 20060 20061var _xhr2 = _interopRequireDefault(_xhr); 20062 20063var _aesDecrypter = require('aes-decrypter'); 20064 20065var _binUtils = require('./bin-utils'); 20066 20067var _binUtils2 = _interopRequireDefault(_binUtils); 20068 20069var _videojsContribMediaSources = require('videojs-contrib-media-sources'); 20070 20071var _m3u8Parser = require('m3u8-parser'); 20072 20073var _m3u8Parser2 = _interopRequireDefault(_m3u8Parser); 20074 20075var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 20076 20077var _videoJs2 = _interopRequireDefault(_videoJs); 20078 20079var _masterPlaylistController = require('./master-playlist-controller'); 20080 20081var _config = require('./config'); 20082 20083var _config2 = _interopRequireDefault(_config); 20084 20085var _renditionMixin = require('./rendition-mixin'); 20086 20087var _renditionMixin2 = _interopRequireDefault(_renditionMixin); 20088 20089var _globalWindow = require('global/window'); 20090 20091var _globalWindow2 = _interopRequireDefault(_globalWindow); 20092 20093var _playbackWatcher = require('./playback-watcher'); 20094 20095var _playbackWatcher2 = _interopRequireDefault(_playbackWatcher); 20096 20097var _reloadSourceOnError = require('./reload-source-on-error'); 20098 20099var _reloadSourceOnError2 = _interopRequireDefault(_reloadSourceOnError); 20100 20101var _playlistSelectorsJs = require('./playlist-selectors.js'); 20102 20103var Hls = { 20104 PlaylistLoader: _playlistLoader2['default'], 20105 Playlist: _playlist2['default'], 20106 Decrypter: _aesDecrypter.Decrypter, 20107 AsyncStream: _aesDecrypter.AsyncStream, 20108 decrypt: _aesDecrypter.decrypt, 20109 utils: _binUtils2['default'], 20110 20111 STANDARD_PLAYLIST_SELECTOR: _playlistSelectorsJs.lastBandwidthSelector, 20112 INITIAL_PLAYLIST_SELECTOR: _playlistSelectorsJs.lowestBitrateCompatibleVariantSelector, 20113 comparePlaylistBandwidth: _playlistSelectorsJs.comparePlaylistBandwidth, 20114 comparePlaylistResolution: _playlistSelectorsJs.comparePlaylistResolution, 20115 20116 xhr: (0, _xhr2['default'])() 20117}; 20118 20119// 0.5 MB/s 20120var INITIAL_BANDWIDTH = 4194304; 20121 20122// Define getter/setters for config properites 20123['GOAL_BUFFER_LENGTH', 'MAX_GOAL_BUFFER_LENGTH', 'GOAL_BUFFER_LENGTH_RATE', 'BUFFER_LOW_WATER_LINE', 'MAX_BUFFER_LOW_WATER_LINE', 'BUFFER_LOW_WATER_LINE_RATE', 'BANDWIDTH_VARIANCE'].forEach(function (prop) { 20124 Object.defineProperty(Hls, prop, { 20125 get: function get() { 20126 _videoJs2['default'].log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing'); 20127 return _config2['default'][prop]; 20128 }, 20129 set: function set(value) { 20130 _videoJs2['default'].log.warn('using Hls.' + prop + ' is UNSAFE be sure you know what you are doing'); 20131 20132 if (typeof value !== 'number' || value < 0) { 20133 _videoJs2['default'].log.warn('value of Hls.' + prop + ' must be greater than or equal to 0'); 20134 return; 20135 } 20136 20137 _config2['default'][prop] = value; 20138 } 20139 }); 20140}); 20141 20142/** 20143 * Updates the selectedIndex of the QualityLevelList when a mediachange happens in hls. 20144 * 20145 * @param {QualityLevelList} qualityLevels The QualityLevelList to update. 20146 * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info. 20147 * @function handleHlsMediaChange 20148 */ 20149var handleHlsMediaChange = function handleHlsMediaChange(qualityLevels, playlistLoader) { 20150 var newPlaylist = playlistLoader.media(); 20151 var selectedIndex = -1; 20152 20153 for (var i = 0; i < qualityLevels.length; i++) { 20154 if (qualityLevels[i].id === newPlaylist.uri) { 20155 selectedIndex = i; 20156 break; 20157 } 20158 } 20159 20160 qualityLevels.selectedIndex_ = selectedIndex; 20161 qualityLevels.trigger({ 20162 selectedIndex: selectedIndex, 20163 type: 'change' 20164 }); 20165}; 20166 20167/** 20168 * Adds quality levels to list once playlist metadata is available 20169 * 20170 * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to. 20171 * @param {Object} hls Hls object to listen to for media events. 20172 * @function handleHlsLoadedMetadata 20173 */ 20174var handleHlsLoadedMetadata = function handleHlsLoadedMetadata(qualityLevels, hls) {
20175 hls.representations().forEach(function (rep) { 20176 qualityLevels.addQualityLevel(rep); 20177 }); 20178 handleHlsMediaChange(qualityLevels, hls.playlists); 20179}; 20180 20181// HLS is a source handler, not a tech. Make sure attempts to use it 20182// as one do not cause exceptions. 20183Hls.canPlaySource = function () { 20184 return _videoJs2['default'].log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 20185}; 20186 20187/** 20188 * Whether the browser has built-in HLS support. 20189 */ 20190Hls.supportsNativeHls = (function () { 20191 var video = _globalDocument2['default'].createElement('video'); 20192 20193 // native HLS is definitely not supported if HTML5 video isn't 20194 if (!_videoJs2['default'].getTech('Html5').isSupported()) { 20195 return false; 20196 } 20197 20198 // HLS manifests can go by many mime-types 20199 var canPlay = [ 20200 // Apple santioned 20201 'application/vnd.apple.mpegurl', 20202 // Apple sanctioned for backwards compatibility 20203 'audio/mpegurl', 20204 // Very common 20205 'audio/x-mpegurl', 20206 // Very common 20207 'application/x-mpegurl', 20208 // Included for completeness 20209 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl']; 20210 20211 return canPlay.some(function (canItPlay) { 20212 return (/maybe|probably/i.test(video.canPlayType(canItPlay)) 20213 ); 20214 }); 20215})(); 20216 20217/** 20218 * HLS is a source handler, not a tech. Make sure attempts to use it 20219 * as one do not cause exceptions. 20220 */ 20221Hls.isSupported = function () { 20222 return _videoJs2['default'].log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 20223}; 20224 20225var Component = _videoJs2['default'].getComponent('Component'); 20226 20227/** 20228 * The Hls Handler object, where we orchestrate all of the parts 20229 * of HLS to interact with video.js 20230 * 20231 * @class HlsHandler 20232 * @extends videojs.Component 20233 * @param {Object} source the soruce object 20234 * @param {Tech} tech the parent tech object 20235 * @param {Object} options optional and required options 20236 */ 20237 20238var HlsHandler = (function (_Component) { 20239 _inherits(HlsHandler, _Component); 20240 20241 function HlsHandler(source, tech, options) { 20242 var _this = this; 20243 20244 _classCallCheck(this, HlsHandler); 20245 20246 _get(Object.getPrototypeOf(HlsHandler.prototype), 'constructor', this).call(this, tech, options.hls); 20247 20248 // tech.player() is deprecated but setup a reference to HLS for 20249 // backwards-compatibility 20250 if (tech.options_ && tech.options_.playerId) { 20251 var _player = (0, _videoJs2['default'])(tech.options_.playerId); 20252 20253 if (!_player.hasOwnProperty('hls')) { 20254 Object.defineProperty(_player, 'hls', { 20255 get: function get() { 20256 _videoJs2['default'].log.warn('player.hls is deprecated. Use player.tech_.hls instead.'); 20257 tech.trigger({ type: 'usage', name: 'hls-player-access' }); 20258 return _this; 20259 } 20260 }); 20261 } 20262 } 20263 20264 this.tech_ = tech; 20265 this.source_ = source; 20266 this.stats = {}; 20267 this.ignoreNextSeekingEvent_ = false; 20268 this.setOptions_(); 20269 20270 // overriding native HLS only works if audio tracks have been emulated 20271 // error early if we're misconfigured: 20272 if (this.options_.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) { 20273 throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB'); 20274 } 20275 20276 // listen for fullscreenchange events for this player so that we 20277 // can adjust our quality selection quickly 20278 this.on(_globalDocument2['default'], ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) { 20279 var fullscreenElement = _globalDocument2['default'].fullscreenElement || _globalDocument2['default'].webkitFullscreenElement || _globalDocument2['default'].mozFullScreenElement || _globalDocument2['default'].msFullscreenElement; 20280 20281 if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) { 20282 _this.masterPlaylistController_.fastQualityChange_(); 20283 } 20284 }); 20285 20286 this.on(this.tech_, 'seeking', function () { 20287 if (this.ignoreNextSeekingEvent_) { 20288 this.ignoreNextSeekingEvent_ = false; 20289 return; 20290 } 20291 20292 this.setCurrentTime(this.tech_.currentTime()); 20293 }); 20294 this.on(this.tech_, 'error', function () { 20295 if (this.masterPlaylistController_) { 20296 this.masterPlaylistController_.pauseLoading(); 20297 } 20298 }); 20299 20300 this.on(this.tech_, 'play', this.play); 20301 } 20302 20303 /** 20304 * The Source Handler object, which informs video.js what additional 20305 * MIME types are supported and sets up playback. It is registered 20306 * automatically to the appropriate tech based on the capabilities of 20307 * the browser it is running in. It is not necessary to use or modify 20308 * this object in normal usage. 20309 */ 20310 20311 _createClass(HlsHandler, [{ 20312 key: 'setOptions_', 20313 value: function setOptions_() { 20314 var _this2 = this; 20315 20316 // defaults 20317 this.options_.withCredentials = this.options_.withCredentials || false; 20318 20319 if (typeof this.options_.blacklistDuration !== 'number') { 20320 this.options_.blacklistDuration = 5 * 60; 20321 } 20322 20323 // start playlist selection at a reasonable bandwidth for 20324 // broadband internet (0.5 MB/s) or mobile (0.0625 MB/s) 20325 if (typeof this.options_.bandwidth !== 'number') { 20326 this.options_.bandwidth = INITIAL_BANDWIDTH; 20327 } 20328 20329 // If the bandwidth number is unchanged from the initial setting 20330 // then this takes precedence over the enableLowInitialPlaylist option 20331 this.options_.enableLowInitialPlaylist = this.options_.enableLowInitialPlaylist && this.options_.bandwidth === INITIAL_BANDWIDTH; 20332 20333 // grab options passed to player.src 20334 ['withCredentials', 'bandwidth'].forEach(function (option) { 20335 if (typeof _this2.source_[option] !== 'undefined') { 20336 _this2.options_[option] = _this2.source_[option]; 20337 } 20338 }); 20339 20340 this.bandwidth = this.options_.bandwidth; 20341 } 20342 20343 /** 20344 * called when player.src gets called, handle a new source 20345 * 20346 * @param {Object} src the source object to handle 20347 */ 20348 }, { 20349 key: 'src', 20350 value: function src(_src) { 20351 var _this3 = this; 20352 20353 // do nothing if the src is falsey 20354 if (!_src) { 20355 return; 20356 } 20357 this.setOptions_(); 20358 // add master playlist controller options 20359 this.options_.url = this.source_.src; 20360 this.options_.tech = this.tech_; 20361 this.options_.externHls = Hls; 20362 20363 this.masterPlaylistController_ = new _masterPlaylistController.MasterPlaylistController(this.options_); 20364 this.playbackWatcher_ = new _playbackWatcher2['default'](_videoJs2['default'].mergeOptions(this.options_, { 20365 seekable: function seekable() { 20366 return _this3.seekable(); 20367 } 20368 })); 20369 20370 this.masterPlaylistController_.on('error', function () { 20371 var player = _videoJs2['default'].players[_this3.tech_.options_.playerId]; 20372 20373 player.error(_this3.masterPlaylistController_.error); 20374 }); 20375 20376 // `this` in selectPlaylist should be the HlsHandler for backwards 20377 // compatibility with < v2 20378 this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls.STANDARD_PLAYLIST_SELECTOR.bind(this); 20379 20380 this.masterPlaylistController_.selectInitialPlaylist = Hls.INITIAL_PLAYLIST_SELECTOR.bind(this); 20381 20382 // re-expose some internal objects for backwards compatibility with < v2 20383 this.playlists = this.masterPlaylistController_.masterPlaylistLoader_; 20384 this.mediaSource = this.masterPlaylistController_.mediaSource; 20385 20386 // Proxy assignment of some properties to the master playlist 20387 // controller. Using a custom property for backwards compatibility 20388 // with < v2 20389 Object.defineProperties(this, { 20390 selectPlaylist: { 20391 get: function get() { 20392 return this.masterPlaylistController_.selectPlaylist; 20393 }, 20394 set: function set(selectPlaylist) { 20395 this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this); 20396 } 20397 }, 20398 throughput: { 20399 get: function get() { 20400 return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate; 20401 }, 20402 set: function set(throughput) { 20403 this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; 20404 // By setting `count` to 1 the throughput value becomes the starting value 20405 // for the cumulative average 20406 this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1; 20407 } 20408 }, 20409 bandwidth: { 20410 get: function get() { 20411 return this.masterPlaylistController_.mainSegmentLoader_.bandwidth; 20412 }, 20413 set: function set(bandwidth) { 20414 this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; 20415 // setting the bandwidth manually resets the throughput counter 20416 // `count` is set to zero that current value of `rate` isn't included 20417 // in the cumulative average 20418 this.masterPlaylistController_.mainSegmentLoader_.throughput = { 20419 rate: 0, 20420 count: 0 20421 }; 20422 } 20423 }, 20424 /** 20425 * `systemBandwidth` is a combination of two serial processes bit-rates. The first 20426 * is the network bitrate provided by `bandwidth` and the second is the bitrate of 20427 * the entire process after that - decryption, transmuxing, and appending - provided 20428 * by `throughput`. 20429 * 20430 * Since the two process are serial, the overall system bandwidth is given by: 20431 * sysBandwidth = 1 / (1 / bandwidth + 1 / throughput) 20432 */ 20433 systemBandwidth: { 20434 get: function get() { 20435 var invBandwidth = 1 / (this.bandwidth || 1); 20436 var invThroughput = undefined; 20437 20438 if (this.throughput > 0) { 20439 invThroughput = 1 / this.throughput; 20440 } else { 20441 invThroughput = 0; 20442 } 20443 20444 var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput)); 20445 20446 return systemBitrate; 20447 }, 20448 set: function set() { 20449 _videoJs2['default'].log.error('The "systemBandwidth" property is read-only'); 20450 } 20451 } 20452 }); 20453 20454 Object.defineProperties(this.stats, { 20455 bandwidth: { 20456 get: function get() { 20457 return _this3.bandwidth || 0; 20458 }, 20459 enumerable: true 20460 }, 20461 mediaRequests: { 20462 get: function get() { 20463 return _this3.masterPlaylistController_.mediaRequests_() || 0; 20464 }, 20465 enumerable: true 20466 }, 20467 mediaRequestsAborted: { 20468 get: function get() { 20469 return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0; 20470 }, 20471 enumerable: true 20472 }, 20473 mediaRequestsTimedout: { 20474 get: function get() { 20475 return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0; 20476 }, 20477 enumerable: true 20478 }, 20479 mediaRequestsErrored: { 20480 get: function get() { 20481 return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0; 20482 }, 20483 enumerable: true 20484 }, 20485 mediaTransferDuration: { 20486 get: function get() { 20487 return _this3.masterPlaylistController_.mediaTransferDuration_() || 0; 20488 }, 20489 enumerable: true 20490 }, 20491 mediaBytesTransferred: { 20492 get: function get() { 20493 return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0; 20494 }, 20495 enumerable: true 20496 }, 20497 mediaSecondsLoaded: { 20498 get: function get() { 20499 return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0; 20500 }, 20501 enumerable: true 20502 } 20503 }); 20504 20505 this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_)); 20506 20507 this.masterPlaylistController_.on('selectedinitialmedia', function () { 20508 // Add the manual rendition mix-in to HlsHandler 20509 (0, _renditionMixin2['default'])(_this3); 20510 }); 20511 20512 // the bandwidth of the primary segment loader is our best 20513 // estimate of overall bandwidth 20514 this.on(this.masterPlaylistController_, 'progress', function () { 20515 this.tech_.trigger('progress'); 20516 }); 20517 20518 // In the live case, we need to ignore the very first `seeking` event since 20519 // that will be the result of the seek-to-live behavior 20520 this.on(this.masterPlaylistController_, 'firstplay', function () { 20521 this.ignoreNextSeekingEvent_ = true; 20522 }); 20523 20524 this.tech_.ready(function () { 20525 return _this3.setupQualityLevels_(); 20526 }); 20527 20528 // do nothing if the tech has been disposed already 20529 // this can occur if someone sets the src in player.ready(), for instance 20530 if (!this.tech_.el()) { 20531 return; 20532 } 20533 20534 this.tech_.src(_videoJs2['default'].URL.createObjectURL(this.masterPlaylistController_.mediaSource)); 20535 } 20536 20537 /** 20538 * Initializes the quality levels and sets listeners to update them. 20539 * 20540 * @method setupQualityLevels_ 20541 * @private 20542 */ 20543 }, { 20544 key: 'setupQualityLevels_', 20545 value: function setupQualityLevels_() { 20546 var _this4 = this; 20547 20548 var player = _videoJs2['default'].players[this.tech_.options_.playerId]; 20549 20550 if (player && player.qualityLevels) {
vendor: 1,476 bytes, lines 20551-20610
20551 this.qualityLevels_ = player.qualityLevels(); 20552 20553 this.masterPlaylistController_.on('selectedinitialmedia', function () { 20554 handleHlsLoadedMetadata(_this4.qualityLevels_, _this4); 20555 }); 20556 20557 this.playlists.on('mediachange', function () { 20558 handleHlsMediaChange(_this4.qualityLevels_, _this4.playlists); 20559 }); 20560 } 20561 } 20562 20563 /** 20564 * Begin playing the video. 20565 */ 20566 }, { 20567 key: 'play', 20568 value: function play() { 20569 this.masterPlaylistController_.play(); 20570 } 20571 20572 /** 20573 * a wrapper around the function in MasterPlaylistController 20574 */ 20575 }, { 20576 key: 'setCurrentTime', 20577 value: function setCurrentTime(currentTime) { 20578 this.masterPlaylistController_.setCurrentTime(currentTime); 20579 } 20580 20581 /** 20582 * a wrapper around the function in MasterPlaylistController 20583 */ 20584 }, { 20585 key: 'duration', 20586 value: function duration() { 20587 return this.masterPlaylistController_.duration(); 20588 } 20589 20590 /** 20591 * a wrapper around the function in MasterPlaylistController 20592 */ 20593 }, { 20594 key: 'seekable', 20595 value: function seekable() { 20596 return this.masterPlaylistController_.seekable(); 20597 } 20598 20599 /** 20600 * Abort all outstanding work and cleanup. 20601 */ 20602 }, { 20603 key: 'dispose', 20604 value: function dispose() { 20605 if (this.playbackWatcher_) { 20606 this.playbackWatcher_.dispose(); 20607 } 20608 if (this.masterPlaylistController_) { 20609 this.masterPlaylistController_.dispose(); 20610 }
20611 if (this.qualityLevels_) { 20612 this.qualityLevels_.dispose(); 20613 } 20614 _get(Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this); 20615 } 20616 }]); 20617 20618 return HlsHandler; 20619})(Component); 20620 20621var HlsSourceHandler = function HlsSourceHandler(mode) { 20622 return { 20623 canHandleSource: function canHandleSource(srcObj) { 20624 var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; 20625 20626 var localOptions = _videoJs2['default'].mergeOptions(_videoJs2['default'].options, options); 20627 20628 // this forces video.js to skip this tech/mode if its not the one we have been 20629 // overriden to use, by returing that we cannot handle the source. 20630 if (localOptions.hls && localOptions.hls.mode && localOptions.hls.mode !== mode) { 20631 return false; 20632 } 20633 return HlsSourceHandler.canPlayType(srcObj.type, localOptions); 20634 }, 20635 handleSource: function handleSource(source, tech) { 20636 var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; 20637 20638 var localOptions = _videoJs2['default'].mergeOptions(_videoJs2['default'].options, options, { hls: { mode: mode } }); 20639 20640 if (mode === 'flash') { 20641 // We need to trigger this asynchronously to give others the chance 20642 // to bind to the event when a source is set at player creation 20643 tech.setTimeout(function () { 20644 tech.trigger('loadstart'); 20645 }, 1); 20646 } 20647 20648 tech.hls = new HlsHandler(source, tech, localOptions); 20649 tech.hls.xhr = (0, _xhr2['default'])(); 20650 20651 tech.hls.src(source.src); 20652 return tech.hls; 20653 }, 20654 canPlayType: function canPlayType(type) { 20655 var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; 20656 20657 var localOptions = _videoJs2['default'].mergeOptions(_videoJs2['default'].options, options); 20658 20659 if (HlsSourceHandler.canPlayType(type, localOptions)) { 20660 return 'maybe'; 20661 } 20662 return ''; 20663 } 20664 }; 20665}; 20666 20667HlsSourceHandler.canPlayType = function (type, options) { 20668 // No support for IE 10 or below 20669 if (_videoJs2['default'].browser.IE_VERSION && _videoJs2['default'].browser.IE_VERSION <= 10) { 20670 return false; 20671 } 20672 20673 var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i; 20674 20675 // favor native HLS support if it's available 20676 if (!options.hls.overrideNative && Hls.supportsNativeHls) { 20677 return false; 20678 } 20679 return mpegurlRE.test(type); 20680}; 20681 20682if (typeof _videoJs2['default'].MediaSource === 'undefined' || typeof _videoJs2['default'].URL === 'undefined') { 20683 _videoJs2['default'].MediaSource = _videojsContribMediaSources.MediaSource; 20684 _videoJs2['default'].URL = _videojsContribMediaSources.URL; 20685} 20686 20687var flashTech = _videoJs2['default'].getTech('Flash'); 20688 20689// register source handlers with the appropriate techs 20690if (_videojsContribMediaSources.MediaSource.supportsNativeMediaSources()) { 20691 _videoJs2['default'].getTech('Html5').registerSourceHandler(HlsSourceHandler('html5'), 0); 20692} 20693if (_globalWindow2['default'].Uint8Array && flashTech) { 20694 flashTech.registerSourceHandler(HlsSourceHandler('flash')); 20695} 20696 20697_videoJs2['default'].HlsHandler = HlsHandler; 20698_videoJs2['default'].HlsSourceHandler = HlsSourceHandler; 20699_videoJs2['default'].Hls = Hls; 20700if (!_videoJs2['default'].use) { 20701 _videoJs2['default'].registerComponent('Hls', Hls); 20702} 20703_videoJs2['default'].m3u8 = _m3u8Parser2['default']; 20704_videoJs2['default'].options.hls = _videoJs2['default'].options.hls || {}; 20705 20706if (_videoJs2['default'].registerPlugin) { 20707 _videoJs2['default'].registerPlugin('reloadSourceOnError', _reloadSourceOnError2['default']); 20708} else { 20709 _videoJs2['default'].plugin('reloadSourceOnError', _reloadSourceOnError2['default']); 20710} 20711 20712module.exports = { 20713 Hls: Hls, 20714 HlsHandler: HlsHandler, 20715 HlsSourceHandler: HlsSourceHandler 20716}; 20717}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 20718},{"./bin-utils":2,"./config":3,"./master-playlist-controller":5,"./playback-watcher":8,"./playlist":11,"./playlist-loader":9,"./playlist-selectors.js":10,"./reload-source-on-error":13,"./rendition-mixin":14,"./xhr":21,"aes-decrypter":25,"global/document":31,"global/window":32,"m3u8-parser":33,"videojs-contrib-media-sources":74}]},{},[77])(77) 20719});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.