1/** 2 * videojs-contrib-hls 3 * @version 3.6.7 4 * @copyright 2016 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":25}],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 * utils to help dump binary data to the console 160 */ 161var utils = { 162 hexDump: function hexDump(data) { 163 var bytes = Array.prototype.slice.call(data); 164 var step = 16; 165 var result = ''; 166 var hex = undefined; 167 var ascii = undefined; 168 169 for (var j = 0;
169 j < bytes.length / step; j++) { 170 hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(''); 171 ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join(''); 172 result += hex + ' ' + ascii + '\n'; 173 } 174 return result; 175 }, 176 tagDump: function tagDump(tag) { 177 return utils.hexDump(tag.bytes); 178 }, 179 textRanges: function textRanges(ranges) { 180 var result = ''; 181 var i = undefined; 182 183 for (i = 0; i < ranges.length; i++) { 184 result += textRange(ranges, i) + ' '; 185 } 186 return result; 187 } 188}; 189 190exports['default'] = utils; 191module.exports = exports['default']; 192},{}],3:[function(require,module,exports){ 193"use strict"; 194 195Object.defineProperty(exports, "__esModule", { 196 value: true 197}); 198exports["default"] = { 199 GOAL_BUFFER_LENGTH: 30 200}; 201module.exports = exports["default"]; 202},{}],4:[function(require,module,exports){ 203(function (global){ 204/** 205 * @file gap-skipper.js 206 */ 207'use strict'; 208 209Object.defineProperty(exports, '__esModule', { 210 value: true 211}); 212 213var _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; }; })(); 214 215function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 216 217function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 218 219var _ranges = require('./ranges'); 220 221var _ranges2 = _interopRequireDefault(_ranges); 222 223var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 224 225var _videoJs2 = _interopRequireDefault(_videoJs); 226 227// Set of events that reset the gap-skipper logic and clear the timeout 228var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error']; 229 230/** 231 * The gap skipper object handles all scenarios 232 * where the player runs into the end of a buffered 233 * region and there is a buffered region ahead. 234 * 235 * It then handles the skipping behavior by setting a 236 * timer to the size (in time) of the gap. This gives 237 * the hls segment fetcher time to close the gap and 238 * resume playing before the timer is triggered and 239 * the gap skipper simply seeks over the gap as a 240 * last resort to resume playback. 241 * 242 * @class GapSkipper 243 */ 244 245var GapSkipper = (function () { 246 /** 247 * Represents a GapSKipper object. 248 * @constructor 249 * @param {object} options an object that includes the tech and settings 250 */ 251 252 function GapSkipper(options) { 253 var _this = this; 254 255 _classCallCheck(this, GapSkipper); 256 257 this.tech_ = options.tech; 258 this.consecutiveUpdates = 0; 259 this.lastRecordedTime = null; 260 this.timer_ = null; 261 this.checkCurrentTimeTimeout_ = null; 262 263 if (options.debug) { 264 this.logger_ = _videoJs2['default'].log.bind(_videoJs2['default'], 'gap-skipper ->'); 265 } 266 this.logger_('initialize'); 267 268 var waitingHandler = function waitingHandler() { 269 return _this.waiting_(); 270 }; 271 var cancelTimerHandler = function cancelTimerHandler() { 272 return _this.cancelTimer_(); 273 }; 274 275 this.tech_.on('waiting', waitingHandler); 276 this.tech_.on(timerCancelEvents, cancelTimerHandler); 277 this.monitorCurrentTime_(); 278 279 // Define the dispose function to clean up our events 280 this.dispose = function () { 281 _this.logger_('dispose'); 282 _this.tech_.off('waiting', waitingHandler); 283 _this.tech_.off(timerCancelEvents, cancelTimerHandler); 284 if (_this.checkCurrentTimeTimeout_) { 285 clearTimeout(_this.checkCurrentTimeTimeout_); 286 } 287 _this.cancelTimer_(); 288 }; 289 } 290 291 /** 292 * Periodically check for timeupdates to see if a gap has been encountered. 293 * 294 * @private 295 */ 296 297 _createClass(GapSkipper, [{ 298 key: 'monitorCurrentTime_', 299 value: function monitorCurrentTime_() { 300 this.checkCurrentTime_(); 301 302 if (this.checkCurrentTimeTimeout_) { 303 clearTimeout(this.checkCurrentTimeTimeout_); 304 } 305 306 // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 307 this.checkCurrentTimeTimeout_ = setTimeout(this.monitorCurrentTime_.bind(this), 250); 308 } 309 310 /** 311 * Handler for `waiting` events from the player 312 * 313 * @private 314 */ 315 }, { 316 key: 'waiting_', 317 value: function waiting_() { 318 if (!this.tech_.seeking()) { 319 this.setTimer_(); 320 } 321 } 322 323 /** 324 * The purpose of this function is to emulate the "waiting" event on 325 * browsers that do not emit it when they are waiting for more 326 * data to continue playback 327 * 328 * @private 329 */ 330 }, { 331 key: 'checkCurrentTime_', 332 value: function checkCurrentTime_() { 333 if (this.tech_.paused() || this.tech_.seeking()) { 334 return; 335 } 336 337 var currentTime = this.tech_.currentTime(); 338 339 if (this.consecutiveUpdates === 5 && currentTime === this.lastRecordedTime) { 340 this.consecutiveUpdates++; 341 this.waiting_(); 342 } else if (currentTime === this.lastRecordedTime) { 343 this.consecutiveUpdates++; 344 } else { 345 this.consecutiveUpdates = 0; 346 this.lastRecordedTime = currentTime; 347 } 348 } 349 350 /** 351 * Cancels any pending timers and resets the 'timeupdate' mechanism 352 * designed to detect that we are stalled 353 * 354 * @private 355 */ 356 }, { 357 key: 'cancelTimer_', 358 value: function cancelTimer_() { 359 this.consecutiveUpdates = 0; 360 361 if (this.timer_) { 362 this.logger_('cancelTimer_'); 363 clearTimeout(this.timer_); 364 } 365 366 this.timer_ = null; 367 } 368 369 /** 370 * Timer callback. If playback still has not proceeded, then we seek 371 * to the start of the next buffered region. 372 * 373 * @private 374 */ 375 }, { 376 key: 'skipTheGap_', 377 value: function skipTheGap_(scheduledCurrentTime) { 378 var buffered = this.tech_.buffered(); 379 var currentTime = this.tech_.currentTime(); 380 var nextRange = _ranges2['default'].findNextRange(buffered, currentTime); 381 382 this.consecutiveUpdates = 0; 383 this.timer_ = null; 384 385 if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) { 386 return; 387 } 388 389 this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); 390 391 // only seek if we still have not played 392 this.tech_.setCurrentTime(nextRange.start(0) + _ranges2['default'].TIME_FUDGE_FACTOR); 393 } 394 }, { 395 key: 'gapFromVideoUnderflow_', 396 value: function gapFromVideoUnderflow_(buffered, currentTime) { 397 // At least in Chrome, if there is a gap in the video buffer, the audio will continue 398 // playing for ~3 seconds after the video gap starts. This is done to account for 399 // video buffer underflow/underrun (note that this is not done when there is audio 400 // buffer underflow/underrun -- in that case the video will stop as soon as it 401 // encounters the gap, as audio stalls are more noticeable/jarring to a user than
402 // video stalls). The player's time will reflect the playthrough of audio, so the 403 // time will appear as if we are in a buffered region, even if we are stuck in a 404 // "gap." 405 // 406 // Example: 407 // video buffer: 0 => 10.1, 10.2 => 20 408 // audio buffer: 0 => 20 409 // overall buffer: 0 => 10.1, 10.2 => 20 410 // current time: 13 411 // 412 // Chrome's video froze at 10 seconds, where the video buffer encountered the gap, 413 // however, the audio continued playing until it reached ~3 seconds past the gap 414 // (13 seconds), at which point it stops as well. Since current time is past the 415 // gap, findNextRange will return no ranges. 416 // 417 // To check for this issue, we see if there is a gap that starts somewhere within 418 // a 3 second range (3 seconds +/- 1 second) back from our current time. 419 var gaps = _ranges2['default'].findGaps(buffered); 420 421 for (var i = 0; i < gaps.length; i++) { 422 var start = gaps.start(i); 423 var end = gaps.end(i); 424 425 // gap is starts no more than 4 seconds back 426 if (currentTime - start < 4 && currentTime - start > 2) { 427 return { 428 start: start, 429 end: end 430 }; 431 } 432 } 433 434 return null; 435 } 436 437 /** 438 * Set a timer to skip the unbuffered region. 439 * 440 * @private 441 */ 442 }, { 443 key: 'setTimer_', 444 value: function setTimer_() { 445 var buffered = this.tech_.buffered(); 446 var currentTime = this.tech_.currentTime(); 447 var nextRange = _ranges2['default'].findNextRange(buffered, currentTime); 448 449 if (this.timer_ !== null) { 450 return; 451 } 452 453 if (nextRange.length === 0) { 454 // Even if there is no available next range, there is still a possibility we are 455 // stuck in a gap due to video underflow. 456 var gap = this.gapFromVideoUnderflow_(buffered, currentTime); 457 458 if (gap) { 459 this.logger_('setTimer_:', 'Encountered a gap in video', 'from: ', gap.start, 'to: ', gap.end, 'seeking to current time: ', currentTime); 460 // Even though the video underflowed and was stuck in a gap, the audio overplayed 461 // the gap, leading currentTime into a buffered range. Seeking to currentTime 462 // allows the video to catch up to the audio position without losing any audio 463 // (only suffering ~3 seconds of frozen video and a pause in audio playback). 464 this.tech_.setCurrentTime(currentTime); 465 } 466 return; 467 } 468 469 var difference = nextRange.start(0) - currentTime; 470 471 this.logger_('setTimer_:', 'stopped at:', currentTime, 'setting timer for:', difference, 'seeking to:', nextRange.start(0)); 472 473 this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime); 474 } 475 476 /** 477 * A debugging logger noop that is set to console.log only if debugging 478 * is enabled globally 479 * 480 * @private 481 */ 482 }, { 483 key: 'logger_', 484 value: function logger_() {} 485 }]); 486 487 return GapSkipper; 488})(); 489 490exports['default'] = GapSkipper; 491module.exports = exports['default']; 492}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 493},{"./ranges":8}],5:[function(require,module,exports){ 494(function (global){ 495/** 496 * @file master-playlist-controller.js 497 */ 498'use strict'; 499 500Object.defineProperty(exports, '__esModule', { 501 value: true 502}); 503 504var _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; }; })(); 505 506var _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); } } }; 507 508function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 509 510function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 511 512function _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
512onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 513 514var _playlistLoader = require('./playlist-loader'); 515 516var _playlistLoader2 = _interopRequireDefault(_playlistLoader); 517 518var _segmentLoader = require('./segment-loader'); 519 520var _segmentLoader2 = _interopRequireDefault(_segmentLoader); 521 522var _ranges = require('./ranges'); 523 524var _ranges2 = _interopRequireDefault(_ranges); 525 526var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 527 528var _videoJs2 = _interopRequireDefault(_videoJs); 529 530var _adCueTags = require('./ad-cue-tags'); 531 532var _adCueTags2 = _interopRequireDefault(_adCueTags); 533 534// 5 minute blacklist 535var BLACKLIST_DURATION = 5 * 60 * 1000; 536var Hls = undefined; 537 538/** 539 * determine if an object a is differnt from 540 * and object b. both only having one dimensional 541 * properties 542 * 543 * @param {Object} a object one 544 * @param {Object} b object two 545 * @return {Boolean} if the object has changed or not 546 */ 547var objectChanged = function objectChanged(a, b) { 548 if (typeof a !== typeof b) { 549 return true; 550 } 551 // if we have a different number of elements 552 // something has changed 553 if (Object.keys(a).length !== Object.keys(b).length) { 554 return true; 555 } 556 557 for (var prop in a) { 558 if (a[prop] !== b[prop]) { 559 return true; 560 } 561 } 562 return false; 563}; 564 565/** 566 * Parses a codec string to retrieve the number of codecs specified, 567 * the video codec and object type indicator, and the audio profile. 568 * 569 * @private 570 */ 571var parseCodecs = function parseCodecs(codecs) { 572 var result = { 573 codecCount: 0, 574 videoCodec: null, 575 videoObjectTypeIndicator: null, 576 audioProfile: null 577 }; 578 var parsed = undefined; 579 580 result.codecCount = codecs.split(',').length; 581 result.codecCount = result.codecCount || 2; 582 583 // parse the video codec 584 parsed = /(^|\s|,)+(avc1)([^ ,]*)/i.exec(codecs); 585 if (parsed) { 586 result.videoCodec = parsed[2]; 587 result.videoObjectTypeIndicator = parsed[3]; 588 } 589 590 // parse the last field of the audio codec 591 result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs); 592 result.audioProfile = result.audioProfile && result.audioProfile[2]; 593 594 return result; 595}; 596 597/** 598 * Calculates the MIME type strings for a working configuration of 599 * SourceBuffers to play variant streams in a master playlist. If 600 * there is no possible working configuration, an empty array will be 601 * returned. 602 * 603 * @param master {Object} the m3u8 object for the master playlist 604 * @param media {Object} the m3u8 object for the variant playlist 605 * @return {Array} the MIME type strings. If the array has more than 606 * one entry, the first element should be applied to the video 607 * SourceBuffer and the second to the audio SourceBuffer. 608 * 609 * @private 610 */ 611var mimeTypesForPlaylist_ = function mimeTypesForPlaylist_(master, media) { 612 var container = 'mp2t'; 613 var codecs = { 614 videoCodec: 'avc1', 615 videoObjectTypeIndicator: '.4d400d', 616 audioProfile: '2' 617 }; 618 var audioGroup = []; 619 var mediaAttributes = undefined; 620 var previousGroup = null; 621 622 if (!media) { 623 // not enough information, return an error 624 return []; 625 } 626 // An initialization segment means the media playlists is an iframe 627 // playlist or is using the mp4 container. We don't currently 628 // support iframe playlists, so assume this is signalling mp4 629 // fragments. 630 // the existence check for segments can be removed once 631 // https://github.com/videojs/m3u8-parser/issues/8 is closed 632 if (media.segments && media.segments.length && media.segments[0].map) { 633 container = 'mp4'; 634 } 635 636 // if the codecs were explicitly specified, use them instead of the 637 // defaults 638 mediaAttributes = media.attributes || {}; 639 if (mediaAttributes.CODECS) { 640 (function () { 641 var parsedCodecs = parseCodecs(mediaAttributes.CODECS); 642 643 Object.keys(parsedCodecs).forEach(function (key) { 644 codecs[key] = parsedCodecs[key] || codecs[key]; 645 }); 646 })(); 647 } 648 649 if (master.mediaGroups.AUDIO) { 650 audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; 651 } 652 653 // if audio could be muxed or unmuxed, use mime types appropriate 654 // for both scenarios 655 for (var groupId in audioGroup) { 656 if (previousGroup && !!audioGroup[groupId].uri !== !!previousGroup.uri) { 657 // one source buffer with muxed video and audio and another for 658 // the alternate audio 659 return ['video/' + container + '; codecs="' + codecs.videoCodec + codecs.videoObjectTypeIndicator + ', mp4a.40.' + codecs.audioProfile + '"', 'audio/' + container + '; codecs="mp4a.40.' + codecs.audioProfile + '"']; 660 } 661 previousGroup = audioGroup[groupId]; 662 } 663 // if all video and audio is unmuxed, use two single-codec mime 664 // types 665 if (previousGroup && previousGroup.uri) { 666 return ['video/' + container + '; codecs="' + codecs.videoCodec + codecs.videoObjectTypeIndicator + '"', 'audio/' + container + '; codecs="mp4a.40.' + codecs.audioProfile + '"']; 667 } 668 669 // all video and audio are muxed, use a dual-codec mime type 670 return ['video/' + container + '; codecs="' + codecs.videoCodec + codecs.videoObjectTypeIndicator + ', mp4a.40.' + codecs.audioProfile + '"']; 671}; 672 673exports.mimeTypesForPlaylist_ = mimeTypesForPlaylist_; 674/** 675 * the master playlist controller controller all interactons 676 * between playlists and segmentloaders. At this time this mainly 677 * involves a master playlist and a series of audio playlists 678 * if they are available 679 * 680 * @class MasterPlaylistController 681 * @extends videojs.EventTarget 682 */ 683 684var MasterPlaylistController = (function (_videojs$EventTarget) { 685 _inherits(MasterPlaylistController, _videojs$EventTarget); 686 687 function MasterPlaylistController(options) { 688 var _this = this; 689 690 _classCallCheck(this, MasterPlaylistController); 691 692 _get(Object.getPrototypeOf(MasterPlaylistController.prototype), 'constructor', this).call(this); 693 694 var url = options.url; 695 var withCredentials = options.withCredentials; 696 var mode = options.mode; 697 var tech = options.tech; 698 var bandwidth = options.bandwidth; 699 var externHls = options.externHls; 700 var useCueTags = options.useCueTags; 701 702 if (!url) { 703 throw new Error('A non-empty playlist URL is required'); 704 } 705 706 Hls = externHls; 707 708 this.withCredentials = withCredentials; 709 this.tech_ = tech; 710 this.hls_ = tech.hls; 711 this.mode_ = mode; 712 this.useCueTags_ = useCueTags; 713 if (this.useCueTags_) { 714 this.cueTagsTrack_ = this.tech_.addTextTrack('metadata', 'ad-cues'); 715 this.cueTagsTrack_.inBandMetadataTrackDispatchType = ''; 716 this.tech_.textTracks().addTrack_(this.cueTagsTrack_); 717 } 718 719 this.audioTracks_ = []; 720 this.requestOptions_ = { 721 withCredentials: this.withCredentials, 722 timeout: null 723 }; 724 725 this.audioGroups_ = {}; 726 727 this.mediaSource = new _videoJs2['default'].MediaSource({ mode: mode }); 728 this.audioinfo_ = null; 729 this.mediaSource.on('audioinfo', this.handleAudioinfoUpdate_.bind(this)); 730 731 // load the media source into the player 732 this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen_.bind(this)); 733 734 var segmentLoaderOptions = { 735 hls: this.hls_, 736 mediaSource: this.mediaSource, 737 currentTime: this.tech_.currentTime.bind(this.tech_), 738 seekable: function seekable() { 739 return _this.seekable(); 740 }, 741 seeking: function seeking() { 742 return _this.tech_.seeking(); 743 }, 744 setCurrentTime: function setCurrentTime(a) { 745 return _this.tech_.setCurrentTime(a); 746 }, 747 hasPlayed: function hasPlayed() { 748 return _this.tech_.played().length !== 0; 749 }, 750 bandwidth: bandwidth 751 }; 752 753 // setup playlist loaders 754 this.masterPlaylistLoader_ = new _playlistLoader2['default'](url, this.hls_, this.withCredentials); 755 this.setupMasterPlaylistLoaderListeners_(); 756 this.audioPlaylistLoader_ = null; 757 758 // setup segment loaders
759 // combined audio/video or just video when alternate audio track is selected 760 this.mainSegmentLoader_ = new _segmentLoader2['default'](segmentLoaderOptions); 761 // alternate audio track 762 this.audioSegmentLoader_ = new _segmentLoader2['default'](segmentLoaderOptions); 763 this.setupSegmentLoaderListeners_(); 764 765 this.masterPlaylistLoader_.start(); 766 } 767 768 /** 769 * Register event handlers on the master playlist loader. A helper 770 * function for construction time. 771 * 772 * @private 773 */ 774 775 _createClass(MasterPlaylistController, [{ 776 key: 'setupMasterPlaylistLoaderListeners_', 777 value: function setupMasterPlaylistLoaderListeners_() { 778 var _this2 = this; 779 780 this.masterPlaylistLoader_.on('loadedmetadata', function () { 781 var media = _this2.masterPlaylistLoader_.media(); 782 var requestTimeout = _this2.masterPlaylistLoader_.targetDuration * 1.5 * 1000; 783 784 _this2.requestOptions_.timeout = requestTimeout; 785 786 // if this isn't a live video and preload permits, start 787 // downloading segments 788 if (media.endList && _this2.tech_.preload() !== 'none') { 789 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 790 _this2.mainSegmentLoader_.expired(_this2.masterPlaylistLoader_.expired_); 791 _this2.mainSegmentLoader_.load(); 792 } 793 794 try { 795 _this2.setupSourceBuffers_(); 796 } catch (e) { 797 _videoJs2['default'].log.warn('Failed to create SourceBuffers', e); 798 return _this2.mediaSource.endOfStream('decode'); 799 } 800 _this2.setupFirstPlay(); 801 802 _this2.fillAudioTracks_(); 803 _this2.setupAudio(); 804 _this2.trigger('audioupdate'); 805 806 _this2.trigger('selectedinitialmedia'); 807 }); 808 809 this.masterPlaylistLoader_.on('loadedplaylist', function () { 810 var updatedPlaylist = _this2.masterPlaylistLoader_.media(); 811 var seekable = undefined; 812 813 if (!updatedPlaylist) { 814 // select the initial variant 815 _this2.initialMedia_ = _this2.selectPlaylist(); 816 _this2.masterPlaylistLoader_.media(_this2.initialMedia_); 817 return; 818 } 819 820 if (_this2.useCueTags_) { 821 _this2.updateAdCues_(updatedPlaylist, _this2.masterPlaylistLoader_.expired_); 822 } 823 824 // TODO: Create a new event on the PlaylistLoader that signals 825 // that the segments have changed in some way and use that to 826 // update the SegmentLoader instead of doing it twice here and 827 // on `mediachange` 828 _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_); 829 _this2.mainSegmentLoader_.expired(_this2.masterPlaylistLoader_.expired_); 830 _this2.updateDuration(); 831 832 // update seekable 833 seekable = _this2.seekable(); 834 if (!updatedPlaylist.endList && seekable.length !== 0) { 835 _this2.mediaSource.addSeekableRange_(seekable.start(0), seekable.end(0)); 836 } 837 }); 838 839 this.masterPlaylistLoader_.on('error', function () { 840 _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error); 841 }); 842 843 this.masterPlaylistLoader_.on('mediachanging', function () { 844 _this2.mainSegmentLoader_.abort(); 845 _this2.mainSegmentLoader_.pause(); 846 }); 847 848 this.masterPlaylistLoader_.on('mediachange', function () { 849 var media = _this2.masterPlaylistLoader_.media(); 850 var requestTimeout = _this2.masterPlaylistLoader_.targetDuration * 1.5 * 1000; 851 var activeAudioGroup = undefined; 852 var activeTrack = undefined; 853 854 // If we don't have any more available playlists, we don't want to 855 // timeout the request. 856 if (_this2.masterPlaylistLoader_.isLowestEnabledRendition_()) { 857 _this2.requestOptions_.timeout = 0; 858 } else { 859 _this2.requestOptions_.timeout = requestTimeout; 860 } 861 862 // TODO: Create a new event on the PlaylistLoader that signals 863 // that the segments have changed in some way and use that to 864 // update the SegmentLoader instead of doing it twice here and 865 // on `loadedplaylist` 866 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 867 _this2.mainSegmentLoader_.expired(_this2.masterPlaylistLoader_.expired_); 868 _this2.mainSegmentLoader_.load(); 869 870 // if the audio group has changed, a new audio track has to be 871 // enabled 872 activeAudioGroup = _this2.activeAudioGroup(); 873 activeTrack = activeAudioGroup.filter(function (track) { 874 return track.enabled; 875 })[0]; 876 if (!activeTrack) { 877 _this2.setupAudio(); 878 _this2.trigger('audioupdate'); 879 } 880 881 _this2.tech_.trigger({ 882 type: 'mediachange', 883 bubbles: true 884 }); 885 }); 886 } 887 888 /** 889 * Register event handlers on the segment loaders. A helper function 890 * for construction time. 891 * 892 * @private 893 */ 894 }, { 895 key: 'setupSegmentLoaderListeners_', 896 value: function setupSegmentLoaderListeners_() { 897 var _this3 = this; 898 899 this.mainSegmentLoader_.on('progress', function () { 900 // figure out what stream the next segment should be downloaded from 901 // with the updated bandwidth information 902 _this3.masterPlaylistLoader_.media(_this3.selectPlaylist()); 903 904 _this3.trigger('progress'); 905 }); 906 907 this.mainSegmentLoader_.on('error', function () { 908 _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error()); 909 }); 910 911 this.audioSegmentLoader_.on('error', function () { 912 _videoJs2['default'].log.warn('Problem encountered with the current alternate audio track' + '. Switching back to default.'); 913 _this3.audioSegmentLoader_.abort(); 914 _this3.audioPlaylistLoader_ = null; 915 _this3.setupAudio(); 916 }); 917 } 918 }, { 919 key: 'handleAudioinfoUpdate_', 920 value: function handleAudioinfoUpdate_(event) { 921 if (Hls.supportsAudioInfoChange_() || !this.audioInfo_ || !
921objectChanged(this.audioInfo_, event.info)) { 922 this.audioInfo_ = event.info; 923 return; 924 } 925 926 var error = 'had different audio properties (channels, sample rate, etc.) ' + 'or changed in some other way. This behavior is currently ' + 'unsupported in Firefox 48 and below due to an issue: \n\n' + 'https://bugzilla.mozilla.org/show_bug.cgi?id=1247138\n\n'; 927 928 var enabledIndex = this.activeAudioGroup().map(function (track) { 929 return track.enabled; 930 }).indexOf(true); 931 var enabledTrack = this.activeAudioGroup()[enabledIndex]; 932 var defaultTrack = this.activeAudioGroup().filter(function (track) { 933 return track.properties_ && track.properties_['default']; 934 })[0]; 935 936 // they did not switch audiotracks 937 // blacklist the current playlist 938 if (!this.audioPlaylistLoader_) { 939 error = 'The rendition that we tried to switch to ' + error + 'Unfortunately that means we will have to blacklist ' + 'the current playlist and switch to another. Sorry!';
940 this.blacklistCurrentPlaylist(); 941 } else { 942 error = 'The audio track \'' + enabledTrack.label + '\' that we tried to ' + ('switch to ' + error + ' Unfortunately this means we will have to ') + ('return you to the main track \'' + defaultTrack.label + '\'. Sorry!'); 943 defaultTrack.enabled = true; 944 this.activeAudioGroup().splice(enabledIndex, 1); 945 this.trigger('audioupdate'); 946 } 947 948 _videoJs2['default'].log.warn(error); 949 this.setupAudio(); 950 } 951 952 /** 953 * get the total number of media requests from the `audiosegmentloader_` 954 * and the `mainSegmentLoader_` 955 * 956 * @private 957 */ 958 }, { 959 key: 'mediaRequests_', 960 value: function mediaRequests_() { 961 return this.audioSegmentLoader_.mediaRequests + this.mainSegmentLoader_.mediaRequests; 962 } 963 964 /** 965 * get the total time that media requests have spent trnasfering 966 * from the `audiosegmentloader_` and the `mainSegmentLoader_` 967 * 968 * @private 969 */ 970 }, { 971 key: 'mediaTransferDuration_', 972 value: function mediaTransferDuration_() { 973 return this.audioSegmentLoader_.mediaTransferDuration + this.mainSegmentLoader_.mediaTransferDuration; 974 } 975 976 /** 977 * get the total number of bytes transfered during media requests 978 * from the `audiosegmentloader_` and the `mainSegmentLoader_` 979 * 980 * @private 981 */ 982 }, { 983 key: 'mediaBytesTransferred_', 984 value: function mediaBytesTransferred_() { 985 return this.audioSegmentLoader_.mediaBytesTransferred + this.mainSegmentLoader_.mediaBytesTransferred; 986 } 987 988 /** 989 * fill our internal list of HlsAudioTracks with data from 990 * the master playlist or use a default 991 * 992 * @private 993 */ 994 }, { 995 key: 'fillAudioTracks_', 996 value: function fillAudioTracks_() { 997 var master = this.master(); 998 var mediaGroups = master.mediaGroups || {}; 999 1000 // force a default if we have none or we are not 1001 // in html5 mode (the only mode to support more than one 1002 // audio track) 1003 if (!mediaGroups || !mediaGroups.AUDIO || Object.keys(mediaGroups.AUDIO).length === 0 || this.mode_ !== 'html5') { 1004 // "main" audio group, track name "default" 1005 mediaGroups.AUDIO = { main: { 'default': { 'default': true } } }; 1006 } 1007 1008 for (var mediaGroup in mediaGroups.AUDIO) { 1009 if (!this.audioGroups_[mediaGroup]) { 1010 this.audioGroups_[mediaGroup] = []; 1011 } 1012 1013 for (var label in mediaGroups.AUDIO[mediaGroup]) { 1014 var properties = mediaGroups.AUDIO[mediaGroup][label]; 1015 var track = new _videoJs2['default'].AudioTrack({ 1016 id: label, 1017 kind: properties['default'] ? 'main' : 'alternative', 1018 enabled: false, 1019 language: properties.language, 1020 label: label 1021 }); 1022 1023 track.properties_ = properties; 1024 this.audioGroups_[mediaGroup].push(track); 1025 } 1026 } 1027 1028 // enable the default active track 1029 (this.activeAudioGroup().filter(function (audioTrack) { 1030 return audioTrack.properties_['default']; 1031 })[0] || this.activeAudioGroup()[0]).enabled = true; 1032 } 1033 1034 /** 1035 * Call load on our SegmentLoaders 1036 */ 1037 }, { 1038 key: 'load', 1039 value: function load() { 1040 this.mainSegmentLoader_.load(); 1041 if (this.audioPlaylistLoader_) { 1042 this.audioSegmentLoader_.load(); 1043 } 1044 } 1045 1046 /** 1047 * Returns the audio group for the currently active primary 1048 * media playlist. 1049 */ 1050 }, { 1051 key: 'activeAudioGroup', 1052 value: function activeAudioGroup() { 1053 var videoPlaylist = this.masterPlaylistLoader_.media(); 1054 var result = undefined; 1055 1056 if (videoPlaylist.attributes && videoPlaylist.attributes.AUDIO) { 1057 result = this.audioGroups_[videoPlaylist.attributes.AUDIO]; 1058 } 1059 1060 return result || this.audioGroups_.main; 1061 } 1062 1063 /** 1064 * Determine the correct audio rendition based on the active 1065 * AudioTrack and initialize a PlaylistLoader and SegmentLoader if 1066 * necessary. This method is called once automatically before 1067 * playback begins to enable the default audio track and should be 1068 * invoked again if the track is changed. 1069 */ 1070 }, { 1071 key: 'setupAudio', 1072 value: function setupAudio() { 1073 var _this4 = this; 1074 1075 // determine whether seperate loaders are required for the audio 1076 // rendition 1077 var audioGroup = this.activeAudioGroup(); 1078 var track = audioGroup.filter(function (audioTrack) { 1079 return audioTrack.enabled; 1080 })[0]; 1081 1082 if (!track) { 1083 track = audioGroup.filter(function (audioTrack) { 1084 return audioTrack.properties_['default']; 1085 })[0] || audioGroup[0]; 1086 track.enabled = true; 1087 } 1088 1089 // stop playlist and segment loading for audio 1090 if (this.audioPlaylistLoader_) { 1091 this.audioPlaylistLoader_.dispose(); 1092 this.audioPlaylistLoader_ = null; 1093 } 1094 this.audioSegmentLoader_.pause(); 1095 this.audioSegmentLoader_.clearBuffer(); 1096 1097 if (!track.properties_.resolvedUri) { 1098 return; 1099 } 1100 1101 // startup playlist and segment loaders for the enabled audio 1102 // track 1103 this.audioPlaylistLoader_ = new _playlistLoader2['default'](track.properties_.resolvedUri, this.hls_, this.withCredentials); 1104 this.audioPlaylistLoader_.start(); 1105 1106 this.audioPlaylistLoader_.on('loadedmetadata', function () { 1107 var audioPlaylist = _this4.audioPlaylistLoader_.media(); 1108 1109 _this4.audioSegmentLoader_.playlist(audioPlaylist, _this4.requestOptions_); 1110 1111 // if the video is already playing, or if this isn't a live video and preload 1112 // permits, start downloading segments 1113 if (!_this4.tech_.paused() || audioPlaylist.endList && _this4.tech_.preload() !== 'none') { 1114 _this4.audioSegmentLoader_.load(); 1115 } 1116 1117 if (!audioPlaylist.endList) { 1118 // trigger the playlist loader to start "expired time"-tracking 1119 _this4.audioPlaylistLoader_.trigger('firstplay'); 1120 } 1121 }); 1122 1123 this.audioPlaylistLoader_.on('loadedplaylist', function () { 1124 var updatedPlaylist = undefined; 1125 1126 if (_this4.audioPlaylistLoader_) { 1127 updatedPlaylist = _this4.audioPlaylistLoader_.media(); 1128 } 1129 1130 if (!updatedPlaylist) { 1131 // only one playlist to select 1132 _this4.audioPlaylistLoader_.media(_this4.audioPlaylistLoader_.playlists.master.playlists[0]); 1133 return; 1134 } 1135 1136 _this4.audioSegmentLoader_.playlist(updatedPlaylist, _this4.requestOptions_); 1137 }); 1138 1139 this.audioPlaylistLoader_.on('error', function () { 1140 _videoJs2['default'].log.warn('Problem encountered loading the alternate audio track' + '. Switching back to default.'); 1141 _this4.audioSegmentLoader_.abort(); 1142 _this4.setupAudio(); 1143 }); 1144 } 1145 1146 /** 1147 * Re-tune playback quality level for the current player 1148 * conditions. This method may perform destructive actions, like 1149 * removing already buffered content, to readjust the currently 1150 * active playlist quickly. 1151 * 1152 * @private 1153 */ 1154 }, { 1155 key: 'fastQualityChange_', 1156 value: function fastQualityChange_() { 1157 var media = this.selectPlaylist(); 1158 1159 if (media !== this.masterPlaylistLoader_.media()) { 1160 this.masterPlaylistLoader_.media(media); 1161 this.mainSegmentLoader_.sourceUpdater_.remove(this.tech_.currentTime() + 5, Infinity); 1162 } 1163 } 1164 1165 /** 1166 * Begin playback. 1167 */ 1168 }, { 1169 key: 'play', 1170 value: function play() { 1171 if (this.setupFirstPlay()) { 1172 return; 1173 } 1174 1175 if (this.tech_.ended()) { 1176 this.tech_.setCurrentTime(0); 1177 } 1178 1179 this.load(); 1180 1181 // if the viewer has paused and we fell out of the live window, 1182 // seek forward to the earliest available position 1183 if (this.tech_.duration() === Infinity) { 1184 if (this.tech_.currentTime() < this.tech_.seekable().start(0)) { 1185 return this.tech_.setCurrentTime(this.tech_.seekable().start(0)); 1186 } 1187 } 1188 } 1189 1190 /** 1191 * Seek to the latest media position if this is a live video and the 1192 * player and video are loaded and initialized. 1193 */ 1194 }, { 1195 key: 'setupFirstPlay', 1196 value: function setupFirstPlay() { 1197 var seekable = undefined; 1198 var media = this.masterPlaylistLoader_.media(); 1199 1200 // check that everything is ready to begin buffering 1201 // 1) the active media playlist is available 1202 if (media && 1203 // 2) the video is a live stream 1204 !media.endList && 1205 1206 // 3) the player is not paused 1207 !this.tech_.paused() && 1208 1209 // 4) the player has not started playing 1210 !this.hasPlayed_) { 1211 1212 // trigger the playlist loader to start "expired time"-tracking 1213 this.masterPlaylistLoader_.trigger('firstplay'); 1214 this.hasPlayed_ = true; 1215 1216 // seek to the latest media position for live videos 1217 seekable = this.seekable(); 1218 if (seekable.length) { 1219 this.tech_.setCurrentTime(seekable.end(0)); 1220 } 1221 1222 // now that we seeked to the current time, load the segment 1223 this.load(); 1224 1225 return true; 1226 } 1227 return false;
1228 } 1229 1230 /** 1231 * handle the sourceopen event on the MediaSource 1232 * 1233 * @private 1234 */ 1235 }, { 1236 key: 'handleSourceOpen_', 1237 value: function handleSourceOpen_() { 1238 // Only attempt to create the source buffer if none already exist. 1239 // handleSourceOpen is also called when we are "re-opening" a source buffer 1240 // after `endOfStream` has been called (in response to a seek for instance) 1241 try { 1242 this.setupSourceBuffers_(); 1243 } catch (e) { 1244 _videoJs2['default'].log.warn('Failed to create Source Buffers', e); 1245 return this.mediaSource.endOfStream('decode'); 1246 } 1247 1248 // if autoplay is enabled, begin playback. This is duplicative of 1249 // code in video.js but is required because play() must be invoked 1250 // *after* the media source has opened. 1251 if (this.tech_.autoplay()) { 1252 this.tech_.play(); 1253 } 1254 1255 this.trigger('sourceopen'); 1256 } 1257 1258 /** 1259 * Blacklists a playlist when an error occurs for a set amount of time 1260 * making it unavailable for selection by the rendition selection algorithm 1261 * and then forces a new playlist (rendition) selection. 1262 * 1263 * @param {Object=} error an optional error that may include the playlist 1264 * to blacklist 1265 */ 1266 }, { 1267 key: 'blacklistCurrentPlaylist', 1268 value: function blacklistCurrentPlaylist() { 1269 var error = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 1270 1271 var currentPlaylist = undefined; 1272 var nextPlaylist = undefined; 1273 1274 // If the `error` was generated by the playlist loader, it will contain 1275 // the playlist we were trying to load (but failed) and that should be 1276 // blacklisted instead of the currently selected playlist which is likely 1277 // out-of-date in this scenario 1278 currentPlaylist = error.playlist || this.masterPlaylistLoader_.media(); 1279 1280 // If there is no current playlist, then an error occurred while we were 1281 // trying to load the master OR while we were disposing of the tech 1282 if (!currentPlaylist) { 1283 this.error = error; 1284 return this.mediaSource.endOfStream('network'); 1285 } 1286 1287 // Blacklist this playlist 1288 currentPlaylist.excludeUntil = Date.now() + BLACKLIST_DURATION; 1289 1290 // Select a new playlist 1291 nextPlaylist = this.selectPlaylist(); 1292 1293 if (nextPlaylist) { 1294 _videoJs2['default'].log.warn('Problem encountered with the current ' + 'HLS playlist. Switching to another playlist.'); 1295 1296 return this.masterPlaylistLoader_.media(nextPlaylist); 1297 } 1298 _videoJs2['default'].log.warn('Problem encountered with the current ' + 'HLS playlist. No suitable alternatives found.'); 1299 // We have no more playlists we can select so we must fail 1300 this.error = error; 1301 return this.mediaSource.endOfStream('network'); 1302 } 1303 1304 /** 1305 * Pause all segment loaders 1306 */ 1307 }, { 1308 key: 'pauseLoading', 1309 value: function pauseLoading() { 1310 this.mainSegmentLoader_.pause(); 1311 if (this.audioPlaylistLoader_) { 1312 this.audioSegmentLoader_.pause(); 1313 } 1314 } 1315 1316 /** 1317 * set the current time on all segment loaders 1318 * 1319 * @param {TimeRange} currentTime the current time to set 1320 * @return {TimeRange} the current time 1321 */ 1322 }, { 1323 key: 'setCurrentTime', 1324 value: function setCurrentTime(currentTime) { 1325 var buffered = _ranges2['default'].findRange(this.tech_.buffered(), currentTime); 1326 1327 if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) { 1328 // return immediately if the metadata is not ready yet 1329 return 0; 1330 } 1331 1332 // it's clearly an edge-case but don't thrown an error if asked to 1333 // seek within an empty playlist 1334 if (!this.masterPlaylistLoader_.media().segments) { 1335 return 0; 1336 } 1337 1338 // if the seek location is already buffered, continue buffering as 1339 // usual 1340 if (buffered && buffered.length) { 1341 return currentTime; 1342 } 1343 1344 // cancel outstanding requests so we begin buffering at the new 1345 // location 1346 this.mainSegmentLoader_.abort(); 1347 if (this.audioPlaylistLoader_) { 1348 this.audioSegmentLoader_.abort(); 1349 } 1350 1351 if (!this.tech_.paused()) { 1352 this.mainSegmentLoader_.load(); 1353 if (this.audioPlaylistLoader_) { 1354 this.audioSegmentLoader_.load(); 1355 } 1356 } 1357 } 1358 1359 /** 1360 * get the current duration 1361 * 1362 * @return {TimeRange} the duration 1363 */ 1364 }, { 1365 key: 'duration', 1366 value: function duration() { 1367 if (!this.masterPlaylistLoader_) { 1368 return 0; 1369 } 1370 1371 if (this.mediaSource) { 1372 return this.mediaSource.duration; 1373 } 1374 1375 return Hls.Playlist.duration(this.masterPlaylistLoader_.media()); 1376 } 1377 1378 /** 1379 * check the seekable range 1380 * 1381 * @return {TimeRange} the seekable range 1382 */ 1383 }, { 1384 key: 'seekable', 1385 value: function seekable() { 1386 var media = undefined; 1387 var mainSeekable = undefined; 1388 var audioSeekable = undefined; 1389 1390 if (!this.masterPlaylistLoader_) { 1391 return _videoJs2['default'].createTimeRanges(); 1392 } 1393 media = this.masterPlaylistLoader_.media(); 1394 if (!media) { 1395 return _videoJs2['default'].createTimeRanges(); 1396 } 1397 1398 mainSeekable = Hls.Playlist.seekable(media, this.masterPlaylistLoader_.expired_); 1399 if (mainSeekable.length === 0) { 1400 return mainSeekable; 1401 } 1402 1403 if (this.audioPlaylistLoader_) { 1404 audioSeekable = Hls.Playlist.seekable(this.audioPlaylistLoader_.media(), this.audioPlaylistLoader_.expired_); 1405 if (audioSeekable.length === 0) { 1406 return audioSeekable; 1407 } 1408 } 1409 1410 if (!audioSeekable) { 1411 // seekable has been calculated based on buffering video data so it 1412 // can be returned directly 1413 return mainSeekable; 1414 } 1415 1416 return _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)]]); 1417 } 1418 1419 /** 1420 * Update the player duration 1421 */ 1422 }, { 1423 key: 'updateDuration', 1424 value: function updateDuration() { 1425 var _this5 = this; 1426 1427 var oldDuration = this.mediaSource.duration; 1428 var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media()); 1429 var buffered = this.tech_.buffered(); 1430 var setDuration = function setDuration() { 1431 _this5.mediaSource.duration = newDuration; 1432 _this5.tech_.trigger('durationchange'); 1433 1434 _this5.mediaSource.removeEventListener('sourceopen', setDuration); 1435 }; 1436 1437 if (buffered.length > 0) { 1438 newDuration = Math.max(newDuration, buffered.end(buffered.length - 1)); 1439 } 1440 1441 // if the duration has changed, invalidate the cached value 1442 if (oldDuration !== newDuration) { 1443 // update the duration 1444 if (this.mediaSource.readyState !== 'open') { 1445 this.mediaSource.addEventListener('sourceopen', setDuration); 1446 } else { 1447 setDuration(); 1448 } 1449 } 1450 } 1451 1452 /** 1453 * dispose of the MasterPlaylistController and everything 1454 * that it controls 1455 */ 1456 }, { 1457 key: 'dispose', 1458 value: function dispose() { 1459 this.masterPlaylistLoader_.dispose(); 1460 this.mainSegmentLoader_.dispose(); 1461 1462 this.audioSegmentLoader_.dispose(); 1463 } 1464 1465 /** 1466 * return the master playlist object if we have one 1467 * 1468 * @return {Object} the master playlist object that we parsed 1469 */ 1470 }, { 1471 key: 'master', 1472 value: function master() { 1473 return this.masterPlaylistLoader_.master; 1474 } 1475 1476 /** 1477 * return the currently selected playlist 1478 * 1479 * @return {Object} the currently selected playlist object that we parsed 1480 */ 1481 }, { 1482 key: 'media', 1483 value: function media() { 1484 // playlist loader will not return media if it has not been fully loaded 1485 return this.masterPlaylistLoader_.media() || this.initialMedia_; 1486 } 1487 1488 /** 1489 * setup our internal source buffers on our segment Loaders 1490 * 1491 * @private 1492 */ 1493 }, { 1494 key: 'setupSourceBuffers_', 1495 value: function setupSourceBuffers_() { 1496 var media = this.masterPlaylistLoader_.media(); 1497 var mimeTypes = undefined; 1498 1499 // wait until a media playlist is available and the Media Source is 1500 // attached 1501 if (!media || this.mediaSource.readyState !== 'open') { 1502 return; 1503 } 1504 1505 mimeTypes = mimeTypesForPlaylist_(this.masterPlaylistLoader_.master, media); 1506 if (mimeTypes.length < 1) { 1507 this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri; 1508 return this.mediaSource.endOfStream('decode'); 1509 } 1510 this.mainSegmentLoader_.mimeType(mimeTypes[0]); 1511 if (mimeTypes[1]) { 1512 this.audioSegmentLoader_.mimeType(mimeTypes[1]); 1513 } 1514 1515 // exclude any incompatible variant streams from future playlist 1516 // selection 1517 this.excludeIncompatibleVariants_(media); 1518 } 1519 1520 /** 1521 * Blacklist playlists that are known to be codec or 1522 * stream-incompatible with the SourceBuffer configuration. For 1523 * instance, Media Source Extensions would cause the video element to 1524 * stall waiting for video data if you switched from a variant with 1525 * video and audio to an audio-only one. 1526 * 1527 * @param {Object} media a media playlist compatible with the current 1528 * set of SourceBuffers. Variants in the current master playlist that
1529 * do not appear to have compatible codec or stream configurations 1530 * will be excluded from the default playlist selection algorithm 1531 * indefinitely. 1532 * @private 1533 */ 1534 }, { 1535 key: 'excludeIncompatibleVariants_', 1536 value: function excludeIncompatibleVariants_(media) { 1537 var master = this.masterPlaylistLoader_.master; 1538 var codecCount = 2; 1539 var videoCodec = null; 1540 var audioProfile = null; 1541 var codecs = undefined; 1542 1543 if (media.attributes && media.attributes.CODECS) { 1544 codecs = parseCodecs(media.attributes.CODECS); 1545 videoCodec = codecs.videoCodec; 1546 audioProfile = codecs.audioProfile; 1547 codecCount = codecs.codecCount; 1548 } 1549 master.playlists.forEach(function (variant) { 1550 var variantCodecs = { 1551 codecCount: 2, 1552 videoCodec: null, 1553 audioProfile: null 1554 }; 1555 1556 if (variant.attributes && variant.attributes.CODECS) { 1557 var codecString = variant.attributes.CODECS; 1558 1559 variantCodecs = parseCodecs(codecString); 1560 if (window.MediaSource && window.MediaSource.isTypeSupported && !window.MediaSource.isTypeSupported('video/mp4; codecs="' + codecString + '"')) { 1561 variant.excludeUntil = Infinity; 1562 } 1563 } 1564 1565 // if the streams differ in the presence or absence of audio or 1566 // video, they are incompatible 1567 if (variantCodecs.codecCount !== codecCount) { 1568 variant.excludeUntil = Infinity; 1569 } 1570 1571 // if h.264 is specified on the current playlist, some flavor of 1572 // it must be specified on all compatible variants 1573 if (variantCodecs.videoCodec !== videoCodec) { 1574 variant.excludeUntil = Infinity; 1575 } 1576 // HE-AAC ("mp4a.40.5") is incompatible with all other versions of 1577 // AAC audio in Chrome 46. Don't mix the two. 1578 if (variantCodecs.audioProfile === '5' && audioProfile !== '5' || audioProfile === '5' && variantCodecs.audioProfile !== '5') { 1579 variant.excludeUntil = Infinity; 1580 } 1581 }); 1582 } 1583 }, { 1584 key: 'updateAdCues_', 1585 value: function updateAdCues_(media) { 1586 var offset = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; 1587 1588 _adCueTags2['default'].updateAdCues(media, this.cueTagsTrack_, offset); 1589 } 1590 }]); 1591 1592 return MasterPlaylistController; 1593})(_videoJs2['default'].EventTarget); 1594 1595exports.MasterPlaylistController = MasterPlaylistController; 1596}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 1597},{"./ad-cue-tags":1,"./playlist-loader":6,"./ranges":8,"./segment-loader":11}],6:[function(require,module,exports){ 1598(function (global){ 1599/** 1600 * @file playlist-loader.js 1601 * 1602 * A state machine that manages the loading, caching, and updating of 1603 * M3U8 playlists. 1604 * 1605 */ 1606'use strict'; 1607 1608Object.defineProperty(exports, '__esModule', { 1609 value: true 1610}); 1611 1612function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 1613 1614var _resolveUrl = require('./resolve-url'); 1615 1616var _resolveUrl2 = _interopRequireDefault(_resolveUrl); 1617 1618var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 1619 1620var _stream = require('./stream'); 1621 1622var _stream2 = _interopRequireDefault(_stream); 1623 1624var _m3u8Parser = require('m3u8-parser'); 1625 1626var _m3u8Parser2 = _interopRequireDefault(_m3u8Parser); 1627 1628var _globalWindow = require('global/window'); 1629 1630var _globalWindow2 = _interopRequireDefault(_globalWindow); 1631 1632/** 1633 * Returns a new array of segments that is the result of merging 1634 * properties from an older list of segments onto an updated 1635 * list. No properties on the updated playlist will be overridden. 1636 * 1637 * @param {Array} original the outdated list of segments 1638 * @param {Array} update the updated list of segments 1639 * @param {Number=} offset the index of the first update 1640 * segment in the original segment list. For non-live playlists, 1641 * this should always be zero and does not need to be 1642 * specified. For live playlists, it should be the difference 1643 * between the media sequence numbers in the original and updated 1644 * playlists. 1645 * @return a list of merged segment objects 1646 */ 1647var updateSegments = function updateSegments(original, update, offset) { 1648 var result = update.slice(); 1649 var length = undefined; 1650 var i = undefined; 1651 1652 offset = offset || 0; 1653 length = Math.min(original.length, update.length + offset); 1654 1655 for (i = offset; i < length; i++) { 1656 result[i - offset] = (0, _videoJs.mergeOptions)(original[i], result[i - offset]); 1657 } 1658 return result; 1659}; 1660 1661/** 1662 * Returns a new master playlist that is the result of merging an 1663 * updated media playlist into the original version. If the 1664 * updated media playlist does not match any of the playlist 1665 * entries in the original master playlist, null is returned. 1666 * 1667 * @param {Object} master a parsed master M3U8 object 1668 * @param {Object} media a parsed media M3U8 object 1669 * @return {Object} a new object that represents the original 1670 * master playlist with the updated media playlist merged in, or 1671 * null if the merge produced no change. 1672 */ 1673var updateMaster = function updateMaster(master, media) { 1674 var changed = false;
1675 var result = (0, _videoJs.mergeOptions)(master, {}); 1676 var i = master.playlists.length; 1677 var playlist = undefined; 1678 var segment = undefined; 1679 var j = undefined; 1680 1681 while (i--) { 1682 playlist = result.playlists[i]; 1683 if (playlist.uri === media.uri) { 1684 // consider the playlist unchanged if the number of segments 1685 // are equal and the media sequence number is unchanged 1686 if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.mediaSequence === media.mediaSequence) { 1687 continue; 1688 } 1689 1690 result.playlists[i] = (0, _videoJs.mergeOptions)(playlist, media); 1691 result.playlists[media.uri] = result.playlists[i]; 1692 1693 // if the update could overlap existing segment information, 1694 // merge the two lists 1695 if (playlist.segments) { 1696 result.playlists[i].segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence); 1697 } 1698 // resolve any missing segment and key URIs 1699 j = 0; 1700 if (result.playlists[i].segments) { 1701 j = result.playlists[i].segments.length; 1702 } 1703 while (j--) { 1704 segment = result.playlists[i].segments[j]; 1705 if (!segment.resolvedUri) { 1706 segment.resolvedUri = (0, _resolveUrl2['default'])(playlist.resolvedUri, segment.uri); 1707 } 1708 if (segment.key && !segment.key.resolvedUri) { 1709 segment.key.resolvedUri = (0, _resolveUrl2['default'])(playlist.resolvedUri, segment.key.uri); 1710 } 1711 if (segment.map && !segment.map.resolvedUri) { 1712 segment.map.resolvedUri = (0, _resolveUrl2['default'])(playlist.resolvedUri, segment.map.uri); 1713 } 1714 } 1715 changed = true; 1716 } 1717 } 1718 return changed ? result : null; 1719}; 1720 1721/** 1722 * Load a playlist from a remote loacation 1723 * 1724 * @class PlaylistLoader 1725 * @extends Stream 1726 * @param {String} srcUrl the url to start with 1727 * @param {Boolean} withCredentials the withCredentials xhr option 1728 * @constructor 1729 */ 1730var PlaylistLoader = function PlaylistLoader(srcUrl, hls, withCredentials) { 1731 var _this = this; 1732 1733 /* eslint-disable consistent-this */ 1734 var loader = this; 1735 /* eslint-enable consistent-this */ 1736 var dispose = undefined; 1737 var mediaUpdateTimeout = undefined; 1738 var request = undefined; 1739 var playlistRequestError = undefined; 1740 var haveMetadata = undefined; 1741 1742 PlaylistLoader.prototype.constructor.call(this); 1743 1744 this.hls_ = hls; 1745 1746 // a flag that disables "expired time"-tracking this setting has 1747 // no effect when not playing a live stream 1748 this.trackExpiredTime_ = false; 1749 1750 if (!srcUrl) { 1751 throw new Error('A non-empty playlist URL is required'); 1752 } 1753 1754 playlistRequestError = function (xhr, url, startingState) { 1755 loader.setBandwidth(request || xhr); 1756 1757 // any in-flight request is now finished 1758 request = null; 1759 1760 if (startingState) { 1761 loader.state = startingState; 1762 } 1763 1764 loader.error = { 1765 playlist: loader.master.playlists[url], 1766 status: xhr.status, 1767 message: 'HLS playlist request error at URL: ' + url, 1768 responseText: xhr.responseText, 1769 code: xhr.status >= 500 ? 4 : 2 1770 }; 1771 1772 loader.trigger('error'); 1773 }; 1774 1775 // update the playlist loader's state in response to a new or 1776 // updated playlist. 1777 haveMetadata = function (xhr, url) { 1778 var parser = undefined; 1779 var refreshDelay = undefined; 1780 var update = undefined; 1781 1782 loader.setBandwidth(request || xhr); 1783 1784 // any in-flight request is now finished 1785 request = null; 1786 1787 loader.state = 'HAVE_METADATA'; 1788 1789 parser = new _m3u8Parser2['default'].Parser(); 1790 parser.push(xhr.responseText); 1791 parser.end(); 1792 parser.manifest.uri = url; 1793 1794 // merge this playlist into the master 1795 update = updateMaster(loader.master, parser.manifest); 1796 refreshDelay = (parser.manifest.targetDuration || 10) * 1000; 1797 loader.targetDuration = parser.manifest.targetDuration; 1798 if (update) { 1799 loader.master = update; 1800 loader.updateMediaPlaylist_(parser.manifest); 1801 } else { 1802 // if the playlist is unchanged since the last reload, 1803 // try again after half the target duration 1804 refreshDelay /= 2; 1805 } 1806 1807 // refresh live playlists after a target duration passes 1808 if (!loader.media().endList) { 1809 _globalWindow2['default'].clearTimeout(mediaUpdateTimeout); 1810 mediaUpdateTimeout = _globalWindow2['default'].setTimeout(function () { 1811 loader.trigger('mediaupdatetimeout'); 1812 }, refreshDelay); 1813 } 1814 1815 loader.trigger('loadedplaylist'); 1816 }; 1817 1818 // initialize the loader state 1819 loader.state = 'HAVE_NOTHING'; 1820 1821 // track the time that has expired from the live window 1822 // this allows the seekable start range to be calculated even if 1823 // all segments with timing information have expired 1824 this.expired_ = 0; 1825 1826 // capture the prototype dispose function 1827 dispose = this.dispose; 1828 1829 /** 1830 * Abort any outstanding work and clean up. 1831 */ 1832 loader.dispose = function () { 1833 loader.stopRequest(); 1834 _globalWindow2['default'].clearTimeout(mediaUpdateTimeout); 1835 dispose.call(this); 1836 }; 1837 1838 loader.stopRequest = function () { 1839 if (request) { 1840 var oldRequest = request; 1841 1842 request = null; 1843 oldRequest.onreadystatechange = null; 1844 oldRequest.abort(); 1845 } 1846 }; 1847 1848 /** 1849 * Returns the number of enabled playlists on the master playlist object 1850 * 1851 * @return {Number} number of eneabled playlists 1852 */ 1853 loader.enabledPlaylists_ = function () { 1854 return loader.master.playlists.filter(function (element, index, array) { 1855 return !element.excludeUntil || element.excludeUntil <= Date.now(); 1856 }).length; 1857 }; 1858 1859 /** 1860 * Returns whether the current playlist is the lowest rendition 1861 * 1862 * @return {Boolean} true if on lowest rendition 1863 */ 1864 loader.isLowestEnabledRendition_ = function () { 1865 var media = loader.media(); 1866 1867 if (!media || !media.attributes) { 1868 return false;
1869 } 1870 1871 var currentBandwidth = loader.media().attributes.BANDWIDTH || 0; 1872 1873 return !(loader.master.playlists.filter(function (element, index, array) { 1874 var enabled = typeof element.excludeUntil === 'undefined' || element.excludeUntil <= Date.now(); 1875 1876 if (!enabled) { 1877 return false; 1878 } 1879 1880 var item = element.attributes.BANDWIDTH; 1881 1882 return item <= currentBandwidth; 1883 }).length > 1); 1884 }; 1885 1886 /** 1887 * When called without any arguments, returns the currently 1888 * active media playlist. When called with a single argument, 1889 * triggers the playlist loader to asynchronously switch to the 1890 * specified media playlist. Calling this method while the 1891 * loader is in the HAVE_NOTHING causes an error to be emitted 1892 * but otherwise has no effect. 1893 * 1894 * @param {Object=} playlis tthe parsed media playlist 1895 * object to switch to 1896 * @return {Playlist} the current loaded media 1897 */ 1898 loader.media = function (playlist) { 1899 var startingState = loader.state; 1900 var mediaChange = undefined; 1901 1902 // getter 1903 if (!playlist) { 1904 return loader.media_; 1905 } 1906 1907 // setter 1908 if (loader.state === 'HAVE_NOTHING') { 1909 throw new Error('Cannot switch media playlist from ' + loader.state); 1910 } 1911 1912 // find the playlist object if the target playlist has been 1913 // specified by URI 1914 if (typeof playlist === 'string') { 1915 if (!loader.master.playlists[playlist]) { 1916 throw new Error('Unknown playlist URI: ' + playlist); 1917 } 1918 playlist = loader.master.playlists[playlist]; 1919 } 1920 1921 mediaChange = !loader.media_ || playlist.uri !== loader.media_.uri; 1922 1923 // switch to fully loaded playlists immediately 1924 if (loader.master.playlists[playlist.uri].endList) { 1925 // abort outstanding playlist requests 1926 if (request) { 1927 request.onreadystatechange = null; 1928 request.abort(); 1929 request = null; 1930 } 1931 loader.state = 'HAVE_METADATA'; 1932 loader.media_ = playlist; 1933 1934 // trigger media change if the active media has been updated 1935 if (mediaChange) { 1936 loader.trigger('mediachanging'); 1937 loader.trigger('mediachange'); 1938 } 1939 return; 1940 } 1941 1942 // switching to the active playlist is a no-op 1943 if (!mediaChange) { 1944 return; 1945 } 1946 1947 loader.state = 'SWITCHING_MEDIA'; 1948 1949 // there is already an outstanding playlist request 1950 if (request) { 1951 if ((0, _resolveUrl2['default'])(loader.master.uri, playlist.uri) === request.url) { 1952 // requesting to switch to the same playlist multiple times 1953 // has no effect after the first 1954 return; 1955 } 1956 request.onreadystatechange = null; 1957 request.abort(); 1958 request = null; 1959 } 1960 1961 // request the new playlist 1962 if (this.media_) { 1963 this.trigger('mediachanging'); 1964 } 1965 request = this.hls_.xhr({ 1966 uri: (0, _resolveUrl2['default'])(loader.master.uri, playlist.uri), 1967 withCredentials: withCredentials 1968 }, function (error, req) { 1969 // disposed 1970 if (!request) { 1971 return; 1972 } 1973 1974 if (error) { 1975 return playlistRequestError(request, playlist.uri, startingState); 1976 } 1977 1978 haveMetadata(req, playlist.uri); 1979 1980 // fire loadedmetadata the first time a media playlist is loaded 1981 if (startingState === 'HAVE_MASTER') { 1982 loader.trigger('loadedmetadata'); 1983 } else { 1984 loader.trigger('mediachange'); 1985 } 1986 }); 1987 }; 1988 1989 /** 1990 * set the bandwidth on an xhr to the bandwidth on the playlist 1991 */ 1992 loader.setBandwidth = function (xhr) { 1993 loader.bandwidth = xhr.bandwidth; 1994 }; 1995 1996 // In a live playlist, don't keep track of the expired time 1997 // until HLS tells us that "first play" has commenced 1998 loader.on('firstplay', function () { 1999 this.trackExpiredTime_ = true; 2000 }); 2001 2002 // live playlist staleness timeout 2003 loader.on('mediaupdatetimeout', function () { 2004 if (loader.state !== 'HAVE_METADATA') { 2005 // only refresh the media playlist if no other activity is going on 2006 return; 2007 } 2008 2009 loader.state = 'HAVE_CURRENT_METADATA'; 2010 request = this.hls_.xhr({ 2011 uri: (0, _resolveUrl2['default'])(loader.master.uri, loader.media().uri), 2012 withCredentials: withCredentials 2013 }, function (error, req) { 2014 // disposed 2015 if (!request) { 2016 return; 2017 } 2018 2019 if (error) { 2020 return playlistRequestError(request, loader.media().uri); 2021 } 2022 haveMetadata(request, loader.media().uri); 2023 }); 2024 }); 2025 2026 /** 2027 * pause loading of the playlist 2028 */ 2029 loader.pause = function () { 2030 loader.stopRequest(); 2031 _globalWindow2['default'].clearTimeout(mediaUpdateTimeout); 2032 }; 2033 2034 /** 2035 * start loading of the playlist 2036 */ 2037 loader.load = function () { 2038 if (loader.started) { 2039 if (!loader.media().endList) { 2040 loader.trigger('mediaupdatetimeout'); 2041 } else { 2042 loader.trigger('loadedplaylist'); 2043 } 2044 } else { 2045 loader.start(); 2046 } 2047 }; 2048 2049 /** 2050 * start loading of the playlist 2051 */ 2052 loader.start = function () { 2053 loader.started = true; 2054 2055 // request the specified URL 2056 request = _this.hls_.xhr({ 2057 uri: srcUrl, 2058 withCredentials: withCredentials 2059 }, function (error, req) { 2060 var parser = undefined; 2061 var playlist = undefined; 2062 var i = undefined; 2063 2064 // disposed 2065 if (!request) { 2066 return; 2067 } 2068 2069 // clear the loader's request reference 2070 request = null; 2071 2072 if (error) { 2073 loader.error = { 2074 status: req.status, 2075 message: 'HLS playlist request error at URL: ' + srcUrl, 2076 responseText: req.responseText, 2077 // MEDIA_ERR_NETWORK 2078 code: 2 2079 }; 2080 return loader.trigger('error'); 2081 } 2082 2083 parser = new _m3u8Parser2['default'].Parser(); 2084 parser.push(req.responseText); 2085 parser.end(); 2086 2087 loader.state = 'HAVE_MASTER'; 2088 2089 parser.manifest.uri = srcUrl; 2090 2091 // loaded a master playlist 2092 if (parser.manifest.playlists) { 2093 loader.master = parser.manifest; 2094 2095 // setup by-URI lookups and resolve media playlist URIs 2096 i = loader.master.playlists.length; 2097 while (i--) { 2098 playlist = loader.master.playlists[i]; 2099 loader.master.playlists[playlist.uri] = playlist; 2100 playlist.resolvedUri = (0, _resolveUrl2['default'])(loader.master.uri, playlist.uri); 2101 } 2102 2103 // resolve any media group URIs 2104 for (var groupKey in loader.master.mediaGroups.AUDIO) { 2105 for (var labelKey in loader.master.mediaGroups.AUDIO[groupKey]) { 2106 var alternateAudio = loader.master.mediaGroups.AUDIO[groupKey][labelKey]; 2107 2108 if (alternateAudio.uri) { 2109 alternateAudio.resolvedUri = (0, _resolveUrl2['default'])(loader.master.uri, alternateAudio.uri); 2110 } 2111 } 2112 } 2113 2114 loader.trigger('loadedplaylist'); 2115 if (!request) { 2116 // no media playlist was specifically selected so start 2117 // from the first listed one 2118 loader.media(parser.manifest.playlists[0]); 2119 } 2120 return; 2121 } 2122 2123 // loaded a media playlist 2124 // infer a master playlist if none was previously requested 2125 loader.master = { 2126 mediaGroups: { 2127 'AUDIO': {}, 2128 'VIDEO': {}, 2129 'CLOSED-CAPTIONS': {}, 2130 'SUBTITLES': {} 2131 }, 2132 uri: _globalWindow2['default'].location.href, 2133 playlists: [{ 2134 uri: srcUrl 2135 }] 2136 }; 2137 loader.master.playlists[srcUrl] = loader.master.playlists[0]; 2138 loader.master.playlists[0].resolvedUri = srcUrl; 2139 haveMetadata(req, srcUrl); 2140 return loader.trigger('loadedmetadata'); 2141 }); 2142 }; 2143}; 2144 2145PlaylistLoader.prototype = new _stream2['default'](); 2146 2147/** 2148 * Update the PlaylistLoader state to reflect the changes in an 2149 * update to the current media playlist. 2150 * 2151 * @param {Object} update the updated media playlist object 2152 */ 2153PlaylistLoader.prototype.updateMediaPlaylist_ = function (update) { 2154 var outdated = undefined; 2155 var i = undefined; 2156 var segment = undefined; 2157 2158 outdated = this.media_; 2159 this.media_ = this.master.playlists[update.uri]; 2160 2161 if (!outdated) { 2162 return; 2163 } 2164 2165 // don't track expired time until this flag is truthy 2166 if (!this.trackExpiredTime_) { 2167 return; 2168 } 2169 2170 // if the update was the result of a rendition switch do not 2171 // attempt to calculate expired_ since media-sequences need not 2172 // correlate between renditions/variants 2173 if (update.uri !== outdated.uri) { 2174 return; 2175 } 2176 2177 // try using precise timing from first segment of the updated 2178 // playlist 2179 if (update.segments.length) { 2180 if (typeof update.segments[0].start !== 'undefined') { 2181 this.expired_ = update.segments[0].start; 2182 return; 2183 } else if (typeof update.segments[0].end !== 'undefined') { 2184 this.expired_ = update.segments[0].end - update.segments[0].duration; 2185 return; 2186 } 2187 } 2188 2189 // calculate expired by walking the outdated playlist 2190 i = update.mediaSequence - outdated.mediaSequence - 1; 2191 2192 for (; i >= 0; i--) { 2193 segment = outdated.segments[i]; 2194 2195 if (!segment) { 2196 // we missed information on this segment completely between 2197 // playlist updates so we'll have to take an educated guess 2198 // once we begin buffering again, any error we introduce can 2199 // be corrected 2200 this.expired_ += outdated.targetDuration || 10; 2201 continue; 2202 } 2203 2204 if (typeof segment.end !== 'undefined') { 2205 this.expired_ = segment.end; 2206 return; 2207 } 2208 if (typeof segment.start !== 'undefined') { 2209 this.expired_ = segment.start + segment.duration; 2210 return; 2211 } 2212 this.expired_ += segment.duration; 2213 } 2214}; 2215 2216exports['default'] = PlaylistLoader; 2217module.exports = exports['default']; 2218}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 2219},{"./resolve-url":10,"./stream":13,"global/window":25,"m3u8-parser":62}],7:[function(require,module,exports){ 2220(function (global){ 2221/** 2222 * @file playlist.js 2223 * 2224 * Playlist related utilities. 2225 */ 2226'use strict'; 2227 2228Object.defineProperty(exports, '__esModule', { 2229 value: true 2230}); 2231 2232function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 2233 2234var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 2235 2236var _globalWindow = require('global/window'); 2237 2238var _globalWindow2 = _interopRequireDefault(_globalWindow); 2239 2240var Playlist = { 2241 /** 2242 * The number of segments that are unsafe to start playback at in 2243 * a live stream. Changing this value can cause playback stalls. 2244 * See HTTP Live Streaming, "Playing the Media Playlist File" 2245 * https://tools.ietf.org/html/draft-pantos-http-live-streaming-18#section-6.3.3 2246 */ 2247 UNSAFE_LIVE_SEGMENTS: 3 2248}; 2249 2250/** 2251 * walk backward until we find a duration we can use 2252 * or return a failure 2253 * 2254 * @param {Playlist} playlist the playlist to walk through 2255 * @param {Number} endSequence the mediaSequence to stop walking on 2256 */ 2257 2258var backwardDuration = function backwardDuration(playlist, endSequence) { 2259 var result = 0; 2260 var i = endSequence - playlist.mediaSequence; 2261 // if a start time is available for segment immediately following 2262 // the interval, use it 2263 var segment = playlist.segments[i]; 2264 2265 // Walk backward until we find the latest segment with timeline 2266 // information that is earlier than endSequence 2267 if (segment) { 2268 if (typeof segment.start !== 'undefined') { 2269 return { result: segment.start, precise: true }; 2270 } 2271 if (typeof segment.end !== 'undefined') { 2272 return { 2273 result: segment.end - segment.duration, 2274 precise: true 2275 }; 2276 } 2277 } 2278 while (i--) { 2279 segment = playlist.segments[i]; 2280 if (typeof segment.end !== 'undefined') { 2281 return { result: result + segment.end, precise: true }; 2282 } 2283 2284 result += segment.duration; 2285 2286 if (typeof segment.start !== 'undefined') { 2287 return { result: result + segment.start, precise: true }; 2288 } 2289 } 2290 return { result: result, precise: false }; 2291}; 2292 2293/** 2294 * walk forward until we find a duration we can use 2295 * or return a failure 2296 * 2297 * @param {Playlist} playlist the playlist to walk through 2298 * @param {Number} endSequence the mediaSequence to stop walking on 2299 */ 2300var forwardDuration = function forwardDuration(playlist, endSequence) { 2301 var result = 0; 2302 var segment = undefined; 2303 var i = endSequence - playlist.mediaSequence; 2304 // Walk forward until we find the earliest segment with timeline 2305 // information 2306 2307 for (; i < playlist.segments.length; i++) { 2308 segment = playlist.segments[i]; 2309 if (typeof segment.start !== 'undefined') { 2310 return { 2311 result: segment.start - result, 2312 precise: true 2313 }; 2314 } 2315 2316 result += segment.duration; 2317 2318 if (typeof segment.end !== 'undefined') { 2319 return { 2320 result: segment.end - result, 2321 precise: true 2322 }; 2323 } 2324 } 2325 // indicate we didn't find a useful duration estimate
2326 return { result: -1, precise: false }; 2327}; 2328 2329/** 2330 * Calculate the media duration from the segments associated with a 2331 * playlist. The duration of a subinterval of the available segments 2332 * may be calculated by specifying an end index. 2333 * 2334 * @param {Object} playlist a media playlist object 2335 * @param {Number=} endSequence an exclusive upper boundary 2336 * for the playlist. Defaults to playlist length. 2337 * @param {Number} expired the amount of time that has dropped 2338 * off the front of the playlist in a live scenario 2339 * @return {Number} the duration between the first available segment 2340 * and end index. 2341 */ 2342var intervalDuration = function intervalDuration(playlist, endSequence, expired) { 2343 var backward = undefined; 2344 var forward = undefined; 2345 2346 if (typeof endSequence === 'undefined') { 2347 endSequence = playlist.mediaSequence + playlist.segments.length; 2348 } 2349 2350 if (endSequence < playlist.mediaSequence) { 2351 return 0; 2352 } 2353 2354 // do a backward walk to estimate the duration 2355 backward = backwardDuration(playlist, endSequence); 2356 if (backward.precise) { 2357 // if we were able to base our duration estimate on timing 2358 // information provided directly from the Media Source, return 2359 // it 2360 return backward.result; 2361 } 2362 2363 // walk forward to see if a precise duration estimate can be made 2364 // that way 2365 forward = forwardDuration(playlist, endSequence); 2366 if (forward.precise) { 2367 // we found a segment that has been buffered and so it's 2368 // position is known precisely 2369 return forward.result; 2370 } 2371 2372 // return the less-precise, playlist-based duration estimate 2373 return backward.result + expired; 2374}; 2375 2376/** 2377 * Calculates the duration of a playlist. If a start and end index 2378 * are specified, the duration will be for the subset of the media 2379 * timeline between those two indices. The total duration for live 2380 * playlists is always Infinity. 2381 * 2382 * @param {Object} playlist a media playlist object 2383 * @param {Number=} endSequence an exclusive upper 2384 * boundary for the playlist. Defaults to the playlist media 2385 * sequence number plus its length. 2386 * @param {Number=} expired the amount of time that has 2387 * dropped off the front of the playlist in a live scenario 2388 * @return {Number} the duration between the start index and end 2389 * index. 2390 */ 2391var duration = function duration(playlist, endSequence, expired) { 2392 if (!playlist) { 2393 return 0; 2394 } 2395 2396 if (typeof expired !== 'number') { 2397 expired = 0; 2398 } 2399 2400 // if a slice of the total duration is not requested, use 2401 // playlist-level duration indicators when they're present 2402 if (typeof endSequence === 'undefined') { 2403 // if present, use the duration specified in the playlist 2404 if (playlist.totalDuration) { 2405 return playlist.totalDuration; 2406 } 2407 2408 // duration should be Infinity for live playlists 2409 if (!playlist.endList) { 2410 return _globalWindow2['default'].Infinity; 2411 } 2412 } 2413 2414 // calculate the total duration based on the segment durations 2415 return intervalDuration(playlist, endSequence, expired); 2416}; 2417 2418exports.duration = duration; 2419/** 2420 * Calculates the interval of time that is currently seekable in a 2421 * playlist. The returned time ranges are relative to the earliest 2422 * moment in the specified playlist that is still available. A full 2423 * seekable implementation for live streams would need to offset 2424 * these values by the duration of content that has expired from the 2425 * stream. 2426 * 2427 * @param {Object} playlist a media playlist object 2428 * @param {Number=} expired the amount of time that has 2429 * dropped off the front of the playlist in a live scenario 2430 * @return {TimeRanges} the periods of time that are valid targets 2431 * for seeking 2432 */ 2433var seekable = function seekable(playlist, expired) { 2434 var start = undefined; 2435 var end = undefined; 2436 var endSequence = undefined; 2437 2438 if (typeof expired !== 'number') { 2439 expired = 0; 2440 } 2441 2442 // without segments, there are no seekable ranges 2443 if (!playlist || !playlist.segments) { 2444 return (0, _videoJs.createTimeRange)(); 2445 } 2446 // when the playlist is complete, the entire duration is seekable 2447 if (playlist.endList) { 2448 return (0, _videoJs.createTimeRange)(0, duration(playlist)); 2449 } 2450 2451 // live playlists should not expose three segment durations worth 2452 // of content from the end of the playlist 2453 // https://tools.ietf.org/html/draft-pantos-http-live-streaming-16#section-6.3.3 2454 start = intervalDuration(playlist, playlist.mediaSequence, expired); 2455 endSequence = Math.max(0, playlist.segments.length - Playlist.UNSAFE_LIVE_SEGMENTS); 2456 end = intervalDuration(playlist, playlist.mediaSequence + endSequence, expired); 2457 return (0, _videoJs.createTimeRange)(start, end); 2458}; 2459 2460exports.seekable = seekable; 2461/** 2462 * Determine the index of the segment that contains a specified 2463 * playback position in a media playlist. 2464 * 2465 * @param {Object} playlist the media playlist to query 2466 * @param {Number} time The number of seconds since the earliest 2467 * possible position to determine the containing segment for 2468 * @param {Number=} expired the duration of content, in 2469 * seconds, that has been removed from this playlist because it 2470 * expired 2471 * @return {Number} The number of the media segment that contains 2472 * that time position. 2473 */ 2474var getMediaIndexForTime_ = function getMediaIndexForTime_(playlist, time, expired) { 2475 var i = undefined; 2476 var segment = undefined; 2477 var originalTime = time; 2478 var numSegments = playlist.segments.length; 2479 var lastSegment = numSegments - 1; 2480 var startIndex = undefined; 2481 var endIndex = undefined; 2482 var knownStart = undefined; 2483 var knownEnd = undefined; 2484 2485 if (!playlist) { 2486 return 0; 2487 } 2488 2489 // when the requested position is earlier than the current set of 2490 // segments, return the earliest segment index 2491 if (time < 0) { 2492 return 0; 2493 } 2494 2495 if (time === 0 && !expired) { 2496 return 0; 2497 } 2498 2499 expired = expired || 0; 2500 2501 // find segments with known timing information that bound the 2502 // target time 2503 for (i = 0; i < numSegments; i++) { 2504 segment = playlist.segments[i];
2505 if (segment.end) { 2506 if (segment.end > time) { 2507 knownEnd = segment.end; 2508 endIndex = i; 2509 break; 2510 } else { 2511 knownStart = segment.end; 2512 startIndex = i + 1; 2513 } 2514 } 2515 } 2516 2517 // time was equal to or past the end of the last segment in the playlist 2518 if (startIndex === numSegments) { 2519 return numSegments; 2520 } 2521 2522 // use the bounds we just found and playlist information to 2523 // estimate the segment that contains the time we are looking for 2524 if (typeof startIndex !== 'undefined') { 2525 // We have a known-start point that is before our desired time so 2526 // walk from that point forwards 2527 time = time - knownStart; 2528 for (i = startIndex; i < (endIndex || numSegments); i++) { 2529 segment = playlist.segments[i]; 2530 time -= segment.duration; 2531 2532 if (time < 0) { 2533 return i; 2534 } 2535 } 2536 2537 if (i >= endIndex) { 2538 // We haven't found a segment but we did hit a known end point 2539 // so fallback to interpolating between the segment index 2540 // based on the known span of the timeline we are dealing with 2541 // and the number of segments inside that span 2542 return startIndex + Math.floor((originalTime - knownStart) / (knownEnd - knownStart) * (endIndex - startIndex)); 2543 } 2544 2545 // We _still_ haven't found a segment so load the last one 2546 return lastSegment; 2547 } else if (typeof endIndex !== 'undefined') { 2548 // We _only_ have a known-end point that is after our desired time so 2549 // walk from that point backwards 2550 time = knownEnd - time; 2551 for (i = endIndex; i >= 0; i--) { 2552 segment = playlist.segments[i]; 2553 time -= segment.duration; 2554 2555 if (time < 0) { 2556 return i; 2557 } 2558 } 2559 2560 // We haven't found a segment so load the first one if time is zero 2561 if (time === 0) { 2562 return 0; 2563 } 2564 return -1; 2565 } 2566 // We known nothing so walk from the front of the playlist, 2567 // subtracting durations until we find a segment that contains 2568 // time and return it 2569 time = time - expired; 2570 2571 if (time < 0) { 2572 return -1; 2573 } 2574 2575 for (i = 0; i < numSegments; i++) { 2576 segment = playlist.segments[i]; 2577 time -= segment.duration; 2578 if (time < 0) { 2579 return i; 2580 } 2581 } 2582 // We are out of possible candidates so load the last one... 2583 // The last one is the least likely to overlap a buffer and therefore 2584 // the one most likely to tell us something about the timeline 2585 return lastSegment; 2586}; 2587 2588exports.getMediaIndexForTime_ = getMediaIndexForTime_; 2589Playlist.duration = duration; 2590Playlist.seekable = seekable; 2591Playlist.getMediaIndexForTime_ = getMediaIndexForTime_; 2592 2593// exports 2594exports['default'] = Playlist; 2595}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 2596},{"global/window":25}],8:[function(require,module,exports){ 2597(function (global){ 2598/** 2599 * ranges 2600 * 2601 * Utilities for working with TimeRanges. 2602 * 2603 */ 2604 2605'use strict'; 2606 2607Object.defineProperty(exports, '__esModule', { 2608 value: true 2609}); 2610 2611var _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'); } }; })(); 2612 2613function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 2614 2615var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 2616 2617var _videoJs2 = _interopRequireDefault(_videoJs); 2618 2619// Fudge factor to account for TimeRanges rounding 2620var TIME_FUDGE_FACTOR = 1 / 30; 2621 2622/** 2623 * Clamps a value to within a range 2624 * @param {Number} num - the value to clamp 2625 * @param {Number} start - the start of the range to clamp within, inclusive 2626 * @param {Number} end - the end of the range to clamp within, inclusive 2627 * @return {Number} 2628 */ 2629var clamp = function clamp(num, _ref) { 2630 var _ref2 = _slicedToArray(_ref, 2); 2631 2632 var start = _ref2[0]; 2633 var end = _ref2[1]; 2634 2635 return Math.min(Math.max(start, num), end); 2636}; 2637var filterRanges = function filterRanges(timeRanges, predicate) { 2638 var results = []; 2639 var i = undefined; 2640 2641 if (timeRanges && timeRanges.length) { 2642 // Search for ranges that match the predicate 2643 for (i = 0; i < timeRanges.length; i++) { 2644 if (predicate(timeRanges.start(i), timeRanges.end(i))) { 2645 results.push([timeRanges.start(i), timeRanges.end(i)]); 2646 } 2647 } 2648 } 2649 2650 return _videoJs2['default'].createTimeRanges(results); 2651}; 2652 2653/** 2654 * Attempts to find the buffered TimeRange that contains the specified 2655 * time. 2656 * @param {TimeRanges} buffered - the TimeRanges object to query 2657 * @param {number} time - the time to filter on. 2658 * @returns {TimeRanges} a new TimeRanges object 2659 */ 2660var findRange = function findRange(buffered, time) { 2661 return filterRanges(buffered, function (start, end) {
2662 return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time; 2663 }); 2664}; 2665 2666/** 2667 * Returns the TimeRanges that begin later than the specified time. 2668 * @param {TimeRanges} timeRanges - the TimeRanges object to query 2669 * @param {number} time - the time to filter on. 2670 * @returns {TimeRanges} a new TimeRanges object. 2671 */ 2672var findNextRange = function findNextRange(timeRanges, time) { 2673 return filterRanges(timeRanges, function (start) { 2674 return start - TIME_FUDGE_FACTOR >= time; 2675 }); 2676}; 2677 2678/** 2679 * Returns gaps within a list of TimeRanges 2680 * @param {TimeRanges} buffered - the TimeRanges object 2681 * @return {TimeRanges} a TimeRanges object of gaps 2682 */ 2683var findGaps = function findGaps(buffered) { 2684 if (buffered.length < 2) { 2685 return _videoJs2['default'].createTimeRanges(); 2686 } 2687 2688 var ranges = []; 2689 2690 for (var i = 1; i < buffered.length; i++) { 2691 var start = buffered.end(i - 1); 2692 var end = buffered.start(i); 2693 2694 ranges.push([start, end]); 2695 } 2696 2697 return _videoJs2['default'].createTimeRanges(ranges); 2698}; 2699 2700/** 2701 * Search for a likely end time for the segment that was just appened 2702 * based on the state of the `buffered` property before and after the 2703 * append. If we fin only one such uncommon end-point return it. 2704 * @param {TimeRanges} original - the buffered time ranges before the update 2705 * @param {TimeRanges} update - the buffered time ranges after the update 2706 * @returns {Number|null} the end time added between `original` and `update`, 2707 * or null if one cannot be unambiguously determined. 2708 */ 2709var findSoleUncommonTimeRangesEnd = function findSoleUncommonTimeRangesEnd(original, update) { 2710 var i = undefined; 2711 var start = undefined; 2712 var end = undefined; 2713 var result = []; 2714 var edges = []; 2715 2716 // In order to qualify as a possible candidate, the end point must: 2717 // 1) Not have already existed in the `original` ranges 2718 // 2) Not result from the shrinking of a range that already existed 2719 // in the `original` ranges 2720 // 3) Not be contained inside of a range that existed in `original` 2721 var overlapsCurrentEnd = function overlapsCurrentEnd(span) { 2722 return span[0] <= end && span[1] >= end; 2723 }; 2724 2725 if (original) { 2726 // Save all the edges in the `original` TimeRanges object 2727 for (i = 0; i < original.length; i++) { 2728 start = original.start(i); 2729 end = original.end(i); 2730 2731 edges.push([start, end]); 2732 } 2733 } 2734 2735 if (update) { 2736 // Save any end-points in `update` that are not in the `original` 2737 // TimeRanges object 2738 for (i = 0; i < update.length; i++) { 2739 start = update.start(i); 2740 end = update.end(i); 2741 2742 if (edges.some(overlapsCurrentEnd)) { 2743 continue; 2744 } 2745 2746 // at this point it must be a unique non-shrinking end edge 2747 result.push(end); 2748 } 2749 } 2750 2751 // we err on the side of caution and return null if didn't find 2752 // exactly *one* differing end edge in the search above 2753 if (result.length !== 1) { 2754 return null; 2755 } 2756 2757 return result[0]; 2758}; 2759 2760/** 2761 * Calculate the intersection of two TimeRanges 2762 * @param {TimeRanges} bufferA 2763 * @param {TimeRanges} bufferB 2764 * @returns {TimeRanges} The interesection of `bufferA` with `bufferB` 2765 */ 2766var bufferIntersection = function bufferIntersection(bufferA, bufferB) { 2767 var start = null; 2768 var end = null; 2769 var arity = 0; 2770 var extents = []; 2771 var ranges = []; 2772 2773 if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) { 2774 return _videoJs2['default'].createTimeRange(); 2775 } 2776 2777 // Handle the case where we have both buffers and create an 2778 // intersection of the two 2779 var count = bufferA.length; 2780 2781 // A) Gather up all start and end times 2782 while (count--) { 2783 extents.push({ time: bufferA.start(count), type: 'start' }); 2784 extents.push({ time: bufferA.end(count), type: 'end' }); 2785 } 2786 count = bufferB.length; 2787 while (count--) { 2788 extents.push({ time: bufferB.start(count), type: 'start' }); 2789 extents.push({ time: bufferB.end(count), type: 'end' }); 2790 } 2791 // B) Sort them by time 2792 extents.sort(function (a, b) { 2793 return a.time - b.time; 2794 }); 2795 2796 // C) Go along one by one incrementing arity for start and decrementing 2797 // arity for ends 2798 for (count = 0; count < extents.length; count++) { 2799 if (extents[count].type === 'start') { 2800 arity++; 2801 2802 // D) If arity is ever incremented to 2 we are entering an 2803 // overlapping range 2804 if (arity === 2) { 2805 start = extents[count].time; 2806 } 2807 } else if (extents[count].type === 'end') { 2808 arity--; 2809
2810 // E) If arity is ever decremented to 1 we leaving an 2811 // overlapping range 2812 if (arity === 1) { 2813 end = extents[count].time; 2814 } 2815 } 2816 2817 // F) Record overlapping ranges 2818 if (start !== null && end !== null) { 2819 ranges.push([start, end]); 2820 start = null; 2821 end = null; 2822 } 2823 } 2824 2825 return _videoJs2['default'].createTimeRanges(ranges); 2826}; 2827 2828/** 2829 * Calculates the percentage of `segmentRange` that overlaps the 2830 * `buffered` time ranges. 2831 * @param {TimeRanges} segmentRange - the time range that the segment 2832 * covers adjusted according to currentTime 2833 * @param {TimeRanges} referenceRange - the original time range that the 2834 * segment covers 2835 * @param {Number} currentTime - time in seconds where the current playback 2836 * is at 2837 * @param {TimeRanges} buffered - the currently buffered time ranges 2838 * @returns {Number} percent of the segment currently buffered 2839 */ 2840var calculateBufferedPercent = function calculateBufferedPercent(adjustedRange, referenceRange, currentTime, buffered) { 2841 var referenceDuration = referenceRange.end(0) - referenceRange.start(0); 2842 var adjustedDuration = adjustedRange.end(0) - adjustedRange.start(0); 2843 var bufferMissingFromAdjusted = referenceDuration - adjustedDuration; 2844 var adjustedIntersection = bufferIntersection(adjustedRange, buffered); 2845 var referenceIntersection = bufferIntersection(referenceRange, buffered); 2846 var adjustedOverlap = 0; 2847 var referenceOverlap = 0; 2848 2849 var count = adjustedIntersection.length; 2850 2851 while (count--) { 2852 adjustedOverlap += adjustedIntersection.end(count) - adjustedIntersection.start(count); 2853 2854 // If the current overlap segment starts at currentTime, then increase the 2855 // overlap duration so that it actually starts at the beginning of referenceRange 2856 // by including the difference between the two Range's durations 2857 // This is a work around for the way Flash has no buffer before currentTime 2858 if (adjustedIntersection.start(count) === currentTime) { 2859 adjustedOverlap += bufferMissingFromAdjusted; 2860 } 2861 } 2862 2863 count = referenceIntersection.length; 2864 2865 while (count--) { 2866 referenceOverlap += referenceIntersection.end(count) - referenceIntersection.start(count); 2867 } 2868 2869 // Use whichever value is larger for the percentage-buffered since that value 2870 // is likely more accurate because the only way 2871 return Math.max(adjustedOverlap, referenceOverlap) / referenceDuration * 100; 2872}; 2873 2874/** 2875 * Return the amount of a range specified by the startOfSegment and segmentDuration 2876 * overlaps the current buffered content. 2877 * 2878 * @param {Number} startOfSegment - the time where the segment begins 2879 * @param {Number} segmentDuration - the duration of the segment in seconds 2880 * @param {Number} currentTime - time in seconds where the current playback 2881 * is at 2882 * @param {TimeRanges} buffered - the state of the buffer 2883 * @returns {Number} percentage of the segment's time range that is 2884 * already in `buffered` 2885 */ 2886var getSegmentBufferedPercent = function getSegmentBufferedPercent(startOfSegment, segmentDuration, currentTime, buffered) { 2887 var endOfSegment = startOfSegment + segmentDuration; 2888 2889 // The entire time range of the segment 2890 var originalSegmentRange = _videoJs2['default'].createTimeRanges([[startOfSegment, endOfSegment]]); 2891 2892 // The adjusted segment time range that is setup such that it starts 2893 // no earlier than currentTime 2894 // Flash has no notion of a back-buffer so adjustedSegmentRange adjusts 2895 // for that and the function will still return 100% if a only half of a 2896 // segment is actually in the buffer as long as the currentTime is also 2897 // half-way through the segment 2898 var adjustedSegmentRange = _videoJs2['default'].createTimeRanges([[clamp(startOfSegment, [currentTime, endOfSegment]), endOfSegment]]); 2899 2900 // This condition happens when the currentTime is beyond the segment's 2901 // end time 2902 if (adjustedSegmentRange.start(0) === adjustedSegmentRange.end(0)) { 2903 return 0; 2904 } 2905 2906 var percent = calculateBufferedPercent(adjustedSegmentRange, originalSegmentRange, currentTime, buffered); 2907 2908 // If the segment is reported as having a zero duration, return 0% 2909 // since it is likely that we will need to fetch the segment 2910 if (isNaN(percent) || percent === Infinity || percent === -Infinity) { 2911 return 0; 2912 } 2913 2914 return percent; 2915}; 2916 2917exports['default'] = { 2918 findRange: findRange, 2919 findNextRange: findNextRange, 2920 findGaps: findGaps, 2921 findSoleUncommonTimeRangesEnd: findSoleUncommonTimeRangesEnd, 2922 getSegmentBufferedPercent: getSegmentBufferedPercent, 2923 TIME_FUDGE_FACTOR: TIME_FUDGE_FACTOR 2924}; 2925module.exports = exports['default']; 2926}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 2927},{}],9:[function(require,module,exports){ 2928/** 2929 * Enable/disable playlist function. It is intended to have the first two 2930 * arguments partially-applied in order to create the final per-playlist 2931 * function. 2932 * 2933 * @param {PlaylistLoader} playlist - The rendition or media-playlist 2934 * @param {Function} changePlaylistFn - A function to be called after a 2935 * playlist's enabled-state has been changed. Will NOT be called if a 2936 * playlist's enabled-state is unchanged 2937 * @param {Boolean=} enable - Value to set the playlist enabled-state to 2938 * or if undefined returns the current enabled-state for the playlist 2939 * @return {Boolean} The current enabled-state of the playlist 2940 */ 2941'use strict'; 2942 2943Object.defineProperty(exports, '__esModule', { 2944 value: true 2945}); 2946 2947function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 2948 2949var enableFunction = function enableFunction(playlist, changePlaylistFn, enable) { 2950 var currentlyEnabled = typeof playlist.excludeUntil === 'undefined' || playlist.excludeUntil <= Date.now(); 2951 2952 if (typeof enable === 'undefined') { 2953 return currentlyEnabled; 2954 } 2955 2956 if (enable !== currentlyEnabled) { 2957 if (enable) { 2958 delete playlist.excludeUntil; 2959 } else { 2960 playlist.excludeUntil = Infinity; 2961 } 2962 2963 // Ensure the outside world knows about our changes 2964 changePlaylistFn(); 2965 } 2966 2967 return enable; 2968}; 2969 2970/** 2971 * The representation object encapsulates the publicly visible information 2972 * in a media playlist along with a setter/getter-type function (enabled) 2973 * for changing the enabled-state of a particular playlist entry 2974 * 2975 * @class Representation 2976 */ 2977 2978var Representation = function Representation(hlsHandler, playlist, id) { 2979 _classCallCheck(this, Representation); 2980 2981 // Get a reference to a bound version of fastQualityChange_ 2982 var fastChangeFunction = hlsHandler.masterPlaylistController_.fastQualityChange_.bind(hlsHandler.masterPlaylistController_); 2983
2984 // Carefully descend into the playlist's attributes since most 2985 // properties are optional 2986 if (playlist.attributes) { 2987 var attributes = playlist.attributes; 2988 2989 if (attributes.RESOLUTION) { 2990 var resolution = attributes.RESOLUTION; 2991 2992 this.width = resolution.width; 2993 this.height = resolution.height; 2994 } 2995 2996 this.bandwidth = attributes.BANDWIDTH; 2997 } 2998 2999 // The id is simply the ordinality of the media playlist 3000 // within the master playlist 3001 this.id = id; 3002 3003 // Partially-apply the enableFunction to create a playlist- 3004 // specific variant 3005 this.enabled = enableFunction.bind(this, playlist, fastChangeFunction); 3006} 3007 3008/** 3009 * A mixin function that adds the `representations` api to an instance 3010 * of the HlsHandler class 3011 * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the 3012 * representation API into 3013 */ 3014; 3015 3016var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) { 3017 var playlists = hlsHandler.playlists; 3018 3019 // Add a single API-specific function to the HlsHandler instance 3020 hlsHandler.representations = function () { 3021 return playlists.master.playlists.map(function (e, i) { 3022 return new Representation(hlsHandler, e, i); 3023 }); 3024 }; 3025}; 3026 3027exports['default'] = renditionSelectionMixin; 3028module.exports = exports['default']; 3029},{}],10:[function(require,module,exports){ 3030/** 3031 * @file resolve-url.js 3032 */ 3033'use strict'; 3034 3035Object.defineProperty(exports, '__esModule', { 3036 value: true 3037}); 3038 3039function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 3040 3041var _globalDocument = require('global/document'); 3042 3043var _globalDocument2 = _interopRequireDefault(_globalDocument); 3044 3045/** 3046 * Constructs a new URI by interpreting a path relative to another 3047 * URI. 3048 * 3049 * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue 3050 * @param {String} basePath a relative or absolute URI 3051 * @param {String} path a path part to combine with the base 3052 * @return {String} a URI that is equivalent to composing `base` 3053 * with `path` 3054 */ 3055var resolveUrl = function resolveUrl(basePath, path) { 3056 // use the base element to get the browser to handle URI resolution 3057 var oldBase = _globalDocument2['default'].querySelector('base'); 3058 var docHead = _globalDocument2['default'].querySelector('head'); 3059 var a = _globalDocument2['default'].createElement('a'); 3060 var base = oldBase; 3061 var oldHref = undefined; 3062 var result = undefined; 3063 3064 // prep the document 3065 if (oldBase) { 3066 oldHref = oldBase.href; 3067 } else { 3068 base = docHead.appendChild(_globalDocument2['default'].createElement('base')); 3069 } 3070 3071 base.href = basePath; 3072 a.href = path; 3073 result = a.href; 3074 3075 // clean up 3076 if (oldBase) { 3077 oldBase.href = oldHref; 3078 } else { 3079 docHead.removeChild(base); 3080 } 3081 return result; 3082}; 3083 3084exports['default'] = resolveUrl; 3085module.exports = exports['default']; 3086},{"global/document":24}],11:[function(require,module,exports){ 3087(function (global){ 3088/** 3089 * @file segment-loader.js 3090 */ 3091'use strict'; 3092 3093Object.defineProperty(exports, '__esModule', { 3094 value: true 3095}); 3096 3097var _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; }; })(); 3098 3099var _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); } } }; 3100 3101function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 3102 3103function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 3104 3105function _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
3105onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 3106 3107var _ranges = require('./ranges'); 3108 3109var _ranges2 = _interopRequireDefault(_ranges); 3110 3111var _playlist = require('./playlist'); 3112 3113var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 3114 3115var _videoJs2 = _interopRequireDefault(_videoJs); 3116 3117var _sourceUpdater = require('./source-updater'); 3118 3119var _sourceUpdater2 = _interopRequireDefault(_sourceUpdater); 3120 3121var _aesDecrypter = require('aes-decrypter'); 3122 3123var _muxJsLibMp4Probe = require('mux.js/lib/mp4/probe'); 3124 3125var _muxJsLibMp4Probe2 = _interopRequireDefault(_muxJsLibMp4Probe); 3126 3127var _config = require('./config'); 3128 3129var _config2 = _interopRequireDefault(_config); 3130 3131var _globalWindow = require('global/window'); 3132 3133var _globalWindow2 = _interopRequireDefault(_globalWindow); 3134 3135// in ms 3136var CHECK_BUFFER_DELAY = 500; 3137 3138/** 3139 * Updates segment with information about its end-point in time and, optionally, 3140 * the segment duration if we have enough information to determine a segment duration 3141 * accurately. 3142 * 3143 * @param {Object} playlist a media playlist object 3144 * @param {Number} segmentIndex the index of segment we last appended 3145 * @param {Number} segmentEnd the known of the segment referenced by segmentIndex 3146 */ 3147var updateSegmentMetadata = function updateSegmentMetadata(playlist, segmentIndex, segmentEnd) { 3148 if (!playlist) { 3149 return false; 3150 } 3151 3152 var segment = playlist.segments[segmentIndex]; 3153 var previousSegment = playlist.segments[segmentIndex - 1]; 3154 3155 if (segmentEnd && segment) { 3156 segment.end = segmentEnd; 3157 3158 // fix up segment durations based on segment end data 3159 if (!previousSegment) { 3160 // first segment is always has a start time of 0 making its duration 3161 // equal to the segment end 3162 segment.duration = segment.end; 3163 } else if (previousSegment.end) { 3164 segment.duration = segment.end - previousSegment.end; 3165 } 3166 return true; 3167 } 3168 return false; 3169}; 3170 3171/** 3172 * Determines if we should call endOfStream on the media source based 3173 * on the state of the buffer or if appened segment was the final 3174 * segment in the playlist. 3175 * 3176 * @param {Object} playlist a media playlist object 3177 * @param {Object} mediaSource the MediaSource object 3178 * @param {Number} segmentIndex the index of segment we last appended 3179 * @returns {Boolean} do we need to call endOfStream on the MediaSource 3180 */ 3181var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) { 3182 if (!playlist) { 3183 return false; 3184 } 3185 3186 var segments = playlist.segments; 3187 3188 // determine a few boolean values to help make the branch below easier 3189 // to read 3190 var appendedLastSegment = segmentIndex === segments.length; 3191 3192 // if we've buffered to the end of the video, we need to call endOfStream 3193 // so that MediaSources can trigger the `ended` event when it runs out of 3194 // buffered data instead of waiting for me 3195 return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment; 3196}; 3197 3198/** 3199 * Turns segment byterange into a string suitable for use in 3200 * HTTP Range requests 3201 */ 3202var byterangeStr = function byterangeStr(byterange) { 3203 var byterangeStart = undefined; 3204 var byterangeEnd = undefined; 3205 3206 // `byterangeEnd` is one less than `offset + length` because the HTTP range 3207 // header uses inclusive ranges 3208 byterangeEnd = byterange.offset + byterange.length - 1; 3209 byterangeStart = byterange.offset; 3210 return 'bytes=' + byterangeStart + '-' + byterangeEnd; 3211}; 3212 3213/** 3214 * Defines headers for use in the xhr request for a particular segment. 3215 */ 3216var segmentXhrHeaders = function segmentXhrHeaders(segment) { 3217 var headers = {}; 3218 3219 if ('byterange' in segment) { 3220 headers.Range = byterangeStr(segment.byterange); 3221 } 3222 return headers; 3223}; 3224 3225/** 3226 * Returns a unique string identifier for a media initialization 3227 * segment. 3228 */ 3229var initSegmentId = function initSegmentId(initSegment) { 3230 var byterange = initSegment.byterange || { 3231 length: Infinity, 3232 offset: 0 3233 }; 3234 3235 return [byterange.length, byterange.offset, initSegment.resolvedUri].join(','); 3236}; 3237 3238/** 3239 * An object that manages segment loading and appending. 3240 * 3241 * @class SegmentLoader 3242 * @param {Object} options required and optional options 3243 * @extends videojs.EventTarget 3244 */ 3245 3246var SegmentLoader = (function (_videojs$EventTarget) { 3247 _inherits(SegmentLoader, _videojs$EventTarget); 3248 3249 function SegmentLoader(options) { 3250 _classCallCheck(this, SegmentLoader); 3251 3252 _get(Object.getPrototypeOf(SegmentLoader.prototype), 'constructor', this).call(this); 3253 var settings = undefined; 3254 3255 // check pre-conditions 3256 if (!options) { 3257 throw new TypeError('Initialization options are required'); 3258 } 3259 if (typeof options.currentTime !== 'function') { 3260 throw new TypeError('No currentTime getter specified'); 3261 } 3262 if (!options.mediaSource) { 3263 throw new TypeError('No MediaSource specified'); 3264 } 3265 settings = _videoJs2['default'].mergeOptions(_videoJs2['default'].options.hls, options); 3266 3267 // public properties 3268 this.state = 'INIT'; 3269 this.bandwidth = settings.bandwidth; 3270 this.roundTrip = NaN; 3271 this.resetStats_(); 3272 3273 // private settings 3274 this.hasPlayed_ = settings.hasPlayed; 3275 this.currentTime_ = settings.currentTime; 3276 this.seekable_ = settings.seekable; 3277 this.seeking_ = settings.seeking; 3278 this.setCurrentTime_ = settings.setCurrentTime; 3279 this.mediaSource_ = settings.mediaSource; 3280 3281 this.hls_ = settings.hls; 3282 3283 // private instance variables 3284 this.checkBufferTimeout_ = null; 3285 this.error_ = void 0; 3286 this.expired_ = 0; 3287 this.timeCorrection_ = 0; 3288 this.currentTimeline_ = -1; 3289 this.zeroOffset_ = NaN; 3290 this.xhr_ = null; 3291 this.pendingSegment_ = null; 3292 this.mimeType_ = null; 3293 this.sourceUpdater_ = null; 3294 this.xhrOptions_ = null; 3295 3296 this.activeInitSegmentId_ = null; 3297 this.initSegments_ = {}; 3298 } 3299 3300 /** 3301 * reset all of our media stats 3302 * 3303 * @private 3304 */ 3305 3306 _createClass(SegmentLoader, [{ 3307 key: 'resetStats_', 3308 value: function resetStats_() { 3309 this.mediaBytesTransferred = 0; 3310 this.mediaRequests = 0; 3311 this.mediaTransferDuration = 0; 3312 } 3313 3314 /** 3315 * dispose of the SegmentLoader and reset to the default state 3316 */ 3317 }, { 3318 key: 'dispose', 3319 value: function dispose() { 3320 this.state = 'DISPOSED'; 3321 this.abort_(); 3322 if (this.sourceUpdater_) { 3323 this.sourceUpdater_.dispose(); 3324 } 3325 this.resetStats_(); 3326 } 3327 3328 /** 3329 * abort anything that is currently doing on with the SegmentLoader 3330 * and reset to a default state 3331 */ 3332 }, { 3333 key: 'abort', 3334 value: function abort() { 3335 if (this.state !== 'WAITING') { 3336 return; 3337 } 3338 3339 this.abort_(); 3340 3341 // don't wait for buffer check timeouts to begin fetching the 3342 // next segment 3343 if (!this.paused()) { 3344 this.state = 'READY'; 3345 this.fillBuffer_(); 3346 } 3347 } 3348 3349 /**
3350 * set an error on the segment loader and null out any pending segements 3351 * 3352 * @param {Error} error the error to set on the SegmentLoader 3353 * @return {Error} the error that was set or that is currently set 3354 */ 3355 }, { 3356 key: 'error', 3357 value: function error(_error) { 3358 if (typeof _error !== 'undefined') { 3359 this.error_ = _error; 3360 } 3361 3362 this.pendingSegment_ = null; 3363 return this.error_; 3364 } 3365 3366 /** 3367 * load a playlist and start to fill the buffer 3368 */ 3369 }, { 3370 key: 'load', 3371 value: function load() { 3372 // un-pause 3373 this.monitorBuffer_(); 3374 3375 // if we don't have a playlist yet, keep waiting for one to be 3376 // specified 3377 if (!this.playlist_) { 3378 return; 3379 } 3380 3381 // if all the configuration is ready, initialize and begin loading 3382 if (this.state === 'INIT' && this.mimeType_) { 3383 return this.init_(); 3384 } 3385 3386 // if we're in the middle of processing a segment already, don't 3387 // kick off an additional segment request 3388 if (!this.sourceUpdater_ || this.state !== 'READY' && this.state !== 'INIT') { 3389 return; 3390 } 3391 3392 this.state = 'READY'; 3393 this.fillBuffer_(); 3394 } 3395 3396 /** 3397 * set a playlist on the segment loader 3398 * 3399 * @param {PlaylistLoader} media the playlist to set on the segment loader 3400 */ 3401 }, { 3402 key: 'playlist', 3403 value: function playlist(media) { 3404 var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; 3405 3406 if (!media) { 3407 return; 3408 } 3409 3410 this.playlist_ = media; 3411 this.xhrOptions_ = options; 3412 3413 // if we were unpaused but waiting for a playlist, start 3414 // buffering now 3415 if (this.mimeType_ && this.state === 'INIT' && !this.paused()) { 3416 return this.init_(); 3417 } 3418 } 3419 3420 /** 3421 * Prevent the loader from fetching additional segments. If there 3422 * is a segment request outstanding, it will finish processing 3423 * before the loader halts. A segment loader can be unpaused by 3424 * calling load(). 3425 */ 3426 }, { 3427 key: 'pause', 3428 value: function pause() { 3429 if (this.checkBufferTimeout_) { 3430 _globalWindow2['default'].clearTimeout(this.checkBufferTimeout_); 3431 3432 this.checkBufferTimeout_ = null; 3433 } 3434 } 3435 3436 /** 3437 * Returns whether the segment loader is fetching additional 3438 * segments when given the opportunity. This property can be 3439 * modified through calls to pause() and load(). 3440 */ 3441 }, { 3442 key: 'paused', 3443 value: function paused() { 3444 return this.checkBufferTimeout_ === null; 3445 } 3446 3447 /** 3448 * setter for expired time on the SegmentLoader 3449 * 3450 * @param {Number} expired the exired time to set 3451 */ 3452 }, { 3453 key: 'expired', 3454 value: function expired(_expired) { 3455 this.expired_ = _expired; 3456 } 3457 3458 /** 3459 * create/set the following mimetype on the SourceBuffer through a 3460 * SourceUpdater 3461 * 3462 * @param {String} mimeType the mime type string to use 3463 */ 3464 }, { 3465 key: 'mimeType', 3466 value: function mimeType(_mimeType) { 3467 if (this.mimeType_) { 3468 return; 3469 } 3470 3471 this.mimeType_ = _mimeType; 3472 // if we were unpaused but waiting for a sourceUpdater, start 3473 // buffering now 3474 if (this.playlist_ && this.state === 'INIT' && !this.paused()) { 3475 this.init_(); 3476 } 3477 } 3478 3479 /** 3480 * As long as the SegmentLoader is in the READY state, periodically 3481 * invoke fillBuffer_(). 3482 * 3483 * @private 3484 */ 3485 }, { 3486 key: 'monitorBuffer_', 3487 value: function monitorBuffer_() { 3488 if (this.state === 'READY') { 3489 this.fillBuffer_(); 3490 } 3491 3492 if (this.checkBufferTimeout_) { 3493 _globalWindow2['default'].clearTimeout(this.checkBufferTimeout_); 3494 } 3495 3496 this.checkBufferTimeout_ = _globalWindow2['default'].setTimeout(this.monitorBuffer_.bind(this), CHECK_BUFFER_DELAY); 3497 } 3498 3499 /** 3500 * Determines what segment request should be made, given current 3501 * playback state. 3502 * 3503 * @param {TimeRanges} buffered - the state of the buffer 3504 * @param {Object} playlist - the playlist object to fetch segments from 3505 * @param {Number} currentTime - the playback position in seconds 3506 * @param {Boolean} hasPlayed - if the player has played before 3507 * @param {Number} expired - the seconds expired off the playlist 3508 * @param {Number} timeCorrection - correction value to add to current time when 3509 * when determining media index to use 3510 * @returns {Object} a segment info object that describes the 3511 * request that should be made or null if no request is necessary 3512 */ 3513 }, { 3514 key: 'checkBuffer_', 3515 value: function checkBuffer_(buffered, playlist, currentTime, hasPlayed, expired, timeCorrection) { 3516 var currentBuffered = _ranges2['default'].findRange(buffered, currentTime); 3517 3518 // There are times when MSE reports the first segment as starting a 3519 // little after 0-time so add a fudge factor to try and fix those cases 3520 // or we end up fetching the same first segment over and over 3521 if (currentBuffered.length === 0 && currentTime === 0) { 3522 currentBuffered = _ranges2['default'].findRange(buffered, currentTime + _ranges2['default'].TIME_FUDGE_FACTOR); 3523 } 3524 3525 var bufferedTime = undefined; 3526 var currentBufferedEnd = undefined; 3527 var mediaIndex = undefined; 3528 3529 if (!playlist.segments.length) { 3530 return null; 3531 } 3532 3533 if (currentBuffered.length === 0) { 3534 // find the segment containing currentTime 3535 mediaIndex = (0, _playlist.getMediaIndexForTime_)(playlist, currentTime + timeCorrection, expired); 3536 } else { 3537 // find the segment adjacent to the end of the current 3538 // buffered region 3539 currentBufferedEnd = currentBuffered.end(0); 3540 bufferedTime = Math.max(0, currentBufferedEnd - currentTime); 3541 3542 // if the video has not yet played only, and we already have 3543 // one segment downloaded do nothing 3544 if (!hasPlayed && bufferedTime >= 1) { 3545 return null; 3546 } 3547 3548 // if there is plenty of content buffered, and the video has 3549 // been played before relax for awhile 3550 if (hasPlayed && bufferedTime >= _config2['default'].GOAL_BUFFER_LENGTH) { 3551 return null; 3552 } 3553 mediaIndex = (0, _playlist.getMediaIndexForTime_)(playlist, currentBufferedEnd + timeCorrection, expired); 3554 } 3555 3556 return mediaIndex; 3557 } 3558 3559 /**
3560 * abort all pending xhr requests and null any pending segements 3561 * 3562 * @private 3563 */ 3564 }, { 3565 key: 'abort_', 3566 value: function abort_() { 3567 if (this.xhr_) { 3568 this.xhr_.abort(); 3569 } 3570 3571 // clear out the segment being processed 3572 this.pendingSegment_ = null; 3573 } 3574 3575 /** 3576 * Once all the starting parameters have been specified, begin 3577 * operation. This method should only be invoked from the INIT 3578 * state. 3579 */ 3580 }, { 3581 key: 'init_', 3582 value: function init_() { 3583 this.state = 'READY'; 3584 this.sourceUpdater_ = new _sourceUpdater2['default'](this.mediaSource_, this.mimeType_); 3585 this.clearBuffer(); 3586 return this.fillBuffer_(); 3587 } 3588 3589 /** 3590 * fill the buffer with segements unless the 3591 * sourceBuffers are currently updating 3592 * 3593 * @private 3594 */ 3595 }, { 3596 key: 'fillBuffer_', 3597 value: function fillBuffer_() { 3598 if (this.sourceUpdater_.updating()) { 3599 return; 3600 } 3601 3602 var buffered = this.sourceUpdater_.buffered(); 3603 var playlist = this.playlist_; 3604 var currentTime = this.currentTime_(); 3605 var hasPlayed = this.hasPlayed_(); 3606 var expired = this.expired_; 3607 var timeCorrection = this.timeCorrection_; 3608 3609 // see if we need to begin loading immediately 3610 var requestIndex = this.checkBuffer_(buffered, playlist, currentTime, hasPlayed, expired, timeCorrection); 3611 3612 if (requestIndex === null) { 3613 return; 3614 } 3615 3616 var isEndOfStream = detectEndOfStream(playlist, this.mediaSource_, requestIndex); 3617 3618 if (isEndOfStream) { 3619 this.mediaSource_.endOfStream(); 3620 return; 3621 } 3622 3623 if (requestIndex === playlist.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) { 3624 return; 3625 } 3626 3627 if (requestIndex < 0 || requestIndex >= playlist.segments.length) { 3628 return; 3629 } 3630 3631 var segment = this.playlist_.segments[requestIndex]; 3632 3633 var request = { 3634 // resolve the segment URL relative to the playlist 3635 uri: segment.resolvedUri, 3636 // the segment's mediaIndex at the time it was requested 3637 mediaIndex: requestIndex, 3638 // the segment's playlist 3639 playlist: playlist, 3640 // unencrypted bytes of the segment 3641 bytes: null, 3642 // when a key is defined for this segment, the encrypted bytes 3643 encryptedBytes: null, 3644 // the state of the buffer before a segment is appended will be 3645 // stored here so that the actual segment duration can be 3646 // determined after it has been appended 3647 buffered: null, 3648 // The target timestampOffset for this segment when we append it 3649 // to the source buffer 3650 timestampOffset: NaN, 3651 // The timeline that the segment is in 3652 timeline: segment.timeline, 3653 // The expected duration of the segment in seconds 3654 duration: segment.duration 3655 }; 3656 3657 var startOfSegment = (0, _playlist.duration)(playlist, playlist.mediaSequence + request.mediaIndex, expired); 3658 3659 // (we are crossing a discontinuity somehow) 3660 // - The "timestampOffset" for the start of this segment is less than 3661 // the currently set timestampOffset 3662 request.timestampOffset = this.sourceUpdater_.timestampOffset(); 3663 if (segment.timeline !== this.currentTimeline_ || startOfSegment < this.sourceUpdater_.timestampOffset()) { 3664 request.timestampOffset = startOfSegment; 3665 } 3666 3667 // Sanity check the segment-index determining logic by calcuating the 3668 // percentage of the chosen segment that is buffered. If more than 90% 3669 // of the segment is buffered then fetching it will likely not help in 3670 // any way 3671 var percentBuffered = _ranges2['default'].getSegmentBufferedPercent(startOfSegment, segment.duration, currentTime, buffered); 3672 3673 if (percentBuffered >= 90) { 3674 // Increment the timeCorrection_ variable to push the fetcher forward 3675 // in time and hopefully skip any gaps or flaws in our understanding 3676 // of the media 3677 var correctionApplied = this.incrementTimeCorrection_(playlist.targetDuration / 2, 1); 3678 3679 if (correctionApplied && !this.paused()) { 3680 this.fillBuffer_(); 3681 } 3682 3683 return; 3684 } 3685 3686 this.loadSegment_(request); 3687 } 3688 3689 /** 3690 * trim the back buffer so we only remove content 3691 * on segment boundaries 3692 * 3693 * @private 3694 * 3695 * @param {Object} segmentInfo - the current segment 3696 * @returns {Number}
3696 removeToTime - the end point in time, in seconds 3697 * that the the buffer should be trimmed. 3698 */ 3699 }, { 3700 key: 'trimBuffer_', 3701 value: function trimBuffer_(segmentInfo) { 3702 var seekable = this.seekable_(); 3703 var currentTime = this.currentTime_(); 3704 var removeToTime = 0; 3705 3706 // Chrome has a hard limit of 150mb of 3707 // buffer and a very conservative "garbage collector" 3708 // We manually clear out the old buffer to ensure 3709 // we don't trigger the QuotaExceeded error 3710 // on the source buffer during subsequent appends 3711 3712 // If we have a seekable range use that as the limit for what can be removed safely 3713 // otherwise remove anything older than 1 minute before the current play head 3714 if (seekable.length && seekable.start(0) > 0 && seekable.start(0) < currentTime) { 3715 removeToTime = seekable.start(0); 3716 } else { 3717 removeToTime = currentTime - 60; 3718 } 3719 3720 // If we are going to remove time from the front of the buffer, make 3721 // sure we aren't discarding a partial segment to avoid throwing 3722 // PLAYER_ERR_TIMEOUT while trying to read a partially discarded segment 3723 for (var i = 0; i <= segmentInfo.playlist.segments.length; i++) { 3724 // Loop through the segments and calculate the duration to compare 3725 // against the removeToTime 3726 var removeDuration = (0, _playlist.duration)(segmentInfo.playlist, segmentInfo.playlist.mediaSequence + i, this.expired_); 3727 3728 // If we are close to next segment begining, remove to end of previous 3729 // segment instead 3730 var previousDuration = (0, _playlist.duration)(segmentInfo.playlist, segmentInfo.playlist.mediaSequence + (i - 1), this.expired_); 3731 3732 if (removeDuration >= removeToTime) { 3733 removeToTime = previousDuration; 3734 break; 3735 } 3736 } 3737 return removeToTime; 3738 } 3739 3740 /** 3741 * load a specific segment from a request into the buffer 3742 * 3743 * @private 3744 */ 3745 }, { 3746 key: 'loadSegment_', 3747 value: function loadSegment_(segmentInfo) { 3748 var segment = undefined; 3749 var keyXhr = undefined; 3750 var initSegmentXhr = undefined; 3751 var segmentXhr = undefined; 3752 var removeToTime = 0; 3753 3754 removeToTime = this.trimBuffer_(segmentInfo); 3755 3756 if (removeToTime > 0) { 3757 this.sourceUpdater_.remove(0, removeToTime); 3758 } 3759 3760 segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex]; 3761 3762 // optionally, request the decryption key 3763 if (segment.key) { 3764 var keyRequestOptions = _videoJs2['default'].mergeOptions(this.xhrOptions_, { 3765 uri: segment.key.resolvedUri, 3766 responseType: 'arraybuffer' 3767 }); 3768 3769 keyXhr = this.hls_.xhr(keyRequestOptions, this.handleResponse_.bind(this)); 3770 } 3771 3772 // optionally, request the associated media init segment 3773 if (segment.map && !this.initSegments_[initSegmentId(segment.map)]) { 3774 var initSegmentOptions = _videoJs2['default'].mergeOptions(this.xhrOptions_, { 3775 uri: segment.map.resolvedUri, 3776 responseType: 'arraybuffer', 3777 headers: segmentXhrHeaders(segment.map) 3778 }); 3779 3780 initSegmentXhr = this.hls_.xhr(initSegmentOptions, this.handleResponse_.bind(this)); 3781 } 3782 this.pendingSegment_ = segmentInfo; 3783 3784 var segmentRequestOptions = _videoJs2['default'].mergeOptions(this.xhrOptions_, { 3785 uri: segmentInfo.uri, 3786 responseType: 'arraybuffer', 3787 headers: segmentXhrHeaders(segment) 3788 }); 3789 3790 segmentXhr = this.hls_.xhr(segmentRequestOptions, this.handleResponse_.bind(this)); 3791 3792 this.xhr_ = { 3793 keyXhr: keyXhr, 3794 initSegmentXhr: initSegmentXhr, 3795 segmentXhr: segmentXhr, 3796 abort: function abort() { 3797 if (this.segmentXhr) { 3798 // Prevent error handler from running. 3799 this.segmentXhr.onreadystatechange = null; 3800 this.segmentXhr.abort(); 3801 this.segmentXhr = null; 3802 } 3803 if (this.initSegmentXhr) { 3804 // Prevent error handler from running. 3805 this.initSegmentXhr.onreadystatechange = null; 3806 this.initSegmentXhr.abort(); 3807 this.initSegmentXhr = null; 3808 } 3809 if (this.keyXhr) { 3810 // Prevent error handler from running. 3811 this.keyXhr.onreadystatechange = null; 3812 this.keyXhr.abort(); 3813 this.keyXhr = null; 3814 } 3815 } 3816 }; 3817 3818 this.state = 'WAITING'; 3819 } 3820 3821 /** 3822 * triggered when a segment response is received 3823 * 3824 * @private 3825 */ 3826 }, { 3827 key: 'handleResponse_', 3828 value: function handleResponse_(error, request) { 3829 var segmentInfo = undefined; 3830 var segment = undefined; 3831 var keyXhrRequest = undefined; 3832 var view = undefined; 3833 3834 // timeout of previously aborted request 3835 if (!this.xhr_ || request !== this.xhr_.segmentXhr && request !== this.xhr_.keyXhr && request !== this.xhr_.initSegmentXhr) { 3836 return; 3837 } 3838 3839 segmentInfo = this.pendingSegment_; 3840 segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex]; 3841 3842 // if a request times out, reset bandwidth tracking 3843 if (request.timedout) { 3844 this.abort_(); 3845 this.bandwidth = 1; 3846 this.roundTrip = NaN; 3847 this.state = 'READY'; 3848 return this.trigger('progress'); 3849 } 3850 3851 // trigger an event for other errors 3852 if (!request.aborted && error) { 3853 // abort will clear xhr_ 3854 keyXhrRequest = this.xhr_.keyXhr; 3855 this.abort_(); 3856 this.error({ 3857 status: request.status, 3858 message: request === keyXhrRequest ? 'HLS key request error at URL: ' + segment.key.uri : 'HLS segment request error at URL: ' + segmentInfo.uri, 3859 code: 2, 3860 xhr: request 3861 }); 3862 this.state = 'READY'; 3863 this.pause(); 3864 return this.trigger('error'); 3865 } 3866 3867 // stop processing if the request was aborted 3868 if (!request.response) { 3869 this.abort_(); 3870 return; 3871 } 3872 3873 if (request === this.xhr_.segmentXhr) { 3874 // the segment request is no longer outstanding 3875 this.xhr_.segmentXhr = null; 3876 3877 // calculate the download bandwidth based on segment request 3878 this.roundTrip = request.roundTripTime; 3879 this.bandwidth = request.bandwidth; 3880 this.mediaBytesTransferred += request.bytesReceived || 0; 3881 this.mediaRequests += 1; 3882 this.mediaTransferDuration += request.roundTripTime || 0; 3883 3884 if (segment.key) { 3885 segmentInfo.encryptedBytes = new Uint8Array(request.response); 3886 } else { 3887 segmentInfo.bytes = new Uint8Array(request.response); 3888 } 3889 } 3890 3891 if (request === this.xhr_.keyXhr) { 3892 keyXhrRequest = this.xhr_.segmentXhr; 3893 // the key request is no longer outstanding 3894 this.xhr_.keyXhr = null; 3895 3896 if (request.response.byteLength !== 16) { 3897 this.abort_(); 3898 this.error({ 3899 status: request.status, 3900 message: 'Invalid HLS key at URL: ' + segment.key.uri, 3901 code: 2, 3902 xhr: request 3903 }); 3904 this.state = 'READY'; 3905 this.pause(); 3906 return this.trigger('error'); 3907 } 3908 3909 view = new DataView(request.response); 3910 segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]); 3911 3912 // if the media sequence is greater than 2^32, the IV will be incorrect 3913 // assuming 10s segments, that would be about 1300 years 3914 segment.key.iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]); 3915 } 3916 3917 if (request === this.xhr_.initSegmentXhr) { 3918 // the init segment request is no longer outstanding 3919 this.xhr_.initSegmentXhr = null; 3920 segment.map.bytes = new Uint8Array(request.response); 3921 this.initSegments_[initSegmentId(segment.map)] = segment.map; 3922 } 3923 3924 if (!this.xhr_.segmentXhr && !this.xhr_.keyXhr && !this.xhr_.initSegmentXhr) { 3925 this.xhr_ = null; 3926 this.processResponse_(); 3927 } 3928 } 3929 3930 /** 3931 * clear anything that is currently in the buffer and throw it away 3932 */ 3933 }, { 3934 key: 'clearBuffer', 3935 value: function clearBuffer() { 3936 if (this.sourceUpdater_ && this.sourceUpdater_.buffered().length) { 3937 this.sourceUpdater_.remove(0, Infinity); 3938 } 3939 } 3940 3941 /** 3942 * Decrypt the segment that is being loaded if necessary 3943 * 3944 * @private 3945 */ 3946 }, { 3947 key: 'processResponse_', 3948 value: function processResponse_() { 3949 var segmentInfo = undefined; 3950 var segment = undefined; 3951 3952 this.state = 'DECRYPTING'; 3953 3954 segmentInfo = this.pendingSegment_; 3955 segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex]; 3956 3957 // some videos don't start from presentation time zero 3958 // if that is the case, set the timestamp offset on the first 3959 // segment to adjust them so that it is not necessary to seek 3960 // before playback can begin 3961 if (segment.map && isNaN(this.zeroOffset_)) { 3962 var timescales = _muxJsLibMp4Probe2['default'].timescale(segment.map.bytes); 3963 var startTime = _muxJsLibMp4Probe2['default'].startTime(timescales, segmentInfo.bytes); 3964 3965 this.zeroOffset_ = startTime; 3966 segmentInfo.timestampOffset -= startTime; 3967 } 3968 3969 if (segment.key) { 3970 // this is an encrypted segment 3971 // incrementally decrypt the segment 3972 /* eslint-disable no-new, handle-callback-err */ 3973 new _aesDecrypter.Decrypter(segmentInfo.encryptedBytes, segment.key.bytes, segment.key.iv, (function (err, bytes) { 3974 // err always null 3975 segmentInfo.bytes = bytes; 3976 this.handleSegment_(); 3977 }).bind(this)); 3978 /* eslint-enable */ 3979 } else { 3980 this.handleSegment_(); 3981 } 3982 } 3983 3984 /** 3985 * append a decrypted segement to the SourceBuffer through a SourceUpdater 3986 * 3987 * @private 3988 */ 3989 }, { 3990 key: 'handleSegment_', 3991 value: function handleSegment_() { 3992 var _this = this; 3993 3994 var segmentInfo = undefined; 3995 var segment = undefined; 3996 3997 this.state = 'APPENDING'; 3998 segmentInfo = this.pendingSegment_; 3999 segmentInfo.buffered = this.sourceUpdater_.buffered(); 4000 segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex]; 4001 this.currentTimeline_ = segmentInfo.timeline; 4002 4003 if (segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) { 4004 this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); 4005 } 4006 4007 // if the media initialization segment is changing, append it 4008 // before the content segment 4009 if (segment.map) { 4010 (function () { 4011 var initId = initSegmentId(segment.map); 4012 4013 if (!_this.activeInitSegmentId_ || _this.activeInitSegmentId_ !== initId) { 4014 var initSegment = _this.initSegments_[initId]; 4015 4016 _this.sourceUpdater_.appendBuffer(initSegment.bytes, function () { 4017 _this.activeInitSegmentId_ = initId; 4018 }); 4019 } 4020 })(); 4021 } 4022 4023 this.sourceUpdater_.appendBuffer(segmentInfo.bytes, this.handleUpdateEnd_.bind(this)); 4024 } 4025 4026 /** 4027 * callback to run when appendBuffer is finished. detects if we are 4028 * in a good state to do things with the data we got, or if we need 4029 * to wait for more 4030 * 4031 * @private 4032 */ 4033 }, { 4034 key: 'handleUpdateEnd_', 4035 value: function handleUpdateEnd_() { 4036 var segmentInfo = this.pendingSegment_; 4037 var currentTime = this.currentTime_(); 4038 4039 this.pendingSegment_ = null; 4040 4041 // add segment metadata if it we have gained information during the 4042 // last append 4043 var timelineUpdated = this.updateTimeline_(segmentInfo); 4044 4045 this.trigger('progress'); 4046 4047 var currentMediaIndex = segmentInfo.mediaIndex; 4048 4049 currentMediaIndex += segmentInfo.playlist.mediaSequence - this.playlist_.mediaSequence; 4050 4051 var currentBuffered = _ranges2['default'].findRange(this.sourceUpdater_.buffered(), currentTime); 4052 4053 // any time an update finishes and the last segment is in the 4054 // buffer, end the stream. this ensures the "ended" event will 4055 // fire if playback reaches that point. 4056 var isEndOfStream = detectEndOfStream(segmentInfo.playlist, this.mediaSource_, currentMediaIndex + 1); 4057
4058 if (isEndOfStream) { 4059 this.mediaSource_.endOfStream(); 4060 } 4061 4062 // when seeking to the beginning of the seekable range, it's 4063 // possible that imprecise timing information may cause the seek to 4064 // end up earlier than the start of the range 4065 // in that case, seek again 4066 var seekable = this.seekable_(); 4067 var next = _ranges2['default'].findNextRange(this.sourceUpdater_.buffered(), currentTime); 4068 4069 if (this.seeking_() && currentBuffered.length === 0) { 4070 if (seekable.length && currentTime < seekable.start(0)) { 4071 4072 if (next.length) { 4073 _videoJs2['default'].log('tried seeking to', currentTime, 'but that was too early, retrying at', next.start(0)); 4074 this.setCurrentTime_(next.start(0) + _ranges2['default'].TIME_FUDGE_FACTOR); 4075 } 4076 } 4077 } 4078 4079 this.state = 'READY'; 4080 4081 if (timelineUpdated) { 4082 this.timeCorrection_ = 0; 4083 if (!this.paused()) { 4084 this.fillBuffer_(); 4085 } 4086 return; 4087 } 4088 4089 // the last segment append must have been entirely in the 4090 // already buffered time ranges. adjust the timeCorrection 4091 // offset to fetch forward until we find a segment that adds 4092 // to the buffered time ranges and improves subsequent media 4093 // index calculations. 4094 var correctionApplied = this.incrementTimeCorrection_(segmentInfo.duration, 4); 4095 4096 if (correctionApplied && !this.paused()) { 4097 this.fillBuffer_(); 4098 } 4099 } 4100 4101 /** 4102 * annotate the segment with any start and end time information 4103 * added by the media processing 4104 * 4105 * @private 4106 * @param {Object} segmentInfo annotate a segment with time info 4107 */ 4108 }, { 4109 key: 'updateTimeline_', 4110 value: function updateTimeline_(segmentInfo) { 4111 var segment = undefined; 4112 var segmentEnd = undefined; 4113 var timelineUpdated = false; 4114 var playlist = segmentInfo.playlist; 4115 var currentMediaIndex = segmentInfo.mediaIndex; 4116 4117 currentMediaIndex += playlist.mediaSequence - this.playlist_.mediaSequence; 4118 segment = playlist.segments[currentMediaIndex]; 4119 4120 // Update segment meta-data (duration and end-point) based on timeline 4121 if (segment && segmentInfo && segmentInfo.playlist.uri === this.playlist_.uri) { 4122 segmentEnd = _ranges2['default'].findSoleUncommonTimeRangesEnd(segmentInfo.buffered, this.sourceUpdater_.buffered()); 4123 timelineUpdated = updateSegmentMetadata(playlist, currentMediaIndex, segmentEnd); 4124 } 4125 4126 return timelineUpdated; 4127 } 4128 4129 /** 4130 * add a number of seconds to the currentTime when determining which 4131 * segment to fetch in order to force the fetcher to advance in cases 4132 * where it may get stuck on the same segment due to buffer gaps or 4133 * missing segment annotation after a rendition switch (especially 4134 * during a live stream) 4135 * 4136 * @private 4137 * @param {Number} secondsToIncrement number of seconds to add to the 4138 * timeCorrection_ variable 4139 * @param {Number} maxSegmentsToWalk maximum number of times we allow this 4140 * function to walk forward 4141 */ 4142 }, { 4143 key: 'incrementTimeCorrection_', 4144 value: function incrementTimeCorrection_(secondsToIncrement, maxSegmentsToWalk) { 4145 // If we have already incremented timeCorrection_ beyond the limit, 4146 // stop searching for a segment and reset timeCorrection_ 4147 if (this.timeCorrection_ >= this.playlist_.targetDuration * maxSegmentsToWalk) { 4148 this.timeCorrection_ = 0; 4149 return false; 4150 } 4151 4152 this.timeCorrection_ += secondsToIncrement; 4153 return true; 4154 } 4155 }]); 4156 4157 return SegmentLoader; 4158})(_videoJs2['default'].EventTarget); 4159 4160exports['default'] = SegmentLoader; 4161module.exports = exports['default']; 4162}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 4163},{"./config":3,"./playlist":7,"./ranges":8,"./source-updater":12,"aes-decrypter":18,"global/window":25,"mux.js/lib/mp4/probe":80}],12:[function(require,module,exports){ 4164(function (global){ 4165/** 4166 * @file source-updater.js 4167 */ 4168'use strict'; 4169 4170Object.defineProperty(exports, '__esModule', { 4171 value: true 4172}); 4173 4174var _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; }; })(); 4175 4176function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 4177 4178function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 4179 4180var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 4181 4182var _videoJs2 = _interopRequireDefault(_videoJs); 4183 4184/** 4185 * A queue of callbacks to be serialized and applied when a
4186 * MediaSource and its associated SourceBuffers are not in the 4187 * updating state. It is used by the segment loader to update the 4188 * underlying SourceBuffers when new data is loaded, for instance. 4189 * 4190 * @class SourceUpdater 4191 * @param {MediaSource} mediaSource the MediaSource to create the 4192 * SourceBuffer from 4193 * @param {String} mimeType the desired MIME type of the underlying 4194 * SourceBuffer 4195 */ 4196 4197var SourceUpdater = (function () { 4198 function SourceUpdater(mediaSource, mimeType) { 4199 var _this = this; 4200 4201 _classCallCheck(this, SourceUpdater); 4202 4203 var createSourceBuffer = function createSourceBuffer() { 4204 _this.sourceBuffer_ = mediaSource.addSourceBuffer(mimeType); 4205 4206 // run completion handlers and process callbacks as updateend 4207 // events fire 4208 _this.onUpdateendCallback_ = function () { 4209 var pendingCallback = _this.pendingCallback_; 4210 4211 _this.pendingCallback_ = null; 4212 4213 if (pendingCallback) { 4214 pendingCallback(); 4215 } 4216 4217 _this.runCallback_(); 4218 }; 4219 4220 _this.sourceBuffer_.addEventListener('updateend', _this.onUpdateendCallback_); 4221 4222 _this.runCallback_(); 4223 }; 4224 4225 this.callbacks_ = []; 4226 this.pendingCallback_ = null; 4227 this.timestampOffset_ = 0; 4228 this.mediaSource = mediaSource; 4229 4230 if (mediaSource.readyState === 'closed') { 4231 mediaSource.addEventListener('sourceopen', createSourceBuffer); 4232 } else { 4233 createSourceBuffer(); 4234 } 4235 } 4236 4237 /** 4238 * Aborts the current segment and resets the segment parser. 4239 * 4240 * @param {Function} done function to call when done 4241 * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void 4242 */ 4243 4244 _createClass(SourceUpdater, [{ 4245 key: 'abort', 4246 value: function abort(done) { 4247 var _this2 = this; 4248 4249 this.queueCallback_(function () { 4250 _this2.sourceBuffer_.abort(); 4251 }, done); 4252 } 4253 4254 /** 4255 * Queue an update to append an ArrayBuffer. 4256 * 4257 * @param {ArrayBuffer} bytes 4258 * @param {Function} done the function to call when done 4259 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data 4260 */ 4261 }, { 4262 key: 'appendBuffer', 4263 value: function appendBuffer(bytes, done) { 4264 var _this3 = this; 4265 4266 this.queueCallback_(function () { 4267 _this3.sourceBuffer_.appendBuffer(bytes); 4268 }, done); 4269 } 4270 4271 /** 4272 * Indicates what TimeRanges are buffered in the managed SourceBuffer. 4273 * 4274 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered 4275 */ 4276 }, { 4277 key: 'buffered', 4278 value: function buffered() { 4279 if (!this.sourceBuffer_) { 4280 return _videoJs2['default'].createTimeRanges(); 4281 } 4282 return this.sourceBuffer_.buffered; 4283 } 4284 4285 /** 4286 * Queue an update to set the duration. 4287 * 4288 * @param {Double} duration what to set the duration to 4289 * @see http://www.w3.org/TR/media-source/#widl-MediaSource-duration 4290 */ 4291 }, { 4292 key: 'duration', 4293 value: function duration(_duration) { 4294 var _this4 = this; 4295 4296 this.queueCallback_(function () { 4297 _this4.sourceBuffer_.duration = _duration; 4298 }); 4299 } 4300 4301 /** 4302 * Queue an update to remove a time range from the buffer. 4303 * 4304 * @param {Number} start where to start the removal 4305 * @param {Number} end where to end the removal 4306 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end 4307 */ 4308 }, { 4309 key: 'remove', 4310 value: function remove(start, end) { 4311 var _this5 = this; 4312 4313 this.queueCallback_(function () { 4314 _this5.sourceBuffer_.remove(start, end); 4315 }); 4316 } 4317 4318 /** 4319 * wether the underlying sourceBuffer is updating or not 4320 * 4321 * @return {Boolean} the updating status of the SourceBuffer 4322 */ 4323 }, { 4324 key: 'updating', 4325 value: function updating() { 4326 return !this.sourceBuffer_ || this.sourceBuffer_.updating; 4327 } 4328 4329 /** 4330 * Set/get the timestampoffset on the SourceBuffer 4331 * 4332 * @return {Number} the timestamp offset 4333 */ 4334 }, { 4335 key: 'timestampOffset', 4336 value: function timestampOffset(offset) { 4337 var _this6 = this; 4338 4339 if (typeof offset !== 'undefined') { 4340 this.queueCallback_(function () { 4341 _this6.sourceBuffer_.timestampOffset = offset; 4342 }); 4343 this.timestampOffset_ = offset; 4344 } 4345 return this.timestampOffset_; 4346 } 4347 4348 /** 4349 * que a callback to run 4350 */ 4351 }, { 4352 key: 'queueCallback_', 4353 value: function queueCallback_(callback, done) { 4354 this.callbacks_.push([callback.bind(this), done]); 4355 this.runCallback_(); 4356 } 4357 4358 /** 4359 * run a queued callback 4360 */ 4361 }, { 4362 key: 'runCallback_', 4363 value: function runCallback_() { 4364 var callbacks = undefined; 4365 4366 if (this.sourceBuffer_ && !this.sourceBuffer_.updating && this.callbacks_.length) { 4367 callbacks = this.callbacks_.shift(); 4368 this.pendingCallback_ = callbacks[1]; 4369 callbacks[0](); 4370 } 4371 } 4372 4373 /** 4374 * dispose of the source updater and the underlying sourceBuffer 4375 */ 4376 }, { 4377 key: 'dispose', 4378 value: function dispose() { 4379 this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_); 4380 if (this.sourceBuffer_ && this.mediaSource.readyState === 'open') { 4381 this.sourceBuffer_.abort(); 4382 } 4383 } 4384 }]); 4385 4386 return SourceUpdater; 4387})(); 4388 4389exports['default'] = SourceUpdater; 4390module.exports = exports['default']; 4391}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 4392},{}],13:[function(require,module,exports){ 4393/** 4394 * @file stream.js 4395 */ 4396/** 4397 * A lightweight readable stream implemention that handles event dispatching. 4398 * 4399 * @class Stream 4400 */ 4401'use strict'; 4402 4403Object.defineProperty(exports, '__esModule', { 4404 value: true 4405}); 4406 4407var _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; }; })(); 4408 4409function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 4410 4411var Stream = (function () { 4412 function Stream() { 4413 _classCallCheck(this, Stream); 4414 4415 this.listeners = {}; 4416 } 4417 4418 /** 4419 * Add a listener for a specified event type. 4420 * 4421 * @param {String} type the event name 4422 * @param {Function} listener the callback to be invoked when an event of 4423 * the specified type occurs 4424 */ 4425 4426 _createClass(Stream, [{ 4427 key: 'on', 4428 value: function on(type, listener) { 4429 if (!this.listeners[type]) { 4430 this.listeners[type] = []; 4431 } 4432 this.listeners[type].push(listener); 4433 } 4434 4435 /** 4436 * Remove a listener for a specified event type. 4437 * 4438 * @param {String} type the event name 4439 * @param {Function} listener a function previously registered for this 4440 * type of event through `on` 4441 * @return {Boolean} if we could turn it off or not 4442 */ 4443 }, { 4444 key: 'off', 4445 value: function off(type, listener) { 4446 var index = undefined; 4447 4448 if (!this.listeners[type]) { 4449 return false;
4450 } 4451 index = this.listeners[type].indexOf(listener); 4452 this.listeners[type].splice(index, 1); 4453 return index > -1; 4454 } 4455 4456 /** 4457 * Trigger an event of the specified type on this stream. Any additional 4458 * arguments to this function are passed as parameters to event listeners. 4459 * 4460 * @param {String} type the event name 4461 */ 4462 }, { 4463 key: 'trigger', 4464 value: function trigger(type) { 4465 var callbacks = undefined; 4466 var i = undefined; 4467 var length = undefined; 4468 var args = undefined; 4469 4470 callbacks = this.listeners[type]; 4471 if (!callbacks) { 4472 return; 4473 } 4474 // Slicing the arguments on every invocation of this method 4475 // can add a significant amount of overhead. Avoid the 4476 // intermediate object creation for the common case of a 4477 // single callback argument 4478 if (arguments.length === 2) { 4479 length = callbacks.length; 4480 for (i = 0; i < length; ++i) { 4481 callbacks[i].call(this, arguments[1]); 4482 } 4483 } else { 4484 args = Array.prototype.slice.call(arguments, 1); 4485 length = callbacks.length; 4486 for (i = 0; i < length; ++i) { 4487 callbacks[i].apply(this, args); 4488 } 4489 } 4490 } 4491 4492 /** 4493 * Destroys the stream and cleans up. 4494 */ 4495 }, { 4496 key: 'dispose', 4497 value: function dispose() { 4498 this.listeners = {}; 4499 } 4500 4501 /** 4502 * Forwards all `data` events on this stream to the destination stream. The 4503 * destination stream should provide a method `push` to receive the data 4504 * events as they arrive. 4505 * 4506 * @param {Stream} destination the stream that will receive all `data` events 4507 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 4508 */ 4509 }, { 4510 key: 'pipe', 4511 value: function pipe(destination) { 4512 this.on('data', function (data) { 4513 destination.push(data); 4514 }); 4515 } 4516 }]); 4517 4518 return Stream; 4519})(); 4520 4521exports['default'] = Stream; 4522module.exports = exports['default']; 4523},{}],14:[function(require,module,exports){ 4524(function (global){ 4525/** 4526 * @file xhr.js 4527 */ 4528 4529/** 4530 * A wrapper for videojs.xhr that tracks bandwidth. 4531 * 4532 * @param {Object} options options for the XHR 4533 * @param {Function} callback the callback to call when done 4534 * @return {Request} the xhr request that is going to be made 4535 */ 4536'use strict'; 4537 4538Object.defineProperty(exports, '__esModule', { 4539 value: true 4540}); 4541 4542var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 4543 4544var xhrFactory = function xhrFactory() { 4545 var xhr = function XhrFunction(options, callback) { 4546 // Add a default timeout for all hls requests 4547 options = (0, _videoJs.mergeOptions)({ 4548 timeout: 45e3 4549 }, options); 4550 4551 // Allow an optional user-specified function to modify the option 4552 // object before we construct the xhr request 4553 if (XhrFunction.beforeRequest && typeof XhrFunction.beforeRequest === 'function') { 4554 var newOptions = XhrFunction.beforeRequest(options); 4555 4556 if (newOptions) { 4557 options = newOptions; 4558 } 4559 } 4560 4561 var request = (0, _videoJs.xhr)(options, function (error, response) { 4562 if (!error && request.response) { 4563 request.responseTime = new Date().getTime(); 4564 request.roundTripTime = request.responseTime - request.requestTime; 4565 request.bytesReceived = request.response.byteLength || request.response.length; 4566 if (!request.bandwidth) { 4567 request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000); 4568 } 4569 } 4570 4571 // videojs.xhr now uses a specific code 4572 // on the error object to signal that a request has 4573 // timed out errors of setting a boolean on the request object 4574 if (error || request.timedout) { 4575 request.timedout = request.timedout || error.code === 'ETIMEDOUT'; 4576 } else { 4577 request.timedout = false; 4578 } 4579 4580 // videojs.xhr no longer considers status codes outside of 200 and 0 4581 // (for file uris) to be errors, but the old XHR did, so emulate that 4582 // behavior. Status 206 may be used in response to byterange requests. 4583 if (!error && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) { 4584 error = new Error('XHR Failed with a response of: ' + (request && (request.response || request.responseText))); 4585 } 4586 4587 callback(error, request); 4588 }); 4589 4590 request.requestTime = new Date().getTime(); 4591 return request; 4592 }; 4593 4594 return xhr; 4595}; 4596 4597exports['default'] = xhrFactory; 4598module.exports = exports['default']; 4599}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 4600},{}],15:[function(require,module,exports){ 4601/** 4602 * @file aes.js 4603 * 4604 * This file contains an adaptation of the AES decryption algorithm 4605 * from the Standford Javascript Cryptography Library. That work is 4606 * covered by the following copyright and permissions notice: 4607 * 4608 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. 4609 * All rights reserved. 4610 * 4611 * Redistribution and use in source and binary forms, with or without 4612 * modification, are permitted provided that the following conditions are 4613 * met: 4614 * 4615 * 1. Redistributions of source code must retain the above copyright 4616 * notice, this list of conditions and the following disclaimer. 4617 * 4618 * 2. Redistributions in binary form must reproduce the above 4619 * copyright notice, this list of conditions and the following 4620 * disclaimer in the documentation and/or other materials provided 4621 * with the distribution. 4622 * 4623 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 4624 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 4625 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 4626 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE 4627 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
4628 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 4629 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 4630 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 4631 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 4632 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 4633 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 4634 * 4635 * The views and conclusions contained in the software and documentation 4636 * are those of the authors and should not be interpreted as representing 4637 * official policies, either expressed or implied, of the authors. 4638 */ 4639 4640/** 4641 * Expand the S-box tables. 4642 * 4643 * @private 4644 */ 4645'use strict'; 4646 4647Object.defineProperty(exports, '__esModule', { 4648 value: true 4649}); 4650 4651var _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; }; })(); 4652 4653function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 4654 4655var precompute = function precompute() { 4656 var tables = [[[], [], [], [], []], [[], [], [], [], []]]; 4657 var encTable = tables[0]; 4658 var decTable = tables[1]; 4659 var sbox = encTable[4]; 4660 var sboxInv = decTable[4]; 4661 var i = undefined; 4662 var x = undefined; 4663 var xInv = undefined; 4664 var d = []; 4665 var th = []; 4666 var x2 = undefined; 4667 var x4 = undefined; 4668 var x8 = undefined; 4669 var s = undefined; 4670 var tEnc = undefined; 4671 var tDec = undefined; 4672 4673 // Compute double and third tables 4674 for (i = 0; i < 256; i++) { 4675 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; 4676 } 4677 4678 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 4679 // Compute sbox 4680 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; 4681 s = s >> 8 ^ s & 255 ^ 99; 4682 sbox[x] = s; 4683 sboxInv[s] = x; 4684 4685 // Compute MixColumns 4686 x8 = d[x4 = d[x2 = d[x]]]; 4687 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; 4688 tEnc = d[s] * 0x101 ^ s * 0x1010100; 4689 4690 for (i = 0; i < 4; i++) { 4691 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; 4692 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; 4693 } 4694 } 4695 4696 // Compactify. Considerable speedup on Firefox. 4697 for (i = 0; i < 5; i++) { 4698 encTable[i] = encTable[i].slice(0); 4699 decTable[i] = decTable[i].slice(0); 4700 } 4701 return tables; 4702}; 4703var aesTables = null; 4704 4705/** 4706 * Schedule out an AES key for both encryption and decryption. This 4707 * is a low-level class. Use a cipher mode to do bulk encryption. 4708 * 4709 * @class AES 4710 * @param key {Array} The key as an array of 4, 6 or 8 words. 4711 */ 4712 4713var AES = (function () { 4714 function AES(key) { 4715 _classCallCheck(this, AES); 4716 4717 /** 4718 * The expanded S-box and inverse S-box tables. These will be computed 4719 * on the client so that we don't have to send them down the wire. 4720 * 4721 * There are two tables, _tables[0] is for encryption and 4722 * _tables[1] is for decryption. 4723 * 4724 * The first 4 sub-tables are the expanded S-box with MixColumns. The 4725 * last (_tables[01][4]) is the S-box itself. 4726 * 4727 * @private 4728 */ 4729 // if we have yet to precompute the S-box tables 4730 // do so now 4731 if (!aesTables) { 4732 aesTables = precompute(); 4733 } 4734 // then make a copy of that object for use 4735 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()]]; 4736 var i = undefined; 4737 var j = undefined; 4738 var tmp = undefined; 4739 var encKey = undefined; 4740 var decKey = undefined; 4741 var sbox = this._tables[0][4]; 4742 var decTable = this._tables[1]; 4743 var keyLen = key.length; 4744 var rcon = 1; 4745 4746 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 4747 throw new Error('Invalid aes key size'); 4748 } 4749 4750 encKey = key.slice(0); 4751 decKey = []; 4752 this._key = [encKey, decKey]; 4753 4754 // schedule encryption keys 4755 for (i = keyLen; i < 4 * keyLen + 28; i++) { 4756 tmp = encKey[i - 1]; 4757 4758 // apply sbox
4759 if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) { 4760 tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; 4761 4762 // shift rows and add rcon 4763 if (i % keyLen === 0) { 4764 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; 4765 rcon = rcon << 1 ^ (rcon >> 7) * 283; 4766 } 4767 } 4768 4769 encKey[i] = encKey[i - keyLen] ^ tmp; 4770 } 4771 4772 // schedule decryption keys 4773 for (j = 0; i; j++, i--) { 4774 tmp = encKey[j & 3 ? i : i - 4]; 4775 if (i <= 4 || j < 4) { 4776 decKey[j] = tmp; 4777 } else { 4778 decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; 4779 } 4780 } 4781 } 4782 4783 /** 4784 * Decrypt 16 bytes, specified as four 32-bit words. 4785 * 4786 * @param {Number} encrypted0 the first word to decrypt 4787 * @param {Number} encrypted1 the second word to decrypt 4788 * @param {Number} encrypted2 the third word to decrypt 4789 * @param {Number} encrypted3 the fourth word to decrypt 4790 * @param {Int32Array} out the array to write the decrypted words 4791 * into 4792 * @param {Number} offset the offset into the output array to start 4793 * writing results 4794 * @return {Array} The plaintext. 4795 */ 4796 4797 _createClass(AES, [{ 4798 key: 'decrypt', 4799 value: function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { 4800 var key = this._key[1]; 4801 // state variables a,b,c,d are loaded with pre-whitened data 4802 var a = encrypted0 ^ key[0]; 4803 var b = encrypted3 ^ key[1]; 4804 var c = encrypted2 ^ key[2]; 4805 var d = encrypted1 ^ key[3]; 4806 var a2 = undefined; 4807 var b2 = undefined; 4808 var c2 = undefined; 4809 4810 // key.length === 2 ? 4811 var nInnerRounds = key.length / 4 - 2; 4812 var i = undefined; 4813 var kIndex = 4; 4814 var table = this._tables[1]; 4815 4816 // load up the tables 4817 var table0 = table[0]; 4818 var table1 = table[1]; 4819 var table2 = table[2]; 4820 var table3 = table[3]; 4821 var sbox = table[4]; 4822 4823 // Inner rounds. Cribbed from OpenSSL. 4824 for (i = 0; i < nInnerRounds; i++) { 4825 a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex]; 4826 b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; 4827 c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; 4828 d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; 4829 kIndex += 4; 4830 a = a2;b = b2;c = c2; 4831 } 4832 4833 // Last round. 4834 for (i = 0; i < 4; i++) { 4835 out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; 4836 a2 = a;a = b;b = c;c = d;d = a2; 4837 } 4838 } 4839 }]); 4840 4841 return AES; 4842})(); 4843 4844exports['default'] = AES; 4845module.exports = exports['default']; 4846},{}],16:[function(require,module,exports){ 4847/** 4848 * @file async-stream.js 4849 */ 4850'use strict'; 4851 4852Object.defineProperty(exports, '__esModule', { 4853 value: true 4854}); 4855 4856var _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; }; })(); 4857 4858var _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); } } }; 4859 4860function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 4861 4862function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 4863 4864function _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
4864onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 4865 4866var _stream = require('./stream'); 4867 4868var _stream2 = _interopRequireDefault(_stream); 4869 4870/** 4871 * A wrapper around the Stream class to use setTiemout 4872 * and run stream "jobs" Asynchronously 4873 * 4874 * @class AsyncStream 4875 * @extends Stream 4876 */ 4877 4878var AsyncStream = (function (_Stream) { 4879 _inherits(AsyncStream, _Stream); 4880 4881 function AsyncStream() { 4882 _classCallCheck(this, AsyncStream); 4883 4884 _get(Object.getPrototypeOf(AsyncStream.prototype), 'constructor', this).call(this, _stream2['default']); 4885 this.jobs = []; 4886 this.delay = 1; 4887 this.timeout_ = null; 4888 } 4889 4890 /** 4891 * process an async job 4892 * 4893 * @private 4894 */ 4895 4896 _createClass(AsyncStream, [{ 4897 key: 'processJob_', 4898 value: function processJob_() { 4899 this.jobs.shift()(); 4900 if (this.jobs.length) { 4901 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 4902 } else { 4903 this.timeout_ = null; 4904 } 4905 } 4906 4907 /** 4908 * push a job into the stream 4909 * 4910 * @param {Function} job the job to push into the stream 4911 */ 4912 }, { 4913 key: 'push', 4914 value: function push(job) { 4915 this.jobs.push(job); 4916 if (!this.timeout_) { 4917 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 4918 } 4919 } 4920 }]); 4921 4922 return AsyncStream; 4923})(_stream2['default']); 4924 4925exports['default'] = AsyncStream; 4926module.exports = exports['default']; 4927},{"./stream":19}],17:[function(require,module,exports){ 4928/** 4929 * @file decrypter.js 4930 * 4931 * An asynchronous implementation of AES-128 CBC decryption with 4932 * PKCS#7 padding. 4933 */ 4934 4935'use strict'; 4936 4937Object.defineProperty(exports, '__esModule', { 4938 value: true 4939}); 4940 4941var _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; }; })(); 4942 4943function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 4944 4945function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 4946 4947var _aes = require('./aes'); 4948 4949var _aes2 = _interopRequireDefault(_aes); 4950 4951var _asyncStream = require('./async-stream'); 4952 4953var _asyncStream2 = _interopRequireDefault(_asyncStream); 4954 4955var _pkcs7 = require('pkcs7'); 4956 4957/** 4958 * Convert network-order (big-endian) bytes into their little-endian 4959 * representation. 4960 */ 4961var ntoh = function ntoh(word) { 4962 return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; 4963}; 4964 4965/** 4966 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. 4967 * 4968 * @param {Uint8Array} encrypted the encrypted bytes 4969 * @param {Uint32Array} key the bytes of the decryption key 4970 * @param {Uint32Array} initVector the initialization vector (IV) to 4971 * use for the first round of CBC. 4972 * @return {Uint8Array} the decrypted bytes 4973 * 4974 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 4975 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 4976 * @see https://tools.ietf.org/html/rfc2315 4977 */ 4978var decrypt = function decrypt(encrypted, key, initVector) { 4979 // word-level access to the encrypted bytes 4980 var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2); 4981 4982 var decipher = new _aes2['default'](Array.prototype.slice.call(key)); 4983 4984 // byte and word-level access for the decrypted output 4985 var decrypted = new Uint8Array(encrypted.byteLength); 4986 var decrypted32 = new Int32Array(decrypted.buffer); 4987 4988 // temporary variables for working with the IV, encrypted, and 4989 // decrypted data 4990 var init0 = undefined; 4991 var init1 = undefined; 4992 var init2 = undefined; 4993 var init3 = undefined; 4994 var encrypted0 = undefined; 4995 var encrypted1 = undefined; 4996 var encrypted2 = undefined; 4997 var encrypted3 = undefined; 4998 4999 // iteration variable 5000 var wordIx = undefined; 5001 5002 // pull out the words of the IV to ensure we don't modify the 5003 // passed-in reference and easier access 5004 init0 = initVector[0]; 5005 init1 = initVector[1]; 5006 init2 = initVector[2]; 5007 init3 = initVector[3]; 5008 5009 // decrypt four word sequences, applying cipher-block chaining (CBC) 5010 // to each decrypted block 5011 for (wordIx = 0;
5011 wordIx < encrypted32.length; wordIx += 4) { 5012 // convert big-endian (network order) words into little-endian 5013 // (javascript order) 5014 encrypted0 = ntoh(encrypted32[wordIx]); 5015 encrypted1 = ntoh(encrypted32[wordIx + 1]); 5016 encrypted2 = ntoh(encrypted32[wordIx + 2]); 5017 encrypted3 = ntoh(encrypted32[wordIx + 3]); 5018 5019 // decrypt the block 5020 decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); 5021 5022 // XOR with the IV, and restore network byte-order to obtain the 5023 // plaintext 5024 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); 5025 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); 5026 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); 5027 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); 5028 5029 // setup the IV for the next round 5030 init0 = encrypted0; 5031 init1 = encrypted1; 5032 init2 = encrypted2; 5033 init3 = encrypted3; 5034 } 5035 5036 return decrypted; 5037}; 5038 5039exports.decrypt = decrypt; 5040/** 5041 * The `Decrypter` class that manages decryption of AES 5042 * data through `AsyncStream` objects and the `decrypt` 5043 * function 5044 * 5045 * @param {Uint8Array} encrypted the encrypted bytes 5046 * @param {Uint32Array} key the bytes of the decryption key 5047 * @param {Uint32Array} initVector the initialization vector (IV) to 5048 * @param {Function} done the function to run when done 5049 * @class Decrypter 5050 */ 5051 5052var Decrypter = (function () { 5053 function Decrypter(encrypted, key, initVector, done) { 5054 _classCallCheck(this, Decrypter); 5055 5056 var step = Decrypter.STEP; 5057 var encrypted32 = new Int32Array(encrypted.buffer); 5058 var decrypted = new Uint8Array(encrypted.byteLength); 5059 var i = 0; 5060 5061 this.asyncStream_ = new _asyncStream2['default'](); 5062 5063 // split up the encryption job and do the individual chunks asynchronously 5064 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 5065 for (i = step; i < encrypted32.length; i += step) { 5066 initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]); 5067 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 5068 } 5069 // invoke the done() callback when everything is finished 5070 this.asyncStream_.push(function () { 5071 // remove pkcs#7 padding from the decrypted bytes 5072 done(null, (0, _pkcs7.unpad)(decrypted)); 5073 }); 5074 } 5075 5076 /** 5077 * a getter for step the maximum number of bytes to process at one time 5078 * 5079 * @return {Number} the value of step 32000 5080 */ 5081 5082 _createClass(Decrypter, [{ 5083 key: 'decryptChunk_', 5084 5085 /** 5086 * @private 5087 */ 5088 value: function decryptChunk_(encrypted, key, initVector, decrypted) { 5089 return function () { 5090 var bytes = decrypt(encrypted, key, initVector); 5091 5092 decrypted.set(bytes, encrypted.byteOffset); 5093 }; 5094 } 5095 }], [{ 5096 key: 'STEP', 5097 get: function get() { 5098 // 4 * 8000; 5099 return 32000; 5100 } 5101 }]); 5102 5103 return Decrypter; 5104})(); 5105 5106exports.Decrypter = Decrypter; 5107exports['default'] = { 5108 Decrypter: Decrypter, 5109 decrypt: decrypt 5110}; 5111},{"./aes":15,"./async-stream":16,"pkcs7":21}],18:[function(require,module,exports){ 5112/** 5113 * @file index.js 5114 * 5115 * Index module to easily import the primary components of AES-128 5116 * decryption. Like this: 5117 * 5118 * ```js 5119 * import {Decrypter, decrypt, AsyncStream} from 'aes-decrypter'; 5120 * ``` 5121 */ 5122'use strict'; 5123 5124Object.defineProperty(exports, '__esModule', { 5125 value: true 5126}); 5127 5128function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 5129 5130var _decrypter = require('./decrypter'); 5131 5132var _asyncStream = require('./async-stream'); 5133 5134var _asyncStream2 = _interopRequireDefault(_asyncStream); 5135 5136exports['default'] = { 5137 decrypt: _decrypter.decrypt, 5138 Decrypter: _decrypter.Decrypter, 5139 AsyncStream: _asyncStream2['default'] 5140}; 5141module.exports = exports['default']; 5142},{"./async-stream":16,"./decrypter":17}],19:[function(require,module,exports){ 5143arguments[4][13][0].apply(exports,arguments) 5144},{"dup":13}],20:[function(require,module,exports){ 5145/* 5146 * pkcs7.pad 5147 * https://github.com/brightcove/pkcs7 5148 * 5149 * Copyright (c) 2014 Brightcove
5150 * Licensed under the apache2 license. 5151 */ 5152 5153'use strict'; 5154 5155var PADDING; 5156 5157/** 5158 * Returns a new Uint8Array that is padded with PKCS#7 padding. 5159 * @param plaintext {Uint8Array} the input bytes before encryption 5160 * @return {Uint8Array} the padded bytes 5161 * @see http://tools.ietf.org/html/rfc5652 5162 */ 5163module.exports = function pad(plaintext) { 5164 var padding = PADDING[(plaintext.byteLength % 16) || 0], 5165 result = new Uint8Array(plaintext.byteLength + padding.length); 5166 result.set(plaintext); 5167 result.set(padding, plaintext.byteLength); 5168 return result; 5169}; 5170 5171// pre-define the padding values 5172PADDING = [ 5173 [16, 16, 16, 16, 5174 16, 16, 16, 16, 5175 16, 16, 16, 16, 5176 16, 16, 16, 16], 5177 5178 [15, 15, 15, 15, 5179 15, 15, 15, 15, 5180 15, 15, 15, 15, 5181 15, 15, 15], 5182 5183 [14, 14, 14, 14, 5184 14, 14, 14, 14, 5185 14, 14, 14, 14, 5186 14, 14], 5187 5188 [13, 13, 13, 13, 5189 13, 13, 13, 13, 5190 13, 13, 13, 13, 5191 13], 5192 5193 [12, 12, 12, 12, 5194 12, 12, 12, 12, 5195 12, 12, 12, 12], 5196 5197 [11, 11, 11, 11, 5198 11, 11, 11, 11, 5199 11, 11, 11], 5200 5201 [10, 10, 10, 10, 5202 10, 10, 10, 10, 5203 10, 10], 5204 5205 [9, 9, 9, 9, 5206 9, 9, 9, 9, 5207 9], 5208 5209 [8, 8, 8, 8, 5210 8, 8, 8, 8], 5211 5212 [7, 7, 7, 7, 5213 7, 7, 7], 5214 5215 [6, 6, 6, 6, 5216 6, 6], 5217 5218 [5, 5, 5, 5, 5219 5], 5220 5221 [4, 4, 4, 4], 5222 5223 [3, 3, 3], 5224 5225 [2, 2], 5226 5227 [1] 5228]; 5229 5230},{}],21:[function(require,module,exports){ 5231/* 5232 * pkcs7 5233 * https://github.com/brightcove/pkcs7 5234 * 5235 * Copyright (c) 2014 Brightcove 5236 * Licensed under the apache2 license. 5237 */ 5238 5239'use strict'; 5240 5241exports.pad = require('./pad.js'); 5242exports.unpad = require('./unpad.js'); 5243 5244},{"./pad.js":20,"./unpad.js":22}],22:[function(require,module,exports){ 5245/* 5246 * pkcs7.unpad 5247 * https://github.com/brightcove/pkcs7 5248 * 5249 * Copyright (c) 2014 Brightcove 5250 * Licensed under the apache2 license. 5251 */ 5252 5253'use strict'; 5254 5255/** 5256 * Returns the subarray of a Uint8Array without PKCS#7 padding. 5257 * @param padded {Uint8Array} unencrypted bytes that have been padded 5258 * @return {Uint8Array} the unpadded bytes 5259 * @see http://tools.ietf.org/html/rfc5652 5260 */ 5261module.exports = function unpad(padded) { 5262 return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]); 5263}; 5264 5265},{}],23:[function(require,module,exports){ 5266 5267},{}],24:[function(require,module,exports){ 5268(function (global){ 5269var topLevel = typeof global !== 'undefined' ? global : 5270 typeof window !== 'undefined' ? window : {} 5271var minDoc = require('min-document'); 5272 5273if (typeof document !== 'undefined') { 5274 module.exports = document; 5275} else { 5276 var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4']; 5277 5278 if (!doccy) { 5279 doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc; 5280 } 5281 5282 module.exports = doccy; 5283} 5284 5285}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 5286},{"min-document":23}],25:[function(require,module,exports){ 5287(function (global){ 5288if (typeof window !== "undefined") { 5289 module.exports = window; 5290} else if (typeof global !== "undefined") { 5291 module.exports = global; 5292} else if (typeof self !== "undefined"){ 5293 module.exports = self; 5294} else { 5295 module.exports = {}; 5296} 5297 5298}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 5299},{}],26:[function(require,module,exports){ 5300/** Used as the `TypeError` message for "Functions" methods. */ 5301var FUNC_ERROR_TEXT = 'Expected a function'; 5302 5303/* Native method references for those with the same name as other `lodash` methods. */ 5304var nativeMax = Math.max; 5305 5306/** 5307 * Creates a function that invokes `func` with the `this` binding of the 5308 * created function and arguments from `start` and beyond provided as an array. 5309 * 5310 * **Note:** This method is based on the [rest parameter](https://developer.mozilla.org/Web/JavaScript/Reference/Functions/rest_parameters). 5311 * 5312 * @static 5313 * @memberOf _ 5314 * @category Function 5315 * @param {Function} func The function to apply a rest parameter to. 5316 * @param {number} [start=func.length-1] The start position of the rest parameter. 5317 * @returns {Function} Returns the new function. 5318 * @example 5319 * 5320 * var say = _.restParam(function(what, names) { 5321 * return what + ' ' + _.initial(names).join(', ') + 5322 * (_.size(names) > 1 ? ', & ' : '') + _.last(names); 5323 * }); 5324 * 5325 * say('hello', 'fred', 'barney', 'pebbles'); 5326 * // => 'hello fred, barney, & pebbles' 5327 */ 5328function restParam(func, start) { 5329 if (typeof func != 'function') { 5330 throw new TypeError(FUNC_ERROR_TEXT); 5331 } 5332 start = nativeMax(start === undefined ? (func.length - 1) : (+start || 0), 0); 5333 return function() { 5334 var args = arguments, 5335 index = -1, 5336 length = nativeMax(args.length - start, 0), 5337 rest = Array(length); 5338 5339 while (++index < length) { 5340 rest[index] = args[start + index]; 5341 } 5342 switch (start) { 5343 case 0: return func.call(this, rest); 5344 case 1: return func.call(this, args[0], rest); 5345 case 2: return func.call(this, args[0], args[1], rest); 5346 } 5347 var otherArgs = Array(start + 1); 5348 index = -1; 5349 while (++index < start) { 5350 otherArgs[index] = args[index]; 5351 } 5352 otherArgs[start] = rest; 5353 return func.apply(this, otherArgs); 5354 }; 5355} 5356 5357module.exports = restParam; 5358 5359},{}],27:[function(require,module,exports){ 5360/** 5361 * Copies the values of `source` to `array`. 5362 * 5363 * @private 5364 * @param {Array} source The array to copy values from. 5365 * @param {Array} [array=[]] The array to copy values to. 5366 * @returns {Array} Returns `array`. 5367 */ 5368function arrayCopy(source, array) { 5369 var index = -1, 5370 length = source.length; 5371 5372 array || (array = Array(length)); 5373 while (++index < length) { 5374 array[index] = source[index]; 5375 } 5376 return array; 5377} 5378 5379module.exports = arrayCopy; 5380 5381},{}],28:[function(require,module,exports){ 5382/** 5383 * A specialized version of `_.forEach` for arrays without support for callback 5384 * shorthands and `this` binding. 5385 * 5386 * @private 5387 * @param {Array} array The array to iterate over. 5388 * @param {Function} iteratee The function invoked per iteration. 5389 * @returns {Array} Returns `array`. 5390 */ 5391function arrayEach(array, iteratee) { 5392 var index = -1, 5393 length = array.length; 5394 5395 while (++index < length) { 5396 if (iteratee(array[index], index, array) === false) { 5397 break; 5398 } 5399 } 5400 return array; 5401} 5402 5403module.exports = arrayEach; 5404 5405},{}],29:[function(require,module,exports){ 5406/** 5407 * Copies properties of `source` to `object`. 5408 * 5409 * @private 5410 * @param {Object} source The object to copy properties from. 5411 * @param {Array} props The property names to copy. 5412 * @param {Object} [object={}] The object to copy properties to. 5413 * @returns {Object} Returns `object`. 5414 */ 5415function baseCopy(source, props, object) { 5416 object || (object = {}); 5417 5418 var index = -1, 5419 length = props.length; 5420 5421 while (++index < length) { 5422 var key = props[index]; 5423 object[key] = source[key]; 5424 } 5425 return object; 5426} 5427 5428module.exports = baseCopy; 5429 5430},{}],30:[function(require,module,exports){ 5431var createBaseFor = require('./createBaseFor'); 5432 5433/** 5434 * The base implementation of `baseForIn` and `baseForOwn` which iterates 5435 * over `object` properties returned by `keysFunc` invoking `iteratee` for 5436 * each property. Iteratee functions may exit iteration early by explicitly 5437 * returning `false`. 5438 * 5439 * @private 5440 * @param {Object} object The object to iterate over. 5441 * @param {Function} iteratee The function invoked per iteration. 5442 * @param {Function} keysFunc The function to get the keys of `object`. 5443 * @returns {Object} Returns `object`. 5444 */ 5445var baseFor = createBaseFor(); 5446 5447module.exports = baseFor; 5448 5449},{"./createBaseFor":37}],31:[function(require,module,exports){ 5450var baseFor = require('./baseFor'), 5451 keysIn = require('../object/keysIn'); 5452 5453/** 5454 * The base implementation of `_.forIn` without support for callback 5455 * shorthands and `this` binding. 5456 * 5457 * @private 5458 * @param {Object} object The object to iterate over. 5459 * @param {Function} iteratee The function invoked per iteration. 5460 * @returns {Object} Returns `object`. 5461 */ 5462function baseForIn(object, iteratee) { 5463 return baseFor(object, iteratee, keysIn); 5464} 5465 5466module.exports = baseForIn; 5467 5468},{"../object/keysIn":58,"./baseFor":30}],32:[function(require,module,exports){ 5469var arrayEach = require('./arrayEach'), 5470 baseMergeDeep = require('./baseMergeDeep'), 5471 isArray = require('../lang/isArray'), 5472 isArrayLike = require('./isArrayLike'), 5473 isObject = require('../lang/isObject'), 5474 isObjectLike = require('./isObjectLike'), 5475 isTypedArray = require('../lang/isTypedArray'), 5476 keys = require('../object/keys'); 5477 5478/** 5479 * The base implementation of `_.merge` without support for argument juggling, 5480 * multiple sources, and `this` binding `customizer` functions. 5481 * 5482 * @private 5483 * @param {Object} object The destination object. 5484 * @param {Object} source The source object. 5485 * @param {Function} [customizer] The function to customize merged values. 5486 * @param {Array} [stackA=[]] Tracks traversed source objects. 5487 * @param {Array} [stackB=[]] Associates values with source counterparts. 5488 * @returns {Object} Returns `object`. 5489 */ 5490function baseMerge(object, source, customizer, stackA, stackB) { 5491 if (!isObject(object)) { 5492 return object; 5493 } 5494 var isSrcArr = isArrayLike(source) && (isArray(source) || isTypedArray(source)), 5495 props = isSrcArr ? undefined : keys(source); 5496 5497 arrayEach(props || source, function(srcValue, key) { 5498 if (props) { 5499 key = srcValue; 5500 srcValue = source[key]; 5501 } 5502 if (isObjectLike(srcValue)) { 5503 stackA || (stackA = []); 5504 stackB || (stackB = []); 5505 baseMergeDeep(object, source, key, baseMerge, customizer, stackA, stackB); 5506 } 5507 else { 5508 var value = object[key], 5509 result = customizer ? customizer(value, srcValue, key, object, source) : undefined, 5510 isCommon = result === undefined; 5511 5512 if (isCommon) { 5513 result = srcValue; 5514 } 5515 if ((result !== undefined || (isSrcArr && !(key in object))) && 5516 (isCommon || (result === result ? (result !== value) : (value === value)))) { 5517 object[key] = result; 5518 } 5519 } 5520 }); 5521 return object; 5522} 5523 5524module.exports = baseMerge; 5525 5526},{"../lang/isArray":49,"../lang/isObject":52,"../lang/isTypedArray":55,"../object/keys":57,"./arrayEach":28,"./baseMergeDeep":33,"./isArrayLike":40,"./isObjectLike":45}],33:[function(require,module,exports){ 5527var arrayCopy = require('./arrayCopy'), 5528 isArguments = require('../lang/isArguments'), 5529 isArray = require('../lang/isArray'), 5530 isArrayLike = require('./isArrayLike'), 5531 isPlainObject = require('../lang/isPlainObject'), 5532 isTypedArray = require('../lang/isTypedArray'), 5533 toPlainObject = require('../lang/toPlainObject'); 5534 5535/** 5536 * A specialized version of `baseMerge` for arrays and objects which performs 5537 * deep merges and tracks traversed objects enabling objects with circular 5538 * references to be merged. 5539 * 5540 * @private 5541 * @param {Object} object The destination object. 5542 * @param {Object} source The source object. 5543 * @param {string} key The key of the value to merge. 5544 * @param {Function} mergeFunc The function to merge values. 5545 * @param {Function} [customizer] The function to customize merged values. 5546 * @param {Array} [stackA=[]] Tracks traversed source objects. 5547 * @param {Array} [stackB=[]] Associates values with source counterparts. 5548 * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. 5549 */ 5550function baseMergeDeep(object, source, key, mergeFunc, customizer, stackA, stackB) { 5551 var length = stackA.length, 5552 srcValue = source[key]; 5553 5554 while (length--) { 5555 if (stackA[length] == srcValue) { 5556 object[key] = stackB[length]; 5557 return; 5558 } 5559 } 5560 var value = object[key], 5561 result = customizer ? customizer(value, srcValue, key, object, source) : undefined, 5562 isCommon = result === undefined; 5563 5564 if (isCommon) { 5565 result = srcValue; 5566 if (isArrayLike(srcValue) && (isArray(srcValue) || isTypedArray(srcValue))) { 5567 result = isArray(value) 5568 ? value 5569 : (isArrayLike(value) ? arrayCopy(value) : []); 5570 } 5571 else if (isPlainObject(srcValue) || isArguments(srcValue)) { 5572 result = isArguments(value) 5573 ? toPlainObject(value) 5574 : (isPlainObject(value) ? value : {}); 5575 } 5576 else { 5577 isCommon = false;
5578 } 5579 } 5580 // Add the source value to the stack of traversed objects and associate 5581 // it with its merged value. 5582 stackA.push(srcValue); 5583 stackB.push(result); 5584 5585 if (isCommon) { 5586 // Recursively merge objects and arrays (susceptible to call stack limits). 5587 object[key] = mergeFunc(result, srcValue, customizer, stackA, stackB); 5588 } else if (result === result ? (result !== value) : (value === value)) { 5589 object[key] = result; 5590 } 5591} 5592 5593module.exports = baseMergeDeep; 5594 5595},{"../lang/isArguments":48,"../lang/isArray":49,"../lang/isPlainObject":53,"../lang/isTypedArray":55,"../lang/toPlainObject":56,"./arrayCopy":27,"./isArrayLike":40}],34:[function(require,module,exports){ 5596var toObject = require('./toObject'); 5597 5598/** 5599 * The base implementation of `_.property` without support for deep paths. 5600 * 5601 * @private 5602 * @param {string} key The key of the property to get. 5603 * @returns {Function} Returns the new function. 5604 */ 5605function baseProperty(key) { 5606 return function(object) { 5607 return object == null ? undefined : toObject(object)[key]; 5608 }; 5609} 5610 5611module.exports = baseProperty; 5612 5613},{"./toObject":47}],35:[function(require,module,exports){ 5614var identity = require('../utility/identity'); 5615 5616/** 5617 * A specialized version of `baseCallback` which only supports `this` binding 5618 * and specifying the number of arguments to provide to `func`. 5619 * 5620 * @private 5621 * @param {Function} func The function to bind. 5622 * @param {*} thisArg The `this` binding of `func`. 5623 * @param {number} [argCount] The number of arguments to provide to `func`. 5624 * @returns {Function} Returns the callback. 5625 */ 5626function bindCallback(func, thisArg, argCount) { 5627 if (typeof func != 'function') { 5628 return identity; 5629 } 5630 if (thisArg === undefined) { 5631 return func; 5632 } 5633 switch (argCount) { 5634 case 1: return function(value) { 5635 return func.call(thisArg, value); 5636 }; 5637 case 3: return function(value, index, collection) { 5638 return func.call(thisArg, value, index, collection); 5639 }; 5640 case 4: return function(accumulator, value, index, collection) { 5641 return func.call(thisArg, accumulator, value, index, collection); 5642 }; 5643 case 5: return function(value, other, key, object, source) { 5644 return func.call(thisArg, value, other, key, object, source); 5645 }; 5646 } 5647 return function() { 5648 return func.apply(thisArg, arguments); 5649 }; 5650} 5651 5652module.exports = bindCallback; 5653 5654},{"../utility/identity":61}],36:[function(require,module,exports){ 5655var bindCallback = require('./bindCallback'), 5656 isIterateeCall = require('./isIterateeCall'), 5657 restParam = require('../function/restParam'); 5658 5659/** 5660 * Creates a `_.assign`, `_.defaults`, or `_.merge` function. 5661 * 5662 * @private 5663 * @param {Function} assigner The function to assign values. 5664 * @returns {Function} Returns the new assigner function. 5665 */ 5666function createAssigner(assigner) { 5667 return restParam(function(object, sources) { 5668 var index = -1, 5669 length = object == null ? 0 : sources.length, 5670 customizer = length > 2 ? sources[length - 2] : undefined, 5671 guard = length > 2 ? sources[2] : undefined, 5672 thisArg = length > 1 ? sources[length - 1] : undefined; 5673 5674 if (typeof customizer == 'function') { 5675 customizer = bindCallback(customizer, thisArg, 5); 5676 length -= 2; 5677 } else { 5678 customizer = typeof thisArg == 'function' ? thisArg : undefined; 5679 length -= (customizer ? 1 : 0); 5680 } 5681 if (guard && isIterateeCall(sources[0], sources[1], guard)) { 5682 customizer = length < 3 ? undefined : customizer; 5683 length = 1; 5684 } 5685 while (++index < length) { 5686 var source = sources[index]; 5687 if (source) { 5688 assigner(object, source, customizer); 5689 } 5690 } 5691 return object; 5692 }); 5693} 5694 5695module.exports = createAssigner; 5696 5697},{"../function/restParam":26,"./bindCallback":35,"./isIterateeCall":43}],37:[function(require,module,exports){ 5698var toObject = require('./toObject'); 5699 5700/** 5701 * Creates a base function for `_.forIn` or `_.forInRight`. 5702 * 5703 * @private 5704 * @param {boolean} [fromRight] Specify iterating from right to left. 5705 * @returns {Function} Returns the new base function. 5706 */ 5707function createBaseFor(fromRight) { 5708 return function(object, iteratee, keysFunc) { 5709 var iterable = toObject(object), 5710 props = keysFunc(object), 5711 length = props.length, 5712 index = fromRight ? length : -1; 5713 5714 while ((fromRight ? index-- : ++index < length)) { 5715 var key = props[index]; 5716 if (iteratee(iterable[key], key, iterable) === false) { 5717 break; 5718 } 5719 } 5720 return object; 5721 }; 5722} 5723 5724module.exports = createBaseFor; 5725 5726},{"./toObject":47}],38:[function(require,module,exports){ 5727var baseProperty = require('./baseProperty'); 5728 5729/** 5730 * Gets the "length" property value of `object`. 5731 * 5732 * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) 5733 * that affects Safari on at least iOS 8.1-8.3 ARM64. 5734 * 5735 * @private 5736 * @param {Object} object The object to query. 5737 * @returns {*} Returns the "length" value. 5738 */ 5739var getLength = baseProperty('length'); 5740 5741module.exports = getLength; 5742 5743},{"./baseProperty":34}],39:[function(require,module,exports){ 5744var isNative = require('../lang/isNative'); 5745 5746/** 5747 * Gets the native function at `key` of `object`. 5748 * 5749 * @private 5750 * @param {Object} object The object to query. 5751 * @param {string} key The key of the method to get. 5752 * @returns {*} Returns the function if it's native, else `undefined`. 5753 */ 5754function getNative(object, key) { 5755 var value = object == null ? undefined : object[key]; 5756 return isNative(value) ? value : undefined; 5757} 5758 5759module.exports = getNative; 5760 5761},{"../lang/isNative":51}],40:[function(require,module,exports){ 5762var getLength = require('./getLength'), 5763 isLength = require('./isLength'); 5764 5765/** 5766 * Checks if `value` is array-like. 5767 * 5768 * @private 5769 * @param {*} value The value to check. 5770 * @returns {boolean} Returns `true` if `value` is array-like, else `false`. 5771 */ 5772function isArrayLike(value) { 5773 return value != null && isLength(getLength(value)); 5774} 5775 5776module.exports = isArrayLike; 5777 5778},{"./getLength":38,"./isLength":44}],41:[function(require,module,exports){ 5779/** 5780 * Checks if `value` is a host object in IE < 9. 5781 * 5782 * @private 5783 * @param {*} value The value to check. 5784 * @returns {boolean} Returns `true` if `value` is a host object, else `false`. 5785 */ 5786var isHostObject = (function() { 5787 try { 5788 Object({ 'toString': 0 } + ''); 5789 } catch(e) { 5790 return function() { return false; }; 5791 } 5792 return function(value) { 5793 // IE < 9 presents many host objects as `Object` objects that can coerce 5794 // to strings despite having improperly defined `toString` methods. 5795 return typeof value.toString != 'function' && typeof (value + '') == 'string'; 5796 }; 5797}()); 5798 5799module.exports = isHostObject; 5800 5801},{}],42:[function(require,module,exports){ 5802/** Used to detect unsigned integer values. */ 5803var reIsUint = /^\d+$/; 5804 5805/** 5806 * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) 5807 * of an array-like value. 5808 */ 5809var MAX_SAFE_INTEGER = 9007199254740991; 5810 5811/** 5812 * Checks if `value` is a valid array-like index. 5813 * 5814 * @private 5815 * @param {*} value The value to check. 5816 * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. 5817 * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. 5818 */ 5819function isIndex(value, length) { 5820 value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1; 5821 length = length == null ? MAX_SAFE_INTEGER : length; 5822 return value > -1 && value % 1 == 0 && value < length; 5823} 5824 5825module.exports = isIndex; 5826 5827},{}],43:[function(require,module,exports){ 5828var isArrayLike = require('./isArrayLike'), 5829 isIndex = require('./isIndex'), 5830 isObject = require('../lang/isObject'); 5831 5832/** 5833 * Checks if the provided arguments are from an iteratee call. 5834 * 5835 * @private 5836 * @param {*} value The potential iteratee value argument. 5837 * @param {*} index The potential iteratee index or key argument. 5838 * @param {*} object The potential iteratee object argument. 5839 * @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`. 5840 */ 5841function isIterateeCall(value, index, object) { 5842 if (!isObject(object)) { 5843 return false;
5844 } 5845 var type = typeof index; 5846 if (type == 'number' 5847 ? (isArrayLike(object) && isIndex(index, object.length)) 5848 : (type == 'string' && index in object)) { 5849 var other = object[index]; 5850 return value === value ? (value === other) : (other !== other); 5851 } 5852 return false; 5853} 5854 5855module.exports = isIterateeCall; 5856 5857},{"../lang/isObject":52,"./isArrayLike":40,"./isIndex":42}],44:[function(require,module,exports){ 5858/** 5859 * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) 5860 * of an array-like value. 5861 */ 5862var MAX_SAFE_INTEGER = 9007199254740991; 5863 5864/** 5865 * Checks if `value` is a valid array-like length. 5866 * 5867 * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). 5868 * 5869 * @private 5870 * @param {*} value The value to check. 5871 * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. 5872 */ 5873function isLength(value) { 5874 return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; 5875} 5876 5877module.exports = isLength; 5878 5879},{}],45:[function(require,module,exports){ 5880/** 5881 * Checks if `value` is object-like. 5882 * 5883 * @private 5884 * @param {*} value The value to check. 5885 * @returns {boolean} Returns `true` if `value` is object-like, else `false`. 5886 */ 5887function isObjectLike(value) { 5888 return !!value && typeof value == 'object'; 5889} 5890 5891module.exports = isObjectLike; 5892 5893},{}],46:[function(require,module,exports){ 5894var isArguments = require('../lang/isArguments'), 5895 isArray = require('../lang/isArray'), 5896 isIndex = require('./isIndex'), 5897 isLength = require('./isLength'), 5898 isString = require('../lang/isString'), 5899 keysIn = require('../object/keysIn'); 5900 5901/** Used for native method references. */ 5902var objectProto = Object.prototype; 5903 5904/** Used to check objects for own properties. */ 5905var hasOwnProperty = objectProto.hasOwnProperty; 5906 5907/** 5908 * A fallback implementation of `Object.keys` which creates an array of the 5909 * own enumerable property names of `object`. 5910 * 5911 * @private 5912 * @param {Object} object The object to query. 5913 * @returns {Array} Returns the array of property names. 5914 */ 5915function shimKeys(object) { 5916 var props = keysIn(object), 5917 propsLength = props.length, 5918 length = propsLength && object.length; 5919 5920 var allowIndexes = !!length && isLength(length) && 5921 (isArray(object) || isArguments(object) || isString(object)); 5922 5923 var index = -1, 5924 result = []; 5925 5926 while (++index < propsLength) { 5927 var key = props[index]; 5928 if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) { 5929 result.push(key); 5930 } 5931 } 5932 return result; 5933} 5934 5935module.exports = shimKeys; 5936 5937},{"../lang/isArguments":48,"../lang/isArray":49,"../lang/isString":54,"../object/keysIn":58,"./isIndex":42,"./isLength":44}],47:[function(require,module,exports){ 5938var isObject = require('../lang/isObject'), 5939 isString = require('../lang/isString'), 5940 support = require('../support'); 5941 5942/** 5943 * Converts `value` to an object if it's not one. 5944 * 5945 * @private 5946 * @param {*} value The value to process. 5947 * @returns {Object} Returns the object. 5948 */ 5949function toObject(value) { 5950 if (support.unindexedChars && isString(value)) { 5951 var index = -1, 5952 length = value.length, 5953 result = Object(value); 5954 5955 while (++index < length) { 5956 result[index] = value.charAt(index); 5957 } 5958 return result; 5959 } 5960 return isObject(value) ? value : Object(value); 5961} 5962 5963module.exports = toObject; 5964 5965},{"../lang/isObject":52,"../lang/isString":54,"../support":60}],48:[function(require,module,exports){ 5966var isArrayLike = require('../internal/isArrayLike'), 5967 isObjectLike = require('../internal/isObjectLike'); 5968 5969/** Used for native method references. */ 5970var objectProto = Object.prototype; 5971 5972/** Used to check objects for own properties. */ 5973var hasOwnProperty = objectProto.hasOwnProperty; 5974 5975/** Native method references. */ 5976var propertyIsEnumerable = objectProto.propertyIsEnumerable; 5977 5978/** 5979 * Checks if `value` is classified as an `arguments` object. 5980 * 5981 * @static 5982 * @memberOf _ 5983 * @category Lang 5984 * @param {*} value The value to check. 5985 * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. 5986 * @example 5987 * 5988 * _.isArguments(function() { return arguments; }()); 5989 * // => true 5990 * 5991 * _.isArguments([1, 2, 3]); 5992 * // => false 5993 */ 5994function isArguments(value) { 5995 return isObjectLike(value) && isArrayLike(value) && 5996 hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee'); 5997} 5998 5999module.exports = isArguments; 6000 6001},{"../internal/isArrayLike":40,"../internal/isObjectLike":45}],49:[function(require,module,exports){ 6002var getNative = require('../internal/getNative'), 6003 isLength = require('../internal/isLength'), 6004 isObjectLike = require('../internal/isObjectLike'); 6005 6006/** `Object#toString` result references. */ 6007var arrayTag = '[object Array]'; 6008 6009/** Used for native method references. */ 6010var objectProto = Object.prototype; 6011 6012/** 6013 * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) 6014 * of values. 6015 */ 6016var objToString = objectProto.toString; 6017 6018/* Native method references for those with the same name as other `lodash` methods. */ 6019var nativeIsArray = getNative(Array, 'isArray'); 6020 6021/** 6022 * Checks if `value` is classified as an `Array` object. 6023 * 6024 * @static 6025 * @memberOf _ 6026 * @category Lang 6027 * @param {*} value The value to check. 6028 * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. 6029 * @example 6030 * 6031 * _.isArray([1, 2, 3]); 6032 * // => true 6033 * 6034 * _.isArray(function() { return arguments; }()); 6035 * // => false 6036 */ 6037var isArray = nativeIsArray || function(value) { 6038 return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag; 6039}; 6040 6041module.exports = isArray; 6042 6043},{"../internal/getNative":39,"../internal/isLength":44,"../internal/isObjectLike":45}],50:[function(require,module,exports){ 6044var isObject = require('./isObject'); 6045 6046/** `Object#toString` result references. */ 6047var funcTag = '[object Function]'; 6048 6049/** Used for native method references. */ 6050var objectProto = Object.prototype; 6051 6052/** 6053 * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) 6054 * of values. 6055 */ 6056var objToString = objectProto.toString; 6057 6058/** 6059 * Checks if `value` is classified as a `Function` object. 6060 * 6061 * @static 6062 * @memberOf _ 6063 * @category Lang 6064 * @param {*} value The value to check. 6065 * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. 6066 * @example 6067 * 6068 * _.isFunction(_); 6069 * // => true 6070 * 6071 * _.isFunction(/abc/); 6072 * // => false 6073 */ 6074function isFunction(value) {
6075 // The use of `Object#toString` avoids issues with the `typeof` operator 6076 // in older versions of Chrome and Safari which return 'function' for regexes 6077 // and Safari 8 which returns 'object' for typed array constructors. 6078 return isObject(value) && objToString.call(value) == funcTag; 6079} 6080 6081module.exports = isFunction; 6082 6083},{"./isObject":52}],51:[function(require,module,exports){ 6084var isFunction = require('./isFunction'), 6085 isHostObject = require('../internal/isHostObject'), 6086 isObjectLike = require('../internal/isObjectLike'); 6087 6088/** Used to detect host constructors (Safari > 5). */ 6089var reIsHostCtor = /^\[object .+?Constructor\]$/; 6090 6091/** Used for native method references. */ 6092var objectProto = Object.prototype; 6093 6094/** Used to resolve the decompiled source of functions. */ 6095var fnToString = Function.prototype.toString; 6096 6097/** Used to check objects for own properties. */ 6098var hasOwnProperty = objectProto.hasOwnProperty; 6099 6100/** Used to detect if a method is native. */ 6101var reIsNative = RegExp('^' + 6102 fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') 6103 .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' 6104); 6105 6106/** 6107 * Checks if `value` is a native function. 6108 * 6109 * @static 6110 * @memberOf _ 6111 * @category Lang 6112 * @param {*} value The value to check. 6113 * @returns {boolean} Returns `true` if `value` is a native function, else `false`. 6114 * @example 6115 * 6116 * _.isNative(Array.prototype.push); 6117 * // => true 6118 * 6119 * _.isNative(_); 6120 * // => false 6121 */ 6122function isNative(value) { 6123 if (value == null) { 6124 return false; 6125 } 6126 if (isFunction(value)) { 6127 return reIsNative.test(fnToString.call(value)); 6128 } 6129 return isObjectLike(value) && (isHostObject(value) ? reIsNative : reIsHostCtor).test(value); 6130} 6131 6132module.exports = isNative; 6133 6134},{"../internal/isHostObject":41,"../internal/isObjectLike":45,"./isFunction":50}],52:[function(require,module,exports){ 6135/** 6136 * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`. 6137 * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) 6138 * 6139 * @static 6140 * @memberOf _ 6141 * @category Lang 6142 * @param {*} value The value to check. 6143 * @returns {boolean} Returns `true` if `value` is an object, else `false`. 6144 * @example 6145 * 6146 * _.isObject({}); 6147 * // => true 6148 * 6149 * _.isObject([1, 2, 3]); 6150 * // => true 6151 * 6152 * _.isObject(1); 6153 * // => false 6154 */ 6155function isObject(value) { 6156 // Avoid a V8 JIT bug in Chrome 19-20. 6157 // See https://code.google.com/p/v8/issues/detail?id=2291 for more details. 6158 var type = typeof value; 6159 return !!value && (type == 'object' || type == 'function'); 6160} 6161 6162module.exports = isObject; 6163 6164},{}],53:[function(require,module,exports){ 6165var baseForIn = require('../internal/baseForIn'), 6166 isArguments = require('./isArguments'), 6167 isHostObject = require('../internal/isHostObject'), 6168 isObjectLike = require('../internal/isObjectLike'), 6169 support = require('../support'); 6170 6171/** `Object#toString` result references. */ 6172var objectTag = '[object Object]'; 6173 6174/** Used for native method references. */ 6175var objectProto = Object.prototype; 6176 6177/** Used to check objects for own properties. */ 6178var hasOwnProperty = objectProto.hasOwnProperty; 6179 6180/** 6181 * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) 6182 * of values. 6183 */ 6184var objToString = objectProto.toString; 6185 6186/** 6187 * Checks if `value` is a plain object, that is, an object created by the 6188 * `Object` constructor or one with a `[[Prototype]]` of `null`. 6189 * 6190 * **Note:** This method assumes objects created by the `Object` constructor 6191 * have no inherited enumerable properties. 6192 * 6193 * @static 6194 * @memberOf _ 6195 * @category Lang 6196 * @param {*} value The value to check. 6197 * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. 6198 * @example 6199 * 6200 * function Foo() { 6201 * this.a = 1; 6202 * } 6203 * 6204 * _.isPlainObject(new Foo); 6205 * // => false 6206 * 6207 * _.isPlainObject([1, 2, 3]); 6208 * // => false 6209 * 6210 * _.isPlainObject({ 'x': 0, 'y': 0 }); 6211 * // => true 6212 * 6213 * _.isPlainObject(Object.create(null)); 6214 * // => true 6215 */ 6216function isPlainObject(value) { 6217 var Ctor; 6218 6219 // Exit early for non `Object` objects. 6220 if (!(isObjectLike(value) && objToString.call(value) == objectTag && !isHostOb
6220ject(value) && !isArguments(value)) || 6221 (!hasOwnProperty.call(value, 'constructor') && (Ctor = value.constructor, typeof Ctor == 'function' && !(Ctor instanceof Ctor)))) { 6222 return false; 6223 } 6224 // IE < 9 iterates inherited properties before own properties. If the first 6225 // iterated property is an object's own property then there are no inherited 6226 // enumerable properties. 6227 var result; 6228 if (support.ownLast) { 6229 baseForIn(value, function(subValue, key, object) { 6230 result = hasOwnProperty.call(object, key); 6231 return false; 6232 }); 6233 return result !== false; 6234 } 6235 // In most environments an object's own properties are iterated before 6236 // its inherited properties. If the last iterated property is an object's 6237 // own property then there are no inherited enumerable properties. 6238 baseForIn(value, function(subValue, key) { 6239 result = key; 6240 }); 6241 return result === undefined || hasOwnProperty.call(value, result); 6242} 6243 6244module.exports = isPlainObject; 6245 6246},{"../internal/baseForIn":31,"../internal/isHostObject":41,"../internal/isObjectLike":45,"../support":60,"./isArguments":48}],54:[function(require,module,exports){ 6247var isObjectLike = require('../internal/isObjectLike'); 6248 6249/** `Object#toString` result references. */ 6250var stringTag = '[object String]'; 6251 6252/** Used for native method references. */ 6253var objectProto = Object.prototype; 6254 6255/** 6256 * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) 6257 * of values. 6258 */ 6259var objToString = objectProto.toString; 6260 6261/** 6262 * Checks if `value` is classified as a `String` primitive or object. 6263 * 6264 * @static 6265 * @memberOf _ 6266 * @category Lang 6267 * @param {*} value The value to check. 6268 * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. 6269 * @example 6270 * 6271 * _.isString('abc'); 6272 * // => true 6273 * 6274 * _.isString(1); 6275 * // => false 6276 */ 6277function isString(value) { 6278 return typeof value == 'string' || (isObjectLike(value) && objToString.call(value) == stringTag); 6279} 6280 6281module.exports = isString; 6282 6283},{"../internal/isObjectLike":45}],55:[function(require,module,exports){ 6284var isLength = require('../internal/isLength'), 6285 isObjectLike = require('../internal/isObjectLike'); 6286 6287/** `Object#toString` result references. */ 6288var argsTag = '[object Arguments]', 6289 arrayTag = '[object Array]', 6290 boolTag = '[object Boolean]', 6291 dateTag = '[object Date]', 6292 errorTag = '[object Error]', 6293 funcTag = '[object Function]', 6294 mapTag = '[object Map]', 6295 numberTag = '[object Number]', 6296 objectTag = '[object Object]', 6297 regexpTag = '[object RegExp]', 6298 setTag = '[object Set]', 6299 stringTag = '[object String]', 6300 weakMapTag = '[object WeakMap]'; 6301 6302var arrayBufferTag = '[object ArrayBuffer]', 6303 float32Tag = '[object Float32Array]', 6304 float64Tag = '[object Float64Array]', 6305 int8Tag = '[object Int8Array]', 6306 int16Tag = '[object Int16Array]', 6307 int32Tag = '[object Int32Array]', 6308 uint8Tag = '[object Uint8Array]', 6309 uint8ClampedTag = '[object Uint8ClampedArray]', 6310 uint16Tag = '[object Uint16Array]', 6311 uint32Tag = '[object Uint32Array]'; 6312 6313/** Used to identify `toStringTag` values of typed arrays. */ 6314var typedArrayTags = {}; 6315typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = 6316typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = 6317typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = 6318typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = 6319typedArrayTags[uint32Tag] = true; 6320typedArrayTags[argsTag] = typedArrayTags[arrayTag] = 6321typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = 6322typedArrayTags[dateTag] = typedArrayTags[errorTag] = 6323typedArrayTags[funcTag] = typedArrayTags[mapTag] = 6324typedArrayTags[numberTag] = typedArrayTags[objectTag] = 6325typedArrayTags[regexpTag] = typedArrayTags[setTag] = 6326typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false; 6327 6328/** Used for native method references. */ 6329var objectProto = Object.prototype; 6330 6331/** 6332 * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) 6333 * of values. 6334 */ 6335var objToString = objectProto.toString; 6336 6337/** 6338 * Checks if `value` is classified as a typed array.
6339 * 6340 * @static 6341 * @memberOf _ 6342 * @category Lang 6343 * @param {*} value The value to check. 6344 * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`. 6345 * @example 6346 * 6347 * _.isTypedArray(new Uint8Array); 6348 * // => true 6349 * 6350 * _.isTypedArray([]); 6351 * // => false 6352 */ 6353function isTypedArray(value) { 6354 return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[objToString.call(value)]; 6355} 6356 6357module.exports = isTypedArray; 6358 6359},{"../internal/isLength":44,"../internal/isObjectLike":45}],56:[function(require,module,exports){ 6360var baseCopy = require('../internal/baseCopy'), 6361 keysIn = require('../object/keysIn'); 6362 6363/** 6364 * Converts `value` to a plain object flattening inherited enumerable 6365 * properties of `value` to own properties of the plain object. 6366 * 6367 * @static 6368 * @memberOf _ 6369 * @category Lang 6370 * @param {*} value The value to convert. 6371 * @returns {Object} Returns the converted plain object. 6372 * @example 6373 * 6374 * function Foo() { 6375 * this.b = 2; 6376 * } 6377 * 6378 * Foo.prototype.c = 3; 6379 * 6380 * _.assign({ 'a': 1 }, new Foo); 6381 * // => { 'a': 1, 'b': 2 } 6382 * 6383 * _.assign({ 'a': 1 }, _.toPlainObject(new Foo)); 6384 * // => { 'a': 1, 'b': 2, 'c': 3 } 6385 */ 6386function toPlainObject(value) { 6387 return baseCopy(value, keysIn(value)); 6388} 6389 6390module.exports = toPlainObject; 6391 6392},{"../internal/baseCopy":29,"../object/keysIn":58}],57:[function(require,module,exports){ 6393var getNative = require('../internal/getNative'), 6394 isArrayLike = require('../internal/isArrayLike'), 6395 isObject = require('../lang/isObject'), 6396 shimKeys = require('../internal/shimKeys'), 6397 support = require('../support'); 6398 6399/* Native method references for those with the same name as other `lodash` methods. */ 6400var nativeKeys = getNative(Object, 'keys'); 6401 6402/** 6403 * Creates an array of the own enumerable property names of `object`. 6404 * 6405 * **Note:** Non-object values are coerced to objects. See the 6406 * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys) 6407 * for more details. 6408 * 6409 * @static 6410 * @memberOf _ 6411 * @category Object 6412 * @param {Object} object The object to query. 6413 * @returns {Array} Returns the array of property names. 6414 * @example 6415 * 6416 * function Foo() { 6417 * this.a = 1; 6418 * this.b = 2; 6419 * } 6420 * 6421 * Foo.prototype.c = 3; 6422 * 6423 * _.keys(new Foo); 6424 * // => ['a', 'b'] (iteration order is not guaranteed) 6425 * 6426 * _.keys('hi'); 6427 * // => ['0', '1'] 6428 */ 6429var keys = !nativeKeys ? shimKeys : function(object) { 6430 var Ctor = object == null ? undefined : object.constructor; 6431 if ((typeof Ctor == 'function' && Ctor.prototype === object) || 6432 (typeof object == 'function' ? support.enumPrototypes : isArrayLike(object))) { 6433 return shimKeys(object); 6434 } 6435 return isObject(object) ? nativeKeys(object) : []; 6436}; 6437 6438module.exports = keys; 6439 6440},{"../internal/getNative":39,"../internal/isArrayLike":40,"../internal/shimKeys":46,"../lang/isObject":52,"../support":60}],58:[function(require,module,exports){ 6441var arrayEach = require('../internal/arrayEach'), 6442 isArguments = require('../lang/isArguments'), 6443 isArray = require('../lang/isArray'), 6444 isFunction = require('../lang/isFunction'), 6445 isIndex = require('../internal/isIndex'), 6446 isLength = require('../internal/isLength'), 6447 isObject = require('../lang/isObject'), 6448 isString = require('../lang/isString'), 6449 support = require('../support'); 6450 6451/** `Object#toString` result references. */ 6452var arrayTag = '[object Array]', 6453 boolTag = '[object Boolean]', 6454 dateTag = '[object Date]', 6455 errorTag = '[object Error]', 6456 funcTag = '[object Function]', 6457 numberTag = '[object Number]', 6458 objectTag = '[object Object]', 6459 regexpTag = '[object RegExp]', 6460 stringTag = '[object String]'; 6461 6462/** Used to fix the JScript `[[DontEnum]]` bug. */ 6463var shadowProps = [ 6464 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 6465 'toLocaleString', 'toString', 'valueOf' 6466]; 6467 6468/** Used for native method references. */ 6469var errorProto = Error.prototype, 6470 objectProto = Object.prototype, 6471 stringProto = String.prototype; 6472 6473/** Used to check objects for own properties. */ 6474var hasOwnProperty = objectProto.hasOwnProperty; 6475 6476/** 6477 * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) 6478 * of values. 6479 */ 6480var objToString = objectProto.toString; 6481 6482/** Used to avoid iterating over non-enumerable properties in IE < 9. */ 6483var nonEnumProps = {}; 6484nonEnumProps[arrayTag] = nonEnumProps[dateTag] = nonEnumProps[numberTag] = { 'constructor': true, 'toLocaleStr
6484ing': true, 'toString': true, 'valueOf': true }; 6485nonEnumProps[boolTag] = nonEnumProps[stringTag] = { 'constructor': true, 'toString': true, 'valueOf': true }; 6486nonEnumProps[errorTag] = nonEnumProps[funcTag] = nonEnumProps[regexpTag] = { 'constructor': true, 'toString': true }; 6487nonEnumProps[objectTag] = { 'constructor': true }; 6488 6489arrayEach(shadowProps, function(key) { 6490 for (var tag in nonEnumProps) { 6491 if (hasOwnProperty.call(nonEnumProps, tag)) { 6492 var props = nonEnumProps[tag]; 6493 props[key] = hasOwnProperty.call(props, key); 6494 } 6495 } 6496}); 6497 6498/** 6499 * Creates an array of the own and inherited enumerable property names of `object`. 6500 * 6501 * **Note:** Non-object values are coerced to objects. 6502 * 6503 * @static 6504 * @memberOf _ 6505 * @category Object 6506 * @param {Object} object The object to query. 6507 * @returns {Array} Returns the array of property names. 6508 * @example 6509 * 6510 * function Foo() { 6511 * this.a = 1; 6512 * this.b = 2; 6513 * } 6514 * 6515 * Foo.prototype.c = 3; 6516 * 6517 * _.keysIn(new Foo); 6518 * // => ['a', 'b', 'c'] (iteration order is not guaranteed) 6519 */ 6520function keysIn(object) { 6521 if (object == null) { 6522 return []; 6523 } 6524 if (!isObject(object)) { 6525 object = Object(object); 6526 } 6527 var length = object.length; 6528 6529 length = (length && isLength(length) && 6530 (isArray(object) || isArguments(object) || isString(object)) && length) || 0; 6531 6532 var Ctor = object.constructor, 6533 index = -1, 6534 proto = (isFunction(Ctor) && Ctor.prototype) || objectProto, 6535 isProto = proto === object, 6536 result = Array(length), 6537 skipIndexes = length > 0, 6538 skipErrorProps = support.enumErrorProps && (object === errorProto || object instanceof Error), 6539 skipProto = support.enumPrototypes && isFunction(object); 6540 6541 while (++index < length) { 6542 result[index] = (index + ''); 6543 } 6544 // lodash skips the `constructor` property when it infers it's iterating 6545 // over a `prototype` object because IE < 9 can't set the `[[Enumerable]]` 6546 // attribute of an existing property and the `constructor` property of a 6547 // prototype defaults to non-enumerable. 6548 for (var key in object) { 6549 if (!(skipProto && key == 'prototype') && 6550 !(skipErrorProps && (key == 'message' || key == 'name')) && 6551 !(skipIndexes && isIndex(key, length)) && 6552 !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { 6553 result.push(key); 6554 } 6555 } 6556 if (support.nonEnumShadows && object !== objectProto) { 6557 var tag = object === stringProto ? stringTag : (object === errorProto ? errorTag : objToString.call(object)), 6558 nonEnums = nonEnumProps[tag] || nonEnumProps[objectTag]; 6559 6560 if (tag == objectTag) { 6561 proto = objectProto; 6562 } 6563 length = shadowProps.length; 6564 while (length--) { 6565 key = shadowProps[length]; 6566 var nonEnum = nonEnums[key]; 6567 if (!(isProto && nonEnum) && 6568 (nonEnum ? hasOwnProperty.call(object, key) : object[key] !== proto[key])) { 6569 result.push(key); 6570 } 6571 } 6572 } 6573 return result; 6574} 6575 6576module.exports = keysIn; 6577 6578},{"../internal/arrayEach":28,"../internal/isIndex":42,"../internal/isLength":44,"../lang/isArguments":48,"../lang/isArray":49,"../lang/isFunction":50,"../lang/isObject":52,"../lang/isString":54,"../support":60}],59:[function(require,module,exports){ 6579var baseMerge = require('../internal/baseMerge'), 6580 createAssigner = require('../internal/createAssigner'); 6581 6582/** 6583 * Recursively merges own enumerable properties of the source object(s), that 6584 * don't resolve to `undefined` into the destination object. Subsequent sources 6585 * overwrite property assignments of previous sources. If `customizer` is 6586 * provided it's invoked to produce the merged values of the destination and 6587 * source properties. If `customizer` returns `undefined` merging is handled 6588 * by the method instead. The `customizer` is bound to `thisArg` and invoked 6589 * with five arguments: (objectValue, sourceValue, key, object, source). 6590 * 6591 * @static 6592 * @memberOf _ 6593 * @category Object 6594 * @param {Object} object The destination object. 6595 * @param {...Object} [sources] The source objects. 6596 * @param {Function} [customizer] The function to customize assigned values. 6597 * @param {*} [thisArg] The `this` binding of `customizer`. 6598 * @returns {Object} Returns `object`. 6599 * @example 6600 * 6601 * var users = { 6602 * 'data': [{ 'user': 'barney' }, { 'user': 'fred' }] 6603 * }; 6604 * 6605 * var ages = { 6606 * 'data': [{ 'age': 36 }, { 'age': 40 }] 6607 * }; 6608 * 6609 * _.merge(users, ages); 6610 * // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] } 6611 * 6612 * // using a customizer callback 6613 * var object = { 6614 * 'fruits': ['apple'], 6615 * 'vegetables': ['beet'] 6616 * }; 6617 * 6618 * var other = { 6619 * 'fruits': ['banana'], 6620 * 'vegetables': ['carrot'] 6621 * }; 6622 * 6623 * _.merge(object, other, function(a, b) { 6624 * if (_.isArray(a)) { 6625 * return a.concat(b); 6626 * } 6627 * }); 6628 * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] } 6629 */ 6630var merge = createAssigner(baseMerge); 6631 6632module.exports = merge; 6633 6634},{"../internal/baseMerge":32,"../internal/createAssigner":36}],60:[function(require,module,exports){ 6635/** Used for native method references. */ 6636var arrayProto = Array.prototype, 6637 errorProto = Error.prototype, 6638 objectProto = Object.prototype; 6639
6640/** Native method references. */ 6641var propertyIsEnumerable = objectProto.propertyIsEnumerable, 6642 splice = arrayProto.splice; 6643 6644/** 6645 * An object environment feature flags. 6646 * 6647 * @static 6648 * @memberOf _ 6649 * @type Object 6650 */ 6651var support = {}; 6652 6653(function(x) { 6654 var Ctor = function() { this.x = x; }, 6655 object = { '0': x, 'length': x }, 6656 props = []; 6657 6658 Ctor.prototype = { 'valueOf': x, 'y': x }; 6659 for (var key in new Ctor) { props.push(key); } 6660 6661 /** 6662 * Detect if `name` or `message` properties of `Error.prototype` are 6663 * enumerable by default (IE < 9, Safari < 5.1). 6664 * 6665 * @memberOf _.support 6666 * @type boolean 6667 */ 6668 support.enumErrorProps = propertyIsEnumerable.call(errorProto, 'message') || 6669 propertyIsEnumerable.call(errorProto, 'name'); 6670 6671 /** 6672 * Detect if `prototype` properties are enumerable by default. 6673 * 6674 * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 6675 * (if the prototype or a property on the prototype has been set) 6676 * incorrectly set the `[[Enumerable]]` value of a function's `prototype` 6677 * property to `true`. 6678 * 6679 * @memberOf _.support 6680 * @type boolean 6681 */ 6682 support.enumPrototypes = propertyIsEnumerable.call(Ctor, 'prototype'); 6683 6684 /** 6685 * Detect if properties shadowing those on `Object.prototype` are non-enumerable. 6686 * 6687 * In IE < 9 an object's own properties, shadowing non-enumerable ones, 6688 * are made non-enumerable as well (a.k.a the JScript `[[DontEnum]]` bug). 6689 * 6690 * @memberOf _.support 6691 * @type boolean 6692 */ 6693 support.nonEnumShadows = !/valueOf/.test(props); 6694 6695 /** 6696 * Detect if own properties are iterated after inherited properties (IE < 9). 6697 * 6698 * @memberOf _.support 6699 * @type boolean 6700 */ 6701 support.ownLast = props[0] != 'x'; 6702 6703 /** 6704 * Detect if `Array#shift` and `Array#splice` augment array-like objects 6705 * correctly. 6706 * 6707 * Firefox < 10, compatibility modes of IE 8, and IE < 9 have buggy Array 6708 * `shift()` and `splice()` functions that fail to remove the last element, 6709 * `value[0]`, of array-like objects even though the "length" property is 6710 * set to `0`. The `shift()` method is buggy in compatibility modes of IE 8, 6711 * while `splice()` is buggy regardless of mode in IE < 9. 6712 * 6713 * @memberOf _.support 6714 * @type boolean 6715 */ 6716 support.spliceObjects = (splice.call(object, 0, 1), !object[0]); 6717 6718 /** 6719 * Detect lack of support for accessing string characters by index. 6720 * 6721 * IE < 8 can't access characters by index. IE 8 can only access characters 6722 * by index on string literals, not string objects. 6723 * 6724 * @memberOf _.support 6725 * @type boolean 6726 */ 6727 support.unindexedChars = ('x'[0] + Object('x')[0]) != 'xx'; 6728}(1, 0)); 6729 6730module.exports = support; 6731 6732},{}],61:[function(require,module,exports){ 6733/** 6734 * This method returns the first argument provided to it. 6735 * 6736 * @static 6737 * @memberOf _ 6738 * @category Utility 6739 * @param {*} value Any value. 6740 * @returns {*} Returns `value`. 6741 * @example 6742 * 6743 * var object = { 'user': 'fred' }; 6744 * 6745 * _.identity(object) === object; 6746 * // => true 6747 */ 6748function identity(value) { 6749 return value; 6750} 6751 6752module.exports = identity; 6753 6754},{}],62:[function(require,module,exports){ 6755/** 6756 * @file m3u8/index.js 6757 * 6758 * Utilities for parsing M3U8 files. If the entire manifest is available, 6759 * `Parser` will create an object representation with enough detail for managing 6760 * playback. `ParseStream` and `LineStream` are lower-level parsing primitives 6761 * that do not assume the entirety of the manifest is ready and expose a 6762 * ReadableStream-like interface. 6763 */ 6764 6765'use strict'; 6766 6767Object.defineProperty(exports, '__esModule', { 6768 value: true 6769}); 6770 6771function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 6772 6773var _lineStream = require('./line-stream'); 6774 6775var _lineStream2 = _interopRequireDefault(_lineStream); 6776 6777var _parseStream = require('./parse-stream'); 6778 6779var _parseStream2 = _interopRequireDefault(_parseStream); 6780 6781var _parser = require('./parser'); 6782 6783var _parser2 = _interopRequireDefault(_parser); 6784 6785exports['default'] = { 6786 LineStream: _lineStream2['default'], 6787 ParseStream: _parseStream2['default'], 6788 Parser: _parser2['default'] 6789}; 6790module.exports = exports['default']; 6791},{"./line-stream":63,"./parse-stream":64,"./parser":65}],63:[function(require,module,exports){ 6792/** 6793 * @file m3u8/line-stream.js 6794 */ 6795'use strict'; 6796 6797Object.defineProperty(exports, '__esModule', { 6798 value: true 6799}); 6800 6801var _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; }; })(); 6802 6803var _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); } } }; 6804 6805function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 6806 6807function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 6808 6809function _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
6809onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 6810 6811var _stream = require('./stream'); 6812 6813var _stream2 = _interopRequireDefault(_stream); 6814 6815/** 6816 * A stream that buffers string input and generates a `data` event for each 6817 * line. 6818 * 6819 * @class LineStream 6820 * @extends Stream 6821 */ 6822 6823var LineStream = (function (_Stream) { 6824 _inherits(LineStream, _Stream); 6825 6826 function LineStream() { 6827 _classCallCheck(this, LineStream); 6828 6829 _get(Object.getPrototypeOf(LineStream.prototype), 'constructor', this).call(this); 6830 this.buffer = ''; 6831 } 6832 6833 /** 6834 * Add new data to be parsed. 6835 * 6836 * @param {String} data the text to process 6837 */ 6838 6839 _createClass(LineStream, [{ 6840 key: 'push', 6841 value: function push(data) { 6842 var nextNewline = undefined; 6843 6844 this.buffer += data; 6845 nextNewline = this.buffer.indexOf('\n'); 6846 6847 for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) { 6848 this.trigger('data', this.buffer.substring(0, nextNewline)); 6849 this.buffer = this.buffer.substring(nextNewline + 1); 6850 } 6851 } 6852 }]); 6853 6854 return LineStream; 6855})(_stream2['default']); 6856 6857exports['default'] = LineStream; 6858module.exports = exports['default']; 6859},{"./stream":66}],64:[function(require,module,exports){ 6860/** 6861 * @file m3u8/parse-stream.js 6862 */ 6863'use strict'; 6864 6865Object.defineProperty(exports, '__esModule', { 6866 value: true 6867}); 6868 6869var _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'); } }; })(); 6870 6871var _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; }; })(); 6872 6873var _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); } } }; 6874 6875function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 6876 6877function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 6878 6879function _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; } 6880 6881var _stream = require('./stream'); 6882 6883var _stream2 = _interopRequireDefault(_stream); 6884 6885/** 6886 * "forgiving" attribute list psuedo-grammar: 6887 * attributes -> keyvalue (',' keyvalue)* 6888 * keyvalue -> key '=' value 6889 * key -> [^=]* 6890 * value -> '"' [^"]* '"' | [^,]* 6891 */ 6892var attributeSeparator = function attributeSeparator() { 6893 var key = '[^=]*'; 6894 var value = '"[^"]*"|[^,]*'; 6895 var keyvalue = '(?:' + key + ')=(?:' + value + ')'; 6896 6897 return new RegExp('(?:^|,)(' + keyvalue + ')'); 6898}; 6899 6900/** 6901 * Parse attributes from a line given the seperator 6902 * 6903 * @param {String} attributes the attibute line to parse 6904 */ 6905var parseAttributes = function parseAttributes(attributes) { 6906 // split the string using attributes as the separator 6907 var attrs = attributes.split(attributeSeparator()); 6908 var i = attrs.length; 6909 var result = {}; 6910 var attr = undefined; 6911 6912 while (i--) { 6913 // filter out unmatched portions of the string 6914 if (attrs[i] === '') { 6915 continue; 6916 } 6917 6918 // split the key and value 6919 attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); 6920 // trim whitespace and remove optional quotes around the value 6921 attr[0] = attr[0].replace(/^\s+|\s+$/g, ''); 6922 attr[1] = attr[1].replace(/^\s+|\s+$/g, ''); 6923 attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1'); 6924 result[attr[0]] = attr[1]; 6925 } 6926 return result; 6927}; 6928 6929/** 6930 * A line-level M3U8 parser event stream. It expects to receive input one 6931 * line at a time and performs a context-free parse of its contents. A stream 6932 * interpretation of a manifest can be useful if the manifest is expected to 6933 * be too large to fit comfortably into memory or the entirety of the input 6934 * is not immediately available. Otherwise, it's probably much easier to work 6935 * with a regular `Parser` object. 6936 * 6937 * Produces `data` events with an object that captures the parser's 6938 * interpretation of the input. That object has a property `tag` that is one 6939 * of `uri`, `comment`, or `tag`. URIs only have a single additional 6940 * property, `line`, which captures the entirety of the input without 6941 * interpretation. Comments similarly have a single additional property 6942 * `text` which is the input without the leading `#`. 6943 * 6944 * Tags always have a property `tagType` which is the lower-cased version of 6945 * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance, 6946 * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized 6947 * tags are given the tag type `unknown` and a single additional property 6948 * `data` with the remainder of the input. 6949 * 6950 * @class ParseStream 6951 * @extends Stream 6952 */ 6953
6954var ParseStream = (function (_Stream) { 6955 _inherits(ParseStream, _Stream); 6956 6957 function ParseStream() { 6958 _classCallCheck(this, ParseStream); 6959 6960 _get(Object.getPrototypeOf(ParseStream.prototype), 'constructor', this).call(this); 6961 } 6962 6963 /** 6964 * Parses an additional line of input. 6965 * 6966 * @param {String} line a single line of an M3U8 file to parse 6967 */ 6968 6969 _createClass(ParseStream, [{ 6970 key: 'push', 6971 value: function push(line) { 6972 var match = undefined; 6973 var event = undefined; 6974 6975 // strip whitespace 6976 line = line.replace(/^[\u0000\s]+|[\u0000\s]+$/g, ''); 6977 if (line.length === 0) { 6978 // ignore empty lines 6979 return; 6980 } 6981 6982 // URIs 6983 if (line[0] !== '#') { 6984 this.trigger('data', { 6985 type: 'uri', 6986 uri: line 6987 }); 6988 return; 6989 } 6990 6991 // Comments 6992 if (line.indexOf('#EXT') !== 0) { 6993 this.trigger('data', { 6994 type: 'comment', 6995 text: line.slice(1) 6996 }); 6997 return; 6998 } 6999 7000 // strip off any carriage returns here so the regex matching 7001 // doesn't have to account for them. 7002 line = line.replace('\r', ''); 7003 7004 // Tags 7005 match = /^#EXTM3U/.exec(line); 7006 if (match) { 7007 this.trigger('data', { 7008 type: 'tag', 7009 tagType: 'm3u' 7010 }); 7011 return; 7012 } 7013 match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(line); 7014 if (match) { 7015 event = { 7016 type: 'tag', 7017 tagType: 'inf' 7018 }; 7019 if (match[1]) { 7020 event.duration = parseFloat(match[1]); 7021 } 7022 if (match[2]) { 7023 event.title = match[2]; 7024 } 7025 this.trigger('data', event); 7026 return; 7027 } 7028 match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(line); 7029 if (match) { 7030 event = { 7031 type: 'tag', 7032 tagType: 'targetduration' 7033 }; 7034 if (match[1]) { 7035 event.duration = parseInt(match[1], 10); 7036 } 7037 this.trigger('data', event); 7038 return; 7039 } 7040 match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(line); 7041 if (match) { 7042 event = { 7043 type: 'tag', 7044 tagType: 'totalduration' 7045 }; 7046 if (match[1]) { 7047 event.duration = parseInt(match[1], 10); 7048 } 7049 this.trigger('data', event); 7050 return; 7051 } 7052 match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(line); 7053 if (match) { 7054 event = { 7055 type: 'tag', 7056 tagType: 'version' 7057 }; 7058 if (match[1]) { 7059 event.version = parseInt(match[1], 10); 7060 } 7061 this.trigger('data', event); 7062 return; 7063 } 7064 match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(line); 7065 if (match) { 7066 event = { 7067 type: 'tag', 7068 tagType: 'media-sequence' 7069 }; 7070 if (match[1]) { 7071 event.number = parseInt(match[1], 10); 7072 } 7073 this.trigger('data', event); 7074 return; 7075 } 7076 match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(line); 7077 if (match) { 7078 event = { 7079 type: 'tag', 7080 tagType: 'discontinuity-sequence' 7081 }; 7082 if (match[1]) { 7083 event.number = parseInt(match[1], 10); 7084 } 7085 this.trigger('data', event); 7086 return; 7087 } 7088 match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(line); 7089 if (match) { 7090 event = { 7091 type: 'tag', 7092 tagType: 'playlist-type' 7093 }; 7094 if (match[1]) { 7095 event.playlistType = match[1]; 7096 } 7097 this.trigger('data', event); 7098 return; 7099 } 7100 match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(line); 7101 if (match) { 7102 event = { 7103 type: 'tag', 7104 tagType: 'byterange' 7105 }; 7106 if (match[1]) { 7107 event.length = parseInt(match[1], 10); 7108 } 7109 if (match[2]) { 7110 event.offset = parseInt(match[2], 10); 7111 } 7112 this.trigger('data', event); 7113 return; 7114 } 7115 match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(line); 7116 if (match) { 7117 event = { 7118 type: 'tag', 7119 tagType: 'allow-cache' 7120 }; 7121 if (match[1]) { 7122 event.allowed = !/NO/.test(match[1]); 7123 } 7124 this.trigger('data', event); 7125 return; 7126 } 7127 match = /^#EXT-X-MAP:?(.*)$/.exec(line); 7128 if (match) { 7129 event = { 7130 type: 'tag', 7131 tagType: 'map' 7132 }; 7133 7134 if (match[1]) { 7135 var attributes = parseAttributes(match[1]); 7136 7137 if (attributes.URI) { 7138 event.uri = attributes.URI; 7139 } 7140 if (attributes.BYTERANGE) { 7141 var _attributes$BYTERANGE$split = attributes.BYTERANGE.split('@'); 7142 7143 var _attributes$BYTERANGE$split2 = _slicedToArray(_attributes$BYTERANGE$split, 2); 7144 7145 var _length = _attributes$BYTERANGE$split2[0]; 7146 var offset = _attributes$BYTERANGE$split2[1]; 7147 7148 event.byterange = {}; 7149 if (_length) { 7150 event.byterange.length = parseInt(_length, 10); 7151 } 7152 if (offset) { 7153 event.byterange.offset = parseInt(offset, 10); 7154 } 7155 } 7156 } 7157 7158 this.trigger('data', event); 7159 return; 7160 } 7161 match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(line); 7162 if (match) { 7163 event = { 7164 type: 'tag', 7165 tagType: 'stream-inf' 7166 }; 7167 if (match[1]) { 7168 event.attributes = parseAttributes(match[1]); 7169 7170 if (event.attributes.RESOLUTION) { 7171 var split = event.attributes.RESOLUTION.split('x'); 7172 var resolution = {}; 7173 7174 if (split[0]) { 7175 resolution.width = parseInt(split[0], 10); 7176 } 7177 if (split[1]) { 7178 resolution.height = parseInt(split[1], 10); 7179 } 7180 event.attributes.RESOLUTION = resolution; 7181 } 7182 if (event.attributes.BANDWIDTH) { 7183 event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); 7184 } 7185 if (event.attributes['PROGRAM-ID']) { 7186 event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10); 7187 } 7188 } 7189 this.trigger('data', event); 7190 return; 7191 } 7192 match = /^#EXT-X-MEDIA:?(.*)$/.exec(line); 7193 if (match) { 7194 event = { 7195 type: 'tag', 7196 tagType: 'media' 7197 }; 7198 if (match[1]) { 7199 event.attributes = parseAttributes(match[1]); 7200 } 7201 this.trigger('data', event); 7202 return; 7203 } 7204 match = /^#EXT-X-ENDLIST/.exec(line); 7205 if (match) { 7206 this.trigger('data', { 7207 type: 'tag', 7208 tagType: 'endlist' 7209 }); 7210 return; 7211 } 7212 match = /^#EXT-X-DISCONTINUITY/.exec(line); 7213 if (match) { 7214 this.trigger('data', { 7215 type: 'tag', 7216 tagType: 'discontinuity' 7217 }); 7218 return; 7219 } 7220 match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(line); 7221 if (match) { 7222 event = { 7223 type: 'tag', 7224 tagType: 'program-date-time' 7225 }; 7226 if (match[1]) { 7227 event.dateTimeString = match[1]; 7228 event.dateTimeObject = new Date(match[1]); 7229 } 7230 this.trigger('data', event); 7231 return; 7232 } 7233 match = /^#EXT-X-KEY:?(.*)$/.exec(line); 7234 if (match) { 7235 event = { 7236 type: 'tag', 7237 tagType: 'key' 7238 }; 7239 if (match[1]) { 7240 event.attributes = parseAttributes(match[1]); 7241 // parse the IV string into a Uint32Array 7242 if (event.attributes.IV) { 7243 if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') { 7244 event.attributes.IV = event.attributes.IV.substring(2); 7245 } 7246 7247 event.attributes.IV = event.attributes.IV.match(/.{8}/g); 7248 event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16); 7249 event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16); 7250 event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16); 7251 event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16); 7252 event.attributes.IV = new Uint32Array(event.attributes.IV); 7253 } 7254 } 7255 this.trigger('data', event); 7256 return; 7257 } 7258 match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(line); 7259 if (match) { 7260 event = { 7261 type: 'tag', 7262 tagType: 'cue-out-cont' 7263 }; 7264 if (match[1]) { 7265 event.data = match[1]; 7266 } else { 7267 event.data = ''; 7268 } 7269 this.trigger('data', event); 7270 return; 7271 } 7272 match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(line); 7273 if (match) { 7274 event = { 7275 type: 'tag', 7276 tagType: 'cue-out' 7277 }; 7278 if (match[1]) { 7279 event.data = match[1]; 7280 } else { 7281 event.data = ''; 7282 } 7283 this.trigger('data', event); 7284 return; 7285 } 7286 match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(line); 7287 if (match) { 7288 event = { 7289 type: 'tag', 7290 tagType: 'cue-in' 7291 }; 7292 if (match[1]) { 7293 event.data = match[1]; 7294 } else { 7295 event.data = ''; 7296 } 7297 this.trigger('data', event); 7298 return; 7299 } 7300 7301 // unknown tag type 7302 this.trigger('data', { 7303 type: 'tag', 7304 data: line.slice(4) 7305 }); 7306 } 7307 }]); 7308 7309 return ParseStream; 7310})(_stream2['default']); 7311 7312exports['default'] = ParseStream; 7313module.exports = exports['default']; 7314},{"./stream":66}],65:[function(require,module,exports){ 7315/** 7316 * @file m3u8/parser.js 7317 */ 7318'use strict'; 7319 7320Object.defineProperty(exports, '__esModule', { 7321 value: true 7322}); 7323 7324var _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; }; })(); 7325 7326var _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); } } }; 7327 7328function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 7329 7330function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 7331 7332function _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
vendor: 24,235 bytes, lines 7332-8119
7332onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 7333 7334var _stream = require('./stream'); 7335 7336var _stream2 = _interopRequireDefault(_stream); 7337 7338var _lineStream = require('./line-stream'); 7339 7340var _lineStream2 = _interopRequireDefault(_lineStream); 7341 7342var _parseStream = require('./parse-stream'); 7343 7344var _parseStream2 = _interopRequireDefault(_parseStream); 7345 7346var _lodashCompatObjectMerge = require('lodash-compat/object/merge'); 7347 7348var _lodashCompatObjectMerge2 = _interopRequireDefault(_lodashCompatObjectMerge); 7349 7350/** 7351 * A parser for M3U8 files. The current interpretation of the input is 7352 * exposed as a property `manifest` on parser objects. It's just two lines to 7353 * create and parse a manifest once you have the contents available as a string: 7354 * 7355 * ```js 7356 * var parser = new m3u8.Parser(); 7357 * parser.push(xhr.responseText); 7358 * ``` 7359 * 7360 * New input can later be applied to update the manifest object by calling 7361 * `push` again. 7362 * 7363 * The parser attempts to create a usable manifest object even if the 7364 * underlying input is somewhat nonsensical. It emits `info` and `warning` 7365 * events during the parse if it encounters input that seems invalid or 7366 * requires some property of the manifest object to be defaulted. 7367 * 7368 * @class Parser 7369 * @extends Stream 7370 */ 7371 7372var Parser = (function (_Stream) { 7373 _inherits(Parser, _Stream); 7374 7375 function Parser() { 7376 _classCallCheck(this, Parser); 7377 7378 _get(Object.getPrototypeOf(Parser.prototype), 'constructor', this).call(this); 7379 this.lineStream = new _lineStream2['default'](); 7380 this.parseStream = new _parseStream2['default'](); 7381 this.lineStream.pipe(this.parseStream); 7382 /* eslint-disable consistent-this */ 7383 var self = this; 7384 /* eslint-enable consistent-this */ 7385 var uris = []; 7386 var currentUri = {}; 7387 // if specified, the active EXT-X-MAP definition 7388 var currentMap = undefined; 7389 // if specified, the active decryption key 7390 var _key = undefined; 7391 var noop = function noop() {}; 7392 var defaultMediaGroups = { 7393 'AUDIO': {}, 7394 'VIDEO': {}, 7395 'CLOSED-CAPTIONS': {}, 7396 'SUBTITLES': {} 7397 }; 7398 // group segments into numbered timelines delineated by discontinuities 7399 var currentTimeline = 0; 7400 7401 // the manifest is empty until the parse stream begins delivering data 7402 this.manifest = { 7403 allowCache: true, 7404 discontinuityStarts: [] 7405 }; 7406 7407 // update the manifest with the m3u8 entry from the parse stream 7408 this.parseStream.on('data', function (entry) { 7409 var mediaGroup = undefined; 7410 var rendition = undefined; 7411 7412 ({ 7413 tag: function tag() { 7414 // switch based on the tag type 7415 (({ 7416 'allow-cache': function allowCache() { 7417 this.manifest.allowCache = entry.allowed; 7418 if (!('allowed' in entry)) { 7419 this.trigger('info', { 7420 message: 'defaulting allowCache to YES' 7421 }); 7422 this.manifest.allowCache = true; 7423 } 7424 }, 7425 byterange: function byterange() { 7426 var byterange = {}; 7427 7428 if ('length' in entry) { 7429 currentUri.byterange = byterange; 7430 byterange.length = entry.length; 7431 7432 if (!('offset' in entry)) { 7433 this.trigger('info', { 7434 message: 'defaulting offset to zero' 7435 }); 7436 entry.offset = 0; 7437 } 7438 } 7439 if ('offset' in entry) { 7440 currentUri.byterange = byterange; 7441 byterange.offset = entry.offset; 7442 } 7443 }, 7444 endlist: function endlist() { 7445 this.manifest.endList = true; 7446 }, 7447 inf: function inf() { 7448 if (!('mediaSequence' in this.manifest)) { 7449 this.manifest.mediaSequence = 0; 7450 this.trigger('info', { 7451 message: 'defaulting media sequence to zero' 7452 }); 7453 } 7454 if (!('discontinuitySequence' in this.manifest)) { 7455 this.manifest.discontinuitySequence = 0; 7456 this.trigger('info', { 7457 message: 'defaulting discontinuity sequence to zero' 7458 }); 7459 } 7460 if (entry.duration > 0) { 7461 currentUri.duration = entry.duration; 7462 } 7463 7464 if (entry.duration === 0) { 7465 currentUri.duration = 0.01; 7466 this.trigger('info', { 7467 message: 'updating zero segment duration to a small value' 7468 }); 7469 } 7470 7471 this.manifest.segments = uris; 7472 }, 7473 key: function key() { 7474 if (!entry.attributes) { 7475 this.trigger('warn', { 7476 message: 'ignoring key declaration without attribute list' 7477 }); 7478 return; 7479 } 7480 // clear the active encryption key 7481 if (entry.attributes.METHOD === 'NONE') { 7482 _key = null; 7483 return; 7484 } 7485 if (!entry.attributes.URI) { 7486 this.trigger('warn', { 7487 message: 'ignoring key declaration without URI' 7488 }); 7489 return; 7490 } 7491 if (!entry.attributes.METHOD) { 7492 this.trigger('warn', { 7493 message: 'defaulting key method to AES-128' 7494 }); 7495 } 7496 7497 // setup an encryption key for upcoming segments 7498 _key = { 7499 method: entry.attributes.METHOD || 'AES-128', 7500 uri: entry.attributes.URI 7501 }; 7502 7503 if (typeof entry.attributes.IV !== 'undefined') { 7504 _key.iv = entry.attributes.IV; 7505 } 7506 }, 7507 'media-sequence': function mediaSequence() { 7508 if (!isFinite(entry.number)) { 7509 this.trigger('warn', { 7510 message: 'ignoring invalid media sequence: ' + entry.number 7511 }); 7512 return; 7513 } 7514 this.manifest.mediaSequence = entry.number; 7515 }, 7516 'discontinuity-sequence': function discontinuitySequence() { 7517 if (!isFinite(entry.number)) { 7518 this.trigger('warn', { 7519 message: 'ignoring invalid discontinuity sequence: ' + entry.number 7520 }); 7521 return; 7522 } 7523 this.manifest.discontinuitySequence = entry.number; 7524 currentTimeline = entry.number; 7525 }, 7526 'playlist-type': function playlistType() { 7527 if (!/VOD|EVENT/.test(entry.playlistType)) { 7528 this.trigger('warn', { 7529 message: 'ignoring unknown playlist type: ' + entry.playlist 7530 }); 7531 return; 7532 } 7533 this.manifest.playlistType = entry.playlistType; 7534 }, 7535 map: function map() { 7536 currentMap = {}; 7537 if (entry.uri) { 7538 currentMap.uri = entry.uri; 7539 } 7540 if (entry.byterange) { 7541 currentMap.byterange = entry.byterange; 7542 } 7543 }, 7544 'stream-inf': function streamInf() { 7545 this.manifest.playlists = uris; 7546 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 7547 7548 if (!entry.attributes) { 7549 this.trigger('warn', { 7550 message: 'ignoring empty stream-inf attributes' 7551 }); 7552 return; 7553 } 7554 7555 if (!currentUri.attributes) { 7556 currentUri.attributes = {}; 7557 } 7558 currentUri.attributes = (0, _lodashCompatObjectMerge2['default'])(currentUri.attributes, entry.attributes); 7559 }, 7560 media: function media() { 7561 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 7562 7563 if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) { 7564 this.trigger('warn', { 7565 message: 'ignoring incomplete or missing media group' 7566 }); 7567 return; 7568 } 7569 7570 // find the media group, creating defaults as necessary 7571 var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE]; 7572 7573 mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {}; 7574 mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; 7575 7576 // collect the rendition metadata 7577 rendition = { 7578 'default': /yes/i.test(entry.attributes.DEFAULT) 7579 }; 7580 if (rendition['default']) { 7581 rendition.autoselect = true; 7582 } else { 7583 rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT); 7584 } 7585 if (entry.attributes.LANGUAGE) { 7586 rendition.language = entry.attributes.LANGUAGE; 7587 } 7588 if (entry.attributes.URI) { 7589 rendition.uri = entry.attributes.URI; 7590 } 7591 if (entry.attributes['INSTREAM-ID']) { 7592 rendition.instreamId = entry.attributes['INSTREAM-ID']; 7593 } 7594 7595 // insert the new rendition 7596 mediaGroup[entry.attributes.NAME] = rendition; 7597 }, 7598 discontinuity: function discontinuity() { 7599 currentTimeline += 1; 7600 currentUri.discontinuity = true; 7601 this.manifest.discontinuityStarts.push(uris.length); 7602 }, 7603 'program-date-time': function programDateTime() { 7604 this.manifest.dateTimeString = entry.dateTimeString; 7605 this.manifest.dateTimeObject = entry.dateTimeObject; 7606 }, 7607 targetduration: function targetduration() { 7608 if (!isFinite(entry.duration) || entry.duration < 0) { 7609 this.trigger('warn', { 7610 message: 'ignoring invalid target duration: ' + entry.duration 7611 }); 7612 return; 7613 } 7614 this.manifest.targetDuration = entry.duration; 7615 }, 7616 totalduration: function totalduration() { 7617 if (!isFinite(entry.duration) || entry.duration < 0) { 7618 this.trigger('warn', { 7619 message: 'ignoring invalid total duration: ' + entry.duration 7620 }); 7621 return; 7622 } 7623 this.manifest.totalDuration = entry.duration; 7624 }, 7625 'cue-out': function cueOut() { 7626 currentUri.cueOut = entry.data; 7627 }, 7628 'cue-out-cont': function cueOutCont() { 7629 currentUri.cueOutCont = entry.data; 7630 }, 7631 'cue-in': function cueIn() { 7632 currentUri.cueIn = entry.data; 7633 } 7634 })[entry.tagType] || noop).call(self); 7635 }, 7636 uri: function uri() { 7637 currentUri.uri = entry.uri; 7638 uris.push(currentUri); 7639 7640 // if no explicit duration was declared, use the target duration 7641 if (this.manifest.targetDuration && !('duration' in currentUri)) { 7642 this.trigger('warn', { 7643 message: 'defaulting segment duration to the target duration' 7644 }); 7645 currentUri.duration = this.manifest.targetDuration; 7646 } 7647 // annotate with encryption information, if necessary 7648 if (_key) { 7649 currentUri.key = _key; 7650 } 7651 currentUri.timeline = currentTimeline; 7652 // annotate with initialization segment information, if necessary 7653 if (currentMap) { 7654 currentUri.map = currentMap; 7655 } 7656 7657 // prepare for the next URI 7658 currentUri = {}; 7659 }, 7660 comment: function comment() { 7661 // comments are not important for playback 7662 } 7663 })[entry.type].call(self); 7664 }); 7665 } 7666 7667 /** 7668 * Parse the input string and update the manifest object. 7669 * 7670 * @param {String} chunk a potentially incomplete portion of the manifest 7671 */ 7672 7673 _createClass(Parser, [{ 7674 key: 'push', 7675 value: function push(chunk) { 7676 this.lineStream.push(chunk); 7677 } 7678 7679 /** 7680 * Flush any remaining input. This can be handy if the last line of an M3U8 7681 * manifest did not contain a trailing newline but the file has been 7682 * completely received. 7683 */ 7684 }, { 7685 key: 'end', 7686 value: function end() { 7687 // flush any buffered input 7688 this.lineStream.push('\n'); 7689 } 7690 }]); 7691 7692 return Parser; 7693})(_stream2['default']); 7694 7695exports['default'] = Parser; 7696module.exports = exports['default']; 7697},{"./line-stream":63,"./parse-stream":64,"./stream":66,"lodash-compat/object/merge":59}],66:[function(require,module,exports){ 7698arguments[4][13][0].apply(exports,arguments) 7699},{"dup":13}],67:[function(require,module,exports){ 7700/** 7701 * mux.js 7702 * 7703 * Copyright (c) 2016 Brightcove 7704 * All rights reserved. 7705 * 7706 * A stream-based aac to mp4 converter. This utility can be used to 7707 * deliver mp4s to a SourceBuffer on platforms that support native 7708 * Media Source Extensions. 7709 */ 7710'use strict'; 7711var Stream = require('../utils/stream.js'); 7712 7713// Constants 7714var AacStream; 7715 7716/** 7717 * Splits an incoming stream of binary data into ADTS and ID3 Frames. 7718 */ 7719 7720AacStream = function() { 7721 var 7722 everything = new Uint8Array(), 7723 timeStamp = 0; 7724 7725 AacStream.prototype.init.call(this); 7726 7727 this.setTimestamp = function(timestamp) { 7728 timeStamp = timestamp; 7729 }; 7730 7731 this.parseId3TagSize = function(header, byteIndex) { 7732 var 7733 returnSize = (header[byteIndex + 6] << 21) | 7734 (header[byteIndex + 7] << 14) | 7735 (header[byteIndex + 8] << 7) | 7736 (header[byteIndex + 9]), 7737 flags = header[byteIndex + 5], 7738 footerPresent = (flags & 16) >> 4; 7739 7740 if (footerPresent) { 7741 return returnSize + 20; 7742 } 7743 return returnSize + 10; 7744 }; 7745 7746 this.parseAdtsSize = function(header, byteIndex) { 7747 var 7748 lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 7749 middle = header[byteIndex + 4] << 3, 7750 highTwo = header[byteIndex + 3] & 0x3 << 11; 7751 7752 return (highTwo | middle) | lowThree; 7753 }; 7754 7755 this.push = function(bytes) { 7756 var 7757 frameSize = 0, 7758 byteIndex = 0, 7759 bytesLeft, 7760 chunk, 7761 packet, 7762 tempLength; 7763 7764 // If there are bytes remaining from the last segment, prepend them to the 7765 // bytes that were pushed in 7766 if (everything.length) { 7767 tempLength = everything.length; 7768 everything = new Uint8Array(bytes.byteLength + tempLength); 7769 everything.set(everything.subarray(0, tempLength)); 7770 everything.set(bytes, tempLength); 7771 } else { 7772 everything = bytes; 7773 } 7774 7775 while (everything.length - byteIndex >= 3) { 7776 if ((everything[byteIndex] === 'I'.charCodeAt(0)) && 7777 (everything[byteIndex + 1] === 'D'.charCodeAt(0)) && 7778 (everything[byteIndex + 2] === '3'.charCodeAt(0))) { 7779 7780 // Exit early because we don't have enough to parse 7781 // the ID3 tag header 7782 if (everything.length - byteIndex < 10) { 7783 break; 7784 } 7785 7786 // check framesize 7787 frameSize = this.parseId3TagSize(everything, byteIndex); 7788 7789 // Exit early if we don't have enough in the buffer 7790 // to emit a full packet 7791 if (frameSize > everything.length) { 7792 break; 7793 } 7794 chunk = { 7795 type: 'timed-metadata', 7796 data: everything.subarray(byteIndex, byteIndex + frameSize) 7797 }; 7798 this.trigger('data', chunk); 7799 byteIndex += frameSize; 7800 continue; 7801 } else if ((everything[byteIndex] & 0xff === 0xff) && 7802 ((everything[byteIndex + 1] & 0xf0) === 0xf0)) { 7803 7804 // Exit early because we don't have enough to parse 7805 // the ADTS frame header 7806 if (everything.length - byteIndex < 7) { 7807 break; 7808 } 7809 7810 frameSize = this.parseAdtsSize(everything, byteIndex); 7811 7812 // Exit early if we don't have enough in the buffer 7813 // to emit a full packet 7814 if (frameSize > everything.length) { 7815 break; 7816 } 7817 7818 packet = { 7819 type: 'audio', 7820 data: everything.subarray(byteIndex, byteIndex + frameSize), 7821 pts: timeStamp, 7822 dts: timeStamp 7823 }; 7824 this.trigger('data', packet); 7825 byteIndex += frameSize; 7826 continue; 7827 } 7828 byteIndex++; 7829 } 7830 bytesLeft = everything.length - byteIndex; 7831 7832 if (bytesLeft > 0) { 7833 everything = everything.subarray(byteIndex); 7834 } else { 7835 everything = new Uint8Array(); 7836 } 7837 }; 7838}; 7839 7840AacStream.prototype = new Stream(); 7841 7842module.exports = AacStream; 7843 7844},{"../utils/stream.js":83}],68:[function(require,module,exports){ 7845'use strict'; 7846 7847var Stream = require('../utils/stream.js'); 7848 7849var AdtsStream; 7850 7851var 7852 ADTS_SAMPLING_FREQUENCIES = [ 7853 96000, 7854 88200, 7855 64000, 7856 48000, 7857 44100, 7858 32000, 7859 24000, 7860 22050, 7861 16000, 7862 12000, 7863 11025, 7864 8000, 7865 7350 7866 ]; 7867 7868/* 7869 * Accepts a ElementaryStream and emits data events with parsed 7870 * AAC Audio Frames of the individual packets. Input audio in ADTS 7871 * format is unpacked and re-emitted as AAC frames. 7872 * 7873 * @see http://wiki.multimedia.cx/index.php?title=ADTS 7874 * @see http://wiki.multimedia.cx/?title=Understanding_AAC 7875 */ 7876AdtsStream = function() { 7877 var buffer; 7878 7879 AdtsStream.prototype.init.call(this); 7880 7881 this.push = function(packet) { 7882 var 7883 i = 0, 7884 frameNum = 0, 7885 frameLength, 7886 protectionSkipBytes, 7887 frameEnd, 7888 oldBuffer, 7889 sampleCount, 7890 adtsFrameDuration; 7891 7892 if (packet.type !== 'audio') { 7893 // ignore non-audio data 7894 return; 7895 } 7896 7897 // Prepend any data in the buffer to the input data so that we can parse 7898 // aac frames the cross a PES packet boundary 7899 if (buffer) { 7900 oldBuffer = buffer; 7901 buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength); 7902 buffer.set(oldBuffer); 7903 buffer.set(packet.data, oldBuffer.byteLength); 7904 } else { 7905 buffer = packet.data; 7906 } 7907 7908 // unpack any ADTS frames which have been fully received 7909 // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS 7910 while (i + 5 < buffer.length) { 7911 7912 // Loook for the start of an ADTS header.. 7913 if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) { 7914 // If a valid header was not found, jump one forward and attempt to 7915 // find a valid ADTS header starting at the next byte 7916 i++; 7917 continue; 7918 } 7919 7920 // The protection skip bit tells us if we have 2 bytes of CRC data at the 7921 // end of the ADTS header 7922 protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; 7923 7924 // Frame length is a 13 bit integer starting 16 bits from the 7925 // end of the sync sequence 7926 frameLength = ((buffer[i + 3] & 0x03) << 11) | 7927 (buffer[i + 4] << 3) | 7928 ((buffer[i + 5] & 0xe0) >> 5); 7929 7930 sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024; 7931 adtsFrameDuration = (sampleCount * 90000) / 7932 ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2]; 7933 7934 frameEnd = i + frameLength; 7935 7936 // If we don't have enough data to actually finish this ADTS frame, return 7937 // and wait for more data 7938 if (buffer.byteLength < frameEnd) { 7939 return; 7940 } 7941 7942 // Otherwise, deliver the complete AAC frame 7943 this.trigger('data', { 7944 pts: packet.pts + (frameNum * adtsFrameDuration), 7945 dts: packet.dts + (frameNum * adtsFrameDuration), 7946 sampleCount: sampleCount, 7947 audioobjecttype: ((buffer[i + 2] >>> 6) & 0x03) + 1, 7948 channelcount: ((buffer[i + 2] & 1) << 2) | 7949 ((buffer[i + 3] & 0xc0) >>> 6), 7950 samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2], 7951 samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2, 7952 // assume ISO/IEC 14496-12 AudioSampleEntry default of 16 7953 samplesize: 16, 7954 data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd) 7955 }); 7956 7957 // If the buffer is empty, clear it and return 7958 if (buffer.byteLength === frameEnd) { 7959 buffer = undefined; 7960 return; 7961 } 7962 7963 frameNum++; 7964 7965 // Remove the finished frame from the buffer and start the process again 7966 buffer = buffer.subarray(frameEnd); 7967 } 7968 }; 7969 this.flush = function() { 7970 this.trigger('done'); 7971 }; 7972}; 7973 7974AdtsStream.prototype = new Stream(); 7975 7976module.exports = AdtsStream; 7977 7978},{"../utils/stream.js":83}],69:[function(require,module,exports){ 7979'use strict'; 7980 7981var Stream = require('../utils/stream.js'); 7982var ExpGolomb = require('../utils/exp-golomb.js'); 7983 7984var H264Stream, NalByteStream; 7985var PROFILES_WITH_OPTIONAL_SPS_DATA; 7986 7987/** 7988 * Accepts a NAL unit byte stream and unpacks the embedded NAL units. 7989 */ 7990NalByteStream = function() { 7991 var 7992 syncPoint = 0, 7993 i, 7994 buffer; 7995 NalByteStream.prototype.init.call(this); 7996 7997 this.push = function(data) { 7998 var swapBuffer; 7999 8000 if (!buffer) { 8001 buffer = data.data; 8002 } else { 8003 swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength); 8004 swapBuffer.set(buffer); 8005 swapBuffer.set(data.data, buffer.byteLength); 8006 buffer = swapBuffer; 8007 } 8008 8009 // Rec. ITU-T H.264, Annex B 8010 // scan for NAL unit boundaries 8011 8012 // a match looks like this: 8013 // 0 0 1 .. NAL .. 0 0 1 8014 // ^ sync point ^ i 8015 // or this: 8016 // 0 0 1 .. NAL .. 0 0 0 8017 // ^ sync point ^ i 8018 8019 // advance the sync point to a NAL start, if necessary 8020 for (; syncPoint < buffer.byteLength - 3; syncPoint++) { 8021 if (buffer[syncPoint + 2] === 1) { 8022 // the sync point is properly aligned 8023 i = syncPoint + 5; 8024 break; 8025 } 8026 } 8027 8028 while (i < buffer.byteLength) { 8029 // look at the current byte to determine if we've hit the end of 8030 // a NAL unit boundary 8031 switch (buffer[i]) { 8032 case 0: 8033 // skip past non-sync sequences 8034 if (buffer[i - 1] !== 0) { 8035 i += 2; 8036 break; 8037 } else if (buffer[i - 2] !== 0) { 8038 i++; 8039 break; 8040 } 8041 8042 // deliver the NAL unit if it isn't empty 8043 if (syncPoint + 3 !== i - 2) { 8044 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 8045 } 8046 8047 // drop trailing zeroes 8048 do { 8049 i++; 8050 } while (buffer[i] !== 1 && i < buffer.length); 8051 syncPoint = i - 2; 8052 i += 3; 8053 break; 8054 case 1: 8055 // skip past non-sync sequences 8056 if (buffer[i - 1] !== 0 || 8057 buffer[i - 2] !== 0) { 8058 i += 3; 8059 break; 8060 } 8061 8062 // deliver the NAL unit 8063 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 8064 syncPoint = i - 2; 8065 i += 3; 8066 break; 8067 default: 8068 // the current byte isn't a one or zero, so it cannot be part 8069 // of a sync sequence 8070 i += 3; 8071 break; 8072 } 8073 } 8074 // filter out the NAL units that were delivered 8075 buffer = buffer.subarray(syncPoint); 8076 i -= syncPoint; 8077 syncPoint = 0; 8078 }; 8079 8080 this.flush = function() { 8081 // deliver the last buffered NAL unit 8082 if (buffer && buffer.byteLength > 3) { 8083 this.trigger('data', buffer.subarray(syncPoint + 3)); 8084 } 8085 // reset the stream state 8086 buffer = null; 8087 syncPoint = 0; 8088 this.trigger('done'); 8089 }; 8090}; 8091NalByteStream.prototype = new Stream(); 8092 8093// values of profile_idc that indicate additional fields are included in the SPS 8094// see Recommendation ITU-T H.264 (4/2013), 8095// 7.3.2.1.1 Sequence parameter set data syntax 8096PROFILES_WITH_OPTIONAL_SPS_DATA = { 8097 100: true, 8098 110: true, 8099 122: true, 8100 244: true, 8101 44: true, 8102 83: true, 8103 86: true, 8104 118: true, 8105 128: true, 8106 138: true, 8107 139: true, 8108 134: true 8109}; 8110 8111/** 8112 * Accepts input from a ElementaryStream and produces H.264 NAL unit data 8113 * events. 8114 */ 8115H264Stream = function() { 8116 var 8117 nalByteStream = new NalByteStream(), 8118 self, 8119 trackId,
vendor: 16,380 bytes, lines 8120-8633
8120 currentPts, 8121 currentDts, 8122 8123 discardEmulationPreventionBytes, 8124 readSequenceParameterSet, 8125 skipScalingList; 8126 8127 H264Stream.prototype.init.call(this); 8128 self = this; 8129 8130 this.push = function(packet) { 8131 if (packet.type !== 'video') { 8132 return; 8133 } 8134 trackId = packet.trackId; 8135 currentPts = packet.pts; 8136 currentDts = packet.dts; 8137 8138 nalByteStream.push(packet); 8139 }; 8140 8141 nalByteStream.on('data', function(data) { 8142 var 8143 event = { 8144 trackId: trackId, 8145 pts: currentPts, 8146 dts: currentDts, 8147 data: data 8148 }; 8149 8150 switch (data[0] & 0x1f) { 8151 case 0x05: 8152 event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr'; 8153 break; 8154 case 0x06: 8155 event.nalUnitType = 'sei_rbsp'; 8156 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 8157 break; 8158 case 0x07: 8159 event.nalUnitType = 'seq_parameter_set_rbsp'; 8160 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 8161 event.config = readSequenceParameterSet(event.escapedRBSP); 8162 break; 8163 case 0x08: 8164 event.nalUnitType = 'pic_parameter_set_rbsp'; 8165 break; 8166 case 0x09: 8167 event.nalUnitType = 'access_unit_delimiter_rbsp'; 8168 break; 8169 8170 default: 8171 break; 8172 } 8173 self.trigger('data', event); 8174 }); 8175 nalByteStream.on('done', function() { 8176 self.trigger('done'); 8177 }); 8178 8179 this.flush = function() { 8180 nalByteStream.flush(); 8181 }; 8182 8183 /** 8184 * Advance the ExpGolomb decoder past a scaling list. The scaling 8185 * list is optionally transmitted as part of a sequence parameter 8186 * set and is not relevant to transmuxing. 8187 * @param count {number} the number of entries in this scaling list 8188 * @param expGolombDecoder {object} an ExpGolomb pointed to the 8189 * start of a scaling list 8190 * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 8191 */ 8192 skipScalingList = function(count, expGolombDecoder) { 8193 var 8194 lastScale = 8, 8195 nextScale = 8, 8196 j, 8197 deltaScale; 8198 8199 for (j = 0; j < count; j++) { 8200 if (nextScale !== 0) { 8201 deltaScale = expGolombDecoder.readExpGolomb(); 8202 nextScale = (lastScale + deltaScale + 256) % 256; 8203 } 8204 8205 lastScale = (nextScale === 0) ? lastScale : nextScale; 8206 } 8207 }; 8208 8209 /** 8210 * Expunge any "Emulation Prevention" bytes from a "Raw Byte 8211 * Sequence Payload" 8212 * @param data {Uint8Array} the bytes of a RBSP from a NAL 8213 * unit 8214 * @return {Uint8Array} the RBSP without any Emulation 8215 * Prevention Bytes 8216 */ 8217 discardEmulationPreventionBytes = function(data) { 8218 var 8219 length = data.byteLength, 8220 emulationPreventionBytesPositions = [], 8221 i = 1, 8222 newLength, newData; 8223 8224 // Find all `Emulation Prevention Bytes` 8225 while (i < length - 2) { 8226 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 8227 emulationPreventionBytesPositions.push(i + 2); 8228 i += 2; 8229 } else { 8230 i++; 8231 } 8232 } 8233 8234 // If no Emulation Prevention Bytes were found just return the original 8235 // array 8236 if (emulationPreventionBytesPositions.length === 0) { 8237 return data; 8238 } 8239 8240 // Create a new array to hold the NAL unit data 8241 newLength = length - emulationPreventionBytesPositions.length; 8242 newData = new Uint8Array(newLength); 8243 var sourceIndex = 0; 8244 8245 for (i = 0; i < newLength; sourceIndex++, i++) { 8246 if (sourceIndex === emulationPreventionBytesPositions[0]) { 8247 // Skip this byte 8248 sourceIndex++; 8249 // Remove this position index 8250 emulationPreventionBytesPositions.shift(); 8251 } 8252 newData[i] = data[sourceIndex]; 8253 } 8254 8255 return newData; 8256 }; 8257 8258 /** 8259 * Read a sequence parameter set and return some interesting video 8260 * properties. A sequence parameter set is the H264 metadata that 8261 * describes the properties of upcoming video frames. 8262 * @param data {Uint8Array} the bytes of a sequence parameter set 8263 * @return {object} an object with configuration parsed from the 8264 * sequence parameter set, including the dimensions of the 8265 * associated video frames. 8266 */ 8267 readSequenceParameterSet = function(data) { 8268 var 8269 frameCropLeftOffset = 0, 8270 frameCropRightOffset = 0, 8271 frameCropTopOffset = 0, 8272 frameCropBottomOffset = 0, 8273 sarScale = 1, 8274 expGolombDecoder, profileIdc, levelIdc, profileCompatibility, 8275 chromaFormatIdc, picOrderCntType, 8276 numRefFramesInPicOrderCntCycle, picWidthInMbsMinus1, 8277 picHeightInMapUnitsMinus1, 8278 frameMbsOnlyFlag, 8279 scalingListCount, 8280 sarRatio, 8281 aspectRatioIdc, 8282 i; 8283 8284 expGolombDecoder = new ExpGolomb(data); 8285 profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc 8286 profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag 8287 levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8) 8288 expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id 8289 8290 // some profiles have more optional data we don't need 8291 if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) { 8292 chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb(); 8293 if (chromaFormatIdc === 3) { 8294 expGolombDecoder.skipBits(1); // separate_colour_plane_flag 8295 } 8296 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8 8297 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8 8298 expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag 8299 if (expGolombDecoder.readBoolean()) { // seq_scaling_matrix_present_flag 8300 scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12; 8301 for (i = 0; i < scalingListCount; i++) { 8302 if (expGolombDecoder.readBoolean()) { // seq_scaling_list_present_flag[ i ] 8303 if (i < 6) { 8304 skipScalingList(16, expGolombDecoder); 8305 } else { 8306 skipScalingList(64, expGolombDecoder); 8307 } 8308 } 8309 } 8310 } 8311 } 8312 8313 expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4 8314 picOrderCntType = expGolombDecoder.readUnsignedExpGolomb(); 8315 8316 if (picOrderCntType === 0) { 8317 expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4 8318 } else if (picOrderCntType === 1) { 8319 expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag 8320 expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic 8321 expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field 8322 numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb(); 8323 for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) { 8324 expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ] 8325 } 8326 } 8327 8328 expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames 8329 expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag 8330 8331 picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 8332 picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 8333 8334 frameMbsOnlyFlag = expGolombDecoder.readBits(1); 8335 if (frameMbsOnlyFlag === 0) { 8336 expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag 8337 } 8338 8339 expGolombDecoder.skipBits(1); // direct_8x8_inference_flag 8340 if (expGolombDecoder.readBoolean()) { // frame_cropping_flag 8341 frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb(); 8342 frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb(); 8343 frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb(); 8344 frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb(); 8345 } 8346 if (expGolombDecoder.readBoolean()) { 8347 // vui_parameters_present_flag 8348 if (expGolombDecoder.readBoolean()) { 8349 // aspect_ratio_info_present_flag 8350 aspectRatioIdc = expGolombDecoder.readUnsignedByte(); 8351 switch (aspectRatioIdc) { 8352 case 1: sarRatio = [1, 1]; break; 8353 case 2: sarRatio = [12, 11]; break; 8354 case 3: sarRatio = [10, 11]; break; 8355 case 4: sarRatio = [16, 11]; break; 8356 case 5: sarRatio = [40, 33]; break; 8357 case 6: sarRatio = [24, 11]; break; 8358 case 7: sarRatio = [20, 11]; break; 8359 case 8: sarRatio = [32, 11]; break; 8360 case 9: sarRatio = [80, 33]; break; 8361 case 10: sarRatio = [18, 11]; break; 8362 case 11: sarRatio = [15, 11]; break; 8363 case 12: sarRatio = [64, 33]; break; 8364 case 13: sarRatio = [160, 99]; break; 8365 case 14: sarRatio = [4, 3]; break; 8366 case 15: sarRatio = [3, 2]; break; 8367 case 16: sarRatio = [2, 1]; break; 8368 case 255: { 8369 sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | 8370 expGolombDecoder.readUnsignedByte(), 8371 expGolombDecoder.readUnsignedByte() << 8 | 8372 expGolombDecoder.readUnsignedByte() ]; 8373 break; 8374 } 8375 } 8376 if (sarRatio) { 8377 sarScale = sarRatio[0] / sarRatio[1]; 8378 } 8379 } 8380 } 8381 return { 8382 profileIdc: profileIdc, 8383 levelIdc: levelIdc, 8384 profileCompatibility: profileCompatibility, 8385 width: Math.ceil((((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale), 8386 height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - (frameCropTopOffset * 2) - (frameCropBottomOffset * 2) 8387 }; 8388 }; 8389 8390}; 8391H264Stream.prototype = new Stream(); 8392 8393module.exports = { 8394 H264Stream: H264Stream, 8395 NalByteStream: NalByteStream 8396}; 8397 8398},{"../utils/exp-golomb.js":82,"../utils/stream.js":83}],70:[function(require,module,exports){ 8399/** 8400 * An object that stores the bytes of an FLV tag and methods for 8401 * querying and manipulating that data. 8402 * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf 8403 */ 8404'use strict'; 8405 8406var FlvTag; 8407 8408// (type:uint, extraData:Boolean = false) extends ByteArray 8409FlvTag = function(type, extraData) { 8410 var 8411 // Counter if this is a metadata tag, nal start marker if this is a video 8412 // tag. unused if this is an audio tag 8413 adHoc = 0, // :uint 8414 8415 // The default size is 16kb but this is not enough to hold iframe 8416 // data and the resizing algorithm costs a bit so we create a larger 8417 // starting buffer for video tags 8418 bufferStartSize = 16384, 8419 8420 // checks whether the FLV tag has enough capacity to accept the proposed 8421 // write and re-allocates the internal buffers if necessary 8422 prepareWrite = function(flv, count) { 8423 var 8424 bytes, 8425 minLength = flv.position + count; 8426 if (minLength < flv.bytes.byteLength) { 8427 // there's enough capacity so do nothing 8428 return; 8429 } 8430 8431 // allocate a new buffer and copy over the data that will not be modified 8432 bytes = new Uint8Array(minLength * 2); 8433 bytes.set(flv.bytes.subarray(0, flv.position), 0); 8434 flv.bytes = bytes; 8435 flv.view = new DataView(flv.bytes.buffer); 8436 }, 8437 8438 // commonly used metadata properties 8439 widthBytes = FlvTag.widthBytes || new Uint8Array('width'.length), 8440 heightBytes = FlvTag.heightBytes || new Uint8Array('height'.length), 8441 videocodecidBytes = FlvTag.videocodecidBytes || new Uint8Array('videocodecid'.length), 8442 i; 8443 8444 if (!FlvTag.widthBytes) { 8445 // calculating the bytes of common metadata names ahead of time makes the 8446 // corresponding writes faster because we don't have to loop over the 8447 // characters 8448 // re-test with test/perf.html if you're planning on changing this 8449 for (i = 0; i < 'width'.length; i++) { 8450 widthBytes[i] = 'width'.charCodeAt(i); 8451 } 8452 for (i = 0; i < 'height'.length; i++) { 8453 heightBytes[i] = 'height'.charCodeAt(i); 8454 } 8455 for (i = 0; i < 'videocodecid'.length; i++) { 8456 videocodecidBytes[i] = 'videocodecid'.charCodeAt(i); 8457 } 8458 8459 FlvTag.widthBytes = widthBytes; 8460 FlvTag.heightBytes = heightBytes; 8461 FlvTag.videocodecidBytes = videocodecidBytes; 8462 } 8463 8464 this.keyFrame = false; // :Boolean 8465 8466 switch (type) { 8467 case FlvTag.VIDEO_TAG: 8468 this.length = 16; 8469 // Start the buffer at 256k 8470 bufferStartSize *= 6; 8471 break; 8472 case FlvTag.AUDIO_TAG: 8473 this.length = 13; 8474 this.keyFrame = true; 8475 break; 8476 case FlvTag.METADATA_TAG: 8477 this.length = 29; 8478 this.keyFrame = true; 8479 break; 8480 default: 8481 throw new Error('Unknown FLV tag type'); 8482 } 8483 8484 this.bytes = new Uint8Array(bufferStartSize); 8485 this.view = new DataView(this.bytes.buffer); 8486 this.bytes[0] = type; 8487 this.position = this.length; 8488 this.keyFrame = extraData; // Defaults to false 8489 8490 // presentation timestamp 8491 this.pts = 0; 8492 // decoder timestamp 8493 this.dts = 0; 8494 8495 // ByteArray#writeBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0) 8496 this.writeBytes = function(bytes, offset, length) { 8497 var 8498 start = offset || 0, 8499 end; 8500 length = length || bytes.byteLength; 8501 end = start + length; 8502 8503 prepareWrite(this, length); 8504 this.bytes.set(bytes.subarray(start, end), this.position); 8505 8506 this.position += length; 8507 this.length = Math.max(this.length, this.position); 8508 }; 8509 8510 // ByteArray#writeByte(value:int):void 8511 this.writeByte = function(byte) { 8512 prepareWrite(this, 1); 8513 this.bytes[this.position] = byte; 8514 this.position++; 8515 this.length = Math.max(this.length, this.position); 8516 }; 8517 8518 // ByteArray#writeShort(value:int):void 8519 this.writeShort = function(short) { 8520 prepareWrite(this, 2); 8521 this.view.setUint16(this.position, short); 8522 this.position += 2; 8523 this.length = Math.max(this.length, this.position); 8524 }; 8525 8526 // Negative index into array 8527 // (pos:uint):int 8528 this.negIndex = function(pos) { 8529 return this.bytes[this.length - pos]; 8530 }; 8531 8532 // The functions below ONLY work when this[0] == VIDEO_TAG. 8533 // We are not going to check for that because we dont want the overhead 8534 // (nal:ByteArray = null):int 8535 this.nalUnitSize = function() { 8536 if (adHoc === 0) { 8537 return 0; 8538 } 8539 8540 return this.length - (adHoc + 4); 8541 }; 8542 8543 this.startNalUnit = function() { 8544 // remember position and add 4 bytes 8545 if (adHoc > 0) { 8546 throw new Error('Attempted to create new NAL wihout closing the old one'); 8547 } 8548 8549 // reserve 4 bytes for nal unit size 8550 adHoc = this.length; 8551 this.length += 4; 8552 this.position = this.length; 8553 }; 8554 8555 // (nal:ByteArray = null):void 8556 this.endNalUnit = function(nalContainer) { 8557 var 8558 nalStart, // :uint 8559 nalLength; // :uint 8560 8561 // Rewind to the marker and write the size 8562 if (this.length === adHoc + 4) { 8563 // we started a nal unit, but didnt write one, so roll back the 4 byte size value 8564 this.length -= 4; 8565 } else if (adHoc > 0) { 8566 nalStart = adHoc + 4; 8567 nalLength = this.length - nalStart; 8568 8569 this.position = adHoc; 8570 this.view.setUint32(this.position, nalLength); 8571 this.position = this.length; 8572 8573 if (nalContainer) { 8574 // Add the tag to the NAL unit 8575 nalContainer.push(this.bytes.subarray(nalStart, nalStart + nalLength)); 8576 } 8577 } 8578 8579 adHoc = 0; 8580 }; 8581 8582 /** 8583 * Write out a 64-bit floating point valued metadata property. This method is 8584 * called frequently during a typical parse and needs to be fast. 8585 */ 8586 // (key:String, val:Number):void 8587 this.writeMetaDataDouble = function(key, val) { 8588 var i; 8589 prepareWrite(this, 2 + key.length + 9); 8590 8591 // write size of property name 8592 this.view.setUint16(this.position, key.length); 8593 this.position += 2; 8594 8595 // this next part looks terrible but it improves parser throughput by 8596 // 10kB/s in my testing 8597 8598 // write property name 8599 if (key === 'width') { 8600 this.bytes.set(widthBytes, this.position); 8601 this.position += 5; 8602 } else if (key === 'height') { 8603 this.bytes.set(heightBytes, this.position); 8604 this.position += 6; 8605 } else if (key === 'videocodecid') { 8606 this.bytes.set(videocodecidBytes, this.position); 8607 this.position += 12; 8608 } else { 8609 for (i = 0; i < key.length; i++) { 8610 this.bytes[this.position] = key.charCodeAt(i); 8611 this.position++; 8612 } 8613 } 8614 8615 // skip null byte 8616 this.position++; 8617 8618 // write property value 8619 this.view.setFloat64(this.position, val); 8620 this.position += 8; 8621 8622 // update flv tag length 8623 this.length = Math.max(this.length, this.position); 8624 ++adHoc; 8625 }; 8626 8627 // (key:String, val:Boolean):void 8628 this.writeMetaDataBoolean = function(key, val) { 8629 var i; 8630 prepareWrite(this, 2); 8631 this.view.setUint16(this.position, key.length); 8632 this.position += 2; 8633 for (i = 0; i < key.length; i++) {
8634 // if key.charCodeAt(i) >= 255, handle error 8635 prepareWrite(this, 1); 8636 this.bytes[this.position] = key.charCodeAt(i); 8637 this.position++; 8638 } 8639 prepareWrite(this, 2); 8640 this.view.setUint8(this.position, 0x01); 8641 this.position++; 8642 this.view.setUint8(this.position, val ? 0x01 : 0x00); 8643 this.position++; 8644 this.length = Math.max(this.length, this.position); 8645 ++adHoc; 8646 }; 8647 8648 // ():ByteArray 8649 this.finalize = function() { 8650 var 8651 dtsDelta, // :int 8652 len; // :int 8653 8654 switch (this.bytes[0]) { 8655 // Video Data 8656 case FlvTag.VIDEO_TAG: 8657 // We only support AVC, 1 = key frame (for AVC, a seekable 8658 // frame), 2 = inter frame (for AVC, a non-seekable frame) 8659 this.bytes[11] = ((this.keyFrame || extraData) ? 0x10 : 0x20) | 0x07; 8660 this.bytes[12] = extraData ? 0x00 : 0x01; 8661 8662 dtsDelta = this.pts - this.dts; 8663 this.bytes[13] = (dtsDelta & 0x00FF0000) >>> 16; 8664 this.bytes[14] = (dtsDelta & 0x0000FF00) >>> 8; 8665 this.bytes[15] = (dtsDelta & 0x000000FF) >>> 0; 8666 break; 8667 8668 case FlvTag.AUDIO_TAG: 8669 this.bytes[11] = 0xAF; // 44 kHz, 16-bit stereo 8670 this.bytes[12] = extraData ? 0x00 : 0x01; 8671 break; 8672 8673 case FlvTag.METADATA_TAG: 8674 this.position = 11; 8675 this.view.setUint8(this.position, 0x02); // String type 8676 this.position++; 8677 this.view.setUint16(this.position, 0x0A); // 10 Bytes 8678 this.position += 2; 8679 // set "onMetaData" 8680 this.bytes.set([0x6f, 0x6e, 0x4d, 0x65, 8681 0x74, 0x61, 0x44, 0x61, 8682 0x74, 0x61], this.position); 8683 this.position += 10; 8684 this.bytes[this.position] = 0x08; // Array type 8685 this.position++; 8686 this.view.setUint32(this.position, adHoc); 8687 this.position = this.length; 8688 this.bytes.set([0, 0, 9], this.position); 8689 this.position += 3; // End Data Tag 8690 this.length = this.position; 8691 break; 8692 } 8693 8694 len = this.length - 11; 8695 8696 // write the DataSize field 8697 this.bytes[ 1] = (len & 0x00FF0000) >>> 16; 8698 this.bytes[ 2] = (len & 0x0000FF00) >>> 8; 8699 this.bytes[ 3] = (len & 0x000000FF) >>> 0; 8700 // write the Timestamp 8701 this.bytes[ 4] = (this.dts & 0x00FF0000) >>> 16; 8702 this.bytes[ 5] = (this.dts & 0x0000FF00) >>> 8; 8703 this.bytes[ 6] = (this.dts & 0x000000FF) >>> 0; 8704 this.bytes[ 7] = (this.dts & 0xFF000000) >>> 24; 8705 // write the StreamID 8706 this.bytes[ 8] = 0; 8707 this.bytes[ 9] = 0; 8708 this.bytes[10] = 0; 8709 8710 // Sometimes we're at the end of the view and have one slot to write a 8711 // uint32, so, prepareWrite of count 4, since, view is uint8 8712 prepareWrite(this, 4); 8713 this.view.setUint32(this.length, this.length); 8714 this.length += 4; 8715 this.position += 4; 8716 8717 // trim down the byte buffer to what is actually being used 8718 this.bytes = this.bytes.subarray(0, this.length); 8719 this.frameTime = FlvTag.frameTime(this.bytes); 8720 // if bytes.bytelength isn't equal to this.length, handle error 8721 return this; 8722 }; 8723}; 8724 8725FlvTag.AUDIO_TAG = 0x08; // == 8, :uint 8726FlvTag.VIDEO_TAG = 0x09; // == 9, :uint 8727FlvTag.METADATA_TAG = 0x12; // == 18, :uint 8728 8729// (tag:ByteArray):Boolean { 8730FlvTag.isAudioFrame = function(tag) { 8731 return FlvTag.AUDIO_TAG === tag[0]; 8732}; 8733 8734// (tag:ByteArray):Boolean { 8735FlvTag.isVideoFrame = function(tag) { 8736 return FlvTag.VIDEO_TAG === tag[0]; 8737}; 8738 8739// (tag:ByteArray):Boolean { 8740FlvTag.isMetaData = function(tag) { 8741 return FlvTag.METADATA_TAG === tag[0]; 8742}; 8743 8744// (tag:ByteArray):Boolean { 8745FlvTag.isKeyFrame = function(tag) { 8746 if (FlvTag.isVideoFrame(tag)) { 8747 return tag[11] === 0x17; 8748 } 8749 8750 if (FlvTag.isAudioFrame(tag)) { 8751 return true; 8752 } 8753 8754 if (FlvTag.isMetaData(tag)) { 8755 return true; 8756 } 8757 8758 return false; 8759}; 8760 8761// (tag:ByteArray):uint { 8762FlvTag.frameTime = function(tag) { 8763 var pts = tag[ 4] << 16; // :uint 8764 pts |= tag[ 5] << 8; 8765 pts |= tag[ 6] << 0; 8766 pts |= tag[ 7] << 24; 8767 return pts; 8768}; 8769 8770module.exports = FlvTag; 8771 8772},{}],71:[function(require,module,exports){ 8773module.exports = { 8774 tag: require('./flv-tag'), 8775 Transmuxer: require('./transmuxer') 8776}; 8777 8778},{"./flv-tag":70,"./transmuxer":72}],72:[function(require,module,exports){ 8779'use strict'; 8780 8781var Stream = require('../utils/stream.js'); 8782var FlvTag = require('./flv-tag.js'); 8783var m2ts = require('../m2ts/m2ts.js'); 8784var AdtsStream = require('../codecs/adts.js'); 8785var H264Stream = require('../codecs/h264').H264Stream; 8786 8787var 8788 Transmuxer, 8789 VideoSegmentStream, 8790 AudioSegmentStream, 8791 CoalesceStream, 8792 collectTimelineInfo, 8793 metaDataTag, 8794 extraDataTag; 8795 8796/** 8797 * Store information about the start and end of the tracka and the 8798 * duration for each frame/sample we process in order to calculate 8799 * the baseMediaDecodeTime 8800 */ 8801collectTimelineInfo = function(track, data) { 8802 if (typeof data.pts === 'number') { 8803 if (track.timelineStartInfo.pts === undefined) { 8804 track.timelineStartInfo.pts = data.pts; 8805 } else { 8806 track.timelineStartInfo.pts = 8807 Math.min(track.timelineStartInfo.pts, data.pts); 8808 } 8809 } 8810 8811 if (typeof data.dts === 'number') { 8812 if (track.timelineStartInfo.dts === undefined) { 8813 track.timelineStartInfo.dts = data.dts; 8814 } else { 8815 track.timelineStartInfo.dts = 8816 Math.min(track.timelineStartInfo.dts, data.dts); 8817 } 8818 } 8819}; 8820 8821metaDataTag = function(track, pts) { 8822 var 8823 tag = new FlvTag(FlvTag.METADATA_TAG); // :FlvTag 8824 8825 tag.dts = pts; 8826 tag.pts = pts; 8827 8828 tag.writeMetaDataDouble('videocodecid', 7); 8829 tag.writeMetaDataDouble('width', track.width); 8830 tag.writeMetaDataDouble('height', track.height); 8831 8832 return tag; 8833}; 8834 8835extraDataTag = function(track, pts) { 8836 var 8837 i, 8838 tag = new FlvTag(FlvTag.VIDEO_TAG, true); 8839 8840 tag.dts = pts; 8841 tag.pts = pts; 8842 8843 tag.writeByte(0x01);// version 8844 tag.writeByte(track.profileIdc);// profile 8845 tag.writeByte(track.profileCompatibility);// compatibility 8846 tag.writeByte(track.levelIdc);// level 8847 tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits) 8848 tag.writeByte(0xE0 | 0x01); // reserved (3 bits), num of SPS (5 bits) 8849 tag.writeShort(track.sps[0].length); // data of SPS 8850 tag.writeBytes(track.sps[0]); // SPS 8851 8852 tag.writeByte(track.pps.length); // num of PPS (will there ever be more that 1 PPS?) 8853 for (i = 0; i < track.pps.length; ++i) { 8854 tag.writeShort(track.pps[i].length); // 2 bytes for length of PPS 8855 tag.writeBytes(track.pps[i]); // data of PPS 8856 } 8857 8858 return tag; 8859}; 8860 8861/** 8862 * Constructs a single-track, media segment from AAC data 8863 * events. The output of this stream can be fed to flash. 8864 */ 8865AudioSegmentStream = function(track) { 8866 var 8867 adtsFrames = [], 8868 oldExtraData; 8869 8870 AudioSegmentStream.prototype.init.call(this); 8871 8872 this.push = function(data) { 8873 collectTimelineInfo(track, data); 8874 8875 if (track && track.channelcount === undefined) { 8876 track.audioobjecttype = data.audioobjecttype; 8877 track.channelcount = data.channelcount; 8878 track.samplerate = data.samplerate; 8879 track.samplingfrequencyindex = data.samplingfrequencyindex; 8880 track.samplesize = data.samplesize; 8881 track.extraData = (track.audioobjecttype << 11) | 8882 (track.samplingfrequencyindex << 7) | 8883 (track.channelcount << 3); 8884 } 8885 8886 data.pts = Math.round(data.pts / 90); 8887 data.dts = Math.round(data.dts / 90); 8888 8889 // buffer audio data until end() is called 8890 adtsFrames.push(data); 8891 }; 8892 8893 this.flush = function() { 8894 var currentFrame, adtsFrame, lastMetaPts, tags = []; 8895 // return early if no audio data has been observed 8896 if (adtsFrames.length === 0) { 8897 this.trigger('done'); 8898 return; 8899 } 8900 8901 lastMetaPts = -Infinity; 8902 8903 while (adtsFrames.length) { 8904 currentFrame = adtsFrames.shift(); 8905 8906 // write out metadata tags every 1 second so that the decoder 8907 // is re-initialized quickly after seeking into a different 8908 // audio configuration 8909 if (track.extraData !== oldExtraData || currentFrame.pts - lastMetaPts >= 1000) { 8910 adtsFrame = new FlvTag(FlvTag.METADATA_TAG); 8911 adtsFrame.pts = currentFrame.pts; 8912 adtsFrame.dts = currentFrame.dts; 8913 8914 // AAC is always 10 8915 adtsFrame.writeMetaDataDouble('audiocodecid', 10); 8916 adtsFrame.writeMetaDataBoolean('stereo', track.channelcount === 2); 8917 adtsFrame.writeMetaDataDouble('audiosamplerate', track.samplerate); 8918 // Is AAC always 16 bit? 8919 adtsFrame.writeMetaDataDouble('audiosamplesize', 16); 8920 8921 tags.push(adtsFrame); 8922 8923 oldExtraData = track.extraData; 8924 8925 adtsFrame = new FlvTag(FlvTag.AUDIO_TAG, true); 8926 // For audio, DTS is always the same as PTS. We want to set the DTS 8927 // however so we can compare with video DTS to determine approximate 8928 // packet order 8929 adtsFrame.pts = currentFrame.pts; 8930 adtsFrame.dts = currentFrame.dts; 8931 8932 adtsFrame.view.setUint16(adtsFrame.position, track.extraData); 8933 adtsFrame.position += 2; 8934 adtsFrame.length = Math.max(adtsFrame.length, adtsFrame.position); 8935 8936 tags.push(adtsFrame); 8937 8938 lastMetaPts = currentFrame.pts; 8939 } 8940 adtsFrame = new FlvTag(FlvTag.AUDIO_TAG); 8941 adtsFrame.pts = currentFrame.pts; 8942 adtsFrame.dts = currentFrame.dts; 8943 8944 adtsFrame.writeBytes(currentFrame.data); 8945 8946 tags.push(adtsFrame); 8947 } 8948 8949 oldExtraData = null; 8950 this.trigger('data', {track: track, tags: tags}); 8951 8952 this.trigger('done'); 8953 }; 8954}; 8955AudioSegmentStream.prototype = new Stream(); 8956 8957/** 8958 * Store FlvTags for the h264 stream 8959 * @param track {object} track metadata configuration 8960 */ 8961VideoSegmentStream = function(track) { 8962 var 8963 nalUnits = [], 8964 config, 8965 h264Frame; 8966 VideoSegmentStream.prototype.init.call(this); 8967 8968 this.finishFrame = function(tags, frame) { 8969 if (!frame) { 8970 return; 8971 } 8972 // Check if keyframe and the length of tags. 8973 // This makes sure we write metadata on the first frame of a segment. 8974 if (config && track && track.newMetadata && 8975 (frame.keyFrame || tags.length === 0)) { 8976 // Push extra data on every IDR frame in case we did a stream change + seek 8977 tags.push(metaDataTag(config, frame.dts)); 8978 tags.push(extraDataTag(track, frame.dts)); 8979 track.newMetadata = false;
vendor: 9,458 bytes, lines 8980-9304
8980 } 8981 8982 frame.endNalUnit(); 8983 tags.push(frame); 8984 h264Frame = null; 8985 }; 8986 8987 this.push = function(data) { 8988 collectTimelineInfo(track, data); 8989 8990 data.pts = Math.round(data.pts / 90); 8991 data.dts = Math.round(data.dts / 90); 8992 8993 // buffer video until flush() is called 8994 nalUnits.push(data); 8995 }; 8996 8997 this.flush = function() { 8998 var 8999 currentNal, 9000 tags = []; 9001 9002 // Throw away nalUnits at the start of the byte stream until we find 9003 // the first AUD 9004 while (nalUnits.length) { 9005 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { 9006 break; 9007 } 9008 nalUnits.shift(); 9009 } 9010 9011 // return early if no video data has been observed 9012 if (nalUnits.length === 0) { 9013 this.trigger('done'); 9014 return; 9015 } 9016 9017 while (nalUnits.length) { 9018 currentNal = nalUnits.shift(); 9019 9020 // record the track config 9021 if (currentNal.nalUnitType === 'seq_parameter_set_rbsp') { 9022 track.newMetadata = true; 9023 config = currentNal.config; 9024 track.width = config.width; 9025 track.height = config.height; 9026 track.sps = [currentNal.data]; 9027 track.profileIdc = config.profileIdc; 9028 track.levelIdc = config.levelIdc; 9029 track.profileCompatibility = config.profileCompatibility; 9030 h264Frame.endNalUnit(); 9031 } else if (currentNal.nalUnitType === 'pic_parameter_set_rbsp') { 9032 track.newMetadata = true; 9033 track.pps = [currentNal.data]; 9034 h264Frame.endNalUnit(); 9035 } else if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { 9036 if (h264Frame) { 9037 this.finishFrame(tags, h264Frame); 9038 } 9039 h264Frame = new FlvTag(FlvTag.VIDEO_TAG); 9040 h264Frame.pts = currentNal.pts; 9041 h264Frame.dts = currentNal.dts; 9042 } else { 9043 if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { 9044 // the current sample is a key frame 9045 h264Frame.keyFrame = true; 9046 } 9047 h264Frame.endNalUnit(); 9048 } 9049 h264Frame.startNalUnit(); 9050 h264Frame.writeBytes(currentNal.data); 9051 } 9052 if (h264Frame) { 9053 this.finishFrame(tags, h264Frame); 9054 } 9055 9056 this.trigger('data', {track: track, tags: tags}); 9057 9058 // Continue with the flush process now 9059 this.trigger('done'); 9060 }; 9061}; 9062 9063VideoSegmentStream.prototype = new Stream(); 9064 9065/** 9066 * The final stage of the transmuxer that emits the flv tags 9067 * for audio, video, and metadata. Also tranlates in time and 9068 * outputs caption data and id3 cues. 9069 */ 9070CoalesceStream = function(options) { 9071 // Number of Tracks per output segment 9072 // If greater than 1, we combine multiple 9073 // tracks into a single segment 9074 this.numberOfTracks = 0; 9075 this.metadataStream = options.metadataStream; 9076 9077 this.videoTags = []; 9078 this.audioTags = []; 9079 this.videoTrack = null; 9080 this.audioTrack = null; 9081 this.pendingCaptions = []; 9082 this.pendingMetadata = []; 9083 this.pendingTracks = 0; 9084 9085 CoalesceStream.prototype.init.call(this); 9086 9087 // Take output from multiple 9088 this.push = function(output) { 9089 // buffer incoming captions until the associated video segment 9090 // finishes 9091 if (output.text) { 9092 return this.pendingCaptions.push(output); 9093 } 9094 // buffer incoming id3 tags until the final flush 9095 if (output.frames) { 9096 return this.pendingMetadata.push(output); 9097 } 9098 9099 if (output.track.type === 'video') { 9100 this.videoTrack = output.track; 9101 this.videoTags = output.tags; 9102 this.pendingTracks++; 9103 } 9104 if (output.track.type === 'audio') { 9105 this.audioTrack = output.track; 9106 this.audioTags = output.tags; 9107 this.pendingTracks++; 9108 } 9109 }; 9110}; 9111 9112CoalesceStream.prototype = new Stream(); 9113CoalesceStream.prototype.flush = function() { 9114 var 9115 id3, 9116 caption, 9117 i, 9118 timelineStartPts, 9119 event = { 9120 tags: {}, 9121 captions: [], 9122 metadata: [] 9123 }; 9124 9125 if (this.pendingTracks < this.numberOfTracks) { 9126 return; 9127 } 9128 9129 if (this.videoTrack) { 9130 timelineStartPts = this.videoTrack.timelineStartInfo.pts; 9131 } else if (this.audioTrack) { 9132 timelineStartPts = this.audioTrack.timelineStartInfo.pts; 9133 } 9134 9135 event.tags.videoTags = this.videoTags; 9136 event.tags.audioTags = this.audioTags; 9137 9138 // Translate caption PTS times into second offsets into the 9139 // video timeline for the segment 9140 for (i = 0; i < this.pendingCaptions.length; i++) { 9141 caption = this.pendingCaptions[i]; 9142 caption.startTime = caption.startPts - timelineStartPts; 9143 caption.startTime /= 90e3; 9144 caption.endTime = caption.endPts - timelineStartPts; 9145 caption.endTime /= 90e3; 9146 event.captions.push(caption); 9147 } 9148 9149 // Translate ID3 frame PTS times into second offsets into the 9150 // video timeline for the segment 9151 for (i = 0; i < this.pendingMetadata.length; i++) { 9152 id3 = this.pendingMetadata[i]; 9153 id3.cueTime = id3.pts - timelineStartPts; 9154 id3.cueTime /= 90e3; 9155 event.metadata.push(id3); 9156 } 9157 // We add this to every single emitted segment even though we only need 9158 // it for the first 9159 event.metadata.dispatchType = this.metadataStream.dispatchType; 9160 9161 // Reset stream state 9162 this.videoTrack = null; 9163 this.audioTrack = null; 9164 this.videoTags = []; 9165 this.audioTags = []; 9166 this.pendingCaptions.length = 0; 9167 this.pendingMetadata.length = 0; 9168 this.pendingTracks = 0; 9169 9170 // Emit the final segment 9171 this.trigger('data', event); 9172 9173 this.trigger('done'); 9174}; 9175 9176/** 9177 * An object that incrementally transmuxes MPEG2 Trasport Stream 9178 * chunks into an FLV. 9179 */ 9180Transmuxer = function(options) { 9181 var 9182 self = this, 9183 9184 packetStream, parseStream, elementaryStream, 9185 videoTimestampRolloverStream, audioTimestampRolloverStream, 9186 timedMetadataTimestampRolloverStream, 9187 adtsStream, h264Stream, 9188 videoSegmentStream, audioSegmentStream, captionStream, 9189 coalesceStream; 9190 9191 Transmuxer.prototype.init.call(this); 9192 9193 options = options || {}; 9194 9195 // expose the metadata stream 9196 this.metadataStream = new m2ts.MetadataStream(); 9197 9198 options.metadataStream = this.metadataStream; 9199 9200 // set up the parsing pipeline 9201 packetStream = new m2ts.TransportPacketStream(); 9202 parseStream = new m2ts.TransportParseStream(); 9203 elementaryStream = new m2ts.ElementaryStream(); 9204 videoTimestampRolloverStream = new m2ts.TimestampRolloverStream('video'); 9205 audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio'); 9206 timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata'); 9207 9208 adtsStream = new AdtsStream(); 9209 h264Stream = new H264Stream(); 9210 coalesceStream = new CoalesceStream(options); 9211 9212 // disassemble MPEG2-TS packets into elementary streams 9213 packetStream 9214 .pipe(parseStream) 9215 .pipe(elementaryStream); 9216 9217 // !!THIS ORDER IS IMPORTANT!! 9218 // demux the streams 9219 elementaryStream 9220 .pipe(videoTimestampRolloverStream) 9221 .pipe(h264Stream); 9222 elementaryStream 9223 .pipe(audioTimestampRolloverStream) 9224 .pipe(adtsStream); 9225 9226 elementaryStream 9227 .pipe(timedMetadataTimestampRolloverStream) 9228 .pipe(this.metadataStream) 9229 .pipe(coalesceStream); 9230 // if CEA-708 parsing is available, hook up a caption stream 9231 captionStream = new m2ts.CaptionStream(); 9232 h264Stream.pipe(captionStream) 9233 .pipe(coalesceStream); 9234 9235 // hook up the segment streams once track metadata is delivered 9236 elementaryStream.on('data', function(data) { 9237 var i, videoTrack, audioTrack; 9238 9239 if (data.type === 'metadata') { 9240 i = data.tracks.length; 9241 9242 // scan the tracks listed in the metadata 9243 while (i--) { 9244 if (data.tracks[i].type === 'video') { 9245 videoTrack = data.tracks[i]; 9246 } else if (data.tracks[i].type === 'audio') { 9247 audioTrack = data.tracks[i]; 9248 } 9249 } 9250 9251 // hook up the video segment stream to the first track with h264 data 9252 if (videoTrack && !videoSegmentStream) { 9253 coalesceStream.numberOfTracks++; 9254 videoSegmentStream = new VideoSegmentStream(videoTrack); 9255 9256 // Set up the final part of the video pipeline 9257 h264Stream 9258 .pipe(videoSegmentStream) 9259 .pipe(coalesceStream); 9260 } 9261 9262 if (audioTrack && !audioSegmentStream) { 9263 // hook up the audio segment stream to the first track with aac data 9264 coalesceStream.numberOfTracks++; 9265 audioSegmentStream = new AudioSegmentStream(audioTrack); 9266 9267 // Set up the final part of the audio pipeline 9268 adtsStream 9269 .pipe(audioSegmentStream) 9270 .pipe(coalesceStream); 9271 } 9272 } 9273 }); 9274 9275 // feed incoming data to the front of the parsing pipeline 9276 this.push = function(data) { 9277 packetStream.push(data); 9278 }; 9279 9280 // flush any buffered data 9281 this.flush = function() { 9282 // Start at the top of the pipeline and flush all pending work 9283 packetStream.flush(); 9284 }; 9285 9286 // Re-emit any data coming from the coalesce stream to the outside world 9287 coalesceStream.on('data', function(event) { 9288 self.trigger('data', event); 9289 }); 9290 9291 // Let the consumer know we have finished flushing the entire pipeline 9292 coalesceStream.on('done', function() { 9293 self.trigger('done'); 9294 }); 9295 9296 // For information on the FLV format, see 9297 // http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf. 9298 // Technically, this function returns the header and a metadata FLV tag 9299 // if duration is greater than zero 9300 // duration in seconds 9301 // @return {object} the bytes of the FLV header as a Uint8Array 9302 this.getFlvHeader = function(duration, audio, video) { // :ByteArray { 9303 var 9304 headBytes = new Uint8Array(3 + 1 + 1 + 4),
9305 head = new DataView(headBytes.buffer), 9306 metadata, 9307 result, 9308 metadataLength; 9309 9310 // default arguments 9311 duration = duration || 0; 9312 audio = audio === undefined ? true : audio; 9313 video = video === undefined ? true : video; 9314 9315 // signature 9316 head.setUint8(0, 0x46); // 'F' 9317 head.setUint8(1, 0x4c); // 'L' 9318 head.setUint8(2, 0x56); // 'V' 9319 9320 // version 9321 head.setUint8(3, 0x01); 9322 9323 // flags 9324 head.setUint8(4, (audio ? 0x04 : 0x00) | (video ? 0x01 : 0x00)); 9325 9326 // data offset, should be 9 for FLV v1 9327 head.setUint32(5, headBytes.byteLength); 9328 9329 // init the first FLV tag 9330 if (duration <= 0) { 9331 // no duration available so just write the first field of the first 9332 // FLV tag 9333 result = new Uint8Array(headBytes.byteLength + 4); 9334 result.set(headBytes); 9335 result.set([0, 0, 0, 0], headBytes.byteLength); 9336 return result; 9337 } 9338 9339 // write out the duration metadata tag 9340 metadata = new FlvTag(FlvTag.METADATA_TAG); 9341 metadata.pts = metadata.dts = 0; 9342 metadata.writeMetaDataDouble('duration', duration); 9343 metadataLength = metadata.finalize().length; 9344 result = new Uint8Array(headBytes.byteLength + metadataLength); 9345 result.set(headBytes); 9346 result.set(head.byteLength, metadataLength); 9347 9348 return result; 9349 }; 9350}; 9351Transmuxer.prototype = new Stream(); 9352 9353// forward compatibility 9354module.exports = Transmuxer; 9355 9356},{"../codecs/adts.js":68,"../codecs/h264":69,"../m2ts/m2ts.js":74,"../utils/stream.js":83,"./flv-tag.js":70}],73:[function(require,module,exports){ 9357/** 9358 * mux.js 9359 * 9360 * Copyright (c) 2015 Brightcove 9361 * All rights reserved. 9362 * 9363 * Reads in-band caption information from a video elementary 9364 * stream. Captions must follow the CEA-708 standard for injection 9365 * into an MPEG-2 transport streams. 9366 * @see https://en.wikipedia.org/wiki/CEA-708 9367 */ 9368 9369'use strict'; 9370 9371// ----------------- 9372// Link To Transport 9373// ----------------- 9374 9375// Supplemental enhancement information (SEI) NAL units have a 9376// payload type field to indicate how they are to be 9377// interpreted. CEAS-708 caption content is always transmitted with 9378// payload type 0x04. 9379var USER_DATA_REGISTERED_ITU_T_T35 = 4, 9380 RBSP_TRAILING_BITS = 128, 9381 Stream = require('../utils/stream'); 9382 9383/** 9384 * Parse a supplemental enhancement information (SEI) NAL unit. 9385 * Stops parsing once a message of type ITU T T35 has been found. 9386 * 9387 * @param bytes {Uint8Array} the bytes of a SEI NAL unit 9388 * @return {object} the parsed SEI payload 9389 * @see Rec. ITU-T H.264, 7.3.2.3.1 9390 */ 9391var parseSei = function(bytes) { 9392 var 9393 i = 0, 9394 result = { 9395 payloadType: -1, 9396 payloadSize: 0 9397 }, 9398 payloadType = 0, 9399 payloadSize = 0; 9400 9401 // go through the sei_rbsp parsing each each individual sei_message 9402 while (i < bytes.byteLength) { 9403 // stop once we have hit the end of the sei_rbsp 9404 if (bytes[i] === RBSP_TRAILING_BITS) { 9405 break; 9406 } 9407 9408 // Parse payload type 9409 while (bytes[i] === 0xFF) { 9410 payloadType += 255; 9411 i++; 9412 } 9413 payloadType += bytes[i++]; 9414 9415 // Parse payload size 9416 while (bytes[i] === 0xFF) { 9417 payloadSize += 255; 9418 i++; 9419 } 9420 payloadSize += bytes[i++]; 9421 9422 // this sei_message is a 608/708 caption so save it and break 9423 // there can only ever be one caption message in a frame's sei 9424 if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) { 9425 result.payloadType = payloadType; 9426 result.payloadSize = payloadSize; 9427 result.payload = bytes.subarray(i, i + payloadSize); 9428 break; 9429 } 9430 9431 // skip the payload and parse the next message 9432 i += payloadSize; 9433 payloadType = 0; 9434 payloadSize = 0; 9435 } 9436 9437 return result; 9438}; 9439 9440// see ANSI/SCTE 128-1 (2013), section 8.1 9441var parseUserData = function(sei) { 9442 // itu_t_t35_contry_code must be 181 (United States) for 9443 // captions 9444 if (sei.payload[0] !== 181) { 9445 return null; 9446 } 9447 9448 // itu_t_t35_provider_code should be 49 (ATSC) for captions 9449 if (((sei.payload[1] << 8) | sei.payload[2]) !== 49) { 9450 return null; 9451 } 9452 9453 // the user_identifier should be "GA94" to indicate ATSC1 data 9454 if (String.fromCharCode(sei.payload[3], 9455 sei.payload[4], 9456 sei.payload[5], 9457 sei.payload[6]) !== 'GA94') { 9458 return null; 9459 } 9460 9461 // finally, user_data_type_code should be 0x03 for caption data 9462 if (sei.payload[7] !== 0x03) { 9463 return null; 9464 } 9465 9466 // return the user_data_type_structure and strip the trailing 9467 // marker bits 9468 return sei.payload.subarray(8, sei.payload.length - 1); 9469}; 9470 9471// see CEA-708-D, section 4.4 9472var parseCaptionPackets = function(pts, userData) { 9473 var results = [], i, count, offset, data; 9474 9475 // if this is just filler, return immediately 9476 if (!(userData[0] & 0x40)) { 9477 return results; 9478 } 9479 9480 // parse out the cc_data_1 and cc_data_2 fields 9481 count = userData[0] & 0x1f; 9482 for (i = 0; i < count; i++) { 9483 offset = i * 3; 9484 data = { 9485 type: userData[offset + 2] & 0x03, 9486 pts: pts 9487 }; 9488 9489 // capture cc data when cc_valid is 1 9490 if (userData[offset + 2] & 0x04) { 9491 data.ccData = (userData[offset + 3] << 8) | userData[offset + 4]; 9492 results.push(data); 9493 } 9494 } 9495 return results; 9496}; 9497 9498var CaptionStream = function() { 9499 CaptionStream.prototype.init.call(this); 9500 9501 this.captionPackets_ = []; 9502 9503 this.field1_ = new Cea608Stream(); // eslint-disable-line no-use-before-define 9504 9505 // forward data and done events from field1_ to this CaptionStream 9506 this.field1_.on('data', this.trigger.bind(this, 'data')); 9507 this.field1_.on('done', this.trigger.bind(this, 'done')); 9508}; 9509CaptionStream.prototype = new Stream(); 9510CaptionStream.prototype.push = function(event) { 9511 var sei, userData; 9512 9513 // only examine SEI NALs 9514 if (event.nalUnitType !== 'sei_rbsp') { 9515 return; 9516 } 9517 9518 // parse the sei 9519 sei = parseSei(event.escapedRBSP); 9520 9521 // ignore everything but user_data_registered_itu_t_t35 9522 if (sei.payloadType !== USER_DATA_REGISTERED_ITU_T_T35) { 9523 return; 9524 } 9525 9526 // parse out the user data payload 9527 userData = parseUserData(sei); 9528 9529 // ignore unrecognized userData 9530 if (!userData) { 9531 return; 9532 } 9533 9534 // parse out CC data packets and save them for later 9535 this.captionPackets_ = this.captionPackets_.concat(parseCaptionPackets(event.pts, userData)); 9536}; 9537 9538CaptionStream.prototype.flush = function() { 9539 // make sure we actually parsed captions before proceeding 9540 if (!this.captionPackets_.length) { 9541 this.field1_.flush(); 9542 return; 9543 } 9544 9545 // In Chrome, the Array#sort function is not stable so add a 9546 // presortIndex that we can use to ensure we get a stable-sort 9547 this.captionPackets_.forEach(function(elem, idx) { 9548 elem.presortIndex = idx; 9549 }); 9550 9551 // sort caption byte-pairs based on their PTS values 9552 this.captionPackets_.sort(function(a, b) { 9553 if (a.pts === b.pts) { 9554 return a.presortIndex - b.presortIndex; 9555 } 9556 return a.pts - b.pts; 9557 }); 9558 9559 // Push each caption into Cea608Stream 9560 this.captionPackets_.forEach(this.field1_.push, this.field1_); 9561 9562 this.captionPackets_.length = 0; 9563 this.field1_.flush(); 9564 return; 9565}; 9566// ---------------------- 9567// Session to Application 9568// ---------------------- 9569 9570var BASIC_CHARACTER_TRANSLATION = { 9571 0x2a: 0xe1, 9572 0x5c: 0xe9, 9573 0x5e: 0xed, 9574 0x5f: 0xf3, 9575 0x60: 0xfa, 9576 0x7b: 0xe7, 9577 0x7c: 0xf7, 9578 0x7d: 0xd1, 9579 0x7e: 0xf1, 9580 0x7f: 0x2588 9581}; 9582 9583var getCharFromCode = function(code) { 9584 if (code === null) { 9585 return ''; 9586 } 9587 code = BASIC_CHARACTER_TRANSLATION[code] || code; 9588 return String.fromCharCode(code); 9589}; 9590 9591// Constants for the byte codes recognized by Cea608Stream. This 9592// list is not exhaustive. For a more comprehensive listing and 9593// semantics see 9594// http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf 9595var PADDING = 0x0000, 9596 9597 // Pop-on Mode 9598 RESUME_CAPTION_LOADING = 0x1420, 9599 END_OF_CAPTION = 0x142f, 9600 9601 // Roll-up Mode 9602 ROLL_UP_2_ROWS = 0x1425, 9603 ROLL_UP_3_ROWS = 0x1426, 9604 ROLL_UP_4_ROWS = 0x1427, 9605 CARRIAGE_RETURN = 0x142d, 9606 // Erasure 9607 BACKSPACE = 0x1421, 9608 ERASE_DISPLAYED_MEMORY = 0x142c, 9609 ERASE_NON_DISPLAYED_MEMORY = 0x142e; 9610 9611// the index of the last row in a CEA-608 display buffer 9612var BOTTOM_ROW = 14; 9613// CEA-608 captions are rendered onto a 34x15 matrix of character 9614// cells. The "bottom" row is the last element in the outer array. 9615var createDisplayBuffer = function() { 9616 var result = [], i = BOTTOM_ROW + 1; 9617 while (i--) { 9618 result.push(''); 9619 } 9620 return result; 9621}; 9622 9623var Cea608Stream = function() { 9624 Cea608Stream.prototype.init.call(this); 9625 9626 this.mode_ = 'popOn'; 9627 // When in roll-up mode, the index of the last row that will 9628 // actually display captions. If a caption is shifted to a row 9629 // with a lower index than this, it is cleared from the display 9630 // buffer 9631 this.topRow_ = 0; 9632 this.startPts_ = 0; 9633 this.displayed_ = createDisplayBuffer(); 9634 this.nonDisplayed_ = createDisplayBuffer(); 9635 this.lastControlCode_ = null; 9636 9637 this.push = function(packet) { 9638 // Ignore other channels 9639 if (packet.type !== 0) { 9640 return; 9641 } 9642 var data, swap, char0, char1; 9643 // remove the parity bits 9644 data = packet.ccData & 0x7f7f; 9645 9646 // ignore duplicate control codes 9647 if (data === this.lastControlCode_) { 9648 this.lastControlCode_ = null; 9649 return; 9650 } 9651 9652 // Store control codes 9653 if ((data & 0xf000) === 0x1000) { 9654 this.lastControlCode_ = data; 9655 } else { 9656 this.lastControlCode_ = null; 9657 } 9658 9659 switch (data) { 9660 case PADDING: 9661 break; 9662 case RESUME_CAPTION_LOADING: 9663 this.mode_ = 'popOn'; 9664 break; 9665 case END_OF_CAPTION: 9666 // if a caption was being displayed, it's gone now 9667 this.flushDisplayed(packet.pts); 9668 9669 // flip memory 9670 swap = this.displayed_; 9671 this.displayed_ = this.nonDisplayed_; 9672 this.nonDisplayed_ = swap; 9673 9674 // start measuring the time to display the caption 9675 this.startPts_ = packet.pts; 9676 break; 9677 9678 case ROLL_UP_2_ROWS: 9679 this.topRow_ = BOTTOM_ROW - 1; 9680 this.mode_ = 'rollUp'; 9681 break; 9682 case ROLL_UP_3_ROWS: 9683 this.topRow_ = BOTTOM_ROW - 2; 9684 this.mode_ = 'rollUp'; 9685 break; 9686 case ROLL_UP_4_ROWS: 9687 this.topRow_ = BOTTOM_ROW - 3; 9688 this.mode_ = 'rollUp'; 9689 break; 9690 case CARRIAGE_RETURN: 9691 this.flushDisplayed(packet.pts); 9692 this.shiftRowsUp_(); 9693 this.startPts_ = packet.pts; 9694 break; 9695 9696 case BACKSPACE: 9697 if (this.mode_ === 'popOn') { 9698 this.nonDisplayed_[BOTTOM_ROW] = this.nonDisplayed_[BOTTOM_ROW].slice(0, -1); 9699 } else { 9700 this.displayed_[BOTTOM_ROW] = this.displayed_[BOTTOM_ROW].slice(0, -1); 9701 } 9702 break; 9703 case ERASE_DISPLAYED_MEMORY: 9704 this.flushDisplayed(packet.pts); 9705 this.displayed_ = createDisplayBuffer(); 9706 break; 9707 case ERASE_NON_DISPLAYED_MEMORY: 9708 this.nonDisplayed_ = createDisplayBuffer(); 9709 break; 9710 default: 9711 char0 = data >>> 8; 9712 char1 = data & 0xff;
9713 9714 // Look for a Channel 1 Preamble Address Code 9715 if (char0 >= 0x10 && char0 <= 0x17 && 9716 char1 >= 0x40 && char1 <= 0x7F && 9717 (char0 !== 0x10 || char1 < 0x60)) { 9718 // Follow Safari's lead and replace the PAC with a space 9719 char0 = 0x20; 9720 // we only want one space so make the second character null 9721 // which will get become '' in getCharFromCode 9722 char1 = null; 9723 } 9724 9725 // Look for special character sets 9726 if ((char0 === 0x11 || char0 === 0x19) && 9727 (char1 >= 0x30 && char1 <= 0x3F)) { 9728 // Put in eigth note and space 9729 char0 = 0x266A; 9730 char1 = ''; 9731 } 9732 9733 // ignore unsupported control codes 9734 if ((char0 & 0xf0) === 0x10) { 9735 return; 9736 } 9737 9738 // character handling is dependent on the current mode 9739 this[this.mode_](packet.pts, char0, char1); 9740 break; 9741 } 9742 }; 9743}; 9744Cea608Stream.prototype = new Stream(); 9745// Trigger a cue point that captures the current state of the 9746// display buffer 9747Cea608Stream.prototype.flushDisplayed = function(pts) { 9748 var content = this.displayed_ 9749 // remove spaces from the start and end of the string 9750 .map(function(row) { 9751 return row.trim(); 9752 }) 9753 // remove empty rows 9754 .filter(function(row) { 9755 return row.length; 9756 }) 9757 // combine all text rows to display in one cue 9758 .join('\n'); 9759 9760 if (content.length) { 9761 this.trigger('data', { 9762 startPts: this.startPts_, 9763 endPts: pts, 9764 text: content 9765 }); 9766 } 9767}; 9768 9769// Mode Implementations 9770Cea608Stream.prototype.popOn = function(pts, char0, char1) { 9771 var baseRow = this.nonDisplayed_[BOTTOM_ROW]; 9772 9773 // buffer characters 9774 baseRow += getCharFromCode(char0); 9775 baseRow += getCharFromCode(char1); 9776 this.nonDisplayed_[BOTTOM_ROW] = baseRow; 9777}; 9778 9779Cea608Stream.prototype.rollUp = function(pts, char0, char1) { 9780 var baseRow = this.displayed_[BOTTOM_ROW]; 9781 if (baseRow === '') { 9782 // we're starting to buffer new display input, so flush out the 9783 // current display 9784 this.flushDisplayed(pts); 9785 9786 this.startPts_ = pts; 9787 } 9788 9789 baseRow += getCharFromCode(char0); 9790 baseRow += getCharFromCode(char1); 9791 9792 this.displayed_[BOTTOM_ROW] = baseRow; 9793}; 9794Cea608Stream.prototype.shiftRowsUp_ = function() { 9795 var i; 9796 // clear out inactive rows 9797 for (i = 0; i < this.topRow_; i++) { 9798 this.displayed_[i] = ''; 9799 } 9800 // shift displayed rows up 9801 for (i = this.topRow_; i < BOTTOM_ROW; i++) { 9802 this.displayed_[i] = this.displayed_[i + 1]; 9803 } 9804 // clear out the bottom row 9805 this.displayed_[BOTTOM_ROW] = ''; 9806}; 9807 9808// exports 9809module.exports = { 9810 CaptionStream: CaptionStream, 9811 Cea608Stream: Cea608Stream 9812}; 9813 9814},{"../utils/stream":83}],74:[function(require,module,exports){ 9815/** 9816 * mux.js 9817 * 9818 * Copyright (c) 2015 Brightcove 9819 * All rights reserved. 9820 * 9821 * A stream-based mp2t to mp4 converter. This utility can be used to 9822 * deliver mp4s to a SourceBuffer on platforms that support native 9823 * Media Source Extensions. 9824 */ 9825'use strict'; 9826var Stream = require('../utils/stream.js'), 9827 CaptionStream = require('./caption-stream'), 9828 StreamTypes = require('./stream-types'), 9829 TimestampRolloverStream = require('./timestamp-rollover-stream').TimestampRolloverStream; 9830 9831var m2tsStreamTypes = require('./stream-types.js'); 9832 9833// object types 9834var TransportPacketStream, TransportParseStream, ElementaryStream; 9835 9836// constants 9837var 9838 MP2T_PACKET_LENGTH = 188, // bytes 9839 SYNC_BYTE = 0x47; 9840 9841/** 9842 * Splits an incoming stream of binary data into MPEG-2 Transport 9843 * Stream packets. 9844 */ 9845TransportPacketStream = function() { 9846 var 9847 buffer = new Uint8Array(MP2T_PACKET_LENGTH), 9848 bytesInBuffer = 0; 9849 9850 TransportPacketStream.prototype.init.call(this); 9851 9852 // Deliver new bytes to the stream. 9853 9854 this.push = function(bytes) { 9855 var 9856 startIndex = 0, 9857 endIndex = MP2T_PACKET_LENGTH, 9858 everything; 9859 9860 // If there are bytes remaining from the last segment, prepend them to the 9861 // bytes that were pushed in 9862 if (bytesInBuffer) { 9863 everything = new Uint8Array(bytes.byteLength + bytesInBuffer); 9864 everything.set(buffer.subarray(0, bytesInBuffer)); 9865 everything.set(bytes, bytesInBuffer); 9866 bytesInBuffer = 0; 9867 } else { 9868 everything = bytes; 9869 } 9870 9871 // While we have enough data for a packet 9872 while (endIndex < everything.byteLength) { 9873 // Look for a pair of start and end sync bytes in the data.. 9874 if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) { 9875 // We found a packet so emit it and jump one whole packet forward in 9876 // the stream 9877 this.trigger('data', everything.subarray(startIndex, endIndex)); 9878 startIndex += MP2T_PACKET_LENGTH; 9879 endIndex += MP2T_PACKET_LENGTH; 9880 continue; 9881 } 9882 // If we get here, we have somehow become de-synchronized and we need to step 9883 // forward one byte at a time until we find a pair of sync bytes that denote 9884 // a packet 9885 startIndex++; 9886 endIndex++; 9887 } 9888 9889 // If there was some data left over at the end of the segment that couldn't
vendor: 5,342 bytes, lines 9890-10038
9890 // possibly be a whole packet, keep it because it might be the start of a packet 9891 // that continues in the next segment 9892 if (startIndex < everything.byteLength) { 9893 buffer.set(everything.subarray(startIndex), 0); 9894 bytesInBuffer = everything.byteLength - startIndex; 9895 } 9896 }; 9897 9898 this.flush = function() { 9899 // If the buffer contains a whole packet when we are being flushed, emit it 9900 // and empty the buffer. Otherwise hold onto the data because it may be 9901 // important for decoding the next segment 9902 if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) { 9903 this.trigger('data', buffer); 9904 bytesInBuffer = 0; 9905 } 9906 this.trigger('done'); 9907 }; 9908}; 9909TransportPacketStream.prototype = new Stream(); 9910 9911/** 9912 * Accepts an MP2T TransportPacketStream and emits data events with parsed 9913 * forms of the individual transport stream packets. 9914 */ 9915TransportParseStream = function() { 9916 var parsePsi, parsePat, parsePmt, self; 9917 TransportParseStream.prototype.init.call(this); 9918 self = this; 9919 9920 this.packetsWaitingForPmt = []; 9921 this.programMapTable = undefined; 9922 9923 parsePsi = function(payload, psi) { 9924 var offset = 0; 9925 9926 // PSI packets may be split into multiple sections and those 9927 // sections may be split into multiple packets. If a PSI 9928 // section starts in this packet, the payload_unit_start_indicator 9929 // will be true and the first byte of the payload will indicate 9930 // the offset from the current position to the start of the 9931 // section. 9932 if (psi.payloadUnitStartIndicator) { 9933 offset += payload[offset] + 1; 9934 } 9935 9936 if (psi.type === 'pat') { 9937 parsePat(payload.subarray(offset), psi); 9938 } else { 9939 parsePmt(payload.subarray(offset), psi); 9940 } 9941 }; 9942 9943 parsePat = function(payload, pat) { 9944 pat.section_number = payload[7]; // eslint-disable-line camelcase 9945 pat.last_section_number = payload[8]; // eslint-disable-line camelcase 9946 9947 // skip the PSI header and parse the first PMT entry 9948 self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11]; 9949 pat.pmtPid = self.pmtPid; 9950 }; 9951 9952 /** 9953 * Parse out the relevant fields of a Program Map Table (PMT). 9954 * @param payload {Uint8Array} the PMT-specific portion of an MP2T 9955 * packet. The first byte in this array should be the table_id 9956 * field. 9957 * @param pmt {object} the object that should be decorated with 9958 * fields parsed from the PMT. 9959 */ 9960 parsePmt = function(payload, pmt) { 9961 var sectionLength, tableEnd, programInfoLength, offset; 9962 9963 // PMTs can be sent ahead of the time when they should actually 9964 // take effect. We don't believe this should ever be the case 9965 // for HLS but we'll ignore "forward" PMT declarations if we see 9966 // them. Future PMT declarations have the current_next_indicator 9967 // set to zero. 9968 if (!(payload[5] & 0x01)) { 9969 return; 9970 } 9971 9972 // overwrite any existing program map table 9973 self.programMapTable = {}; 9974 9975 // the mapping table ends at the end of the current section 9976 sectionLength = (payload[1] & 0x0f) << 8 | payload[2]; 9977 tableEnd = 3 + sectionLength - 4; 9978 9979 // to determine where the table is, we have to figure out how 9980 // long the program info descriptors are 9981 programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; 9982 9983 // advance the offset to the first entry in the mapping table 9984 offset = 12 + programInfoLength; 9985 while (offset < tableEnd) { 9986 // add an entry that maps the elementary_pid to the stream_type 9987 self.programMapTable[(payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]] = payload[offset]; 9988 9989 // move to the next table entry 9990 // skip past the elementary stream descriptors, if present 9991 offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5; 9992 } 9993 9994 // record the map on the packet as well 9995 pmt.programMapTable = self.programMapTable; 9996 9997 // if there are any packets waiting for a PMT to be found, process them now 9998 while (self.packetsWaitingForPmt.length) { 9999 self.processPes_.apply(self, self.packetsWaitingForPmt.shift()); 10000 } 10001 }; 10002 10003 /** 10004 * Deliver a new MP2T packet to the stream. 10005 */ 10006 this.push = function(packet) { 10007 var 10008 result = {}, 10009 offset = 4; 10010 10011 result.payloadUnitStartIndicator = !!(packet[1] & 0x40); 10012 10013 // pid is a 13-bit field starting at the last bit of packet[1] 10014 result.pid = packet[1] & 0x1f; 10015 result.pid <<= 8; 10016 result.pid |= packet[2]; 10017 10018 // if an adaption field is present, its length is specified by the 10019 // fifth byte of the TS packet header. The adaptation field is 10020 // used to add stuffing to PES packets that don't fill a complete 10021 // TS packet, and to specify some forms of timing and control data 10022 // that we do not currently use. 10023 if (((packet[3] & 0x30) >>> 4) > 0x01) { 10024 offset += packet[offset] + 1; 10025 } 10026 10027 // parse the rest of the packet based on the type 10028 if (result.pid === 0) { 10029 result.type = 'pat'; 10030 parsePsi(packet.subarray(offset), result); 10031 this.trigger('data', result); 10032 } else if (result.pid === this.pmtPid) { 10033 result.type = 'pmt'; 10034 parsePsi(packet.subarray(offset), result); 10035 this.trigger('data', result); 10036 } else if (this.programMapTable === undefined) { 10037 // When we have not seen a PMT yet, defer further processing of 10038 // PES packets until one has been parsed
10039 this.packetsWaitingForPmt.push([packet, offset, result]); 10040 } else { 10041 this.processPes_(packet, offset, result); 10042 } 10043 }; 10044 10045 this.processPes_ = function(packet, offset, result) { 10046 result.streamType = this.programMapTable[result.pid]; 10047 result.type = 'pes'; 10048 result.data = packet.subarray(offset); 10049 10050 this.trigger('data', result); 10051 }; 10052 10053}; 10054TransportParseStream.prototype = new Stream(); 10055TransportParseStream.STREAM_TYPES = { 10056 h264: 0x1b, 10057 adts: 0x0f 10058}; 10059 10060/** 10061 * Reconsistutes program elementary stream (PES) packets from parsed 10062 * transport stream packets. That is, if you pipe an 10063 * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output 10064 * events will be events which capture the bytes for individual PES 10065 * packets plus relevant metadata that has been extracted from the 10066 * container. 10067 */ 10068ElementaryStream = function() { 10069 var 10070 self = this, 10071 // PES packet fragments 10072 video = { 10073 data: [], 10074 size: 0 10075 }, 10076 audio = { 10077 data: [], 10078 size: 0 10079 }, 10080 timedMetadata = { 10081 data: [], 10082 size: 0 10083 }, 10084 parsePes = function(payload, pes) { 10085 var ptsDtsFlags; 10086 10087 // find out if this packets starts a new keyframe 10088 pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; 10089 // PES packets may be annotated with a PTS value, or a PTS value 10090 // and a DTS value. Determine what combination of values is 10091 // available to work with. 10092 ptsDtsFlags = payload[7]; 10093 10094 // PTS and DTS are normally stored as a 33-bit number. Javascript 10095 // performs all bitwise operations on 32-bit integers but javascript 10096 // supports a much greater range (52-bits) of integer using standard 10097 // mathematical operations. 10098 // We construct a 31-bit value using bitwise operators over the 31 10099 // most significant bits and then multiply by 4 (equal to a left-shift 10100 // of 2) before we add the final 2 least significant bits of the 10101 // timestamp (equal to an OR.) 10102 if (ptsDtsFlags & 0xC0) { 10103 // the PTS and DTS are not written out directly. For information 10104 // on how they are encoded, see 10105 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 10106 pes.pts = (payload[9] & 0x0E) << 27 | 10107 (payload[10] & 0xFF) << 20 | 10108 (payload[11] & 0xFE) << 12 | 10109 (payload[12] & 0xFF) << 5 | 10110 (payload[13] & 0xFE) >>> 3; 10111 pes.pts *= 4; // Left shift by 2 10112 pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs 10113 pes.dts = pes.pts; 10114 if (ptsDtsFlags & 0x40) { 10115 pes.dts = (payload[14] & 0x0E) << 27 | 10116 (payload[15] & 0xFF) << 20 | 10117 (payload[16] & 0xFE) << 12 | 10118 (payload[17] & 0xFF) << 5 | 10119 (payload[18] & 0xFE) >>> 3; 10120 pes.dts *= 4; // Left shift by 2 10121 pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs 10122 } 10123 } 10124 // the data section starts immediately after the PES header. 10125 // pes_header_data_length specifies the number of header bytes 10126 // that follow the last byte of the field. 10127 pes.data = payload.subarray(9 + payload[8]); 10128 }, 10129 flushStream = function(stream, type) { 10130 var 10131 packetData = new Uint8Array(stream.size), 10132 event = { 10133 type: type 10134 }, 10135 i = 0, 10136 fragment; 10137 10138 // do nothing if there is no buffered data 10139 if (!stream.data.length) { 10140 return; 10141 } 10142 event.trackId = stream.data[0].pid; 10143 10144 // reassemble the packet 10145 while (stream.data.length) { 10146 fragment = stream.data.shift(); 10147 10148 packetData.set(fragment.data, i); 10149 i += fragment.data.byteLength; 10150 } 10151 10152 // parse assembled packet's PES header 10153 parsePes(packetData, event); 10154 10155 stream.size = 0; 10156 10157 self.trigger('data', event); 10158 }; 10159 10160 ElementaryStream.prototype.init.call(this); 10161 10162 this.push = function(data) { 10163 ({ 10164 pat: function() { 10165 // we have to wait for the PMT to arrive as well before we 10166 // have any meaningful metadata 10167 }, 10168 pes: function() { 10169 var stream, streamType; 10170 10171 switch (data.streamType) { 10172 case StreamTypes.H264_STREAM_TYPE: 10173 case m2tsStreamTypes.H264_STREAM_TYPE: 10174 stream = video; 10175 streamType = 'video'; 10176 break; 10177 case StreamTypes.ADTS_STREAM_TYPE: 10178 stream = audio; 10179 streamType = 'audio'; 10180 break; 10181 case StreamTypes.METADATA_STREAM_TYPE: 10182 stream = timedMetadata; 10183 streamType = 'timed-metadata'; 10184 break; 10185 default: 10186 // ignore unknown stream types 10187 return; 10188 } 10189 10190 // if a new packet is starting, we can flush the completed 10191 // packet 10192 if (data.payloadUnitStartIndicator) { 10193 flushStream(stream, streamType); 10194 } 10195 10196 // buffer this fragment until we are sure we've received the 10197 // complete payload 10198 stream.data.push(data); 10199 stream.size += data.data.byteLength; 10200 }, 10201 pmt: function() { 10202 var 10203 event = { 10204 type: 'metadata', 10205 tracks: [] 10206 }, 10207 programMapTable = data.programMapTable, 10208 k, 10209 track; 10210 10211 // translate streams to tracks 10212 for (k in programMapTable) { 10213 if (programMapTable.hasOwnProperty(k)) { 10214 track = { 10215 timelineStartInfo: { 10216 baseMediaDecodeTime: 0 10217 } 10218 }; 10219 track.id = +k; 10220 if (programMapTable[k] === m2tsStreamTypes.H264_STREAM_TYPE) { 10221 track.codec = 'avc'; 10222 track.type = 'video'; 10223 } else if (programMapTable[k] === m2tsStreamTypes.ADTS_STREAM_TYPE) { 10224 track.codec = 'adts'; 10225 track.type = 'audio'; 10226 } 10227 event.tracks.push(track); 10228 } 10229 } 10230 self.trigger('data', event); 10231 } 10232 })[data.type](); 10233 }; 10234 10235 /** 10236 * Flush any remaining input. Video PES packets may be of variable 10237 * length. Normally, the start of a new video packet can trigger the 10238 * finalization of the previous packet. That is not possible if no 10239 * more video is forthcoming, however. In that case, some other 10240 * mechanism (like the end of the file) has to be employed. When it is 10241 * clear that no additional data is forthcoming, calling this method 10242 * will flush the buffered packets. 10243 */ 10244 this.flush = function() { 10245 // !!THIS ORDER IS IMPORTANT!! 10246 // video first then audio 10247 flushStream(video, 'video'); 10248 flushStream(audio, 'audio'); 10249 flushStream(timedMetadata, 'timed-metadata'); 10250 this.trigger('done'); 10251 }; 10252}; 10253ElementaryStream.prototype = new Stream(); 10254 10255var m2ts = { 10256 PAT_PID: 0x0000, 10257 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
vendor: 8,252 bytes, lines 10258-10504
10258 TransportPacketStream: TransportPacketStream, 10259 TransportParseStream: TransportParseStream, 10260 ElementaryStream: ElementaryStream, 10261 TimestampRolloverStream: TimestampRolloverStream, 10262 CaptionStream: CaptionStream.CaptionStream, 10263 Cea608Stream: CaptionStream.Cea608Stream, 10264 MetadataStream: require('./metadata-stream') 10265}; 10266 10267for (var type in StreamTypes) { 10268 if (StreamTypes.hasOwnProperty(type)) { 10269 m2ts[type] = StreamTypes[type]; 10270 } 10271} 10272 10273module.exports = m2ts; 10274 10275},{"../utils/stream.js":83,"./caption-stream":73,"./metadata-stream":75,"./stream-types":76,"./stream-types.js":76,"./timestamp-rollover-stream":77}],75:[function(require,module,exports){ 10276/** 10277 * Accepts program elementary stream (PES) data events and parses out 10278 * ID3 metadata from them, if present. 10279 * @see http://id3.org/id3v2.3.0 10280 */ 10281'use strict'; 10282var 10283 Stream = require('../utils/stream'), 10284 StreamTypes = require('./stream-types'), 10285 // return a percent-encoded representation of the specified byte range 10286 // @see http://en.wikipedia.org/wiki/Percent-encoding 10287 percentEncode = function(bytes, start, end) { 10288 var i, result = ''; 10289 for (i = start; i < end; i++) { 10290 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 10291 } 10292 return result; 10293 }, 10294 // return the string representation of the specified byte range, 10295 // interpreted as UTf-8. 10296 parseUtf8 = function(bytes, start, end) { 10297 return decodeURIComponent(percentEncode(bytes, start, end)); 10298 }, 10299 // return the string representation of the specified byte range, 10300 // interpreted as ISO-8859-1. 10301 parseIso88591 = function(bytes, start, end) { 10302 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 10303 }, 10304 parseSyncSafeInteger = function(data) { 10305 return (data[0] << 21) | 10306 (data[1] << 14) | 10307 (data[2] << 7) | 10308 (data[3]); 10309 }, 10310 tagParsers = { 10311 TXXX: function(tag) { 10312 var i; 10313 if (tag.data[0] !== 3) { 10314 // ignore frames with unrecognized character encodings 10315 return; 10316 } 10317 10318 for (i = 1; i < tag.data.length; i++) { 10319 if (tag.data[i] === 0) { 10320 // parse the text fields 10321 tag.description = parseUtf8(tag.data, 1, i); 10322 // do not include the null terminator in the tag value 10323 tag.value = parseUtf8(tag.data, i + 1, tag.data.length - 1); 10324 break; 10325 } 10326 } 10327 tag.data = tag.value; 10328 }, 10329 WXXX: function(tag) { 10330 var i; 10331 if (tag.data[0] !== 3) { 10332 // ignore frames with unrecognized character encodings 10333 return; 10334 } 10335 10336 for (i = 1; i < tag.data.length; i++) { 10337 if (tag.data[i] === 0) { 10338 // parse the description and URL fields 10339 tag.description = parseUtf8(tag.data, 1, i); 10340 tag.url = parseUtf8(tag.data, i + 1, tag.data.length); 10341 break; 10342 } 10343 } 10344 }, 10345 PRIV: function(tag) { 10346 var i; 10347 10348 for (i = 0; i < tag.data.length; i++) { 10349 if (tag.data[i] === 0) { 10350 // parse the description and URL fields 10351 tag.owner = parseIso88591(tag.data, 0, i); 10352 break; 10353 } 10354 } 10355 tag.privateData = tag.data.subarray(i + 1); 10356 tag.data = tag.privateData; 10357 } 10358 }, 10359 MetadataStream; 10360 10361MetadataStream = function(options) { 10362 var 10363 settings = { 10364 debug: !!(options && options.debug), 10365 10366 // the bytes of the program-level descriptor field in MP2T 10367 // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and 10368 // program element descriptors" 10369 descriptor: options && options.descriptor 10370 }, 10371 // the total size in bytes of the ID3 tag being parsed 10372 tagSize = 0, 10373 // tag data that is not complete enough to be parsed 10374 buffer = [], 10375 // the total number of bytes currently in the buffer 10376 bufferSize = 0, 10377 i; 10378 10379 MetadataStream.prototype.init.call(this); 10380 10381 // calculate the text track in-band metadata track dispatch type 10382 // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track 10383 this.dispatchType = StreamTypes.METADATA_STREAM_TYPE.toString(16); 10384 if (settings.descriptor) { 10385 for (i = 0; i < settings.descriptor.length; i++) { 10386 this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2); 10387 } 10388 } 10389 10390 this.push = function(chunk) { 10391 var tag, frameStart, frameSize, frame, i, frameHeader; 10392 if (chunk.type !== 'timed-metadata') { 10393 return; 10394 } 10395 10396 // if data_alignment_indicator is set in the PES header, 10397 // we must have the start of a new ID3 tag. Assume anything 10398 // remaining in the buffer was malformed and throw it out 10399 if (chunk.dataAlignmentIndicator) { 10400 bufferSize = 0; 10401 buffer.length = 0; 10402 } 10403 10404 // ignore events that don't look like ID3 data 10405 if (buffer.length === 0 && 10406 (chunk.data.length < 10 || 10407 chunk.data[0] !== 'I'.charCodeAt(0) || 10408 chunk.data[1] !== 'D'.charCodeAt(0) || 10409 chunk.data[2] !== '3'.charCodeAt(0))) { 10410 if (settings.debug) { 10411 // eslint-disable-next-line no-console 10412 console.log('Skipping unrecognized metadata packet'); 10413 } 10414 return; 10415 } 10416 10417 // add this chunk to the data we've collected so far 10418 10419 buffer.push(chunk); 10420 bufferSize += chunk.data.byteLength; 10421 10422 // grab the size of the entire frame from the ID3 header 10423 if (buffer.length === 1) { 10424 // the frame size is transmitted as a 28-bit integer in the 10425 // last four bytes of the ID3 header. 10426 // The most significant bit of each byte is dropped and the 10427 // results concatenated to recover the actual value. 10428 tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); 10429 10430 // ID3 reports the tag size excluding the header but it's more 10431 // convenient for our comparisons to include it 10432 tagSize += 10; 10433 } 10434 10435 // if the entire frame has not arrived, wait for more data 10436 if (bufferSize < tagSize) { 10437 return; 10438 } 10439 10440 // collect the entire frame so it can be parsed 10441 tag = { 10442 data: new Uint8Array(tagSize), 10443 frames: [], 10444 pts: buffer[0].pts, 10445 dts: buffer[0].dts 10446 }; 10447 for (i = 0; i < tagSize;) { 10448 tag.data.set(buffer[0].data.subarray(0, tagSize - i), i); 10449 i += buffer[0].data.byteLength; 10450 bufferSize -= buffer[0].data.byteLength; 10451 buffer.shift(); 10452 } 10453 10454 // find the start of the first frame and the end of the tag 10455 frameStart = 10; 10456 if (tag.data[5] & 0x40) { 10457 // advance the frame start past the extended header 10458 frameStart += 4; // header size field 10459 frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); 10460 10461 // clip any padding off the end 10462 tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20)); 10463 } 10464 10465 // parse one or more ID3 frames 10466 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 10467 do { 10468 // determine the number of bytes in this frame 10469 frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8)); 10470 if (frameSize < 1) { 10471 // eslint-disable-next-line no-console 10472 return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.'); 10473 } 10474 frameHeader = String.fromCharCode(tag.data[frameStart], 10475 tag.data[frameStart + 1], 10476 tag.data[frameStart + 2], 10477 tag.data[frameStart + 3]); 10478 10479 10480 frame = { 10481 id: frameHeader, 10482 data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10) 10483 }; 10484 frame.key = frame.id; 10485 if (tagParsers[frame.id]) { 10486 tagParsers[frame.id](frame); 10487 10488 // handle the special PRIV frame used to indicate the start 10489 // time for raw AAC data 10490 if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') { 10491 var 10492 d = frame.data, 10493 size = ((d[3] & 0x01) << 30) | 10494 (d[4] << 22) | 10495 (d[5] << 14) | 10496 (d[6] << 6) | 10497 (d[7] >>> 2); 10498 10499 size *= 4; 10500 size += d[7] & 0x03; 10501 frame.timeStamp = size; 10502 // in raw AAC, all subsequent data will be timestamped based 10503 // on the value of this frame 10504 // we couldn't have known the appropriate pts and dts before
10505 // parsing this ID3 tag so set those values now 10506 if (tag.pts === undefined && tag.dts === undefined) { 10507 tag.pts = frame.timeStamp; 10508 tag.dts = frame.timeStamp; 10509 } 10510 this.trigger('timestamp', frame); 10511 } 10512 } 10513 tag.frames.push(frame); 10514 10515 frameStart += 10; // advance past the frame header 10516 frameStart += frameSize; // advance past the frame body 10517 } while (frameStart < tagSize); 10518 this.trigger('data', tag); 10519 }; 10520}; 10521MetadataStream.prototype = new Stream(); 10522 10523module.exports = MetadataStream; 10524 10525},{"../utils/stream":83,"./stream-types":76}],76:[function(require,module,exports){ 10526'use strict'; 10527 10528module.exports = { 10529 H264_STREAM_TYPE: 0x1B, 10530 ADTS_STREAM_TYPE: 0x0F, 10531 METADATA_STREAM_TYPE: 0x15 10532}; 10533 10534},{}],77:[function(require,module,exports){ 10535/** 10536 * mux.js 10537 * 10538 * Copyright (c) 2016 Brightcove 10539 * All rights reserved. 10540 * 10541 * Accepts program elementary stream (PES) data events and corrects 10542 * decode and presentation time stamps to account for a rollover 10543 * of the 33 bit value. 10544 */ 10545 10546'use strict'; 10547 10548var Stream = require('../utils/stream'); 10549 10550var MAX_TS = 8589934592; 10551 10552var RO_THRESH = 4294967296; 10553 10554var handleRollover = function(value, reference) { 10555 var direction = 1; 10556 10557 if (value > reference) { 10558 // If the current timestamp value is greater than our reference timestamp and we detect a 10559 // timestamp rollover, this means the roll over is happening in the opposite direction. 10560 // Example scenario: Enter a long stream/video just after a rollover occurred. The reference 10561 // point will be set to a small number, e.g. 1. The user then seeks backwards over the 10562 // rollover point. In loading this segment, the timestamp values will be very large, 10563 // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust 10564 // the time stamp to be `value - 2^33`. 10565 direction = -1; 10566 } 10567 10568 // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will 10569 // cause an incorrect adjustment. 10570 while (Math.abs(reference - value) > RO_THRESH) { 10571 value += (direction * MAX_TS); 10572 } 10573 10574 return value; 10575}; 10576 10577var TimestampRolloverStream = function(type) { 10578 var lastDTS, referenceDTS; 10579 10580 TimestampRolloverStream.prototype.init.call(this); 10581 10582 this.type_ = type; 10583 10584 this.push = function(data) { 10585 if (data.type !== this.type_) { 10586 return; 10587 } 10588 10589 if (referenceDTS === undefined) { 10590 referenceDTS = data.dts; 10591 } 10592 10593 data.dts = handleRollover(data.dts, referenceDTS); 10594 data.pts = handleRollover(data.pts, referenceDTS); 10595 10596 lastDTS = data.dts; 10597 10598 this.trigger('data', data); 10599 }; 10600 10601 this.flush = function() { 10602 referenceDTS = lastDTS; 10603 this.trigger('done'); 10604 }; 10605 10606}; 10607 10608TimestampRolloverStream.prototype = new Stream(); 10609 10610module.exports = { 10611 TimestampRolloverStream: TimestampRolloverStream, 10612 handleRollover: handleRollover 10613}; 10614 10615},{"../utils/stream":83}],78:[function(require,module,exports){ 10616module.exports = { 10617 generator: require('./mp4-generator'), 10618 Transmuxer: require('./transmuxer').Transmuxer, 10619 AudioSegmentStream: require('./transmuxer').AudioSegmentStream, 10620 VideoSegmentStream: require('./transmuxer').VideoSegmentStream 10621}; 10622 10623},{"./mp4-generator":79,"./transmuxer":81}],79
vendor: 22,118 bytes, lines 10623-11395
10623:[function(require,module,exports){ 10624/** 10625 * mux.js 10626 * 10627 * Copyright (c) 2015 Brightcove 10628 * All rights reserved. 10629 * 10630 * Functions that generate fragmented MP4s suitable for use with Media 10631 * Source Extensions. 10632 */ 10633'use strict'; 10634 10635var UINT32_MAX = Math.pow(2, 32) - 1; 10636 10637var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, 10638 trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, 10639 trun, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, 10640 AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; 10641 10642// pre-calculate constants 10643(function() { 10644 var i; 10645 types = { 10646 avc1: [], // codingname 10647 avcC: [], 10648 btrt: [], 10649 dinf: [], 10650 dref: [], 10651 esds: [], 10652 ftyp: [], 10653 hdlr: [], 10654 mdat: [], 10655 mdhd: [], 10656 mdia: [], 10657 mfhd: [], 10658 minf: [], 10659 moof: [], 10660 moov: [], 10661 mp4a: [], // codingname 10662 mvex: [], 10663 mvhd: [], 10664 sdtp: [], 10665 smhd: [], 10666 stbl: [], 10667 stco: [], 10668 stsc: [], 10669 stsd: [], 10670 stsz: [], 10671 stts: [], 10672 styp: [], 10673 tfdt: [], 10674 tfhd: [], 10675 traf: [], 10676 trak: [], 10677 trun: [], 10678 trex: [], 10679 tkhd: [], 10680 vmhd: [] 10681 }; 10682 10683 // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we 10684 // don't throw an error 10685 if (typeof Uint8Array === 'undefined') { 10686 return; 10687 } 10688 10689 for (i in types) { 10690 if (types.hasOwnProperty(i)) { 10691 types[i] = [ 10692 i.charCodeAt(0), 10693 i.charCodeAt(1), 10694 i.charCodeAt(2), 10695 i.charCodeAt(3) 10696 ]; 10697 } 10698 } 10699 10700 MAJOR_BRAND = new Uint8Array([ 10701 'i'.charCodeAt(0), 10702 's'.charCodeAt(0), 10703 'o'.charCodeAt(0), 10704 'm'.charCodeAt(0) 10705 ]); 10706 AVC1_BRAND = new Uint8Array([ 10707 'a'.charCodeAt(0), 10708 'v'.charCodeAt(0), 10709 'c'.charCodeAt(0), 10710 '1'.charCodeAt(0) 10711 ]); 10712 MINOR_VERSION = new Uint8Array([0, 0, 0, 1]); 10713 VIDEO_HDLR = new Uint8Array([ 10714 0x00, // version 0 10715 0x00, 0x00, 0x00, // flags 10716 0x00, 0x00, 0x00, 0x00, // pre_defined 10717 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' 10718 0x00, 0x00, 0x00, 0x00, // reserved 10719 0x00, 0x00, 0x00, 0x00, // reserved 10720 0x00, 0x00, 0x00, 0x00, // reserved 10721 0x56, 0x69, 0x64, 0x65, 10722 0x6f, 0x48, 0x61, 0x6e, 10723 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' 10724 ]); 10725 AUDIO_HDLR = new Uint8Array([ 10726 0x00, // version 0 10727 0x00, 0x00, 0x00, // flags 10728 0x00, 0x00, 0x00, 0x00, // pre_defined 10729 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun' 10730 0x00, 0x00, 0x00, 0x00, // reserved 10731 0x00, 0x00, 0x00, 0x00, // reserved 10732 0x00, 0x00, 0x00, 0x00, // reserved 10733 0x53, 0x6f, 0x75, 0x6e, 10734 0x64, 0x48, 0x61, 0x6e, 10735 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' 10736 ]); 10737 HDLR_TYPES = { 10738 video: VIDEO_HDLR, 10739 audio: AUDIO_HDLR 10740 }; 10741 DREF = new Uint8Array([ 10742 0x00, // version 0 10743 0x00, 0x00, 0x00, // flags 10744 0x00, 0x00, 0x00, 0x01, // entry_count 10745 0x00, 0x00, 0x00, 0x0c, // entry_size 10746 0x75, 0x72, 0x6c, 0x20, // 'url' type 10747 0x00, // version 0 10748 0x00, 0x00, 0x01 // entry_flags 10749 ]); 10750 SMHD = new Uint8Array([ 10751 0x00, // version 10752 0x00, 0x00, 0x00, // flags 10753 0x00, 0x00, // balance, 0 means centered 10754 0x00, 0x00 // reserved 10755 ]); 10756 STCO = new Uint8Array([ 10757 0x00, // version 10758 0x00, 0x00, 0x00, // flags 10759 0x00, 0x00, 0x00, 0x00 // entry_count 10760 ]); 10761 STSC = STCO; 10762 STSZ = new Uint8Array([ 10763 0x00, // version 10764 0x00, 0x00, 0x00, // flags 10765 0x00, 0x00, 0x00, 0x00, // sample_size 10766 0x00, 0x00, 0x00, 0x00 // sample_count 10767 ]); 10768 STTS = STCO; 10769 VMHD = new Uint8Array([ 10770 0x00, // version 10771 0x00, 0x00, 0x01, // flags 10772 0x00, 0x00, // graphicsmode 10773 0x00, 0x00, 10774 0x00, 0x00, 10775 0x00, 0x00 // opcolor 10776 ]); 10777}()); 10778 10779box = function(type) { 10780 var 10781 payload = [], 10782 size = 0, 10783 i, 10784 result, 10785 view; 10786 10787 for (i = 1; i < arguments.length; i++) { 10788 payload.push(arguments[i]); 10789 } 10790 10791 i = payload.length; 10792 10793 // calculate the total size we need to allocate 10794 while (i--) { 10795 size += payload[i].byteLength; 10796 } 10797 result = new Uint8Array(size + 8); 10798 view = new DataView(result.buffer, result.byteOffset, result.byteLength); 10799 view.setUint32(0, result.byteLength); 10800 result.set(type, 4); 10801 10802 // copy the payload into the result 10803 for (i = 0, size = 8; i < payload.length; i++) { 10804 result.set(payload[i], size); 10805 size += payload[i].byteLength; 10806 } 10807 return result; 10808}; 10809 10810dinf = function() { 10811 return box(types.dinf, box(types.dref, DREF)); 10812}; 10813 10814esds = function(track) { 10815 return box(types.esds, new Uint8Array([ 10816 0x00, // version 10817 0x00, 0x00, 0x00, // flags 10818 10819 // ES_Descriptor 10820 0x03, // tag, ES_DescrTag 10821 0x19, // length 10822 0x00, 0x00, // ES_ID 10823 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority 10824 10825 // DecoderConfigDescriptor 10826 0x04, // tag, DecoderConfigDescrTag 10827 0x11, // length 10828 0x40, // object type 10829 0x15, // streamType 10830 0x00, 0x06, 0x00, // bufferSizeDB 10831 0x00, 0x00, 0xda, 0xc0, // maxBitrate 10832 0x00, 0x00, 0xda, 0xc0, // avgBitrate 10833 10834 // DecoderSpecificInfo 10835 0x05, // tag, DecoderSpecificInfoTag 10836 0x02, // length 10837 // ISO/IEC 14496-3, AudioSpecificConfig 10838 // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35 10839 (track.audioobjecttype << 3) | (track.samplingfrequencyindex >>> 1), 10840 (track.samplingfrequencyindex << 7) | (track.channelcount << 3), 10841 0x06, 0x01, 0x02 // GASpecificConfig 10842 ])); 10843}; 10844 10845ftyp = function() { 10846 return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND); 10847}; 10848 10849hdlr = function(type) { 10850 return box(types.hdlr, HDLR_TYPES[type]); 10851}; 10852mdat = function(data) { 10853 return box(types.mdat, data); 10854}; 10855mdhd = function(track) { 10856 var result = new Uint8Array([ 10857 0x00, // version 0 10858 0x00, 0x00, 0x00, // flags 10859 0x00, 0x00, 0x00, 0x02, // creation_time 10860 0x00, 0x00, 0x00, 0x03, // modification_time 10861 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 10862 10863 (track.duration >>> 24) & 0xFF, 10864 (track.duration >>> 16) & 0xFF, 10865 (track.duration >>> 8) & 0xFF, 10866 track.duration & 0xFF, // duration 10867 0x55, 0xc4, // 'und' language (undetermined) 10868 0x00, 0x00 10869 ]); 10870 10871 // Use the sample rate from the track metadata, when it is 10872 // defined. The sample rate can be parsed out of an ADTS header, for 10873 // instance. 10874 if (track.samplerate) { 10875 result[12] = (track.samplerate >>> 24) & 0xFF; 10876 result[13] = (track.samplerate >>> 16) & 0xFF; 10877 result[14] = (track.samplerate >>> 8) & 0xFF; 10878 result[15] = (track.samplerate) & 0xFF; 10879 } 10880 10881 return box(types.mdhd, result); 10882}; 10883mdia = function(track) { 10884 return box(types.mdia, mdhd(track), hdlr(track.type), minf(track)); 10885}; 10886mfhd = function(sequenceNumber) { 10887 return box(types.mfhd, new Uint8Array([ 10888 0x00, 10889 0x00, 0x00, 0x00, // flags 10890 (sequenceNumber & 0xFF000000) >> 24, 10891 (sequenceNumber & 0xFF0000) >> 16, 10892 (sequenceNumber & 0xFF00) >> 8, 10893 sequenceNumber & 0xFF // sequence_number 10894 ])); 10895}; 10896minf = function(track) { 10897 return box(types.minf, 10898 track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), 10899 dinf(), 10900 stbl(track)); 10901}; 10902moof = function(sequenceNumber, tracks) { 10903 var 10904 trackFragments = [], 10905 i = tracks.length; 10906 // build traf boxes for each track fragment 10907 while (i--) { 10908 trackFragments[i] = traf(tracks[i]); 10909 } 10910 return box.apply(null, [ 10911 types.moof, 10912 mfhd(sequenceNumber) 10913 ].concat(trackFragments)); 10914}; 10915/** 10916 * Returns a movie box. 10917 * @param tracks {array} the tracks associated with this movie 10918 * @see ISO/IEC 14496-12:2012(E), section 8.2.1 10919 */ 10920moov = function(tracks) { 10921 var 10922 i = tracks.length, 10923 boxes = []; 10924 10925 while (i--) { 10926 boxes[i] = trak(tracks[i]); 10927 } 10928 10929 return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks))); 10930}; 10931mvex = function(tracks) { 10932 var 10933 i = tracks.length, 10934 boxes = []; 10935 10936 while (i--) { 10937 boxes[i] = trex(tracks[i]); 10938 } 10939 return box.apply(null, [types.mvex].concat(boxes)); 10940}; 10941mvhd = function(duration) { 10942 var 10943 bytes = new Uint8Array([ 10944 0x00, // version 0 10945 0x00, 0x00, 0x00, // flags 10946 0x00, 0x00, 0x00, 0x01, // creation_time 10947 0x00, 0x00, 0x00, 0x02, // modification_time 10948 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 10949 (duration & 0xFF000000) >> 24, 10950 (duration & 0xFF0000) >> 16, 10951 (duration & 0xFF00) >> 8, 10952 duration & 0xFF, // duration 10953 0x00, 0x01, 0x00, 0x00, // 1.0 rate 10954 0x01, 0x00, // 1.0 volume 10955 0x00, 0x00, // reserved 10956 0x00, 0x00, 0x00, 0x00, // reserved 10957 0x00, 0x00, 0x00, 0x00, // reserved 10958 0x00, 0x01, 0x00, 0x00, 10959 0x00, 0x00, 0x00, 0x00, 10960 0x00, 0x00, 0x00, 0x00, 10961 0x00, 0x00, 0x00, 0x00, 10962 0x00, 0x01, 0x00, 0x00, 10963 0x00, 0x00, 0x00, 0x00, 10964 0x00, 0x00, 0x00, 0x00, 10965 0x00, 0x00, 0x00, 0x00, 10966 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix 10967 0x00, 0x00, 0x00, 0x00, 10968 0x00, 0x00, 0x00, 0x00, 10969 0x00, 0x00, 0x00, 0x00, 10970 0x00, 0x00, 0x00, 0x00, 10971 0x00, 0x00, 0x00, 0x00, 10972 0x00, 0x00, 0x00, 0x00, // pre_defined 10973 0xff, 0xff, 0xff, 0xff // next_track_ID 10974 ]); 10975 return box(types.mvhd, bytes); 10976}; 10977 10978sdtp = function(track) { 10979 var 10980 samples = track.samples || [], 10981 bytes = new Uint8Array(4 + samples.length), 10982 flags, 10983 i; 10984 10985 // leave the full box header (4 bytes) all zero 10986 10987 // write the sample table 10988 for (i = 0; i < samples.length; i++) { 10989 flags = samples[i].flags; 10990 10991 bytes[i + 4] = (flags.dependsOn << 4) | 10992 (flags.isDependedOn << 2) | 10993 (flags.hasRedundancy); 10994 } 10995 10996 return box(types.sdtp, 10997 bytes); 10998}; 10999 11000stbl = function(track) { 11001 return box(types.stbl, 11002 stsd(track), 11003 box(types.stts, STTS), 11004 box(types.stsc, STSC), 11005 box(types.stsz, STSZ), 11006 box(types.stco, STCO)); 11007}; 11008 11009(function() { 11010 var videoSample, audioSample; 11011 11012 stsd = function(track) { 11013 11014 return box(types.stsd, new Uint8Array([ 11015 0x00, // version 0 11016 0x00, 0x00, 0x00, // flags 11017 0x00, 0x00, 0x00, 0x01 11018 ]), track.type === 'video' ? videoSample(track) : audioSample(track)); 11019 }; 11020 11021 videoSample = function(track) { 11022 var 11023 sps = track.sps || [], 11024 pps = track.pps || [], 11025 sequenceParameterSets = [], 11026 pictureParameterSets = [], 11027 i; 11028 11029 // assemble the SPSs 11030 for (i = 0; i < sps.length; i++) { 11031 sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8); 11032 sequenceParameterSets.push((sps[i].byteLength & 0xFF)); // sequenceParameterSetLength 11033 sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS 11034 } 11035 11036 // assemble the PPSs 11037 for (i = 0; i < pps.length; i++) { 11038 pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8); 11039 pictureParameterSets.push((pps[i].byteLength & 0xFF)); 11040 pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i])); 11041 } 11042 11043 return box(types.avc1, new Uint8Array([ 11044 0x00, 0x00, 0x00, 11045 0x00, 0x00, 0x00, // reserved 11046 0x00, 0x01, // data_reference_index 11047 0x00, 0x00, // pre_defined 11048 0x00, 0x00, // reserved 11049 0x00, 0x00, 0x00, 0x00, 11050 0x00, 0x00, 0x00, 0x00, 11051 0x00, 0x00, 0x00, 0x00, // pre_defined 11052 (track.width & 0xff00) >> 8, 11053 track.width & 0xff, // width 11054 (track.height & 0xff00) >> 8, 11055 track.height & 0xff, // height 11056 0x00, 0x48, 0x00, 0x00, // horizresolution 11057 0x00, 0x48, 0x00, 0x00, // vertresolution 11058 0x00, 0x00, 0x00, 0x00, // reserved 11059 0x00, 0x01, // frame_count 11060 0x13, 11061 0x76, 0x69, 0x64, 0x65, 11062 0x6f, 0x6a, 0x73, 0x2d, 11063 0x63, 0x6f, 0x6e, 0x74, 11064 0x72, 0x69, 0x62, 0x2d, 11065 0x68, 0x6c, 0x73, 0x00, 11066 0x00, 0x00, 0x00, 0x00, 11067 0x00, 0x00, 0x00, 0x00, 11068 0x00, 0x00, 0x00, // compressorname 11069 0x00, 0x18, // depth = 24 11070 0x11, 0x11 // pre_defined = -1 11071 ]), box(types.avcC, new Uint8Array([ 11072 0x01, // configurationVersion 11073 track.profileIdc, // AVCProfileIndication 11074 track.profileCompatibility, // profile_compatibility 11075 track.levelIdc, // AVCLevelIndication 11076 0xff // lengthSizeMinusOne, hard-coded to 4 bytes 11077 ].concat([ 11078 sps.length // numOfSequenceParameterSets 11079 ]).concat(sequenceParameterSets).concat([ 11080 pps.length // numOfPictureParameterSets 11081 ]).concat(pictureParameterSets))), // "PPS" 11082 box(types.btrt, new Uint8Array([ 11083 0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB 11084 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate 11085 0x00, 0x2d, 0xc6, 0xc0 11086 ])) // avgBitrate 11087 ); 11088 }; 11089 11090 audioSample = function(track) { 11091 return box(types.mp4a, new Uint8Array([ 11092 11093 // SampleEntry, ISO/IEC 14496-12 11094 0x00, 0x00, 0x00, 11095 0x00, 0x00, 0x00, // reserved 11096 0x00, 0x01, // data_reference_index 11097 11098 // AudioSampleEntry, ISO/IEC 14496-12 11099 0x00, 0x00, 0x00, 0x00, // reserved 11100 0x00, 0x00, 0x00, 0x00, // reserved 11101 (track.channelcount & 0xff00) >> 8, 11102 (track.channelcount & 0xff), // channelcount 11103 11104 (track.samplesize & 0xff00) >> 8, 11105 (track.samplesize & 0xff), // samplesize 11106 0x00, 0x00, // pre_defined 11107 0x00, 0x00, // reserved 11108 11109 (track.samplerate & 0xff00) >> 8, 11110 (track.samplerate & 0xff), 11111 0x00, 0x00 // samplerate, 16.16 11112 11113 // MP4AudioSampleEntry, ISO/IEC 14496-14 11114 ]), esds(track)); 11115 }; 11116}()); 11117 11118tkhd = function(track) { 11119 var result = new Uint8Array([ 11120 0x00, // version 0 11121 0x00, 0x00, 0x07, // flags 11122 0x00, 0x00, 0x00, 0x00, // creation_time 11123 0x00, 0x00, 0x00, 0x00, // modification_time 11124 (track.id & 0xFF000000) >> 24, 11125 (track.id & 0xFF0000) >> 16, 11126 (track.id & 0xFF00) >> 8, 11127 track.id & 0xFF, // track_ID 11128 0x00, 0x00, 0x00, 0x00, // reserved 11129 (track.duration & 0xFF000000) >> 24, 11130 (track.duration & 0xFF0000) >> 16, 11131 (track.duration & 0xFF00) >> 8, 11132 track.duration & 0xFF, // duration 11133 0x00, 0x00, 0x00, 0x00, 11134 0x00, 0x00, 0x00, 0x00, // reserved 11135 0x00, 0x00, // layer 11136 0x00, 0x00, // alternate_group 11137 0x01, 0x00, // non-audio track volume 11138 0x00, 0x00, // reserved 11139 0x00, 0x01, 0x00, 0x00, 11140 0x00, 0x00, 0x00, 0x00, 11141 0x00, 0x00, 0x00, 0x00, 11142 0x00, 0x00, 0x00, 0x00, 11143 0x00, 0x01, 0x00, 0x00, 11144 0x00, 0x00, 0x00, 0x00, 11145 0x00, 0x00, 0x00, 0x00, 11146 0x00, 0x00, 0x00, 0x00, 11147 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix 11148 (track.width & 0xFF00) >> 8, 11149 track.width & 0xFF, 11150 0x00, 0x00, // width 11151 (track.height & 0xFF00) >> 8, 11152 track.height & 0xFF, 11153 0x00, 0x00 // height 11154 ]); 11155 11156 return box(types.tkhd, result); 11157}; 11158 11159/** 11160 * Generate a track fragment (traf) box. A traf box collects metadata 11161 * about tracks in a movie fragment (moof) box. 11162 */ 11163traf = function(track) { 11164 var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, 11165 sampleDependencyTable, dataOffset, 11166 upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime; 11167 11168 trackFragmentHeader = box(types.tfhd, new Uint8Array([ 11169 0x00, // version 0 11170 0x00, 0x00, 0x3a, // flags 11171 (track.id & 0xFF000000) >> 24, 11172 (track.id & 0xFF0000) >> 16, 11173 (track.id & 0xFF00) >> 8, 11174 (track.id & 0xFF), // track_ID 11175 0x00, 0x00, 0x00, 0x01, // sample_description_index 11176 0x00, 0x00, 0x00, 0x00, // default_sample_duration 11177 0x00, 0x00, 0x00, 0x00, // default_sample_size 11178 0x00, 0x00, 0x00, 0x00 // default_sample_flags 11179 ])); 11180 11181 upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1)); 11182 lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1)); 11183 11184 trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([ 11185 0x01, // version 1 11186 0x00, 0x00, 0x00, // flags 11187 // baseMediaDecodeTime 11188 (upperWordBaseMediaDecodeTime >>> 24) & 0xFF, 11189 (upperWordBaseMediaDecodeTime >>> 16) & 0xFF, 11190 (upperWordBaseMediaDecodeTime >>> 8) & 0xFF, 11191 upperWordBaseMediaDecodeTime & 0xFF, 11192 (lowerWordBaseMediaDecodeTime >>> 24) & 0xFF, 11193 (lowerWordBaseMediaDecodeTime >>> 16) & 0xFF, 11194 (lowerWordBaseMediaDecodeTime >>> 8) & 0xFF, 11195 lowerWordBaseMediaDecodeTime & 0xFF 11196 ])); 11197 11198 // the data offset specifies the number of bytes from the start of 11199 // the containing moof to the first payload byte of the associated 11200 // mdat 11201 dataOffset = (32 + // tfhd 11202 20 + // tfdt 11203 8 + // traf header 11204 16 + // mfhd 11205 8 + // moof header 11206 8); // mdat header 11207 11208 // audio tracks require less metadata 11209 if (track.type === 'audio') { 11210 trackFragmentRun = trun(track, dataOffset); 11211 return box(types.traf, 11212 trackFragmentHeader, 11213 trackFragmentDecodeTime, 11214 trackFragmentRun); 11215 } 11216 11217 // video tracks should contain an independent and disposable samples 11218 // box (sdtp) 11219 // generate one and adjust offsets to match 11220 sampleDependencyTable = sdtp(track); 11221 trackFragmentRun = trun(track, 11222 sampleDependencyTable.length + dataOffset); 11223 return box(types.traf, 11224 trackFragmentHeader, 11225 trackFragmentDecodeTime, 11226 trackFragmentRun, 11227 sampleDependencyTable); 11228}; 11229 11230/** 11231 * Generate a track box. 11232 * @param track {object} a track definition 11233 * @return {Uint8Array} the track box 11234 */ 11235trak = function(track) { 11236 track.duration = track.duration || 0xffffffff; 11237 return box(types.trak, 11238 tkhd(track), 11239 mdia(track)); 11240}; 11241 11242trex = function(track) { 11243 var result = new Uint8Array([ 11244 0x00, // version 0 11245 0x00, 0x00, 0x00, // flags 11246 (track.id & 0xFF000000) >> 24, 11247 (track.id & 0xFF0000) >> 16, 11248 (track.id & 0xFF00) >> 8, 11249 (track.id & 0xFF), // track_ID 11250 0x00, 0x00, 0x00, 0x01, // default_sample_description_index 11251 0x00, 0x00, 0x00, 0x00, // default_sample_duration 11252 0x00, 0x00, 0x00, 0x00, // default_sample_size 11253 0x00, 0x01, 0x00, 0x01 // default_sample_flags 11254 ]); 11255 // the last two bytes of default_sample_flags is the sample 11256 // degradation priority, a hint about the importance of this sample 11257 // relative to others. Lower the degradation priority for all sample 11258 // types other than video. 11259 if (track.type !== 'video') { 11260 result[result.length - 1] = 0x00; 11261 } 11262 11263 return box(types.trex, result); 11264}; 11265 11266(function() { 11267 var audioTrun, videoTrun, trunHeader; 11268 11269 // This method assumes all samples are uniform. That is, if a 11270 // duration is present for the first sample, it will be present for 11271 // all subsequent samples. 11272 // see ISO/IEC 14496-12:2012, Section 8.8.8.1 11273 trunHeader = function(samples, offset) { 11274 var durationPresent = 0, sizePresent = 0, 11275 flagsPresent = 0, compositionTimeOffset = 0; 11276 11277 // trun flag constants 11278 if (samples.length) { 11279 if (samples[0].duration !== undefined) { 11280 durationPresent = 0x1; 11281 } 11282 if (samples[0].size !== undefined) { 11283 sizePresent = 0x2; 11284 } 11285 if (samples[0].flags !== undefined) { 11286 flagsPresent = 0x4; 11287 } 11288 if (samples[0].compositionTimeOffset !== undefined) { 11289 compositionTimeOffset = 0x8; 11290 } 11291 } 11292 11293 return [ 11294 0x00, // version 0 11295 0x00, 11296 durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 11297 0x01, // flags 11298 (samples.length & 0xFF000000) >>> 24, 11299 (samples.length & 0xFF0000) >>> 16, 11300 (samples.length & 0xFF00) >>> 8, 11301 samples.length & 0xFF, // sample_count 11302 (offset & 0xFF000000) >>> 24, 11303 (offset & 0xFF0000) >>> 16, 11304 (offset & 0xFF00) >>> 8, 11305 offset & 0xFF // data_offset 11306 ]; 11307 }; 11308 11309 videoTrun = function(track, offset) { 11310 var bytes, samples, sample, i; 11311 11312 samples = track.samples || []; 11313 offset += 8 + 12 + (16 * samples.length); 11314 11315 bytes = trunHeader(samples, offset); 11316 11317 for (i = 0; i < samples.length; i++) { 11318 sample = samples[i]; 11319 bytes = bytes.concat([ 11320 (sample.duration & 0xFF000000) >>> 24, 11321 (sample.duration & 0xFF0000) >>> 16, 11322 (sample.duration & 0xFF00) >>> 8, 11323 sample.duration & 0xFF, // sample_duration 11324 (sample.size & 0xFF000000) >>> 24, 11325 (sample.size & 0xFF0000) >>> 16, 11326 (sample.size & 0xFF00) >>> 8, 11327 sample.size & 0xFF, // sample_size 11328 (sample.flags.isLeading << 2) | sample.flags.dependsOn, 11329 (sample.flags.isDependedOn << 6) | 11330 (sample.flags.hasRedundancy << 4) | 11331 (sample.flags.paddingValue << 1) | 11332 sample.flags.isNonSyncSample, 11333 sample.flags.degradationPriority & 0xF0 << 8, 11334 sample.flags.degradationPriority & 0x0F, // sample_flags 11335 (sample.compositionTimeOffset & 0xFF000000) >>> 24, 11336 (sample.compositionTimeOffset & 0xFF0000) >>> 16, 11337 (sample.compositionTimeOffset & 0xFF00) >>> 8, 11338 sample.compositionTimeOffset & 0xFF // sample_composition_time_offset 11339 ]); 11340 } 11341 return box(types.trun, new Uint8Array(bytes)); 11342 }; 11343 11344 audioTrun = function(track, offset) { 11345 var bytes, samples, sample, i; 11346 11347 samples = track.samples || []; 11348 offset += 8 + 12 + (8 * samples.length); 11349 11350 bytes = trunHeader(samples, offset); 11351 11352 for (i = 0; i < samples.length; i++) { 11353 sample = samples[i]; 11354 bytes = bytes.concat([ 11355 (sample.duration & 0xFF000000) >>> 24, 11356 (sample.duration & 0xFF0000) >>> 16, 11357 (sample.duration & 0xFF00) >>> 8, 11358 sample.duration & 0xFF, // sample_duration 11359 (sample.size & 0xFF000000) >>> 24, 11360 (sample.size & 0xFF0000) >>> 16, 11361 (sample.size & 0xFF00) >>> 8, 11362 sample.size & 0xFF]); // sample_size 11363 } 11364 11365 return box(types.trun, new Uint8Array(bytes)); 11366 }; 11367 11368 trun = function(track, offset) { 11369 if (track.type === 'audio') { 11370 return audioTrun(track, offset); 11371 } 11372 11373 return videoTrun(track, offset); 11374 }; 11375}()); 11376 11377module.exports = { 11378 ftyp: ftyp, 11379 mdat: mdat, 11380 moof: moof, 11381 moov: moov, 11382 initSegment: function(tracks) { 11383 var 11384 fileType = ftyp(), 11385 movie = moov(tracks), 11386 result; 11387 11388 result = new Uint8Array(fileType.byteLength + movie.byteLength); 11389 result.set(fileType); 11390 result.set(movie, fileType.byteLength); 11391 return result; 11392 } 11393}; 11394 11395},{}],
1139580:[function(require,module,exports){ 11396/** 11397 * mux.js 11398 * 11399 * Copyright (c) 2015 Brightcove 11400 * All rights reserved. 11401 * 11402 * Utilities to detect basic properties and metadata about MP4s. 11403 */ 11404'use strict'; 11405 11406var findBox, parseType, timescale, startTime; 11407 11408// Find the data for a box specified by its path 11409findBox = function(data, path) { 11410 var results = [], 11411 i, size, type, end, subresults; 11412 11413 if (!path.length) { 11414 // short-circuit the search for empty paths 11415 return null; 11416 } 11417 11418 for (i = 0; i < data.byteLength;) { 11419 size = data[i] << 24; 11420 size |= data[i + 1] << 16; 11421 size |= data[i + 2] << 8; 11422 size |= data[i + 3]; 11423 11424 type = parseType(data.subarray(i + 4, i + 8)); 11425 11426 end = size > 1 ? i + size : data.byteLength; 11427 11428 if (type === path[0]) { 11429 if (path.length === 1) { 11430 // this is the end of the path and we've found the box we were 11431 // looking for 11432 results.push(data.subarray(i + 8, end)); 11433 } else { 11434 // recursively search for the next box along the path 11435 subresults = findBox(data.subarray(i + 8, end), path.slice(1)); 11436 if (subresults.length) { 11437 results = results.concat(subresults); 11438 } 11439 } 11440 } 11441 i = end; 11442 } 11443 11444 // we've finished searching all of data 11445 return results; 11446}; 11447 11448/** 11449 * Returns the string representation of an ASCII encoded four byte buffer. 11450 * @param buffer {Uint8Array} a four-byte buffer to translate 11451 * @return {string} the corresponding string 11452 */ 11453parseType = function(buffer) { 11454 var result = ''; 11455 result += String.fromCharCode(buffer[0]); 11456 result += String.fromCharCode(buffer[1]); 11457 result += String.fromCharCode(buffer[2]); 11458 result += String.fromCharCode(buffer[3]); 11459 return result; 11460}; 11461 11462/** 11463 * Parses an MP4 initialization segment and extracts the timescale 11464 * values for any declared tracks. Timescale values indicate the 11465 * number of clock ticks per second to assume for time-based values 11466 * elsewhere in the MP4. 11467 * 11468 * To determine the start time of an MP4, you need two pieces of 11469 * information: the timescale unit and the earliest base media decode 11470 * time. Multiple timescales can be specified within an MP4 but the 11471 * base media decode time is always expressed in the timescale from 11472 * the media header box for the track: 11473 * ``` 11474 * moov > trak > mdia > mdhd.timescale 11475 * ``` 11476 * @param init {Uint8Array} the bytes of the init segment 11477 * @return {object} a hash of track ids to timescale values or null if 11478 * the init segment is malformed. 11479 */ 11480timescale = function(init) { 11481 var 11482 result = {}, 11483 traks = findBox(init, ['moov', 'trak']); 11484 11485 // mdhd timescale 11486 return traks.reduce(function(result, trak) { 11487 var tkhd, version, index, id, mdhd; 11488 11489 tkhd = findBox(trak, ['tkhd'])[0]; 11490 if (!tkhd) { 11491 return null; 11492 } 11493 version = tkhd[0]; 11494 index = version === 0 ? 12 : 20; 11495 id = tkhd[index] << 24 | 11496 tkhd[index + 1] << 16 | 11497 tkhd[index + 2] << 8 | 11498 tkhd[index + 3]; 11499 11500 mdhd = findBox(trak, ['mdia', 'mdhd'])[0]; 11501 if (!mdhd) { 11502 return null; 11503 } 11504 version = mdhd[0]; 11505 index = version === 0 ? 12 : 20; 11506 result[id] = mdhd[index] << 24 | 11507 mdhd[index + 1] << 16 | 11508 mdhd[index + 2] << 8 | 11509 mdhd[index + 3]; 11510 return result; 11511 }, result); 11512}; 11513 11514/** 11515 * Determine the base media decode start time, in seconds, for an MP4 11516 * fragment. If multiple fragments are specified, the earliest time is 11517 * returned. 11518 * 11519 * The base media decode time can be parsed from track fragment 11520 * metadata: 11521 * ``` 11522 * moof > traf > tfdt.baseMediaDecodeTime 11523 * ``` 11524 * It requires the timescale value from the mdhd to interpret. 11525 * 11526 * @param timescale {object} a hash of track ids to timescale values. 11527 * @return {number} the earliest base media decode start time for the 11528 * fragment, in seconds 11529 */ 11530startTime = function(timescale, fragment) { 11531 var trafs, baseTimes, result; 11532 11533 // we need info from two childrend of each track fragment box 11534 trafs = findBox(fragment, ['moof', 'traf']); 11535 11536 // determine the start times for each track 11537 baseTimes = [].concat.apply([], trafs.map(function(traf) { 11538 return findBox(traf, ['tfhd']).map(function(tfhd) { 11539 var id, scale, baseTime; 11540 11541 // get the track id from the tfhd 11542 id = tfhd[4] << 24 | 11543 tfhd[5] << 16 | 11544 tfhd[6] << 8 | 11545 tfhd[7]; 11546 // assume a 90kHz clock if no timescale was specified 11547 scale = timescale[id] || 90e3; 11548 11549 // get the base media decode time from the tfdt 11550 baseTime = findBox(traf, ['tfdt']).map(function(tfdt) { 11551 var version, result; 11552 11553 version = tfdt[0]; 11554 result = tfdt[4] << 24 | 11555 tfdt[5] << 16 | 11556 tfdt[6] << 8 | 11557 tfdt[7]; 11558 if (version === 1) { 11559 result *= Math.pow(2, 32); 11560 result += tfdt[8] << 24 | 11561 tfdt[9] << 16 | 11562 tfdt[10] << 8 | 11563 tfdt[11]; 11564 } 11565 return result; 11566 })[0]; 11567 baseTime = baseTime || Infinity; 11568 11569 // convert base time to seconds 11570 return baseTime / scale; 11571 }); 11572 })); 11573 11574 // return the minimum 11575 result = Math.min.apply(null, baseTimes); 11576 return isFinite(result) ? result : 0; 11577}; 11578 11579module.exports = { 11580 parseType: parseType, 11581 timescale: timescale, 11582 startTime: startTime 11583}; 11584 11585},{}],81:[function(require,module,exports){ 11586/** 11587 * mux.js 11588 * 11589 * Copyright (c) 2015 Brightcove 11590 * All rights reserved. 11591 * 11592 * A stream-based mp2t to mp4 converter. This utility can be used to 11593 * deliver mp4s to a SourceBuffer on platforms that support native 11594 * Media Source Extensions. 11595 */ 11596'use strict'; 11597 11598var Stream = require('../utils/stream.js'); 11599var mp4 = require('./mp4-generator.js'); 11600var m2ts = require('../m2ts/m2ts.js'); 11601var AdtsStream = require('../codecs/adts.js'); 11602var H264Stream = require('../codecs/h264').H264Stream; 11603var AacStream = require('../aac'); 11604 11605// constants 11606var AUDIO_PROPERTIES = [ 11607 'audioobjecttype', 11608 'channelcount', 11609 'samplerate', 11610 'samplingfrequencyindex', 11611 'samplesize' 11612]; 11613 11614var VIDEO_PROPERTIES = [ 11615 'width', 11616 'height', 11617 'profileIdc', 11618 'levelIdc', 11619 'profileCompatibility' 11620]; 11621 11622// object types 11623var VideoSegmentStream, AudioSegmentStream, Transmuxer, CoalesceStream; 11624 11625// Helper functions 11626var 11627 createDefaultSample, 11628 isLikelyAacData, 11629 collectDtsInfo, 11630 clearDtsInfo, 11631 calculateTrackBaseMediaDecodeTime, 11632 arrayEquals, 11633 sumFrameByteLengths; 11634 11635/** 11636 * Default sample object 11637 * see ISO/IEC 14496-12:2012, section 8.6.4.3 11638 */ 11639createDefaultSample = function() { 11640 return { 11641 size: 0, 11642 flags: { 11643 isLeading: 0, 11644 dependsOn: 1, 11645 isDependedOn: 0, 11646 hasRedundancy: 0, 11647 degradationPriority: 0 11648 } 11649 }; 11650}; 11651 11652isLikelyAacData = function(data) { 11653 if ((data[0] === 'I'.charCodeAt(0)) && 11654 (data[1] === 'D'.charCodeAt(0)) && 11655 (data[2] === '3'.charCodeAt(0))) { 11656 return true; 11657 } 11658 return false;
vendor: 38,330 bytes, lines 11659-12888
11659}; 11660 11661/** 11662 * Compare two arrays (even typed) for same-ness 11663 */ 11664arrayEquals = function(a, b) { 11665 var 11666 i; 11667 11668 if (a.length !== b.length) { 11669 return false; 11670 } 11671 11672 // compare the value of each element in the array 11673 for (i = 0; i < a.length; i++) { 11674 if (a[i] !== b[i]) { 11675 return false; 11676 } 11677 } 11678 11679 return true; 11680}; 11681 11682/** 11683 * Sum the `byteLength` properties of the data in each AAC frame 11684 */ 11685sumFrameByteLengths = function(array) { 11686 var 11687 i, 11688 currentObj, 11689 sum = 0; 11690 11691 // sum the byteLength's all each nal unit in the frame 11692 for (i = 0; i < array.length; i++) { 11693 currentObj = array[i]; 11694 sum += currentObj.data.byteLength; 11695 } 11696 11697 return sum; 11698}; 11699 11700/** 11701 * Constructs a single-track, ISO BMFF media segment from AAC data 11702 * events. The output of this stream can be fed to a SourceBuffer 11703 * configured with a suitable initialization segment. 11704 */ 11705AudioSegmentStream = function(track) { 11706 var 11707 adtsFrames = [], 11708 sequenceNumber = 0, 11709 earliestAllowedDts = 0; 11710 11711 AudioSegmentStream.prototype.init.call(this); 11712 11713 this.push = function(data) { 11714 collectDtsInfo(track, data); 11715 11716 if (track) { 11717 AUDIO_PROPERTIES.forEach(function(prop) { 11718 track[prop] = data[prop]; 11719 }); 11720 } 11721 11722 // buffer audio data until end() is called 11723 adtsFrames.push(data); 11724 }; 11725 11726 this.setEarliestDts = function(earliestDts) { 11727 earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime; 11728 }; 11729 11730 this.flush = function() { 11731 var 11732 frames, 11733 moof, 11734 mdat, 11735 boxes; 11736 11737 // return early if no audio data has been observed 11738 if (adtsFrames.length === 0) { 11739 this.trigger('done', 'AudioSegmentStream'); 11740 return; 11741 } 11742 11743 frames = this.trimAdtsFramesByEarliestDts_(adtsFrames); 11744 11745 // we have to build the index from byte locations to 11746 // samples (that is, adts frames) in the audio data 11747 track.samples = this.generateSampleTable_(frames); 11748 11749 // concatenate the audio data to constuct the mdat 11750 mdat = mp4.mdat(this.concatenateFrameData_(frames)); 11751 11752 adtsFrames = []; 11753 11754 calculateTrackBaseMediaDecodeTime(track); 11755 moof = mp4.moof(sequenceNumber, [track]); 11756 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); 11757 11758 // bump the sequence number for next time 11759 sequenceNumber++; 11760 11761 boxes.set(moof); 11762 boxes.set(mdat, moof.byteLength); 11763 11764 clearDtsInfo(track); 11765 11766 this.trigger('data', {track: track, boxes: boxes}); 11767 this.trigger('done', 'AudioSegmentStream'); 11768 }; 11769 11770 // If the audio segment extends before the earliest allowed dts 11771 // value, remove AAC frames until starts at or after the earliest 11772 // allowed DTS so that we don't end up with a negative baseMedia- 11773 // DecodeTime for the audio track 11774 this.trimAdtsFramesByEarliestDts_ = function(adtsFrames) { 11775 if (track.minSegmentDts >= earliestAllowedDts) { 11776 return adtsFrames; 11777 } 11778 11779 // We will need to recalculate the earliest segment Dts 11780 track.minSegmentDts = Infinity; 11781 11782 return adtsFrames.filter(function(currentFrame) { 11783 // If this is an allowed frame, keep it and record it's Dts 11784 if (currentFrame.dts >= earliestAllowedDts) { 11785 track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts); 11786 track.minSegmentPts = track.minSegmentDts; 11787 return true; 11788 } 11789 // Otherwise, discard it 11790 return false; 11791 }); 11792 }; 11793 11794 // generate the track's raw mdat data from an array of frames 11795 this.generateSampleTable_ = function(frames) { 11796 var 11797 i, 11798 currentFrame, 11799 samples = []; 11800 11801 for (i = 0; i < frames.length; i++) { 11802 currentFrame = frames[i]; 11803 samples.push({ 11804 size: currentFrame.data.byteLength, 11805 duration: 1024 // For AAC audio, all samples contain 1024 samples 11806 }); 11807 } 11808 return samples; 11809 }; 11810 11811 // generate the track's sample table from an array of frames 11812 this.concatenateFrameData_ = function(frames) { 11813 var 11814 i, 11815 currentFrame, 11816 dataOffset = 0, 11817 data = new Uint8Array(sumFrameByteLengths(frames)); 11818 11819 for (i = 0; i < frames.length; i++) { 11820 currentFrame = frames[i]; 11821 11822 data.set(currentFrame.data, dataOffset); 11823 dataOffset += currentFrame.data.byteLength; 11824 } 11825 return data; 11826 }; 11827}; 11828 11829AudioSegmentStream.prototype = new Stream(); 11830 11831/** 11832 * Constructs a single-track, ISO BMFF media segment from H264 data 11833 * events. The output of this stream can be fed to a SourceBuffer 11834 * configured with a suitable initialization segment. 11835 * @param track {object} track metadata configuration 11836 */ 11837VideoSegmentStream = function(track) { 11838 var 11839 sequenceNumber = 0, 11840 nalUnits = [], 11841 config, 11842 pps; 11843 11844 VideoSegmentStream.prototype.init.call(this); 11845 11846 delete track.minPTS; 11847 11848 this.gopCache_ = []; 11849 11850 this.push = function(nalUnit) { 11851 collectDtsInfo(track, nalUnit); 11852 11853 // record the track config 11854 if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) { 11855 config = nalUnit.config; 11856 track.sps = [nalUnit.data]; 11857 11858 VIDEO_PROPERTIES.forEach(function(prop) { 11859 track[prop] = config[prop]; 11860 }, this); 11861 } 11862 11863 if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && 11864 !pps) { 11865 pps = nalUnit.data; 11866 track.pps = [nalUnit.data]; 11867 } 11868 11869 // buffer video until flush() is called 11870 nalUnits.push(nalUnit); 11871 }; 11872 11873 this.flush = function() { 11874 var 11875 frames, 11876 gopForFusion, 11877 gops, 11878 moof, 11879 mdat, 11880 boxes; 11881 11882 // Throw away nalUnits at the start of the byte stream until 11883 // we find the first AUD 11884 while (nalUnits.length) { 11885 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { 11886 break; 11887 } 11888 nalUnits.shift(); 11889 } 11890 11891 // Return early if no video data has been observed 11892 if (nalUnits.length === 0) { 11893 this.resetStream_(); 11894 this.trigger('done', 'VideoSegmentStream'); 11895 return; 11896 } 11897 11898 // Organize the raw nal-units into arrays that represent 11899 // higher-level constructs such as frames and gops 11900 // (group-of-pictures) 11901 frames = this.groupNalsIntoFrames_(nalUnits); 11902 gops = this.groupFramesIntoGops_(frames); 11903 11904 // If the first frame of this fragment is not a keyframe we have 11905 // a problem since MSE (on Chrome) requires a leading keyframe. 11906 // 11907 // We have two approaches to repairing this situation: 11908 // 1) GOP-FUSION: 11909 // This is where we keep track of the GOPS (group-of-pictures) 11910 // from previous fragments and attempt to find one that we can 11911 // prepend to the current fragment in order to create a valid 11912 // fragment. 11913 // 2) KEYFRAME-PULLING: 11914 // Here we search for the first keyframe in the fragment and 11915 // throw away all the frames between the start of the fragment 11916 // and that keyframe. We then extend the duration and pull the 11917 // PTS of the keyframe forward so that it covers the time range 11918 // of the frames that were disposed of. 11919 // 11920 // #1 is far prefereable over #2 which can cause "stuttering" but 11921 // requires more things to be just right. 11922 if (!gops[0][0].keyFrame) { 11923 // Search for a gop for fusion from our gopCache 11924 gopForFusion = this.getGopForFusion_(nalUnits[0], track); 11925 11926 if (gopForFusion) { 11927 gops.unshift(gopForFusion); 11928 // Adjust Gops' metadata to account for the inclusion of the 11929 // new gop at the beginning 11930 gops.byteLength += gopForFusion.byteLength; 11931 gops.nalCount += gopForFusion.nalCount; 11932 gops.pts = gopForFusion.pts; 11933 gops.dts = gopForFusion.dts; 11934 gops.duration += gopForFusion.duration; 11935 } else { 11936 // If we didn't find a candidate gop fall back to keyrame-pulling 11937 gops = this.extendFirstKeyFrame_(gops); 11938 } 11939 } 11940 collectDtsInfo(track, gops); 11941 11942 // First, we have to build the index from byte locations to 11943 // samples (that is, frames) in the video data 11944 track.samples = this.generateSampleTable_(gops); 11945 11946 // Concatenate the video data and construct the mdat 11947 mdat = mp4.mdat(this.concatenateNalData_(gops)); 11948 11949 // save all the nals in the last GOP into the gop cache 11950 this.gopCache_.unshift({ 11951 gop: gops.pop(), 11952 pps: track.pps, 11953 sps: track.sps 11954 }); 11955 11956 // Keep a maximum of 6 GOPs in the cache 11957 this.gopCache_.length = Math.min(6, this.gopCache_.length); 11958 11959 // Clear nalUnits 11960 nalUnits = []; 11961 11962 calculateTrackBaseMediaDecodeTime(track); 11963 11964 this.trigger('timelineStartInfo', track.timelineStartInfo); 11965 11966 moof = mp4.moof(sequenceNumber, [track]); 11967 11968 // it would be great to allocate this array up front instead of 11969 // throwing away hundreds of media segment fragments 11970 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); 11971 11972 // Bump the sequence number for next time 11973 sequenceNumber++; 11974 11975 boxes.set(moof); 11976 boxes.set(mdat, moof.byteLength); 11977 11978 this.trigger('data', {track: track, boxes: boxes}); 11979 11980 this.resetStream_(); 11981 11982 // Continue with the flush process now 11983 this.trigger('done', 'VideoSegmentStream'); 11984 }; 11985 11986 this.resetStream_ = function() { 11987 clearDtsInfo(track); 11988 11989 // reset config and pps because they may differ across segments 11990 // for instance, when we are rendition switching 11991 config = undefined; 11992 pps = undefined; 11993 }; 11994 11995 // Search for a candidate Gop for gop-fusion from the gop cache and 11996 // return it or return null if no good candidate was found 11997 this.getGopForFusion_ = function(nalUnit) { 11998 var 11999 halfSecond = 45000, // Half-a-second in a 90khz clock 12000 allowableOverlap = 10000, // About 3 frames @ 30fps 12001 nearestDistance = Infinity, 12002 dtsDistance, 12003 nearestGopObj, 12004 currentGop, 12005 currentGopObj, 12006 i; 12007 12008 // Search for the GOP nearest to the beginning of this nal unit 12009 for (i = 0; i < this.gopCache_.length; i++) { 12010 currentGopObj = this.gopCache_[i]; 12011 currentGop = currentGopObj.gop; 12012 12013 // Reject Gops with different SPS or PPS 12014 if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || 12015 !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) { 12016 continue; 12017 } 12018 12019 // Reject Gops that would require a negative baseMediaDecodeTime 12020 if (currentGop.dts < track.timelineStartInfo.dts) { 12021 continue; 12022 } 12023 12024 // The distance between the end of the gop and the start of the nalUnit 12025 dtsDistance = (nalUnit.dts - currentGop.dts) - currentGop.duration; 12026 12027 // Only consider GOPS that start before the nal unit and end within 12028 // a half-second of the nal unit 12029 if (dtsDistance >= -allowableOverlap && 12030 dtsDistance <= halfSecond) { 12031 12032 // Always use the closest GOP we found if there is more than 12033 // one candidate 12034 if (!nearestGopObj || 12035 nearestDistance > dtsDistance) { 12036 nearestGopObj = currentGopObj; 12037 nearestDistance = dtsDistance; 12038 } 12039 } 12040 } 12041 12042 if (nearestGopObj) { 12043 return nearestGopObj.gop; 12044 } 12045 return null; 12046 }; 12047 12048 this.extendFirstKeyFrame_ = function(gops) { 12049 var currentGop; 12050 12051 if (!gops[0][0].keyFrame) { 12052 // Remove the first GOP 12053 currentGop = gops.shift(); 12054 12055 gops.byteLength -= currentGop.byteLength; 12056 gops.nalCount -= currentGop.nalCount; 12057 12058 // Extend the first frame of what is now the 12059 // first gop to cover the time period of the 12060 // frames we just removed 12061 gops[0][0].dts = currentGop.dts; 12062 gops[0][0].pts = currentGop.pts; 12063 gops[0][0].duration += currentGop.duration; 12064 } 12065 12066 return gops; 12067 }; 12068 12069 // Convert an array of nal units into an array of frames with each frame being 12070 // composed of the nal units that make up that frame 12071 // Also keep track of cummulative data about the frame from the nal units such 12072 // as the frame duration, starting pts, etc. 12073 this.groupNalsIntoFrames_ = function(nalUnits) { 12074 var 12075 i, 12076 currentNal, 12077 currentFrame = [], 12078 frames = []; 12079 12080 currentFrame.byteLength = 0; 12081 12082 for (i = 0; i < nalUnits.length; i++) { 12083 currentNal = nalUnits[i]; 12084 12085 // Split on 'aud'-type nal units 12086 if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { 12087 // Since the very first nal unit is expected to be an AUD 12088 // only push to the frames array when currentFrame is not empty 12089 if (currentFrame.length) { 12090 currentFrame.duration = currentNal.dts - currentFrame.dts; 12091 frames.push(currentFrame); 12092 } 12093 currentFrame = [currentNal]; 12094 currentFrame.byteLength = currentNal.data.byteLength; 12095 currentFrame.pts = currentNal.pts; 12096 currentFrame.dts = currentNal.dts; 12097 } else { 12098 // Specifically flag key frames for ease of use later 12099 if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { 12100 currentFrame.keyFrame = true; 12101 } 12102 currentFrame.duration = currentNal.dts - currentFrame.dts; 12103 currentFrame.byteLength += currentNal.data.byteLength; 12104 currentFrame.push(currentNal); 12105 } 12106 } 12107 12108 // For the last frame, use the duration of the previous frame if we 12109 // have nothing better to go on 12110 if (frames.length && 12111 (!currentFrame.duration || 12112 currentFrame.duration <= 0)) { 12113 currentFrame.duration = frames[frames.length - 1].duration; 12114 } 12115 12116 // Push the final frame 12117 frames.push(currentFrame); 12118 return frames; 12119 }; 12120 12121 // Convert an array of frames into an array of Gop with each Gop being composed 12122 // of the frames that make up that Gop 12123 // Also keep track of cummulative data about the Gop from the frames such as the 12124 // Gop duration, starting pts, etc. 12125 this.groupFramesIntoGops_ = function(frames) { 12126 var 12127 i, 12128 currentFrame, 12129 currentGop = [], 12130 gops = []; 12131 12132 // We must pre-set some of the values on the Gop since we 12133 // keep running totals of these values 12134 currentGop.byteLength = 0; 12135 currentGop.nalCount = 0; 12136 currentGop.duration = 0; 12137 currentGop.pts = frames[0].pts; 12138 currentGop.dts = frames[0].dts; 12139 12140 // store some metadata about all the Gops 12141 gops.byteLength = 0; 12142 gops.nalCount = 0; 12143 gops.duration = 0; 12144 gops.pts = frames[0].pts; 12145 gops.dts = frames[0].dts; 12146 12147 for (i = 0; i < frames.length; i++) { 12148 currentFrame = frames[i]; 12149 12150 if (currentFrame.keyFrame) { 12151 // Since the very first frame is expected to be an keyframe 12152 // only push to the gops array when currentGop is not empty 12153 if (currentGop.length) { 12154 gops.push(currentGop); 12155 gops.byteLength += currentGop.byteLength; 12156 gops.nalCount += currentGop.nalCount; 12157 gops.duration += currentGop.duration; 12158 } 12159 12160 currentGop = [currentFrame]; 12161 currentGop.nalCount = currentFrame.length; 12162 currentGop.byteLength = currentFrame.byteLength; 12163 currentGop.pts = currentFrame.pts; 12164 currentGop.dts = currentFrame.dts; 12165 currentGop.duration = currentFrame.duration; 12166 } else { 12167 currentGop.duration += currentFrame.duration; 12168 currentGop.nalCount += currentFrame.length; 12169 currentGop.byteLength += currentFrame.byteLength; 12170 currentGop.push(currentFrame); 12171 } 12172 } 12173 12174 if (gops.length && currentGop.duration <= 0) { 12175 currentGop.duration = gops[gops.length - 1].duration; 12176 } 12177 gops.byteLength += currentGop.byteLength; 12178 gops.nalCount += currentGop.nalCount; 12179 gops.duration += currentGop.duration; 12180 12181 // push the final Gop 12182 gops.push(currentGop); 12183 return gops; 12184 }; 12185 12186 // generate the track's sample table from an array of gops 12187 this.generateSampleTable_ = function(gops, baseDataOffset) { 12188 var 12189 h, i, 12190 sample, 12191 currentGop, 12192 currentFrame, 12193 dataOffset = baseDataOffset || 0, 12194 samples = []; 12195 12196 for (h = 0; h < gops.length; h++) { 12197 currentGop = gops[h]; 12198 12199 for (i = 0; i < currentGop.length; i++) { 12200 currentFrame = currentGop[i]; 12201 12202 sample = createDefaultSample(); 12203 12204 sample.dataOffset = dataOffset; 12205 sample.compositionTimeOffset = currentFrame.pts - currentFrame.dts; 12206 sample.duration = currentFrame.duration; 12207 sample.size = 4 * currentFrame.length; // Space for nal unit size 12208 sample.size += currentFrame.byteLength; 12209 12210 if (currentFrame.keyFrame) { 12211 sample.flags.dependsOn = 2; 12212 } 12213 12214 dataOffset += sample.size; 12215 12216 samples.push(sample); 12217 } 12218 } 12219 return samples; 12220 }; 12221 12222 // generate the track's raw mdat data from an array of gops 12223 this.concatenateNalData_ = function(gops) { 12224 var 12225 h, i, j, 12226 currentGop, 12227 currentFrame, 12228 currentNal, 12229 dataOffset = 0, 12230 nalsByteLength = gops.byteLength, 12231 numberOfNals = gops.nalCount, 12232 totalByteLength = nalsByteLength + 4 * numberOfNals, 12233 data = new Uint8Array(totalByteLength), 12234 view = new DataView(data.buffer); 12235 12236 // For each Gop.. 12237 for (h = 0; h < gops.length; h++) { 12238 currentGop = gops[h]; 12239 12240 // For each Frame.. 12241 for (i = 0; i < currentGop.length; i++) { 12242 currentFrame = currentGop[i]; 12243 12244 // For each NAL.. 12245 for (j = 0; j < currentFrame.length; j++) { 12246 currentNal = currentFrame[j]; 12247 12248 view.setUint32(dataOffset, currentNal.data.byteLength); 12249 dataOffset += 4; 12250 data.set(currentNal.data, dataOffset); 12251 dataOffset += currentNal.data.byteLength; 12252 } 12253 } 12254 } 12255 return data; 12256 }; 12257}; 12258 12259VideoSegmentStream.prototype = new Stream(); 12260 12261/** 12262 * Store information about the start and end of the track and the 12263 * duration for each frame/sample we process in order to calculate 12264 * the baseMediaDecodeTime 12265 */ 12266collectDtsInfo = function(track, data) { 12267 if (typeof data.pts === 'number') { 12268 if (track.timelineStartInfo.pts === undefined) { 12269 track.timelineStartInfo.pts = data.pts; 12270 } 12271 12272 if (track.minSegmentPts === undefined) { 12273 track.minSegmentPts = data.pts; 12274 } else { 12275 track.minSegmentPts = Math.min(track.minSegmentPts, data.pts); 12276 } 12277 12278 if (track.maxSegmentPts === undefined) { 12279 track.maxSegmentPts = data.pts; 12280 } else { 12281 track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts); 12282 } 12283 } 12284 12285 if (typeof data.dts === 'number') { 12286 if (track.timelineStartInfo.dts === undefined) { 12287 track.timelineStartInfo.dts = data.dts; 12288 } 12289 12290 if (track.minSegmentDts === undefined) { 12291 track.minSegmentDts = data.dts; 12292 } else { 12293 track.minSegmentDts = Math.min(track.minSegmentDts, data.dts); 12294 } 12295 12296 if (track.maxSegmentDts === undefined) { 12297 track.maxSegmentDts = data.dts; 12298 } else { 12299 track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts); 12300 } 12301 } 12302}; 12303 12304/** 12305 * Clear values used to calculate the baseMediaDecodeTime between 12306 * tracks 12307 */ 12308clearDtsInfo = function(track) { 12309 delete track.minSegmentDts; 12310 delete track.maxSegmentDts; 12311 delete track.minSegmentPts; 12312 delete track.maxSegmentPts; 12313}; 12314 12315/** 12316 * Calculate the track's baseMediaDecodeTime based on the earliest 12317 * DTS the transmuxer has ever seen and the minimum DTS for the 12318 * current track 12319 */ 12320calculateTrackBaseMediaDecodeTime = function(track) { 12321 var 12322 oneSecondInPTS = 90000, // 90kHz clock 12323 scale, 12324 // Calculate the distance, in time, that this segment starts from the start 12325 // of the timeline (earliest time seen since the transmuxer initialized) 12326 timeSinceStartOfTimeline = track.minSegmentDts - track.timelineStartInfo.dts, 12327 // Calculate the first sample's effective compositionTimeOffset 12328 firstSampleCompositionOffset = track.minSegmentPts - track.minSegmentDts; 12329 12330 // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where 12331 // we want the start of the first segment to be placed 12332 track.baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; 12333 12334 // Add to that the distance this segment is from the very first 12335 track.baseMediaDecodeTime += timeSinceStartOfTimeline; 12336 12337 // Subtract this segment's "compositionTimeOffset" so that the first frame of 12338 // this segment is displayed exactly at the `baseMediaDecodeTime` or at the 12339 // end of the previous segment 12340 track.baseMediaDecodeTime -= firstSampleCompositionOffset; 12341 12342 // baseMediaDecodeTime must not become negative 12343 track.baseMediaDecodeTime = Math.max(0, track.baseMediaDecodeTime); 12344 12345 if (track.type === 'audio') { 12346 // Audio has a different clock equal to the sampling_rate so we need to 12347 // scale the PTS values into the clock rate of the track 12348 scale = track.samplerate / oneSecondInPTS; 12349 track.baseMediaDecodeTime *= scale; 12350 track.baseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime); 12351 } 12352}; 12353 12354/** 12355 * A Stream that can combine multiple streams (ie. audio & video) 12356 * into a single output segment for MSE. Also supports audio-only 12357 * and video-only streams. 12358 */ 12359CoalesceStream = function(options, metadataStream) { 12360 // Number of Tracks per output segment 12361 // If greater than 1, we combine multiple 12362 // tracks into a single segment 12363 this.numberOfTracks = 0; 12364 this.metadataStream = metadataStream; 12365 12366 if (typeof options.remux !== 'undefined') { 12367 this.remuxTracks = !!options.remux; 12368 } else { 12369 this.remuxTracks = true; 12370 } 12371 12372 this.pendingTracks = []; 12373 this.videoTrack = null; 12374 this.pendingBoxes = []; 12375 this.pendingCaptions = []; 12376 this.pendingMetadata = []; 12377 this.pendingBytes = 0; 12378 this.emittedTracks = 0; 12379 12380 CoalesceStream.prototype.init.call(this); 12381 12382 // Take output from multiple 12383 this.push = function(output) { 12384 // buffer incoming captions until the associated video segment 12385 // finishes 12386 if (output.text) { 12387 return this.pendingCaptions.push(output); 12388 } 12389 // buffer incoming id3 tags until the final flush 12390 if (output.frames) { 12391 return this.pendingMetadata.push(output); 12392 } 12393 12394 // Add this track to the list of pending tracks and store 12395 // important information required for the construction of 12396 // the final segment 12397 this.pendingTracks.push(output.track); 12398 this.pendingBoxes.push(output.boxes); 12399 this.pendingBytes += output.boxes.byteLength; 12400 12401 if (output.track.type === 'video') { 12402 this.videoTrack = output.track; 12403 } 12404 if (output.track.type === 'audio') { 12405 this.audioTrack = output.track; 12406 } 12407 }; 12408}; 12409 12410CoalesceStream.prototype = new Stream(); 12411CoalesceStream.prototype.flush = function(flushSource) { 12412 var 12413 offset = 0, 12414 event = { 12415 captions: [], 12416 metadata: [], 12417 info: {} 12418 }, 12419 caption, 12420 id3, 12421 initSegment, 12422 timelineStartPts = 0, 12423 i; 12424 12425 if (this.pendingTracks.length < this.numberOfTracks) { 12426 if (flushSource !== 'VideoSegmentStream' && 12427 flushSource !== 'AudioSegmentStream') { 12428 // Return because we haven't received a flush from a data-generating 12429 // portion of the segment (meaning that we have only recieved meta-data 12430 // or captions.) 12431 return; 12432 } else if (this.remuxTracks) { 12433 // Return until we have enough tracks from the pipeline to remux (if we 12434 // are remuxing audio and video into a single MP4) 12435 return; 12436 } else if (this.pendingTracks.length === 0) { 12437 // In the case where we receive a flush without any data having been 12438 // received we consider it an emitted track for the purposes of coalescing 12439 // `done` events. 12440 // We do this for the case where there is an audio and video track in the 12441 // segment but no audio data. (seen in several playlists with alternate 12442 // audio tracks and no audio present in the main TS segments.) 12443 this.emittedTracks++; 12444 12445 if (this.emittedTracks >= this.numberOfTracks) { 12446 this.trigger('done'); 12447 this.emittedTracks = 0; 12448 } 12449 return; 12450 } 12451 } 12452 12453 if (this.videoTrack) { 12454 timelineStartPts = this.videoTrack.timelineStartInfo.pts; 12455 VIDEO_PROPERTIES.forEach(function(prop) { 12456 event.info[prop] = this.videoTrack[prop]; 12457 }, this); 12458 } else if (this.audioTrack) { 12459 timelineStartPts = this.audioTrack.timelineStartInfo.pts; 12460 AUDIO_PROPERTIES.forEach(function(prop) { 12461 event.info[prop] = this.audioTrack[prop]; 12462 }, this); 12463 } 12464 12465 if (this.pendingTracks.length === 1) { 12466 event.type = this.pendingTracks[0].type; 12467 } else { 12468 event.type = 'combined'; 12469 } 12470 12471 this.emittedTracks += this.pendingTracks.length; 12472 12473 initSegment = mp4.initSegment(this.pendingTracks); 12474 12475 // Create a new typed array to hold the init segment 12476 event.initSegment = new Uint8Array(initSegment.byteLength); 12477 12478 // Create an init segment containing a moov 12479 // and track definitions 12480 event.initSegment.set(initSegment); 12481 12482 // Create a new typed array to hold the moof+mdats 12483 event.data = new Uint8Array(this.pendingBytes); 12484 12485 // Append each moof+mdat (one per track) together 12486 for (i = 0; i < this.pendingBoxes.length; i++) { 12487 event.data.set(this.pendingBoxes[i], offset); 12488 offset += this.pendingBoxes[i].byteLength; 12489 } 12490 12491 // Translate caption PTS times into second offsets into the 12492 // video timeline for the segment 12493 for (i = 0; i < this.pendingCaptions.length; i++) { 12494 caption = this.pendingCaptions[i]; 12495 caption.startTime = (caption.startPts - timelineStartPts); 12496 caption.startTime /= 90e3; 12497 caption.endTime = (caption.endPts - timelineStartPts); 12498 caption.endTime /= 90e3; 12499 event.captions.push(caption); 12500 } 12501 12502 // Translate ID3 frame PTS times into second offsets into the 12503 // video timeline for the segment 12504 for (i = 0; i < this.pendingMetadata.length; i++) { 12505 id3 = this.pendingMetadata[i]; 12506 id3.cueTime = (id3.pts - timelineStartPts); 12507 id3.cueTime /= 90e3; 12508 event.metadata.push(id3); 12509 } 12510 // We add this to every single emitted segment even though we only need 12511 // it for the first 12512 event.metadata.dispatchType = this.metadataStream.dispatchType; 12513 12514 // Reset stream state 12515 this.pendingTracks.length = 0; 12516 this.videoTrack = null; 12517 this.pendingBoxes.length = 0; 12518 this.pendingCaptions.length = 0; 12519 this.pendingBytes = 0; 12520 this.pendingMetadata.length = 0; 12521 12522 // Emit the built segment 12523 this.trigger('data', event); 12524 12525 // Only emit `done` if all tracks have been flushed and emitted 12526 if (this.emittedTracks >= this.numberOfTracks) { 12527 this.trigger('done'); 12528 this.emittedTracks = 0; 12529 } 12530}; 12531/** 12532 * A Stream that expects MP2T binary data as input and produces 12533 * corresponding media segments, suitable for use with Media Source 12534 * Extension (MSE) implementations that support the ISO BMFF byte 12535 * stream format, like Chrome. 12536 */ 12537Transmuxer = function(options) { 12538 var 12539 self = this, 12540 hasFlushed = true, 12541 videoTrack, 12542 audioTrack; 12543 12544 Transmuxer.prototype.init.call(this); 12545 12546 options = options || {}; 12547 this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0; 12548 this.transmuxPipeline_ = {}; 12549 12550 this.setupAacPipeline = function() { 12551 var pipeline = {}; 12552 this.transmuxPipeline_ = pipeline; 12553 12554 pipeline.type = 'aac'; 12555 pipeline.metadataStream = new m2ts.MetadataStream(); 12556 12557 // set up the parsing pipeline 12558 pipeline.aacStream = new AacStream(); 12559 pipeline.audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio'); 12560 pipeline.timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata'); 12561 pipeline.adtsStream = new AdtsStream(); 12562 pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream); 12563 pipeline.headOfPipeline = pipeline.aacStream; 12564 12565 pipeline.aacStream 12566 .pipe(pipeline.audioTimestampRolloverStream) 12567 .pipe(pipeline.adtsStream); 12568 pipeline.aacStream 12569 .pipe(pipeline.timedMetadataTimestampRolloverStream) 12570 .pipe(pipeline.metadataStream) 12571 .pipe(pipeline.coalesceStream); 12572 12573 pipeline.metadataStream.on('timestamp', function(frame) { 12574 pipeline.aacStream.setTimestamp(frame.timeStamp); 12575 }); 12576 12577 pipeline.aacStream.on('data', function(data) { 12578 if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) { 12579 audioTrack = audioTrack || { 12580 timelineStartInfo: { 12581 baseMediaDecodeTime: self.baseMediaDecodeTime 12582 }, 12583 codec: 'adts', 12584 type: 'audio' 12585 }; 12586 // hook up the audio segment stream to the first track with aac data 12587 pipeline.coalesceStream.numberOfTracks++; 12588 pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack); 12589 // Set up the final part of the audio pipeline 12590 pipeline.adtsStream 12591 .pipe(pipeline.audioSegmentStream) 12592 .pipe(pipeline.coalesceStream); 12593 } 12594 }); 12595 12596 // Re-emit any data coming from the coalesce stream to the outside world 12597 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); 12598 // Let the consumer know we have finished flushing the entire pipeline 12599 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 12600 }; 12601 12602 this.setupTsPipeline = function() { 12603 var pipeline = {}; 12604 this.transmuxPipeline_ = pipeline; 12605 12606 pipeline.type = 'ts'; 12607 pipeline.metadataStream = new m2ts.MetadataStream(); 12608 12609 // set up the parsing pipeline 12610 pipeline.packetStream = new m2ts.TransportPacketStream(); 12611 pipeline.parseStream = new m2ts.TransportParseStream(); 12612 pipeline.elementaryStream = new m2ts.ElementaryStream(); 12613 pipeline.videoTimestampRolloverStream = new m2ts.TimestampRolloverStream('video'); 12614 pipeline.audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio'); 12615 pipeline.timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata'); 12616 pipeline.adtsStream = new AdtsStream(); 12617 pipeline.h264Stream = new H264Stream(); 12618 pipeline.captionStream = new m2ts.CaptionStream(); 12619 pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream); 12620 pipeline.headOfPipeline = pipeline.packetStream; 12621 12622 // disassemble MPEG2-TS packets into elementary streams 12623 pipeline.packetStream 12624 .pipe(pipeline.parseStream) 12625 .pipe(pipeline.elementaryStream); 12626 12627 // !!THIS ORDER IS IMPORTANT!! 12628 // demux the streams 12629 pipeline.elementaryStream 12630 .pipe(pipeline.videoTimestampRolloverStream) 12631 .pipe(pipeline.h264Stream); 12632 pipeline.elementaryStream 12633 .pipe(pipeline.audioTimestampRolloverStream) 12634 .pipe(pipeline.adtsStream); 12635 12636 pipeline.elementaryStream 12637 .pipe(pipeline.timedMetadataTimestampRolloverStream) 12638 .pipe(pipeline.metadataStream) 12639 .pipe(pipeline.coalesceStream); 12640 12641 // Hook up CEA-608/708 caption stream 12642 pipeline.h264Stream.pipe(pipeline.captionStream) 12643 .pipe(pipeline.coalesceStream); 12644 12645 pipeline.elementaryStream.on('data', function(data) { 12646 var i; 12647 12648 if (data.type === 'metadata') { 12649 i = data.tracks.length; 12650 12651 // scan the tracks listed in the metadata 12652 while (i--) { 12653 if (!videoTrack && data.tracks[i].type === 'video') { 12654 videoTrack = data.tracks[i]; 12655 videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 12656 } else if (!audioTrack && data.tracks[i].type === 'audio') { 12657 audioTrack = data.tracks[i]; 12658 audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 12659 } 12660 } 12661 12662 // hook up the video segment stream to the first track with h264 data 12663 if (videoTrack && !pipeline.videoSegmentStream) { 12664 pipeline.coalesceStream.numberOfTracks++; 12665 pipeline.videoSegmentStream = new VideoSegmentStream(videoTrack); 12666 12667 pipeline.videoSegmentStream.on('timelineStartInfo', function(timelineStartInfo) { 12668 // When video emits timelineStartInfo data after a flush, we forward that 12669 // info to the AudioSegmentStream, if it exists, because video timeline 12670 // data takes precedence. 12671 if (audioTrack) { 12672 audioTrack.timelineStartInfo = timelineStartInfo; 12673 // On the first segment we trim AAC frames that exist before the 12674 // very earliest DTS we have seen in video because Chrome will 12675 // interpret any video track with a baseMediaDecodeTime that is 12676 // non-zero as a gap. 12677 pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts); 12678 } 12679 }); 12680 12681 // Set up the final part of the video pipeline 12682 pipeline.h264Stream 12683 .pipe(pipeline.videoSegmentStream) 12684 .pipe(pipeline.coalesceStream); 12685 } 12686 12687 if (audioTrack && !pipeline.audioSegmentStream) { 12688 // hook up the audio segment stream to the first track with aac data 12689 pipeline.coalesceStream.numberOfTracks++; 12690 pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack); 12691 12692 // Set up the final part of the audio pipeline 12693 pipeline.adtsStream 12694 .pipe(pipeline.audioSegmentStream) 12695 .pipe(pipeline.coalesceStream); 12696 } 12697 } 12698 }); 12699 12700 // Re-emit any data coming from the coalesce stream to the outside world 12701 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); 12702 // Let the consumer know we have finished flushing the entire pipeline 12703 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 12704 }; 12705 12706 // hook up the segment streams once track metadata is delivered 12707 this.setBaseMediaDecodeTime = function(baseMediaDecodeTime) { 12708 var pipeline = this.transmuxPipeline_; 12709 12710 this.baseMediaDecodeTime = baseMediaDecodeTime; 12711 if (audioTrack) { 12712 audioTrack.timelineStartInfo.dts = undefined; 12713 audioTrack.timelineStartInfo.pts = undefined; 12714 clearDtsInfo(audioTrack); 12715 audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 12716 } 12717 if (videoTrack) { 12718 if (pipeline.videoSegmentStream) { 12719 pipeline.videoSegmentStream.gopCache_ = []; 12720 } 12721 videoTrack.timelineStartInfo.dts = undefined; 12722 videoTrack.timelineStartInfo.pts = undefined; 12723 clearDtsInfo(videoTrack); 12724 videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 12725 } 12726 }; 12727 12728 // feed incoming data to the front of the parsing pipeline 12729 this.push = function(data) { 12730 if (hasFlushed) { 12731 var isAac = isLikelyAacData(data); 12732 12733 if (isAac && this.transmuxPipeline_.type !== 'aac') { 12734 this.setupAacPipeline(); 12735 } else if (!isAac && this.transmuxPipeline_.type !== 'ts') { 12736 this.setupTsPipeline(); 12737 } 12738 hasFlushed = false; 12739 } 12740 this.transmuxPipeline_.headOfPipeline.push(data); 12741 }; 12742 12743 // flush any buffered data 12744 this.flush = function() { 12745 hasFlushed = true; 12746 // Start at the top of the pipeline and flush all pending work 12747 this.transmuxPipeline_.headOfPipeline.flush(); 12748 }; 12749}; 12750Transmuxer.prototype = new Stream(); 12751 12752module.exports = { 12753 Transmuxer: Transmuxer, 12754 VideoSegmentStream: VideoSegmentStream, 12755 AudioSegmentStream: AudioSegmentStream, 12756 AUDIO_PROPERTIES: AUDIO_PROPERTIES, 12757 VIDEO_PROPERTIES: VIDEO_PROPERTIES 12758}; 12759 12760},{"../aac":67,"../codecs/adts.js":68,"../codecs/h264":69,"../m2ts/m2ts.js":74,"../utils/stream.js":83,"./mp4-generator.js":79}],82:[function(require,module,exports){ 12761'use strict'; 12762 12763var ExpGolomb; 12764 12765/** 12766 * Parser for exponential Golomb codes, a variable-bitwidth number encoding 12767 * scheme used by h264. 12768 */ 12769ExpGolomb = function(workingData) { 12770 var 12771 // the number of bytes left to examine in workingData 12772 workingBytesAvailable = workingData.byteLength, 12773 12774 // the current word being examined 12775 workingWord = 0, // :uint 12776 12777 // the number of bits left to examine in the current word 12778 workingBitsAvailable = 0; // :uint; 12779 12780 // ():uint 12781 this.length = function() { 12782 return (8 * workingBytesAvailable); 12783 }; 12784 12785 // ():uint 12786 this.bitsAvailable = function() { 12787 return (8 * workingBytesAvailable) + workingBitsAvailable; 12788 }; 12789 12790 // ():void 12791 this.loadWord = function() { 12792 var 12793 position = workingData.byteLength - workingBytesAvailable, 12794 workingBytes = new Uint8Array(4), 12795 availableBytes = Math.min(4, workingBytesAvailable); 12796 12797 if (availableBytes === 0) { 12798 throw new Error('no bytes available'); 12799 } 12800 12801 workingBytes.set(workingData.subarray(position, 12802 position + availableBytes)); 12803 workingWord = new DataView(workingBytes.buffer).getUint32(0); 12804 12805 // track the amount of workingData that has been processed 12806 workingBitsAvailable = availableBytes * 8; 12807 workingBytesAvailable -= availableBytes; 12808 }; 12809 12810 // (count:int):void 12811 this.skipBits = function(count) { 12812 var skipBytes; // :int 12813 if (workingBitsAvailable > count) { 12814 workingWord <<= count; 12815 workingBitsAvailable -= count; 12816 } else { 12817 count -= workingBitsAvailable; 12818 skipBytes = Math.floor(count / 8); 12819 12820 count -= (skipBytes * 8); 12821 workingBytesAvailable -= skipBytes; 12822 12823 this.loadWord(); 12824 12825 workingWord <<= count; 12826 workingBitsAvailable -= count; 12827 } 12828 }; 12829 12830 // (size:int):uint 12831 this.readBits = function(size) { 12832 var 12833 bits = Math.min(workingBitsAvailable, size), // :uint 12834 valu = workingWord >>> (32 - bits); // :uint 12835 // if size > 31, handle error 12836 workingBitsAvailable -= bits; 12837 if (workingBitsAvailable > 0) { 12838 workingWord <<= bits; 12839 } else if (workingBytesAvailable > 0) { 12840 this.loadWord(); 12841 } 12842 12843 bits = size - bits; 12844 if (bits > 0) { 12845 return valu << bits | this.readBits(bits); 12846 } 12847 return valu; 12848 }; 12849 12850 // ():uint 12851 this.skipLeadingZeros = function() { 12852 var leadingZeroCount; // :uint 12853 for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) { 12854 if ((workingWord & (0x80000000 >>> leadingZeroCount)) !== 0) { 12855 // the first bit of working word is 1 12856 workingWord <<= leadingZeroCount; 12857 workingBitsAvailable -= leadingZeroCount; 12858 return leadingZeroCount; 12859 } 12860 } 12861 12862 // we exhausted workingWord and still have not found a 1 12863 this.loadWord(); 12864 return leadingZeroCount + this.skipLeadingZeros(); 12865 }; 12866 12867 // ():void 12868 this.skipUnsignedExpGolomb = function() { 12869 this.skipBits(1 + this.skipLeadingZeros()); 12870 }; 12871 12872 // ():void 12873 this.skipExpGolomb = function() { 12874 this.skipBits(1 + this.skipLeadingZeros()); 12875 }; 12876 12877 // ():uint 12878 this.readUnsignedExpGolomb = function() { 12879 var clz = this.skipLeadingZeros(); // :uint 12880 return this.readBits(clz + 1) - 1; 12881 }; 12882 12883 // ():int 12884 this.readExpGolomb = function() { 12885 var valu = this.readUnsignedExpGolomb(); // :int 12886 if (0x01 & valu) { 12887 // the number is odd if the low order bit is set 12888 return (1 + valu) >>> 1;
12888 // add 1 to make it even, and divide by 2 12889 } 12890 return -1 * (valu >>> 1); // divide by two then make it negative 12891 }; 12892 12893 // Some convenience functions 12894 // :Boolean 12895 this.readBoolean = function() { 12896 return this.readBits(1) === 1; 12897 }; 12898 12899 // ():int 12900 this.readUnsignedByte = function() { 12901 return this.readBits(8); 12902 }; 12903 12904 this.loadWord(); 12905}; 12906 12907module.exports = ExpGolomb; 12908 12909},{}],83:[function(require,module,exports){ 12910/** 12911 * mux.js 12912 * 12913 * Copyright (c) 2014 Brightcove 12914 * All rights reserved. 12915 * 12916 * A lightweight readable stream implemention that handles event dispatching. 12917 * Objects that inherit from streams should call init in their constructors. 12918 */ 12919'use strict'; 12920 12921var Stream = function() { 12922 this.init = function() { 12923 var listeners = {}; 12924 /** 12925 * Add a listener for a specified event type. 12926 * @param type {string} the event name 12927 * @param listener {function} the callback to be invoked when an event of 12928 * the specified type occurs 12929 */ 12930 this.on = function(type, listener) { 12931 if (!listeners[type]) { 12932 listeners[type] = []; 12933 } 12934 listeners[type] = listeners[type].concat(listener); 12935 }; 12936 /** 12937 * Remove a listener for a specified event type. 12938 * @param type {string} the event name 12939 * @param listener {function} a function previously registered for this 12940 * type of event through `on` 12941 */ 12942 this.off = function(type, listener) { 12943 var index; 12944 if (!listeners[type]) { 12945 return false; 12946 } 12947 index = listeners[type].indexOf(listener); 12948 listeners[type] = listeners[type].slice(); 12949 listeners[type].splice(index, 1); 12950 return index > -1; 12951 }; 12952 /** 12953 * Trigger an event of the specified type on this stream. Any additional 12954 * arguments to this function are passed as parameters to event listeners. 12955 * @param type {string} the event name 12956 */ 12957 this.trigger = function(type) { 12958 var callbacks, i, length, args; 12959 callbacks = listeners[type]; 12960 if (!callbacks) { 12961 return; 12962 } 12963 // Slicing the arguments on every invocation of this method 12964 // can add a significant amount of overhead. Avoid the 12965 // intermediate object creation for the common case of a 12966 // single callback argument 12967 if (arguments.length === 2) { 12968 length = callbacks.length; 12969 for (i = 0; i < length; ++i) { 12970 callbacks[i].call(this, arguments[1]); 12971 } 12972 } else { 12973 args = []; 12974 i = arguments.length; 12975 for (i = 1; i < arguments.length; ++i) { 12976 args.push(arguments[i]); 12977 } 12978 length = callbacks.length; 12979 for (i = 0; i < length; ++i) { 12980 callbacks[i].apply(this, args); 12981 } 12982 } 12983 }; 12984 /** 12985 * Destroys the stream and cleans up. 12986 */ 12987 this.dispose = function() { 12988 listeners = {}; 12989 }; 12990 }; 12991}; 12992 12993/** 12994 * Forwards all `data` events on this stream to the destination stream. The 12995 * destination stream should provide a method `push` to receive the data 12996 * events as they arrive. 12997 * @param destination {stream} the stream that will receive all `data` events 12998 * @param autoFlush {boolean} if false, we will not call `flush` on the destination 12999 * when the current stream emits a 'done' event 13000 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 13001 */ 13002Stream.prototype.pipe = function(destination) { 13003 this.on('data', function(data) { 13004 destination.push(data); 13005 }); 13006 13007 this.on('done', function(flushSource) { 13008 destination.flush(flushSource); 13009 }); 13010 13011 return destination; 13012}; 13013 13014// Default stream functions that are expected to be overridden to perform 13015// actual work. These are provided by the prototype as a sort of no-op 13016// implementation so that we don't have to check for their existence in the 13017// `pipe` function above. 13018Stream.prototype.push = function(data) { 13019 this.trigger('data', data); 13020}; 13021 13022Stream.prototype.flush = function(flushSource) { 13023 this.trigger('done', flushSource); 13024}; 13025 13026module.exports = Stream; 13027 13028},{}],84:[function(require,module,exports){ 13029(function (global){ 13030/** 13031 * @file add-text-track-data.js 13032 */ 13033'use strict'; 13034 13035Object.defineProperty(exports, '__esModule', { 13036 value: true 13037}); 13038 13039function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 13040 13041var _globalWindow = require('global/window'); 13042 13043var _globalWindow2 = _interopRequireDefault(_globalWindow); 13044 13045var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 13046 13047var _videoJs2 = _interopRequireDefault(_videoJs); 13048 13049/** 13050 * Define properties on a cue for backwards compatability, 13051 * but warn the user that the way that they are using it 13052 * is depricated and will be removed at a later date. 13053 * 13054 * @param {Cue} cue the cue to add the properties on 13055 * @private 13056 */ 13057var deprecateOldCue = function deprecateOldCue(cue) { 13058 Object.defineProperties(cue.frame, { 13059 id: { 13060 get: function get() { 13061 _videoJs2['default'].log.warn('cue.frame.id is deprecated. Use cue.value.key instead.'); 13062 return cue.value.key; 13063 } 13064 }, 13065 value: { 13066 get: function get() { 13067 _videoJs2['default'].log.warn('cue.frame.value is deprecated. Use cue.value.data instead.'); 13068 return cue.value.data; 13069 } 13070 }, 13071 privateData: { 13072 get: function get() { 13073 _videoJs2['default'].log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.'); 13074 return cue.value.data; 13075 } 13076 } 13077 }); 13078}; 13079 13080var durationOfVideo = function durationOfVideo(duration) { 13081 var dur = undefined; 13082 13083 if (isNaN(duration) || Math.abs(duration) === Infinity) { 13084 dur = Number.MAX_VALUE; 13085 } else { 13086 dur = duration; 13087 } 13088 return dur; 13089}; 13090/** 13091 * Add text track data to a source handler given the captions and 13092 * metadata from the buffer. 13093 * 13094 * @param {Object} sourceHandler the flash or virtual source buffer 13095 * @param {Array} captionArray an array of caption data 13096 * @param {Array} cue an array of meta data 13097 * @private 13098 */ 13099var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) { 13100 var Cue = _globalWindow2['default'].WebKitDataCue || _globalWindow2['default'].VTTCue; 13101 13102 if (captionArray) { 13103 captionArray.forEach(function (caption) { 13104 this.inbandTextTrack_.addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text)); 13105 }, sourceHandler); 13106 } 13107 13108 if (metadataArray) { 13109 var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration); 13110 13111 metadataArray.forEach(function (metadata) { 13112 var time = metadata.cueTime + this.timestampOffset; 13113
13114 metadata.frames.forEach(function (frame) { 13115 var cue = new Cue(time, time, frame.value || frame.url || frame.data || ''); 13116 13117 cue.frame = frame; 13118 cue.value = frame; 13119 deprecateOldCue(cue); 13120 13121 this.metadataTrack_.addCue(cue); 13122 }, this); 13123 }, sourceHandler); 13124 13125 // Updating the metadeta cues so that 13126 // the endTime of each cue is the startTime of the next cue 13127 // the endTime of last cue is the duration of the video 13128 if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) { 13129 var cues = sourceHandler.metadataTrack_.cues; 13130 var cuesArray = []; 13131 13132 for (var j = 0; j < cues.length; j++) { 13133 cuesArray.push(cues[j]); 13134 } 13135 cuesArray.sort(function (first, second) { 13136 return first.startTime - second.startTime; 13137 }); 13138 13139 for (var i = 0; i < cuesArray.length - 1; i++) { 13140 if (cuesArray[i].endTime !== cuesArray[i + 1].startTime) { 13141 cuesArray[i].endTime = cuesArray[i + 1].startTime; 13142 } 13143 } 13144 cuesArray[cuesArray.length - 1].endTime = videoDuration; 13145 } 13146 } 13147}; 13148 13149exports['default'] = { 13150 addTextTrackData: addTextTrackData, 13151 durationOfVideo: durationOfVideo 13152}; 13153module.exports = exports['default']; 13154}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 13155},{"global/window":25}],85:[function(require,module,exports){ 13156/** 13157 * @file codec-utils.js 13158 */ 13159 13160/** 13161 * Check if a codec string refers to an audio codec. 13162 * 13163 * @param {String} codec codec string to check 13164 * @return {Boolean} if this is an audio codec 13165 * @private 13166 */ 13167'use strict'; 13168 13169Object.defineProperty(exports, '__esModule', { 13170 value: true 13171}); 13172var isAudioCodec = function isAudioCodec(codec) { 13173 return (/mp4a\.\d+.\d+/i.test(codec) 13174 ); 13175}; 13176 13177/** 13178 * Check if a codec string refers to a video codec. 13179 * 13180 * @param {String} codec codec string to check 13181 * @return {Boolean} if this is a video codec 13182 * @private 13183 */ 13184var isVideoCodec = function isVideoCodec(codec) { 13185 return (/avc1\.[\da-f]+/i.test(codec) 13186 ); 13187}; 13188 13189/** 13190 * Parse a content type header into a type and parameters 13191 * object 13192 * 13193 * @param {String} type the content type header 13194 * @return {Object} the parsed content-type 13195 * @private 13196 */ 13197var parseContentType = function parseContentType(type) { 13198 var object = { type: '', parameters: {} }; 13199 var parameters = type.trim().split(';'); 13200 13201 // first parameter should always be content-type 13202 object.type = parameters.shift().trim(); 13203 parameters.forEach(function (parameter) { 13204 var pair = parameter.trim().split('='); 13205 13206 if (pair.length > 1) { 13207 var _name = pair[0].replace(/"/g, '').trim(); 13208 var value = pair[1].replace(/"/g, '').trim(); 13209 13210 object.parameters[_name] = value; 13211 } 13212 }); 13213 13214 return object; 13215}; 13216 13217exports['default'] = { 13218 isAudioCodec: isAudioCodec, 13219 parseContentType: parseContentType, 13220 isVideoCodec: isVideoCodec 13221}; 13222module.exports = exports['default']; 13223},{}],86:[function(require,module,exports){ 13224/** 13225 * @file create-text-tracks-if-necessary.js 13226 */ 13227 13228/** 13229 * Create text tracks on video.js if they exist on a segment. 13230 * 13231 * @param {Object} sourceBuffer the VSB or FSB 13232 * @param {Object} mediaSource the HTML or Flash media source 13233 * @param {Object} segment the segment that may contain the text track 13234 * @private 13235 */ 13236'use strict'; 13237 13238Object.defineProperty(exports, '__esModule', { 13239 value: true 13240}); 13241var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) { 13242 // create an in-band caption track if one is present in the segment 13243 if (segment.captions && segment.captions.length && !sourceBuffer.inbandTextTrack_) { 13244 sourceBuffer.inbandTextTrack_ = mediaSource.player_.addTextTrack('captions', 'cc1'); 13245 } 13246 13247 if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) { 13248 sourceBuffer.metadataTrack_ = mediaSource.player_.addTextTrack('metadata', 'Timed Metadata'); 13249 sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType; 13250 } 13251}; 13252 13253exports['default'] = createTextTracksIfNecessary; 13254module.exports = exports['default']; 13255},{}],87:[function(require,module,exports){ 13256/** 13257 * @file flash-constants.js 13258 */ 13259/** 13260 * The maximum size in bytes for append operations to the video.js 13261 * SWF. Calling through to Flash blocks and can be expensive so 13262 * tuning this parameter may improve playback on slower
13263 * systems. There are two factors to consider: 13264 * - Each interaction with the SWF must be quick or you risk dropping 13265 * video frames. To maintain 60fps for the rest of the page, each append 13266 * must not take longer than 16ms. Given the likelihood that the page 13267 * will be executing more javascript than just playback, you probably 13268 * want to aim for less than 8ms. We aim for just 4ms. 13269 * - Bigger appends significantly increase throughput. The total number of 13270 * bytes over time delivered to the SWF must exceed the video bitrate or 13271 * playback will stall. 13272 * 13273 * We adaptively tune the size of appends to give the best throughput 13274 * possible given the performance of the system. To do that we try to append 13275 * as much as possible in TIME_PER_TICK and while tuning the size of appends 13276 * dynamically so that we only append about 4-times in that 4ms span. 13277 * 13278 * The reason we try to keep the number of appends around four is due to 13279 * externalities such as Flash load and garbage collection that are highly 13280 * variable and having 4 iterations allows us to exit the loop early if 13281 * an iteration takes longer than expected. 13282 * 13283 * @private 13284 */ 13285"use strict"; 13286 13287Object.defineProperty(exports, "__esModule", { 13288 value: true 13289}); 13290var flashConstants = { 13291 TIME_BETWEEN_TICKS: Math.floor(1000 / 500), 13292 TIME_PER_TICK: Math.floor(1000 / 100), 13293 // 1kb 13294 BYTES_PER_CHUNK: 4 * 1024, 13295 MIN_CHUNK: 4096, 13296 MAX_CHUNK: 4 * 1024 * 1024 13297}; 13298 13299exports["default"] = flashConstants; 13300module.exports = exports["default"]; 13301},{}],88:[function(require,module,exports){ 13302(function (global){ 13303/** 13304 * @file flash-media-source.js 13305 */ 13306'use strict'; 13307 13308Object.defineProperty(exports, '__esModule', { 13309 value: true 13310}); 13311 13312var _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; }; })(); 13313 13314var _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); } } }; 13315 13316function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 13317 13318function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 13319 13320function _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; } 13321 13322var _globalDocument = require('global/document'); 13323 13324var _globalDocument2 = _interopRequireDefault(_globalDocument); 13325 13326var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 13327 13328var _videoJs2 = _interopRequireDefault(_videoJs); 13329 13330var _flashSourceBuffer = require('./flash-source-buffer'); 13331 13332var _flashSourceBuffer2 = _interopRequireDefault(_flashSourceBuffer); 13333 13334var _flashConstants = require('./flash-constants'); 13335 13336var _flashConstants2 = _interopRequireDefault(_flashConstants); 13337 13338var _codecUtils = require('./codec-utils'); 13339 13340/** 13341 * A flash implmentation of HTML MediaSources and a polyfill 13342 * for browsers that don't support native or HTML MediaSources.. 13343 * 13344 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource 13345 * @class FlashMediaSource 13346 * @extends videojs.EventTarget 13347 */ 13348 13349var FlashMediaSource = (function (_videojs$EventTarget) { 13350 _inherits(FlashMediaSource, _videojs$EventTarget); 13351 13352 function FlashMediaSource() { 13353 var _this = this; 13354 13355 _classCallCheck(this, FlashMediaSource); 13356 13357 _get(Object.getPrototypeOf(FlashMediaSource.prototype), 'constructor', this).call(this); 13358 this.sourceBuffers = []; 13359 this.readyState = 'closed'; 13360 13361 this.on(['sourceopen', 'webkitsourceopen'], function (event) { 13362 // find the swf where we will push media data 13363 _this.swfObj = _globalDocument2['default'].getElementById(event.swfId); 13364 _this.player_ = (0, _videoJs2['default'])(_this.swfObj.parentNode); 13365 _this.tech_ = _this.swfObj.tech; 13366 _this.readyState = 'open'; 13367 13368 _this.tech_.on('seeking', function () { 13369 var i = _this.sourceBuffers.length; 13370 13371 while (i--) { 13372 _this.sourceBuffers[i].abort(); 13373 } 13374 }); 13375 13376 // trigger load events 13377 if (_this.swfObj) { 13378 _this.swfObj.vjs_load(); 13379 } 13380 }); 13381 } 13382 13383 /** 13384 * Set or return the presentation duration. 13385 * 13386 * @param {Double} value the duration of the media in seconds 13387 * @param {Double} the current presentation duration 13388 * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration 13389 */ 13390 13391 /** 13392 * We have this function so that the html and flash interfaces 13393 * are the same. 13394 * 13395 * @private 13396 */ 13397 13398 _createClass(FlashMediaSource, [{ 13399 key: 'addSeekableRange_', 13400 value: function addSeekableRange_() {} 13401 // intentional no-op 13402 13403 /** 13404 * Create a new flash source buffer and add it to our flash media s
13404ource. 13405 * 13406 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer 13407 * @param {String} type the content-type of the source 13408 * @return {Object} the flash source buffer 13409 */ 13410 13411 }, { 13412 key: 'addSourceBuffer', 13413 value: function addSourceBuffer(type) { 13414 var parsedType = (0, _codecUtils.parseContentType)(type); 13415 var sourceBuffer = undefined; 13416 13417 // if this is an FLV type, we'll push data to flash 13418 if (parsedType.type === 'video/mp2t') { 13419 // Flash source buffers 13420 sourceBuffer = new _flashSourceBuffer2['default'](this); 13421 } else { 13422 throw new Error('NotSupportedError (Video.js)'); 13423 } 13424 13425 this.sourceBuffers.push(sourceBuffer); 13426 return sourceBuffer; 13427 } 13428 13429 /** 13430 * Signals the end of the stream. 13431 * 13432 * @link https://w3c.github.io/media-source/#widl-MediaSource-endOfStream-void-EndOfStreamError-error 13433 * @param {String=} error Signals that a playback error 13434 * has occurred. If specified, it must be either "network" or 13435 * "decode". 13436 */ 13437 }, { 13438 key: 'endOfStream', 13439 value: function endOfStream(error) { 13440 if (error === 'network') { 13441 // MEDIA_ERR_NETWORK 13442 this.tech_.error(2); 13443 } else if (error === 'decode') { 13444 // MEDIA_ERR_DECODE 13445 this.tech_.error(3); 13446 } 13447 if (this.readyState !== 'ended') { 13448 this.readyState = 'ended'; 13449 this.swfObj.vjs_endOfStream(); 13450 } 13451 } 13452 }]); 13453 13454 return FlashMediaSource; 13455})(_videoJs2['default'].EventTarget); 13456 13457exports['default'] = FlashMediaSource; 13458try { 13459 Object.defineProperty(FlashMediaSource.prototype, 'duration', { 13460 /** 13461 * Return the presentation duration. 13462 * 13463 * @return {Double} the duration of the media in seconds 13464 * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration 13465 */ 13466 get: function get() { 13467 if (!this.swfObj) { 13468 return NaN; 13469 } 13470 // get the current duration from the SWF 13471 return this.swfObj.vjs_getProperty('duration'); 13472 }, 13473 /** 13474 * Set the presentation duration. 13475 * 13476 * @param {Double} value the duration of the media in seconds 13477 * @return {Double} the duration of the media in seconds 13478 * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration 13479 */ 13480 set: function set(value) { 13481 var i = undefined; 13482 var oldDuration = this.swfObj.vjs_getProperty('duration'); 13483 13484 this.swfObj.vjs_setProperty('duration', value); 13485 13486 if (value < oldDuration) { 13487 // In MSE, this triggers the range removal algorithm which causes 13488 // an update to occur 13489 for (i = 0; i < this.sourceBuffers.length; i++) { 13490 this.sourceBuffers[i].remove(value, oldDuration); 13491 } 13492 } 13493 13494 return value; 13495 } 13496 }); 13497} catch (e) { 13498 // IE8 throws if defineProperty is called on a non-DOM node. We 13499 // don't support IE8 but we shouldn't throw an error if loaded 13500 // there. 13501 FlashMediaSource.prototype.duration = NaN; 13502} 13503 13504for (var property in _flashConstants2['default']) { 13505 FlashMediaSource[property] = _flashConstants2['default'][property]; 13506} 13507module.exports = exports['default']; 13508}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 13509},{"./codec-utils":85,"./flash-constants":87,"./flash-source-buffer":89,"global/document":24}],89:[function(require,module,exports){ 13510(function (global){ 13511/** 13512 * @file flash-source-buffer.js 13513 */ 13514'use strict'; 13515 13516Object.defineProperty(exports, '__esModule', { 13517 value: true 13518}); 13519 13520var _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; }; })(); 13521 13522var _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); } } }; 13523 13524function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 13525 13526function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 13527 13528function _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
13528onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 13529 13530var _globalWindow = require('global/window'); 13531 13532var _globalWindow2 = _interopRequireDefault(_globalWindow); 13533 13534var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 13535 13536var _videoJs2 = _interopRequireDefault(_videoJs); 13537 13538var _muxJsLibFlv = require('mux.js/lib/flv'); 13539 13540var _muxJsLibFlv2 = _interopRequireDefault(_muxJsLibFlv); 13541 13542var _removeCuesFromTrack = require('./remove-cues-from-track'); 13543 13544var _removeCuesFromTrack2 = _interopRequireDefault(_removeCuesFromTrack); 13545 13546var _createTextTracksIfNecessary = require('./create-text-tracks-if-necessary'); 13547 13548var _createTextTracksIfNecessary2 = _interopRequireDefault(_createTextTracksIfNecessary); 13549 13550var _addTextTrackData = require('./add-text-track-data'); 13551 13552var _flashConstants = require('./flash-constants'); 13553 13554var _flashConstants2 = _interopRequireDefault(_flashConstants); 13555 13556/** 13557 * A wrapper around the setTimeout function that uses 13558 * the flash constant time between ticks value. 13559 * 13560 * @param {Function} func the function callback to run 13561 * @private 13562 */ 13563var scheduleTick = function scheduleTick(func) { 13564 // Chrome doesn't invoke requestAnimationFrame callbacks 13565 // in background tabs, so use setTimeout. 13566 _globalWindow2['default'].setTimeout(func, _flashConstants2['default'].TIME_BETWEEN_TICKS); 13567}; 13568 13569/** 13570 * Round a number to a specified number of places much like 13571 * toFixed but return a number instead of a string representation. 13572 * 13573 * @param {Number} num A number 13574 * @param {Number} places The number of decimal places which to 13575 * round 13576 * @private 13577 */ 13578var toDecimalPlaces = function toDecimalPlaces(num, places) { 13579 if (typeof places !== 'number' || places < 0) { 13580 places = 0; 13581 } 13582 13583 var scale = Math.pow(10, places); 13584 13585 return Math.round(num * scale) / scale; 13586}; 13587 13588/** 13589 * A SourceBuffer implementation for Flash rather than HTML. 13590 * 13591 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource 13592 * @param {Object} mediaSource the flash media source 13593 * @class FlashSourceBuffer 13594 * @extends videojs.EventTarget 13595 */ 13596 13597var FlashSourceBuffer = (function (_videojs$EventTarget) { 13598 _inherits(FlashSourceBuffer, _videojs$EventTarget); 13599 13600 function FlashSourceBuffer(mediaSource) { 13601 var _this = this; 13602 13603 _classCallCheck(this, FlashSourceBuffer); 13604 13605 _get(Object.getPrototypeOf(FlashSourceBuffer.prototype), 'constructor', this).call(this); 13606 var encodedHeader = undefined; 13607 13608 // Start off using the globally defined value but refine 13609 // as we append data into flash 13610 this.chunkSize_ = _flashConstants2['default'].BYTES_PER_CHUNK; 13611 13612 // byte arrays queued to be appended 13613 this.buffer_ = []; 13614 13615 // the total number of queued bytes 13616 this.bufferSize_ = 0; 13617 13618 // to be able to determine the correct position to seek to, we 13619 // need to retain information about the mapping between the 13620 // media timeline and PTS values 13621 this.basePtsOffset_ = NaN; 13622 13623 this.mediaSource_ = mediaSource; 13624 13625 // indicates whether the asynchronous continuation of an operation 13626 // is still being processed 13627 // see https://w3c.github.io/media-source/#widl-SourceBuffer-updating 13628 this.updating = false; 13629 this.timestampOffset_ = 0; 13630 13631 // TS to FLV transmuxer 13632 this.segmentParser_ = new _muxJsLibFlv2['default'].Transmuxer(); 13633 this.segmentParser_.on('data', this.receiveBuffer_.bind(this)); 13634 encodedHeader = _globalWindow2['default'].btoa(String.fromCharCode.apply(null, Array.prototype.slice.call(this.segmentParser_.getFlvHeader()))); 13635 this.mediaSource_.swfObj.vjs_appendBuffer(encodedHeader); 13636 13637 this.one('updateend', function () { 13638 _this.mediaSource_.tech_.trigger('loadedmetadata'); 13639 }); 13640 13641 Object.defineProperty(this, 'timestampOffset', { 13642 get: function get() { 13643 return this.timestampOffset_; 13644 }, 13645 set: function set(val) { 13646 if (typeof val === 'number' && val >= 0) { 13647 this.timestampOffset_ = val; 13648 this.segmentParser_ = new _muxJsLibFlv2['default'].Transmuxer(); 13649 this.segmentParser_.on('data', this.receiveBuffer_.bind(this)); 13650 // We have to tell flash to expect a discontinuity 13651 this.mediaSource_.swfObj.vjs_discontinuity(); 13652 // the media <-> PTS mapping must be re-established after 13653 // the discontinuity 13654 this.basePtsOffset_ = NaN; 13655 } 13656 } 13657 }); 13658 13659 Object.defineProperty(this, 'buffered', { 13660 get: function get() { 13661 if (!this.mediaSource_ || !this.mediaSource_.swfObj || !('vjs_getProperty' in this.mediaSource_.swfObj)) { 13662 return _videoJs2['default'].createTimeRange(); 13663 } 13664 13665 var buffered = this.mediaSource_.swfObj.vjs_getProperty('buffered'); 13666 13667 if (buffered && buffered.length) { 13668 buffered[0][0] = toDecimalPlaces(buffered[0][0], 3); 13669 buffered[0][1] = toDecimalPlaces(buffered[0][1], 3); 13670 } 13671 return _videoJs2['default'].createTimeRanges(buffered); 13672 } 13673 }); 13674 13675 // On a seek we remove all text track data since flash has no concept 13676 // of a buffered-range and everything else is reset on seek 13677 this.mediaSource_.player_.on('seeked', function () { 13678 (0, _removeCuesFromTrack2['default'])(0, Infinity, _this.metadataTrack_); 13679 (0, _removeCuesFromTrack2['default'])(0, Infinity, _this.inbandTextTrack_); 13680 }); 13681 } 13682 13683 /** 13684 * Append bytes to the sourcebuffers buffer, in this case we 13685 * have to append it to swf object. 13686 * 13687 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer 13688 * @param {Array} bytes 13689 */ 13690 13691 _createClass(FlashSourceBuffer, [{ 13692 key: 'appendBuffer', 13693 value: function appendBuffer(bytes) { 13694 var _this2 = this; 13695 13696 var error = undefined; 13697 var chunk = 512 * 1024; 13698 var i = 0; 13699 13700 if (this.updating) { 13701 error = new Error('SourceBuffer.append() cannot be called ' + 'while an update is in progress');
13702 error.name = 'InvalidStateError'; 13703 error.code = 11; 13704 throw error; 13705 } 13706 13707 this.updating = true; 13708 this.mediaSource_.readyState = 'open'; 13709 this.trigger({ type: 'update' }); 13710 13711 // this is here to use recursion 13712 var chunkInData = function chunkInData() { 13713 _this2.segmentParser_.push(bytes.subarray(i, i + chunk)); 13714 i += chunk; 13715 if (i < bytes.byteLength) { 13716 scheduleTick(chunkInData); 13717 } else { 13718 scheduleTick(_this2.segmentParser_.flush.bind(_this2.segmentParser_)); 13719 } 13720 }; 13721 13722 chunkInData(); 13723 } 13724 13725 /** 13726 * Reset the parser and remove any data queued to be sent to the SWF. 13727 * 13728 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort 13729 */ 13730 }, { 13731 key: 'abort', 13732 value: function abort() { 13733 this.buffer_ = []; 13734 this.bufferSize_ = 0; 13735 this.mediaSource_.swfObj.vjs_abort(); 13736 13737 // report any outstanding updates have ended 13738 if (this.updating) { 13739 this.updating = false; 13740 this.trigger({ type: 'updateend' }); 13741 } 13742 } 13743 13744 /** 13745 * Flash cannot remove ranges already buffered in the NetStream 13746 * but seeking clears the buffer entirely. For most purposes, 13747 * having this operation act as a no-op is acceptable. 13748 * 13749 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove 13750 * @param {Double} start start of the section to remove 13751 * @param {Double} end end of the section to remove 13752 */ 13753 }, { 13754 key: 'remove', 13755 value: function remove(start, end) { 13756 (0, _removeCuesFromTrack2['default'])(start, end, this.metadataTrack_); 13757 (0, _removeCuesFromTrack2['default'])(start, end, this.inbandTextTrack_); 13758 this.trigger({ type: 'update' }); 13759 this.trigger({ type: 'updateend' }); 13760 } 13761 13762 /** 13763 * Receive a buffer from the flv. 13764 * 13765 * @param {Object} segment 13766 * @private 13767 */ 13768 }, { 13769 key: 'receiveBuffer_', 13770 value: function receiveBuffer_(segment) { 13771 var _this3 = this; 13772 13773 // create an in-band caption track if one is present in the segment 13774 (0, _createTextTracksIfNecessary2['default'])(this, this.mediaSource_, segment); 13775 (0, _addTextTrackData.addTextTrackData)(this, segment.captions, segment.metadata); 13776 13777 // Do this asynchronously since convertTagsToData_ can be time consuming 13778 scheduleTick(function () { 13779 var flvBytes = _this3.convertTagsToData_(segment); 13780 13781 if (_this3.buffer_.length === 0) { 13782 scheduleTick(_this3.processBuffer_.bind(_this3)); 13783 } 13784 13785 if (flvBytes) { 13786 _this3.buffer_.push(flvBytes); 13787 _this3.bufferSize_ += flvBytes.byteLength; 13788 } 13789 }); 13790 } 13791 13792 /** 13793 * Append a portion of the current buffer to the SWF. 13794 * 13795 * @private 13796 */ 13797 }, { 13798 key: 'processBuffer_', 13799 value: function processBuffer_() { 13800 var chunk = undefined; 13801 var i = undefined; 13802 var length = undefined; 13803 var binary = undefined; 13804 var b64str = undefined; 13805 var startByte = 0; 13806 var appendIterations = 0; 13807 var startTime = +new Date(); 13808 var appendTime = undefined; 13809 13810 if (!this.buffer_.length) { 13811 if (this.updating !== false) { 13812 this.updating = false; 13813 this.trigger({ type: 'updateend' }); 13814 } 13815 // do nothing if the buffer is empty 13816 return; 13817 } 13818 13819 do { 13820 appendIterations++; 13821 // concatenate appends up to the max append size 13822 chunk = this.buffer_[0].subarray(startByte, startByte + this.chunkSize_); 13823 13824 // requeue any bytes that won't make it this round 13825 if (chunk.byteLength < this.chunkSize_ || this.buffer_[0].byteLength === startByte + this.chunkSize_) { 13826 startByte = 0; 13827 this.buffer_.shift(); 13828 } else { 13829 startByte += this.chunkSize_; 13830 } 13831 13832 this.bufferSize_ -= chunk.byteLength; 13833 13834 // base64 encode the bytes 13835 binary = ''; 13836 length = chunk.byteLength; 13837 for (i = 0; i < length; i++) { 13838 binary += String.fromCharCode(chunk[i]); 13839 } 13840 b64str = _globalWindow2['default'].btoa(binary); 13841 13842 // bypass normal ExternalInterface calls and pass xml directly 13843 // IE can be slow by default 13844 this.mediaSource_.swfObj.CallFunction('<invoke name="vjs_appendBuffer"' + 'returntype="javascript"><arguments><string>' + b64str + '</string></arguments></invoke>'); 13845 appendTime = new Date() - startTime; 13846 } while (this.buffer_.length && appendTime < _flashConstants2['default'].TIME_PER_TICK); 13847 13848 if (this.buffer_.length && startByte) { 13849 this.buffer_[0] = this.buffer_[0].subarray(startByte); 13850 } 13851 13852 if (appendTime >= _flashConstants2['default'].TIME_PER_TICK) { 13853 // We want to target 4 iterations per time-slot so that gives us 13854 // room to adjust to changes in Flash load and other externalities 13855 // such as garbage collection while still maximizing throughput 13856 this.chunkSize_ = Math.floor(this.chunkSize_ * (appendIterations / 4)); 13857 } 13858 13859 // We also make sure that the chunk-size doesn't drop below 1KB or
13860 // go above 1MB as a sanity check 13861 this.chunkSize_ = Math.max(_flashConstants2['default'].MIN_CHUNK, Math.min(this.chunkSize_, _flashConstants2['default'].MAX_CHUNK)); 13862 13863 // schedule another append if necessary 13864 if (this.bufferSize_ !== 0) { 13865 scheduleTick(this.processBuffer_.bind(this)); 13866 } else { 13867 this.updating = false; 13868 this.trigger({ type: 'updateend' }); 13869 } 13870 } 13871 13872 /** 13873 * Turns an array of flv tags into a Uint8Array representing the 13874 * flv data. Also removes any tags that are before the current 13875 * time so that playback begins at or slightly after the right 13876 * place on a seek 13877 * 13878 * @private 13879 * @param {Object} segmentData object of segment data 13880 */ 13881 }, { 13882 key: 'convertTagsToData_', 13883 value: function convertTagsToData_(segmentData) { 13884 var segmentByteLength = 0; 13885 var tech = this.mediaSource_.tech_; 13886 var targetPts = 0; 13887 var i = undefined; 13888 var j = undefined; 13889 var segment = undefined; 13890 var filteredTags = []; 13891 var tags = this.getOrderedTags_(segmentData); 13892 13893 // Establish the media timeline to PTS translation if we don't 13894 // have one already 13895 if (isNaN(this.basePtsOffset_) && tags.length) { 13896 this.basePtsOffset_ = tags[0].pts; 13897 } 13898 13899 // Trim to currentTime if it's ahead of buffered or buffered doesn't exist 13900 if (tech.seeking()) { 13901 targetPts = Math.max(targetPts, tech.currentTime() - this.timestampOffset); 13902 } 13903 13904 // PTS values are represented in milliseconds 13905 targetPts *= 1e3; 13906 targetPts += this.basePtsOffset_; 13907 13908 // skip tags with a presentation time less than the seek target 13909 for (i = 0; i < tags.length; i++) { 13910 if (tags[i].pts >= targetPts) { 13911 filteredTags.push(tags[i]); 13912 } 13913 } 13914 13915 if (filteredTags.length === 0) { 13916 return; 13917 } 13918 13919 // concatenate the bytes into a single segment 13920 for (i = 0; i < filteredTags.length; i++) { 13921 segmentByteLength += filteredTags[i].bytes.byteLength; 13922 } 13923 segment = new Uint8Array(segmentByteLength); 13924 for (i = 0, j = 0; i < filteredTags.length; i++) { 13925 segment.set(filteredTags[i].bytes, j); 13926 j += filteredTags[i].bytes.byteLength; 13927 } 13928 13929 return segment; 13930 } 13931 13932 /** 13933 * Assemble the FLV tags in decoder order. 13934 * 13935 * @private 13936 * @param {Object} segmentData object of segment data 13937 */ 13938 }, { 13939 key: 'getOrderedTags_', 13940 value: function getOrderedTags_(segmentData) { 13941 var videoTags = segmentData.tags.videoTags; 13942 var audioTags = segmentData.tags.audioTags; 13943 var tag = undefined; 13944 var tags = []; 13945 13946 while (videoTags.length || audioTags.length) { 13947 if (!videoTags.length) { 13948 // only audio tags remain 13949 tag = audioTags.shift(); 13950 } else if (!audioTags.length) { 13951 // only video tags remain 13952 tag = videoTags.shift(); 13953 } else if (audioTags[0].dts < videoTags[0].dts) { 13954 // audio should be decoded next 13955 tag = audioTags.shift(); 13956 } else { 13957 // video should be decoded next 13958 tag = videoTags.shift(); 13959 } 13960 13961 tags.push(tag.finalize()); 13962 } 13963 13964 return tags; 13965 } 13966 }]); 13967 13968 return FlashSourceBuffer; 13969})(_videoJs2['default'].EventTarget); 13970 13971exports['default'] = FlashSourceBuffer; 13972module.exports = exports['default']; 13973}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 13974},{"./add-text-track-data":84,"./create-text-tracks-if-necessary":86,"./flash-constants":87,"./remove-cues-from-track":91,"global/window":25,"mux.js/lib/flv":71}],90:[function(require,module,exports){ 13975(function (global){ 13976/** 13977 * @file html-media-source.js 13978 */ 13979'use strict'; 13980 13981Object.defineProperty(exports, '__esModule', { 13982 value: true 13983}); 13984 13985var _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; }; })(); 13986 13987var _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); } } }; 13988 13989function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 13990 13991function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 13992 13993function _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
13993onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 13994 13995var _globalWindow = require('global/window'); 13996 13997var _globalWindow2 = _interopRequireDefault(_globalWindow); 13998 13999var _globalDocument = require('global/document'); 14000 14001var _globalDocument2 = _interopRequireDefault(_globalDocument); 14002 14003var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 14004 14005var _videoJs2 = _interopRequireDefault(_videoJs); 14006 14007var _virtualSourceBuffer = require('./virtual-source-buffer'); 14008 14009var _virtualSourceBuffer2 = _interopRequireDefault(_virtualSourceBuffer); 14010 14011var _addTextTrackData = require('./add-text-track-data'); 14012 14013var _codecUtils = require('./codec-utils'); 14014 14015/** 14016 * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard 14017 * `avc1.<hhhhhh>` 14018 * 14019 * @param {Array} codecs an array of codec strings to fix 14020 * @return {Array} the translated codec array 14021 * @private 14022 */ 14023var translateLegacyCodecs = function translateLegacyCodecs(codecs) { 14024 return codecs.map(function (codec) { 14025 return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) { 14026 var profileHex = ('00' + Number(profile).toString(16)).slice(-2); 14027 var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2); 14028 14029 return 'avc1.' + profileHex + '00' + avcLevelHex; 14030 }); 14031 }); 14032}; 14033 14034/** 14035 * Our MediaSource implementation in HTML, mimics native 14036 * MediaSource where/if possible. 14037 * 14038 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource 14039 * @class HtmlMediaSource 14040 * @extends videojs.EventTarget 14041 */ 14042 14043var HtmlMediaSource = (function (_videojs$EventTarget) { 14044 _inherits(HtmlMediaSource, _videojs$EventTarget); 14045 14046 function HtmlMediaSource() { 14047 var _this = this; 14048 14049 _classCallCheck(this, HtmlMediaSource); 14050 14051 _get(Object.getPrototypeOf(HtmlMediaSource.prototype), 'constructor', this).call(this); 14052 var property = undefined; 14053 14054 this.nativeMediaSource_ = new _globalWindow2['default'].MediaSource(); 14055 // delegate to the native MediaSource's methods by default 14056 for (property in this.nativeMediaSource_) { 14057 if (!(property in HtmlMediaSource.prototype) && typeof this.nativeMediaSource_[property] === 'function') { 14058 this[property] = this.nativeMediaSource_[property].bind(this.nativeMediaSource_); 14059 } 14060 } 14061 14062 // emulate `duration` and `seekable` until seeking can be 14063 // handled uniformly for live streams 14064 // see https://github.com/w3c/media-source/issues/5 14065 this.duration_ = NaN; 14066 Object.defineProperty(this, 'duration', { 14067 get: function get() { 14068 if (this.duration_ === Infinity) { 14069 return this.duration_; 14070 } 14071 return this.nativeMediaSource_.duration; 14072 }, 14073 set: function set(duration) { 14074 this.duration_ = duration; 14075 if (duration !== Infinity) { 14076 this.nativeMediaSource_.duration = duration; 14077 return; 14078 } 14079 } 14080 }); 14081 Object.defineProperty(this, 'seekable', { 14082 get: function get() { 14083 if (this.duration_ === Infinity) { 14084 return _videoJs2['default'].createTimeRanges([[0, this.nativeMediaSource_.duration]]); 14085 } 14086 return this.nativeMediaSource_.seekable; 14087 } 14088 }); 14089 14090 Object.defineProperty(this, 'readyState', { 14091 get: function get() { 14092 return this.nativeMediaSource_.readyState; 14093 } 14094 }); 14095 14096 Object.defineProperty(this, 'activeSourceBuffers', { 14097 get: function get() { 14098 return this.activeSourceBuffers_; 14099 } 14100 }); 14101 14102 // the list of virtual and native SourceBuffers created by this 14103 // MediaSource 14104 this.sourceBuffers = []; 14105 14106 this.activeSourceBuffers_ = []; 14107 14108 /** 14109 * update the list of active source buffers based upon various 14110 * imformation from HLS and video.js 14111 * 14112 * @private 14113 */ 14114 this.updateActiveSourceBuffers_ = function () { 14115 // Retain the reference but empty the array 14116 _this.activeSourceBuffers_.length = 0; 14117 14118 // By default, the audio in the combined virtual source buffer is enabled 14119 // and the audio-only source buffer (if it exists) is disabled. 14120 var combined = false;
14121 var audioOnly = true; 14122 14123 // TODO: maybe we can store the sourcebuffers on the track objects? 14124 // safari may do something like this 14125 for (var i = 0; i < _this.player_.audioTracks().length; i++) { 14126 var track = _this.player_.audioTracks()[i]; 14127 14128 if (track.enabled && track.kind !== 'main') { 14129 // The enabled track is an alternate audio track so disable the audio in 14130 // the combined source buffer and enable the audio-only source buffer. 14131 combined = true; 14132 audioOnly = false; 14133 break; 14134 } 14135 } 14136 14137 // Since we currently support a max of two source buffers, add all of the source 14138 // buffers (in order). 14139 _this.sourceBuffers.forEach(function (sourceBuffer) { 14140 /* eslinst-disable */ 14141 // TODO once codecs are required, we can switch to using the codecs to determine 14142 // what stream is the video stream, rather than relying on videoTracks 14143 /* eslinst-enable */ 14144 14145 sourceBuffer.appendAudioInitSegment_ = true; 14146 14147 if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { 14148 // combined 14149 sourceBuffer.audioDisabled_ = combined; 14150 } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) { 14151 // If the "combined" source buffer is video only, then we do not want 14152 // disable the audio-only source buffer (this is mostly for demuxed 14153 // audio and video hls) 14154 sourceBuffer.audioDisabled_ = true; 14155 audioOnly = false; 14156 } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { 14157 // audio only 14158 sourceBuffer.audioDisabled_ = audioOnly; 14159 if (audioOnly) { 14160 return; 14161 } 14162 } 14163 14164 _this.activeSourceBuffers_.push(sourceBuffer); 14165 }); 14166 }; 14167 14168 this.onPlayerMediachange_ = function () { 14169 _this.sourceBuffers.forEach(function (sourceBuffer) { 14170 sourceBuffer.appendAudioInitSegment_ = true; 14171 }); 14172 }; 14173 14174 // Re-emit MediaSource events on the polyfill 14175 ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) { 14176 this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this)); 14177 }, this); 14178 14179 // capture the associated player when the MediaSource is 14180 // successfully attached 14181 this.on('sourceopen', function (event) { 14182 // Get the player this MediaSource is attached to 14183 var video = _globalDocument2['default'].querySelector('[src="' + _this.url_ + '"]'); 14184 14185 if (!video) { 14186 return; 14187 } 14188 14189 _this.player_ = (0, _videoJs2['default'])(video.parentNode); 14190 14191 if (_this.player_.audioTracks && _this.player_.audioTracks()) { 14192 _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_); 14193 _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_); 14194 _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_); 14195 } 14196 14197 _this.player_.on('mediachange', _this.onPlayerMediachange_); 14198 }); 14199 14200 this.on('sourceended', function (event) { 14201 var duration = (0, _addTextTrackData.durationOfVideo)(_this.duration); 14202 14203 for (var i = 0; i < _this.sourceBuffers.length; i++) { 14204 var sourcebuffer = _this.sourceBuffers[i]; 14205 var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues; 14206 14207 if (cues && cues.length) { 14208 cues[cues.length - 1].endTime = duration; 14209 } 14210 } 14211 }); 14212 14213 // explicitly terminate any WebWorkers that were created 14214 // by SourceHandlers 14215 this.on('sourceclose', function (event) { 14216 this.sourceBuffers.forEach(function (sourceBuffer) { 14217 if (sourceBuffer.transmuxer_) { 14218 sourceBuffer.transmuxer_.terminate(); 14219 } 14220 }); 14221 this.sourceBuffers.length = 0; 14222 if (!this.player_) { 14223 return; 14224 } 14225 14226 if (this.player_.audioTracks && this.player_.audioTracks()) { 14227 this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_); 14228 this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_); 14229 this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_); 14230 } 14231 14232 this.player_.off('mediachange', this.onPlayerMediachange_); 14233 }); 14234 } 14235 14236 /** 14237 * Add a range that that can now be seeked to. 14238 * 14239 * @param {Double} start where to start the addition 14240 * @param {Double} end where to end the addition 14241 * @private 14242 */ 14243 14244 _createClass(HtmlMediaSource, [{ 14245 key: 'addSeekableRange_', 14246 value: function addSeekableRange_(start, end) { 14247 var error = undefined; 14248 14249 if (this.duration !== Infinity) { 14250 error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity');
14251 error.name = 'InvalidStateError'; 14252 error.code = 11; 14253 throw error; 14254 } 14255 14256 if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) { 14257 this.nativeMediaSource_.duration = end; 14258 } 14259 } 14260 14261 /** 14262 * Add a source buffer to the media source. 14263 * 14264 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer 14265 * @param {String} type the content-type of the content 14266 * @return {Object} the created source buffer 14267 */ 14268 }, { 14269 key: 'addSourceBuffer', 14270 value: function addSourceBuffer(type) { 14271 var buffer = undefined; 14272 var parsedType = (0, _codecUtils.parseContentType)(type); 14273 14274 // Create a VirtualSourceBuffer to transmux MPEG-2 transport 14275 // stream segments into fragmented MP4s 14276 if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) { 14277 var codecs = []; 14278 14279 if (parsedType.parameters && parsedType.parameters.codecs) { 14280 codecs = parsedType.parameters.codecs.split(','); 14281 codecs = translateLegacyCodecs(codecs); 14282 codecs = codecs.filter(function (codec) { 14283 return (0, _codecUtils.isAudioCodec)(codec) || (0, _codecUtils.isVideoCodec)(codec); 14284 }); 14285 } 14286 14287 if (codecs.length === 0) { 14288 codecs = ['avc1.4d400d', 'mp4a.40.2']; 14289 } 14290 14291 buffer = new _virtualSourceBuffer2['default'](this, codecs); 14292 14293 if (this.sourceBuffers.length !== 0) { 14294 // If another VirtualSourceBuffer already exists, then we are creating a 14295 // SourceBuffer for an alternate audio track and therefore we know that 14296 // the source has both an audio and video track. 14297 // That means we should trigger the manual creation of the real 14298 // SourceBuffers instead of waiting for the transmuxer to return data 14299 this.sourceBuffers[0].createRealSourceBuffers_(); 14300 buffer.createRealSourceBuffers_(); 14301 14302 // Automatically disable the audio on the first source buffer if 14303 // a second source buffer is ever created 14304 this.sourceBuffers[0].audioDisabled_ = true; 14305 } 14306 } else { 14307 // delegate to the native implementation 14308 buffer = this.nativeMediaSource_.addSourceBuffer(type); 14309 } 14310 14311 this.sourceBuffers.push(buffer); 14312 return buffer; 14313 } 14314 }]); 14315 14316 return HtmlMediaSource; 14317})(_videoJs2['default'].EventTarget); 14318 14319exports['default'] = HtmlMediaSource; 14320module.exports = exports['default']; 14321}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 14322},{"./add-text-track-data":84,"./codec-utils":85,"./virtual-source-buffer":94,"global/document":24,"global/window":25}],91:[function(require,module,exports){ 14323/** 14324 * @file remove-cues-from-track.js 14325 */ 14326 14327/** 14328 * Remove cues from a track on video.js. 14329 * 14330 * @param {Double} start start of where we should remove the cue 14331 * @param {Double} end end of where the we should remove the cue 14332 * @param {Object} track the text track to remove the cues from 14333 * @private 14334 */ 14335"use strict"; 14336 14337Object.defineProperty(exports, "__esModule", { 14338 value: true 14339}); 14340var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) { 14341 var i = undefined; 14342 var cue = undefined; 14343 14344 if (!track) { 14345 return; 14346 } 14347 14348 i = track.cues.length; 14349 14350 while (i--) { 14351 cue = track.cues[i]; 14352 14353 // Remove any overlapping cue 14354 if (cue.startTime <= end && cue.endTime >= start) { 14355 track.removeCue(cue); 14356 } 14357 } 14358}; 14359 14360exports["default"] = removeCuesFromTrack; 14361module.exports = exports["default"]; 14362},{}],92:[function(require,module,exports){ 14363/** 14364 * @file transmuxer-worker.js 14365 */ 14366 14367/** 14368 * videojs-contrib-media-sources 14369 * 14370 * Copyright (c) 2015 Brightcove 14371 * All rights reserved. 14372 * 14373 * Handles communication between the browser-world and the mux.js 14374 * transmuxer running inside of a WebWorker by exposing a simple 14375 * message-based interface to a Transmuxer object. 14376 */ 14377'use strict'; 14378 14379Object.defineProperty(exports, '__esModule', { 14380 value: true 14381}); 14382 14383var _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; }; })(); 14384 14385function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 14386 14387function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 14388 14389var _globalWindow = require('global/window'); 14390 14391var _globalWindow2 = _interopRequireDefault(_globalWindow); 14392 14393var _muxJsLibMp4 = require('mux.js/lib/mp4'); 14394 14395var _muxJsLibMp42 = _interopRequireDefault(_muxJsLibMp4); 14396 14397/**
14398 * Re-emits tranmsuxer events by converting them into messages to the 14399 * world outside the worker. 14400 * 14401 * @param {Object} transmuxer the transmuxer to wire events on 14402 * @private 14403 */ 14404var wireTransmuxerEvents = function wireTransmuxerEvents(transmuxer) { 14405 transmuxer.on('data', function (segment) { 14406 // transfer ownership of the underlying ArrayBuffer 14407 // instead of doing a copy to save memory 14408 // ArrayBuffers are transferable but generic TypedArrays are not 14409 // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects) 14410 var initArray = segment.initSegment; 14411 14412 segment.initSegment = { 14413 data: initArray.buffer, 14414 byteOffset: initArray.byteOffset, 14415 byteLength: initArray.byteLength 14416 }; 14417 14418 var typedArray = segment.data; 14419 14420 segment.data = typedArray.buffer; 14421 _globalWindow2['default'].postMessage({ 14422 action: 'data', 14423 segment: segment, 14424 byteOffset: typedArray.byteOffset, 14425 byteLength: typedArray.byteLength 14426 }, [segment.data]); 14427 }); 14428 14429 if (transmuxer.captionStream) { 14430 transmuxer.captionStream.on('data', function (caption) { 14431 _globalWindow2['default'].postMessage({ 14432 action: 'caption', 14433 data: caption 14434 }); 14435 }); 14436 } 14437 14438 transmuxer.on('done', function (data) { 14439 _globalWindow2['default'].postMessage({ action: 'done' }); 14440 }); 14441}; 14442 14443/** 14444 * All incoming messages route through this hash. If no function exists 14445 * to handle an incoming message, then we ignore the message. 14446 * 14447 * @class MessageHandlers 14448 * @param {Object} options the options to initialize with 14449 */ 14450 14451var MessageHandlers = (function () { 14452 function MessageHandlers(options) { 14453 _classCallCheck(this, MessageHandlers); 14454 14455 this.options = options || {}; 14456 this.init(); 14457 } 14458 14459 /** 14460 * Our web wroker interface so that things can talk to mux.js 14461 * that will be running in a web worker. the scope is passed to this by 14462 * webworkify. 14463 * 14464 * @param {Object} self the scope for the web worker 14465 */ 14466 14467 /** 14468 * initialize our web worker and wire all the events. 14469 */ 14470 14471 _createClass(MessageHandlers, [{ 14472 key: 'init', 14473 value: function init() { 14474 if (this.transmuxer) { 14475 this.transmuxer.dispose(); 14476 } 14477 this.transmuxer = new _muxJsLibMp42['default'].Transmuxer(this.options); 14478 wireTransmuxerEvents(this.transmuxer); 14479 } 14480 14481 /** 14482 * Adds data (a ts segment) to the start of the transmuxer pipeline for 14483 * processing. 14484 * 14485 * @param {ArrayBuffer} data data to push into the muxer 14486 */ 14487 }, { 14488 key: 'push', 14489 value: function push(data) { 14490 // Cast array buffer to correct type for transmuxer 14491 var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); 14492 14493 this.transmuxer.push(segment); 14494 } 14495 14496 /** 14497 * Recreate the transmuxer so that the next segment added via `push` 14498 * start with a fresh transmuxer. 14499 */ 14500 }, { 14501 key: 'reset', 14502 value: function reset() { 14503 this.init(); 14504 } 14505 14506 /** 14507 * Set the value that will be used as the `baseMediaDecodeTime` time for the 14508 * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime` 14509 * set relative to the first based on the PTS values. 14510 * 14511 * @param {Object} data used to set the timestamp offset in the muxer 14512 */ 14513 }, { 14514 key: 'setTimestampOffset', 14515 value: function setTimestampOffset(data) { 14516 var timestampOffset = data.timestampOffset || 0; 14517 14518 this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000)); 14519 } 14520 14521 /** 14522 * Forces the pipeline to finish processing the last segment and emit it's 14523 * results. 14524 * 14525 * @param {Object} data event data, not really used 14526 */ 14527 }, { 14528 key: 'flush', 14529 value: function flush(data) { 14530 this.transmuxer.flush(); 14531 } 14532 }]); 14533 14534 return MessageHandlers; 14535})(); 14536 14537var Worker = function Worker(self) { 14538 self.onmessage = function (event) { 14539 if (event.data.action === 'init' && event.data.options) { 14540 this.messageHandlers = new MessageHandlers(event.data.options); 14541 return; 14542 } 14543 14544 if (!this.messageHandlers) { 14545 this.messageHandlers = new MessageHandlers(); 14546 } 14547 14548 if (event.data && event.data.action && event.data.action !== 'init') { 14549 if (this.messageHandlers[event.data.action]) { 14550 this.messageHandlers[event.data.action](event.data); 14551 } 14552 } 14553 }; 14554}; 14555 14556exports['default'] = function (self) { 14557 return new Worker(self); 14558}; 14559 14560module.exports = exports['default']; 14561},{"global/window":25,"mux.js/lib/mp4":78}],93:[function(require,module,exports){ 14562(function (global){ 14563/** 14564 * @file videojs-contrib-media-sources.js 14565 */ 14566'use strict'; 14567 14568Object.defineProperty(exports, '__esModule', { 14569 value: true 14570}); 14571 14572function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 14573 14574var _globalWindow = require('global/window'); 14575 14576var _globalWindow2 = _interopRequireDefault(_globalWindow); 14577 14578var _flashMediaSource = require('./flash-media-source'); 14579 14580var _flashMediaSource2 = _interopRequireDefault(_flashMediaSource); 14581 14582var _htmlMediaSource = require('./html-media-source'); 14583 14584var _htmlMediaSource2 = _interopRequireDefault(_htmlMediaSource); 14585 14586var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 14587 14588var _videoJs2 = _interopRequireDefault(_videoJs); 14589 14590var urlCount = 0; 14591 14592// ------------ 14593// Media Source 14594// ------------ 14595 14596var defaults = { 14597 // how to determine the MediaSource implementation to use. There 14598 // are three available modes: 14599 // - auto: use native MediaSources where available and Flash 14600 // everywhere else 14601 // - html5: always use native MediaSources 14602 // - flash: always use the Flash MediaSource polyfill 14603 mode: 'auto' 14604}; 14605 14606// store references to the media sources so they can be connected 14607// to a video element (a swf object) 14608// TODO: can we store this somewhere local to this module? 14609_videoJs2['default'].mediaSources = {}; 14610 14611/** 14612 * Provide a method for a swf object to notify JS that a
14613 * media source is now open. 14614 * 14615 * @param {String} msObjectURL string referencing the MSE Object URL 14616 * @param {String} swfId the swf id 14617 */ 14618var open = function open(msObjectURL, swfId) { 14619 var mediaSource = _videoJs2['default'].mediaSources[msObjectURL]; 14620 14621 if (mediaSource) { 14622 mediaSource.trigger({ type: 'sourceopen', swfId: swfId }); 14623 } else { 14624 throw new Error('Media Source not found (Video.js)'); 14625 } 14626}; 14627 14628/** 14629 * Check to see if the native MediaSource object exists and supports 14630 * an MP4 container with both H.264 video and AAC-LC audio. 14631 * 14632 * @return {Boolean} if native media sources are supported 14633 */ 14634var supportsNativeMediaSources = function supportsNativeMediaSources() { 14635 return !!_globalWindow2['default'].MediaSource && !!_globalWindow2['default'].MediaSource.isTypeSupported && _globalWindow2['default'].MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"'); 14636}; 14637 14638/** 14639 * An emulation of the MediaSource API so that we can support 14640 * native and non-native functionality such as flash and 14641 * video/mp2t videos. returns an instance of HtmlMediaSource or 14642 * FlashMediaSource depending on what is supported and what options 14643 * are passed in. 14644 * 14645 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource 14646 * @param {Object} options options to use during setup. 14647 */ 14648var MediaSource = function MediaSource(options) { 14649 var settings = _videoJs2['default'].mergeOptions(defaults, options); 14650 14651 this.MediaSource = { 14652 open: open, 14653 supportsNativeMediaSources: supportsNativeMediaSources 14654 }; 14655 14656 // determine whether HTML MediaSources should be used 14657 if (settings.mode === 'html5' || settings.mode === 'auto' && supportsNativeMediaSources()) { 14658 return new _htmlMediaSource2['default'](); 14659 } 14660 14661 // otherwise, emulate them through the SWF 14662 return new _flashMediaSource2['default'](); 14663}; 14664 14665exports.MediaSource = MediaSource; 14666MediaSource.open = open; 14667MediaSource.supportsNativeMediaSources = supportsNativeMediaSources; 14668 14669/** 14670 * A wrapper around the native URL for our MSE object 14671 * implementation, this object is exposed under videojs.URL 14672 * 14673 * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL 14674 */ 14675var URL = { 14676 /** 14677 * A wrapper around the native createObjectURL for our objects. 14678 * This function maps a native or emulated mediaSource to a blob 14679 * url so that it can be loaded into video.js 14680 * 14681 * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL 14682 * @param {MediaSource} object the object to create a blob url to 14683 */ 14684 createObjectURL: function createObjectURL(object) { 14685 var objectUrlPrefix = 'blob:vjs-media-source/'; 14686 var url = undefined; 14687 14688 // use the native MediaSource to generate an object URL 14689 if (object instanceof _htmlMediaSource2['default']) { 14690 url = _globalWindow2['default'].URL.createObjectURL(object.nativeMediaSource_); 14691 object.url_ = url; 14692 return url; 14693 } 14694 // if the object isn't an emulated MediaSource, delegate to the 14695 // native implementation 14696 if (!(object instanceof _flashMediaSource2['default'])) { 14697 url = _globalWindow2['default'].URL.createObjectURL(object); 14698 object.url_ = url; 14699 return url; 14700 } 14701 14702 // build a URL that can be used to map back to the emulated 14703 // MediaSource 14704 url = objectUrlPrefix + urlCount; 14705 14706 urlCount++; 14707 14708 // setup the mapping back to object 14709 _videoJs2['default'].mediaSources[url] = object; 14710 14711 return url; 14712 } 14713}; 14714 14715exports.URL = URL; 14716_videoJs2['default'].MediaSource = MediaSource; 14717_videoJs2['default'].URL = URL; 14718}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 14719},{"./flash-media-source":88,"./html-media-source":90,"global/window":25}],94:[function(require,module,exports){ 14720(function (global){ 14721/** 14722 * @file virtual-source-buffer.js 14723 */ 14724'use strict'; 14725 14726Object.defineProperty(exports, '__esModule', { 14727 value: true 14728}); 14729 14730var _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; }; })(); 14731 14732var _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); } } }; 14733 14734function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 14735 14736function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 14737 14738function _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
14738onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 14739 14740var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 14741 14742var _videoJs2 = _interopRequireDefault(_videoJs); 14743 14744var _createTextTracksIfNecessary = require('./create-text-tracks-if-necessary'); 14745 14746var _createTextTracksIfNecessary2 = _interopRequireDefault(_createTextTracksIfNecessary); 14747 14748var _removeCuesFromTrack = require('./remove-cues-from-track'); 14749 14750var _removeCuesFromTrack2 = _interopRequireDefault(_removeCuesFromTrack); 14751 14752var _addTextTrackData = require('./add-text-track-data'); 14753 14754var _webworkify = require('webworkify'); 14755 14756var _webworkify2 = _interopRequireDefault(_webworkify); 14757 14758var _transmuxerWorker = require('./transmuxer-worker'); 14759 14760var _transmuxerWorker2 = _interopRequireDefault(_transmuxerWorker); 14761 14762var _codecUtils = require('./codec-utils'); 14763 14764/** 14765 * VirtualSourceBuffers exist so that we can transmux non native formats 14766 * into a native format, but keep the same api as a native source buffer. 14767 * It creates a transmuxer, that works in its own thread (a web worker) and 14768 * that transmuxer muxes the data into a native format. VirtualSourceBuffer will 14769 * then send all of that data to the naive sourcebuffer so that it is 14770 * indestinguishable from a natively supported format. 14771 * 14772 * @param {HtmlMediaSource} mediaSource the parent mediaSource 14773 * @param {Array} codecs array of codecs that we will be dealing with 14774 * @class VirtualSourceBuffer 14775 * @extends video.js.EventTarget 14776 */ 14777 14778var VirtualSourceBuffer = (function (_videojs$EventTarget) { 14779 _inherits(VirtualSourceBuffer, _videojs$EventTarget); 14780 14781 function VirtualSourceBuffer(mediaSource, codecs) { 14782 var _this = this; 14783 14784 _classCallCheck(this, VirtualSourceBuffer); 14785 14786 _get(Object.getPrototypeOf(VirtualSourceBuffer.prototype), 'constructor', this).call(this, _videoJs2['default'].EventTarget); 14787 this.timestampOffset_ = 0; 14788 this.pendingBuffers_ = []; 14789 this.bufferUpdating_ = false; 14790 this.mediaSource_ = mediaSource; 14791 this.codecs_ = codecs; 14792 this.audioCodec_ = null; 14793 this.videoCodec_ = null; 14794 this.audioDisabled_ = false; 14795 this.appendAudioInitSegment_ = true; 14796 14797 var options = { 14798 remux: false 14799 }; 14800 14801 this.codecs_.forEach(function (codec) { 14802 if ((0, _codecUtils.isAudioCodec)(codec)) { 14803 _this.audioCodec_ = codec; 14804 } else if ((0, _codecUtils.isVideoCodec)(codec)) { 14805 _this.videoCodec_ = codec; 14806 } 14807 }); 14808 14809 // append muxed segments to their respective native buffers as 14810 // soon as they are available 14811 this.transmuxer_ = (0, _webworkify2['default'])(_transmuxerWorker2['default']); 14812 this.transmuxer_.postMessage({ action: 'init', options: options }); 14813 14814 this.transmuxer_.onmessage = function (event) { 14815 if (event.data.action === 'data') { 14816 return _this.data_(event); 14817 } 14818 14819 if (event.data.action === 'done') { 14820 return _this.done_(event); 14821 } 14822 }; 14823 14824 // this timestampOffset is a property with the side-effect of resetting 14825 // baseMediaDecodeTime in the transmuxer on the setter 14826 Object.defineProperty(this, 'timestampOffset', { 14827 get: function get() { 14828 return this.timestampOffset_; 14829 }, 14830 set: function set(val) { 14831 if (typeof val === 'number' && val >= 0) { 14832 this.timestampOffset_ = val; 14833 this.appendAudioInitSegment_ = true; 14834 14835 // We have to tell the transmuxer to set the baseMediaDecodeTime to 14836 // the desired timestampOffset for the next segment 14837 this.transmuxer_.postMessage({ 14838 action: 'setTimestampOffset', 14839 timestampOffset: val 14840 }); 14841 } 14842 } 14843 }); 14844 14845 // setting the append window affects both source buffers 14846 Object.defineProperty(this, 'appendWindowStart', { 14847 get: function get() { 14848 return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart; 14849 }, 14850 set: function set(start) { 14851 if (this.videoBuffer_) { 14852 this.videoBuffer_.appendWindowStart = start; 14853 } 14854 if (this.audioBuffer_) { 14855 this.audioBuffer_.appendWindowStart = start; 14856 } 14857 } 14858 }); 14859 14860 // this buffer is "updating" if either of its native buffers are 14861 Object.defineProperty(this, 'updating', { 14862 get: function get() { 14863 return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating); 14864 } 14865 }); 14866 14867 // the buffered property is the intersection of the buffered 14868 // ranges of the native source buffers 14869 Object.defineProperty(this, 'buffered', { 14870 get: function get() { 14871 var start = null; 14872 var end = null; 14873 var arity = 0; 14874 var extents = []; 14875 var ranges = []; 14876 14877 // neither buffer has been created yet 14878 if (!this.videoBuffer_ && !this.audioBuffer_) { 14879 return _videoJs2['default'].createTimeRange(); 14880 } 14881 14882 // only one buffer is configured 14883 if (!this.videoBuffer_) { 14884 return this.audioBuffer_.buffered; 14885 } 14886 if (!this.audioBuffer_) { 14887 return this.videoBuffer_.buffered; 14888 } 14889 14890 // both buffers are configured 14891 if (this.audioDisabled_) { 14892 return this.videoBuffer_.buffered; 14893 } 14894 14895 // both buffers are empty 14896 if (this.videoBuffer_.buffered.length === 0 && this.audioBuffer_.buffered.length === 0) { 14897 return _videoJs2['default'].createTimeRange(); 14898 } 14899 14900 // Handle the case where we have both buffers and create an 14901 // intersection of the two 14902 var videoBuffered = this.videoBuffer_.buffered; 14903 var audioBuffered = this.audioBuffer_.buffered; 14904 var count = videoBuffered.length; 14905 14906 // A) Gather up all start and end times 14907 while (count--) { 14908 extents.push({ time: videoBuffered.start(count), type: 'start' }); 14909 extents.push({ time: videoBuffered.end(count), type: 'end' }); 14910 } 14911 count = audioBuffered.length; 14912 while (count--) { 14913 extents.push({ time: audioBuffered.start(count), type: 'start' }); 14914 extents.push({ time: audioBuffered.end(count), type: 'end' }); 14915 } 14916 // B) Sort them by time 14917 extents.sort(function (a, b) { 14918 return a.time - b.time; 14919 }); 14920 14921 // C) Go along one by one incrementing arity for start and decrementing 14922 // arity for ends 14923 for (count = 0; count < extents.length; count++) { 14924 if (extents[count].type === 'start') { 14925 arity++; 14926 14927 // D) If arity is ever incremented to 2 we are entering an 14928 // overlapping range 14929 if (arity === 2) { 14930 start = extents[count].time; 14931 } 14932 } else if (extents[count].type === 'end') { 14933 arity--; 14934
14935 // E) If arity is ever decremented to 1 we leaving an 14936 // overlapping range 14937 if (arity === 1) { 14938 end = extents[count].time; 14939 } 14940 } 14941 14942 // F) Record overlapping ranges 14943 if (start !== null && end !== null) { 14944 ranges.push([start, end]); 14945 start = null; 14946 end = null; 14947 } 14948 } 14949 14950 return _videoJs2['default'].createTimeRanges(ranges); 14951 } 14952 }); 14953 } 14954 14955 /** 14956 * When we get a data event from the transmuxer 14957 * we call this function and handle the data that 14958 * was sent to us 14959 * 14960 * @private 14961 * @param {Event} event the data event from the transmuxer 14962 */ 14963 14964 _createClass(VirtualSourceBuffer, [{ 14965 key: 'data_', 14966 value: function data_(event) { 14967 var segment = event.data.segment; 14968 14969 // Cast ArrayBuffer to TypedArray 14970 segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength); 14971 14972 segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength); 14973 14974 (0, _createTextTracksIfNecessary2['default'])(this, this.mediaSource_, segment); 14975 14976 // Add the segments to the pendingBuffers array 14977 this.pendingBuffers_.push(segment); 14978 return; 14979 } 14980 14981 /** 14982 * When we get a done event from the transmuxer 14983 * we call this function and we process all 14984 * of the pending data that we have been saving in the 14985 * data_ function 14986 * 14987 * @private 14988 * @param {Event} event the done event from the transmuxer 14989 */ 14990 }, { 14991 key: 'done_', 14992 value: function done_(event) { 14993 // All buffers should have been flushed from the muxer 14994 // start processing anything we have received 14995 this.processPendingSegments_(); 14996 return; 14997 } 14998 14999 /** 15000 * Create our internal native audio/video source buffers and add 15001 * event handlers to them with the following conditions: 15002 * 1. they do not already exist on the mediaSource 15003 * 2. this VSB has a codec for them 15004 * 15005 * @private 15006 */ 15007 }, { 15008 key: 'createRealSourceBuffers_', 15009 value: function createRealSourceBuffers_() { 15010 var _this2 = this; 15011 15012 var types = ['audio', 'video']; 15013 15014 types.forEach(function (type) { 15015 // Don't create a SourceBuffer of this type if we don't have a 15016 // codec for it 15017 if (!_this2[type + 'Codec_']) { 15018 return; 15019 } 15020 15021 // Do nothing if a SourceBuffer of this type already exists 15022 if (_this2[type + 'Buffer_']) { 15023 return; 15024 } 15025 15026 var buffer = null; 15027 15028 // If the mediasource already has a SourceBuffer for the codec 15029 // use that 15030 if (_this2.mediaSource_[type + 'Buffer_']) { 15031 buffer = _this2.mediaSource_[type + 'Buffer_']; 15032 } else { 15033 buffer = _this2.mediaSource_.nativeMediaSource_.addSourceBuffer(type + '/mp4;codecs="' + _this2[type + 'Codec_'] + '"'); 15034 _this2.mediaSource_[type + 'Buffer_'] = buffer; 15035 } 15036 15037 _this2[type + 'Buffer_'] = buffer; 15038 15039 // Wire up the events to the SourceBuffer 15040 ['update', 'updatestart', 'updateend'].forEach(function (event) { 15041 buffer.addEventListener(event, function () { 15042 // if audio is disabled 15043 if (type === 'audio' && _this2.audioDisabled_) { 15044 return; 15045 } 15046 15047 var shouldTrigger = types.every(function (t) { 15048 // skip checking audio's updating status if audio 15049 // is not enabled 15050 if (t === 'audio' && _this2.audioDisabled_) { 15051 return true; 15052 } 15053 // if the other type if updating we don't trigger 15054 if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) { 15055 return false; 15056 } 15057 return true; 15058 }); 15059 15060 if (shouldTrigger) { 15061 return _this2.trigger(event); 15062 } 15063 }); 15064 }); 15065 }); 15066 } 15067 15068 /** 15069 * Emulate the native mediasource function, but our function will 15070 * send all of the proposed segments to the transmuxer so that we 15071 * can transmux them before we append them to our internal 15072 * native source buffers in the correct format. 15073 * 15074 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer 15075 * @param {Uint8Array} segment the segment to append to the buffer 15076 */ 15077 }, { 15078 key: 'appendBuffer', 15079 value: function appendBuffer(segment) { 15080 // Start the internal "updating" state 15081 this.bufferUpdating_ = true; 15082 15083 this.transmuxer_.postMessage({ 15084 action: 'push', 15085 // Send the typed-array of data as an ArrayBuffer so that 15086 // it can be sent as a "Transferable" and avoid the costly 15087 // memory copy 15088 data: segment.buffer, 15089 15090 // To recreate the original typed-array, we need information 15091 // about what portion of the ArrayBuffer it was a view into 15092 byteOffset: segment.byteOffset, 15093 byteLength: segment.byteLength 15094 }, [segment.buffer]); 15095 this.transmuxer_.postMessage({ action: 'flush' }); 15096 } 15097 15098 /** 15099 * Emulate the native mediasource function and remove parts 15100 * of the buffer from any of our internal buffers that exist 15101 * 15102 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove 15103 * @param {Double} start position to start the remove at 15104 * @param {Double} end position to end the remove at 15105 */ 15106 }, { 15107 key: 'remove', 15108 value: function remove(start, end) { 15109 if (this.videoBuffer_) { 15110 this.videoBuffer_.remove(start, end); 15111 } 15112 if (this.audioBuffer_) { 15113 this.audioBuffer_.remove(start, end); 15114 } 15115 15116 // Remove Metadata Cues (id3) 15117 (0, _removeCuesFromTrack2['default'])(start, end, this.metadataTrack_); 15118 15119 // Remove Any Captions 15120 (0, _removeCuesFromTrack2['default'])(start, end, this.inbandTextTrack_); 15121 } 15122 15123 /** 15124 * Process any segments that the muxer has output 15125 * Concatenate segments together based on type and append them into 15126 * their respective sourceBuffers 15127 * 15128 * @private 15129 */ 15130 }, { 15131 key: 'processPendingSegments_', 15132 value: function processPendingSegments_() { 15133 var sortedSegments = { 15134 video: { 15135 segments: [], 15136 bytes: 0 15137 }, 15138 audio: { 15139 segments: [], 15140 bytes: 0 15141 }, 15142 captions: [], 15143 metadata: [] 15144 }; 15145 15146 // Sort segments into separate video/audio arrays and 15147 // keep track of their total byte lengths 15148 sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) { 15149 var type = segment.type; 15150 var data = segment.data; 15151 var initSegment = segment.initSegment; 15152 15153 segmentObj[type].segments.push(data); 15154 segmentObj[type].bytes += data.byteLength; 15155 15156 segmentObj[type].initSegment = initSegment; 15157 15158 // Gather any captions into a single array 15159 if (segment.captions) { 15160 segmentObj.captions = segmentObj.captions.concat(segment.captions); 15161 } 15162 15163 if (segment.info) { 15164 segmentObj[type].info = segment.info; 15165 } 15166 15167 // Gather any metadata into a single array 15168 if (segment.metadata) { 15169 segmentObj.metadata = segmentObj.metadata.concat(segment.metadata); 15170 } 15171 15172 return segmentObj; 15173 }, sortedSegments); 15174 15175 // Create the real source buffers if they don't exist by now since we 15176 // finally are sure what tracks are contained in the source 15177 if (!this.videoBuffer_ && !this.audioBuffer_) { 15178 // Remove any codecs that may have been specified by default but 15179 // are no longer applicable now 15180 if (sortedSegments.video.bytes === 0) { 15181 this.videoCodec_ = null; 15182 } 15183 if (sortedSegments.audio.bytes === 0) { 15184 this.audioCodec_ = null; 15185 } 15186 15187 this.createRealSourceBuffers_(); 15188 } 15189 15190 if (sortedSegments.audio.info) { 15191 this.mediaSource_.trigger({ type: 'audioinfo', info: sortedSegments.audio.info }); 15192 } 15193 if (sortedSegments.video.info) { 15194 this.mediaSource_.trigger({ type: 'videoinfo', info: sortedSegments.video.info }); 15195 } 15196 15197 if (this.appendAudioInitSegment_) { 15198 if (!this.audioDisabled_ && this.audioBuffer_) { 15199 sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment); 15200 sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength; 15201 } 15202 this.appendAudioInitSegment_ = false;
15203 } 15204 15205 // Merge multiple video and audio segments into one and append 15206 if (this.videoBuffer_) { 15207 sortedSegments.video.segments.unshift(sortedSegments.video.initSegment); 15208 sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength; 15209 this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_); 15210 // TODO: are video tracks the only ones with text tracks? 15211 (0, _addTextTrackData.addTextTrackData)(this, sortedSegments.captions, sortedSegments.metadata); 15212 } 15213 if (!this.audioDisabled_ && this.audioBuffer_) { 15214 this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_); 15215 } 15216 15217 this.pendingBuffers_.length = 0; 15218 15219 // We are no longer in the internal "updating" state 15220 this.bufferUpdating_ = false; 15221 } 15222 15223 /** 15224 * Combine all segments into a single Uint8Array and then append them 15225 * to the destination buffer 15226 * 15227 * @param {Object} segmentObj 15228 * @param {SourceBuffer} destinationBuffer native source buffer to append data to 15229 * @private 15230 */ 15231 }, { 15232 key: 'concatAndAppendSegments_', 15233 value: function concatAndAppendSegments_(segmentObj, destinationBuffer) { 15234 var offset = 0; 15235 var tempBuffer = undefined; 15236 15237 if (segmentObj.bytes) { 15238 tempBuffer = new Uint8Array(segmentObj.bytes); 15239 15240 // Combine the individual segments into one large typed-array 15241 segmentObj.segments.forEach(function (segment) { 15242 tempBuffer.set(segment, offset); 15243 offset += segment.byteLength; 15244 }); 15245 15246 destinationBuffer.appendBuffer(tempBuffer); 15247 } 15248 } 15249 15250 /** 15251 * Emulate the native mediasource function. abort any soureBuffer 15252 * actions and throw out any un-appended data. 15253 * 15254 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort 15255 */ 15256 }, { 15257 key: 'abort', 15258 value: function abort() { 15259 if (this.videoBuffer_) { 15260 this.videoBuffer_.abort(); 15261 } 15262 if (this.audioBuffer_) { 15263 this.audioBuffer_.abort(); 15264 } 15265 if (this.transmuxer_) { 15266 this.transmuxer_.postMessage({ action: 'reset' }); 15267 } 15268 this.pendingBuffers_.length = 0; 15269 this.bufferUpdating_ = false; 15270 } 15271 }]); 15272 15273 return VirtualSourceBuffer; 15274})(_videoJs2['default'].EventTarget); 15275 15276exports['default'] = VirtualSourceBuffer; 15277module.exports = exports['default']; 15278}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 15279},{"./add-text-track-data":84,"./codec-utils":85,"./create-text-tracks-if-necessary":86,"./remove-cues-from-track":91,"./transmuxer-worker":92,"webworkify":95}],95:[function(require,module,exports){ 15280var bundleFn = arguments[3]; 15281var sources = arguments[4]; 15282var cache = arguments[5]; 15283 15284var stringify = JSON.stringify; 15285 15286module.exports = function (fn) { 15287 var keys = []; 15288 var wkey; 15289 var cacheKeys = Object.keys(cache); 15290 15291 for (var i = 0, l = cacheKeys.length; i < l; i++) { 15292 var key = cacheKeys[i]; 15293 if (cache[key].exports === fn) { 15294 wkey = key; 15295 break; 15296 } 15297 } 15298 15299 if (!wkey) { 15300 wkey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16); 15301 var wcache = {}; 15302 for (var i = 0, l = cacheKeys.length; i < l; i++) { 15303 var key = cacheKeys[i]; 15304 wcache[key] = key; 15305 } 15306 sources[wkey] = [ 15307 Function(['require','module','exports'], '(' + fn + ')(self)'), 15308 wcache 15309 ]; 15310 } 15311 var skey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16); 15312 15313 var scache = {}; scache[wkey] = wkey; 15314 sources[skey] = [ 15315 Function(['require'],'require(' + stringify(wkey) + ')(self)'), 15316 scache 15317 ]; 15318 15319 var src = '(' + bundleFn + ')({' 15320 + Object.keys(sources).map(function (key) { 15321 return stringify(key) + ':[' 15322 + sources[key][0] 15323 + ',' + stringify(sources[key][1]) + ']' 15324 ; 15325 }).join(',') 15326 + '},{},[' + stringify(skey) + '])' 15327 ; 15328 15329 var URL = window.URL || window.webkitURL || window.mozURL || window.msURL; 15330 15331 return new Worker(URL.createObjectURL( 15332 new Blob([src], { type: 'text/javascript' }) 15333 )); 15334}; 15335 15336},{}],96:[function(require,module,exports){ 15337(function (global){ 15338/** 15339 * @file videojs-contrib-hls.js 15340 * 15341 * The main file for the HLS project. 15342 * License: https://github.com/videojs/videojs-contrib-hls/blob/master/LICENSE 15343 */ 15344'use strict'; 15345 15346var _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; }; })(); 15347 15348var _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); } } }; 15349 15350function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 15351 15352function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 15353 15354function _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
15354onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 15355 15356var _globalDocument = require('global/document'); 15357 15358var _globalDocument2 = _interopRequireDefault(_globalDocument); 15359 15360var _playlistLoader = require('./playlist-loader'); 15361 15362var _playlistLoader2 = _interopRequireDefault(_playlistLoader); 15363 15364var _playlist = require('./playlist'); 15365 15366var _playlist2 = _interopRequireDefault(_playlist); 15367 15368var _xhr = require('./xhr'); 15369 15370var _xhr2 = _interopRequireDefault(_xhr); 15371 15372var _aesDecrypter = require('aes-decrypter'); 15373 15374var _binUtils = require('./bin-utils'); 15375 15376var _binUtils2 = _interopRequireDefault(_binUtils); 15377 15378var _videojsContribMediaSources = require('videojs-contrib-media-sources'); 15379 15380var _m3u8Parser = require('m3u8-parser'); 15381 15382var _m3u8Parser2 = _interopRequireDefault(_m3u8Parser); 15383 15384var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 15385 15386var _videoJs2 = _interopRequireDefault(_videoJs); 15387 15388var _masterPlaylistController = require('./master-playlist-controller'); 15389 15390var _config = require('./config'); 15391 15392var _config2 = _interopRequireDefault(_config); 15393 15394var _renditionMixin = require('./rendition-mixin'); 15395 15396var _renditionMixin2 = _interopRequireDefault(_renditionMixin); 15397 15398var _gapSkipper = require('./gap-skipper'); 15399 15400var _gapSkipper2 = _interopRequireDefault(_gapSkipper); 15401 15402var _globalWindow = require('global/window'); 15403 15404var _globalWindow2 = _interopRequireDefault(_globalWindow); 15405 15406var Hls = { 15407 PlaylistLoader: _playlistLoader2['default'], 15408 Playlist: _playlist2['default'], 15409 Decrypter: _aesDecrypter.Decrypter, 15410 AsyncStream: _aesDecrypter.AsyncStream, 15411 decrypt: _aesDecrypter.decrypt, 15412 utils: _binUtils2['default'], 15413 xhr: (0, _xhr2['default'])() 15414}; 15415 15416Object.defineProperty(Hls, 'GOAL_BUFFER_LENGTH', { 15417 get: function get() { 15418 _videoJs2['default'].log.warn('using Hls.GOAL_BUFFER_LENGTH is UNSAFE be sure ' + 'you know what you are doing'); 15419 return _config2['default'].GOAL_BUFFER_LENGTH; 15420 }, 15421 set: function set(v) { 15422 _videoJs2['default'].log.warn('using Hls.GOAL_BUFFER_LENGTH is UNSAFE be sure ' + 'you know what you are doing'); 15423 if (typeof v !== 'number' || v <= 0) { 15424 _videoJs2['default'].log.warn('value passed to Hls.GOAL_BUFFER_LENGTH ' + 'must be a number and greater than 0'); 15425 return; 15426 } 15427 _config2['default'].GOAL_BUFFER_LENGTH = v; 15428 } 15429}); 15430 15431// A fudge factor to apply to advertised playlist bitrates to account for 15432// temporary flucations in client bandwidth 15433var BANDWIDTH_VARIANCE = 1.2; 15434 15435/** 15436 * Returns the CSS value for the specified property on an element 15437 * using `getComputedStyle`. Firefox has a long-standing issue where 15438 * getComputedStyle() may return null when running in an iframe with 15439 * `display: none`. 15440 * 15441 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 15442 * @param {HTMLElement} el the htmlelement to work on 15443 * @param {string} the proprety to get the style for 15444 */ 15445var safeGetComputedStyle = function safeGetComputedStyle(el, property) { 15446 var result = undefined; 15447 15448 if (!el) { 15449 return ''; 15450 } 15451 15452 result = _globalWindow2['default'].getComputedStyle(el); 15453 if (!result) { 15454 return ''; 15455 } 15456 15457 return result[property]; 15458}; 15459 15460/** 15461 * Chooses the appropriate media playlist based on the current 15462 * bandwidth estimate and the player size. 15463 * 15464 * @return {Playlist} the highest bitrate playlist less than the currently detected 15465 * bandwidth, accounting for some amount of bandwidth variance 15466 */ 15467Hls.STANDARD_PLAYLIST_SELECTOR = function () { 15468 var effectiveBitrate = undefined; 15469 var sortedPlaylists = this.playlists.master.playlists.slice(); 15470 var bandwidthPlaylists = []; 15471 var now = +new Date(); 15472 var i = undefined; 15473 var variant = undefined; 15474 var bandwidthBestVariant = undefined; 15475 var resolutionPlusOne = undefined; 15476 var resolutionPlusOneAttribute = undefined; 15477 var resolutionBestVariant = undefined; 15478 var width = undefined; 15479 var height = undefined; 15480 15481 sortedPlaylists.sort(Hls.comparePlaylistBandwidth); 15482 15483 // filter out any playlists that have been excluded due to 15484 // incompatible configurations or playback errors 15485 sortedPlaylists = sortedPlaylists.filter(function (localVariant) { 15486 if (typeof localVariant.excludeUntil !== 'undefined') { 15487 return now >= localVariant.excludeUntil; 15488 } 15489 return true; 15490 }); 15491 15492 // filter out any variant that has greater effective bitrate 15493 // than the current estimated bandwidth 15494 i = sortedPlaylists.length; 15495 while (i--) {
15496 variant = sortedPlaylists[i]; 15497 15498 // ignore playlists without bandwidth information 15499 if (!variant.attributes || !variant.attributes.BANDWIDTH) { 15500 continue; 15501 } 15502 15503 effectiveBitrate = variant.attributes.BANDWIDTH * BANDWIDTH_VARIANCE; 15504 15505 if (effectiveBitrate < this.bandwidth) { 15506 bandwidthPlaylists.push(variant); 15507 15508 // since the playlists are sorted in ascending order by 15509 // bandwidth, the first viable variant is the best 15510 if (!bandwidthBestVariant) { 15511 bandwidthBestVariant = variant; 15512 } 15513 } 15514 } 15515 15516 i = bandwidthPlaylists.length; 15517 15518 // sort variants by resolution 15519 bandwidthPlaylists.sort(Hls.comparePlaylistResolution); 15520 15521 // forget our old variant from above, 15522 // or we might choose that in high-bandwidth scenarios 15523 // (this could be the lowest bitrate rendition as we go through all of them above) 15524 variant = null; 15525 15526 width = parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10); 15527 height = parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10); 15528 15529 // iterate through the bandwidth-filtered playlists and find 15530 // best rendition by player dimension 15531 while (i--) { 15532 variant = bandwidthPlaylists[i]; 15533 15534 // ignore playlists without resolution information 15535 if (!variant.attributes || !variant.attributes.RESOLUTION || !variant.attributes.RESOLUTION.width || !variant.attributes.RESOLUTION.height) { 15536 continue; 15537 } 15538 15539 // since the playlists are sorted, the first variant that has 15540 // dimensions less than or equal to the player size is the best 15541 var variantResolution = variant.attributes.RESOLUTION; 15542 15543 if (variantResolution.width === width && variantResolution.height === height) { 15544 // if we have the exact resolution as the player use it 15545 resolutionPlusOne = null; 15546 resolutionBestVariant = variant; 15547 break; 15548 } else if (variantResolution.width < width && variantResolution.height < height) { 15549 // if both dimensions are less than the player use the 15550 // previous (next-largest) variant 15551 break; 15552 } else if (!resolutionPlusOne || variantResolution.width < resolutionPlusOneAttribute.width && variantResolution.height < resolutionPlusOneAttribute.height) { 15553 // If we still haven't found a good match keep a 15554 // reference to the previous variant for the next loop 15555 // iteration 15556 15557 // By only saving variants if they are smaller than the 15558 // previously saved variant, we ensure that we also pick 15559 // the highest bandwidth variant that is just-larger-than 15560 // the video player 15561 resolutionPlusOne = variant; 15562 resolutionPlusOneAttribute = resolutionPlusOne.attributes.RESOLUTION; 15563 } 15564 } 15565 15566 // fallback chain of variants 15567 return resolutionPlusOne || resolutionBestVariant || bandwidthBestVariant || sortedPlaylists[0]; 15568}; 15569 15570// HLS is a source handler, not a tech. Make sure attempts to use it 15571// as one do not cause exceptions. 15572Hls.canPlaySource = function () { 15573 return _videoJs2['default'].log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 15574}; 15575 15576/** 15577 * Whether the browser has built-in HLS support. 15578 */ 15579Hls.supportsNativeHls = (function () { 15580 var video = _globalDocument2['default'].createElement('video'); 15581 15582 // native HLS is definitely not supported if HTML5 video isn't 15583 if (!_videoJs2['default'].getComponent('Html5').isSupported()) { 15584 return false; 15585 } 15586 15587 // HLS manifests can go by many mime-types 15588 var canPlay = [ 15589 // Apple santioned 15590 'application/vnd.apple.mpegurl', 15591 // Apple sanctioned for backwards compatibility 15592 'audio/mpegurl', 15593 // Very common 15594 'audio/x-mpegurl', 15595 // Very common 15596 'application/x-mpegurl',
15597 // Included for completeness 15598 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl']; 15599 15600 return canPlay.some(function (canItPlay) { 15601 return (/maybe|probably/i.test(video.canPlayType(canItPlay)) 15602 ); 15603 }); 15604})(); 15605 15606/** 15607 * HLS is a source handler, not a tech. Make sure attempts to use it 15608 * as one do not cause exceptions. 15609 */ 15610Hls.isSupported = function () { 15611 return _videoJs2['default'].log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 15612}; 15613 15614var USER_AGENT = _globalWindow2['default'].navigator && _globalWindow2['default'].navigator.userAgent || ''; 15615 15616/** 15617 * Determines whether the browser supports a change in the audio configuration 15618 * during playback. Currently only Firefox 48 and below do not support this. 15619 * window.isSecureContext is a propterty that was added to window in firefox 49, 15620 * so we can use it to detect Firefox 49+. 15621 * 15622 * @return {Boolean} Whether the browser supports audio config change during playback 15623 */ 15624Hls.supportsAudioInfoChange_ = function () { 15625 if (_videoJs2['default'].browser.IS_FIREFOX) { 15626 var firefoxVersionMap = /Firefox\/([\d.]+)/i.exec(USER_AGENT); 15627 var version = parseInt(firefoxVersionMap[1], 10); 15628 15629 return version >= 49; 15630 } 15631 return true; 15632}; 15633 15634var Component = _videoJs2['default'].getComponent('Component'); 15635 15636/** 15637 * The Hls Handler object, where we orchestrate all of the parts 15638 * of HLS to interact with video.js 15639 * 15640 * @class HlsHandler 15641 * @extends videojs.Component 15642 * @param {Object} source the soruce object 15643 * @param {Tech} tech the parent tech object 15644 * @param {Object} options optional and required options 15645 */ 15646 15647var HlsHandler = (function (_Component) { 15648 _inherits(HlsHandler, _Component); 15649 15650 function HlsHandler(source, tech, options) { 15651 var _this = this; 15652 15653 _classCallCheck(this, HlsHandler); 15654 15655 _get(Object.getPrototypeOf(HlsHandler.prototype), 'constructor', this).call(this, tech); 15656 15657 // tech.player() is deprecated but setup a reference to HLS for 15658 // backwards-compatibility 15659 if (tech.options_ && tech.options_.playerId) { 15660 var _player = (0, _videoJs2['default'])(tech.options_.playerId); 15661 15662 if (!_player.hasOwnProperty('hls')) { 15663 Object.defineProperty(_player, 'hls', { 15664 get: function get() { 15665 _videoJs2['default'].log.warn('player.hls is deprecated. Use player.tech_.hls instead.'); 15666 return _this; 15667 } 15668 }); 15669 } 15670 } 15671 15672 this.tech_ = tech; 15673 this.source_ = source; 15674 this.stats = {}; 15675 15676 // handle global & Source Handler level options 15677 this.options_ = _videoJs2['default'].mergeOptions(_videoJs2['default'].options.hls || {}, options.hls); 15678 this.setOptions_(); 15679 15680 // listen for fullscreenchange events for this player so that we 15681 // can adjust our quality selection quickly 15682 this.on(_globalDocument2['default'], ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) { 15683 var fullscreenElement = _globalDocument2['default'].fullscreenElement || _globalDocument2['default'].webkitFullscreenElement || _globalDocument2['default'].mozFullScreenElement || _globalDocument2['default'].msFullscreenElement; 15684 15685 if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) { 15686 _this.masterPlaylistController_.fastQualityChange_(); 15687 } 15688 }); 15689 15690 this.on(this.tech_, 'seeking', function () { 15691 this.setCurrentTime(this.tech_.currentTime()); 15692 }); 15693 this.on(this.tech_, 'error', function () { 15694 if (this.masterPlaylistController_) { 15695 this.masterPlaylistController_.pauseLoading(); 15696 } 15697 }); 15698 15699 this.audioTrackChange_ = function () { 15700 _this.masterPlaylistController_.setupAudio(); 15701 }; 15702 15703 this.on(this.tech_, 'play', this.play); 15704 } 15705 15706 /** 15707 * The Source Handler object, which informs video.js what additional 15708 * MIME types are supported and sets up playback. It is registered 15709 * automatically to the appropriate tech based on the capabilities of 15710 * the browser it is running in. It is not necessary to use or modify 15711 * this object in normal usage. 15712 */ 15713 15714 _createClass(HlsHandler, [{ 15715 key: 'setOptions_', 15716 value: function setOptions_() { 15717 var _this2 = this; 15718 15719 // defaults 15720 this.options_.withCredentials = this.options_.withCredentials || false; 15721 15722 // start playlist selection at a reasonable bandwidth for 15723 // broadband internet 15724 // 0.5 MB/s 15725 if (typeof this.options_.bandwidth !== 'number') { 15726 this.options_.bandwidth = 4194304; 15727 } 15728 15729 // grab options passed to player.src 15730 ['withCredentials', 'bandwidth'].forEach(function (option) { 15731 if (typeof _this2.source_[option] !== 'undefined') { 15732 _this2.options_[option] = _this2.source_[option]; 15733 } 15734 }); 15735 15736 this.bandwidth = this.options_.bandwidth; 15737 } 15738 15739 /** 15740 * called when player.src gets called, handle a new source 15741 * 15742 * @param {Object} src the source object to handle 15743 */ 15744 }, { 15745 key: 'src', 15746 value: function src(_src) { 15747 var _this3 = this; 15748 15749 // do nothing if the src is falsey 15750 if (!_src) { 15751 return; 15752 } 15753 this.setOptions_(); 15754 // add master playlist controller options 15755 this.options_.url = this.source_.src; 15756 this.options_.tech = this.tech_; 15757 this.options_.externHls = Hls; 15758 this.masterPlaylistController_ = new _masterPlaylistController.MasterPlaylistController(this.options_); 15759 this.gapSkipper_ = new _gapSkipper2['default'](this.options_); 15760 15761 // `this` in selectPlaylist should be the HlsHandler for backwards 15762 // compatibility with < v2 15763 this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls.STANDARD_PLAYLIST_SELECTOR.bind(this); 15764 15765 // re-expose some internal objects for backwards compatibility with < v2 15766 this.playlists = this.masterPlaylistController_.masterPlaylistLoader_; 15767 this.mediaSource = this.masterPlaylistController_.mediaSource; 15768 15769 // Proxy assignment of some properties to the master playlist 15770 // controller. Using a custom property for backwards compatibility 15771 // with < v2 15772 Object.defineProperties(this, { 15773 selectPlaylist: { 15774 get: function get() { 15775 return this.masterPlaylistController_.selectPlaylist; 15776 }, 15777 set: function set(selectPlaylist) { 15778 this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this); 15779 } 15780 }, 15781 bandwidth: { 15782 get: function get() { 15783 return this.masterPlaylistController_.mainSegmentLoader_.bandwidth; 15784 }, 15785 set: function set(bandwidth) { 15786 this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; 15787 } 15788 } 15789 }); 15790 15791 Object.defineProperties(this.stats, { 15792 bandwidth: { 15793 get: function get() { 15794 return _this3.bandwidth || 0; 15795 }, 15796 enumerable: true 15797 }, 15798 mediaRequests: { 15799 get: function get() { 15800 return _this3.masterPlaylistController_.mediaRequests_() || 0; 15801 }, 15802 enumerable: true 15803 }, 15804 mediaTransferDuration: { 15805 get: function get() { 15806 return _this3.masterPlaylistController_.mediaTransferDuration_() || 0; 15807 }, 15808 enumerable: true 15809 }, 15810 mediaBytesTransferred: { 15811 get: function get() { 15812 return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0; 15813 }, 15814 enumerable: true 15815 } 15816 }); 15817 15818 this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_)); 15819 15820 this.masterPlaylistController_.on('sourceopen', function () { 15821 _this3.tech_.audioTracks().addEventListener('change', _this3.audioTrackChange_); 15822 }); 15823 15824 this.masterPlaylistController_.on('selectedinitialmedia', function () { 15825 // Add the manual rendition mix-in to HlsHandler 15826 (0, _renditionMixin2['default'])(_this3); 15827 }); 15828 15829 this.masterPlaylistController_.on('audioupdate', function () { 15830 // clear current audioTracks 15831 _this3.tech_.clearTracks('audio'); 15832 _this3.masterPlaylistController_.activeAudioGroup().forEac
15832h(function (audioTrack) { 15833 _this3.tech_.audioTracks().addTrack(audioTrack); 15834 }); 15835 }); 15836 15837 // the bandwidth of the primary segment loader is our best 15838 // estimate of overall bandwidth 15839 this.on(this.masterPlaylistController_, 'progress', function () { 15840 this.bandwidth = this.masterPlaylistController_.mainSegmentLoader_.bandwidth; 15841 this.tech_.trigger('progress'); 15842 }); 15843 15844 // do nothing if the tech has been disposed already 15845 // this can occur if someone sets the src in player.ready(), for instance 15846 if (!this.tech_.el()) { 15847 return; 15848 } 15849 15850 this.tech_.src(_videoJs2['default'].URL.createObjectURL(this.masterPlaylistController_.mediaSource)); 15851 } 15852 15853 /** 15854 * a helper for grabbing the active audio group from MasterPlaylistController 15855 * 15856 * @private 15857 */ 15858 }, { 15859 key: 'activeAudioGroup_', 15860 value: function activeAudioGroup_() { 15861 return this.masterPlaylistController_.activeAudioGroup(); 15862 } 15863 15864 /** 15865 * Begin playing the video. 15866 */ 15867 }, { 15868 key: 'play', 15869 value: function play() { 15870 this.masterPlaylistController_.play(); 15871 } 15872 15873 /** 15874 * a wrapper around the function in MasterPlaylistController 15875 */ 15876 }, { 15877 key: 'setCurrentTime', 15878 value: function setCurrentTime(currentTime) { 15879 this.masterPlaylistController_.setCurrentTime(currentTime); 15880 } 15881 15882 /** 15883 * a wrapper around the function in MasterPlaylistController 15884 */ 15885 }, { 15886 key: 'duration', 15887 value: function duration() { 15888 return this.masterPlaylistController_.duration(); 15889 } 15890 15891 /** 15892 * a wrapper around the function in MasterPlaylistController 15893 */ 15894 }, { 15895 key: 'seekable', 15896 value: function seekable() { 15897 return this.masterPlaylistController_.seekable(); 15898 } 15899 15900 /** 15901 * Abort all outstanding work and cleanup. 15902 */ 15903 }, { 15904 key: 'dispose', 15905 value: function dispose() { 15906 if (this.masterPlaylistController_) { 15907 this.masterPlaylistController_.dispose(); 15908 } 15909 this.gapSkipper_.dispose(); 15910 this.tech_.audioTracks().removeEventListener('change', this.audioTrackChange_); 15911 _get(Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this); 15912 } 15913 }]); 15914 15915 return HlsHandler; 15916})(Component); 15917 15918var HlsSourceHandler = function HlsSourceHandler(mode) { 15919 return { 15920 canHandleSource: function canHandleSource(srcObj) { 15921 // this forces video.js to skip this tech/mode if its not the one we have been 15922 // overriden to use, by returing that we cannot handle the source. 15923 if (_videoJs2['default'].options.hls && _videoJs2['default'].options.hls.mode && _videoJs2['default'].options.hls.mode !== mode) { 15924 return false; 15925 } 15926 return HlsSourceHandler.canPlayType(srcObj.type); 15927 }, 15928 handleSource: function handleSource(source, tech, options) { 15929 if (mode === 'flash') { 15930 // We need to trigger this asynchronously to give others the chance 15931 // to bind to the event when a source is set at player creation 15932 tech.setTimeout(function () { 15933 tech.trigger('loadstart'); 15934 }, 1); 15935 } 15936 15937 var settings = _videoJs2['default'].mergeOptions(options, { hls: { mode: mode } }); 15938 15939 tech.hls = new HlsHandler(source, tech, settings); 15940 15941 tech.hls.xhr = (0, _xhr2['default'])(); 15942 // Use a global `before` function if specified on videojs.Hls.xhr 15943 // but still allow for a per-player override 15944 if (_videoJs2['default'].Hls.xhr.beforeRequest) { 15945 tech.hls.xhr.beforeRequest = _videoJs2['default'].Hls.xhr.beforeRequest; 15946 } 15947 15948 tech.hls.src(source.src); 15949 return tech.hls; 15950 }, 15951 canPlayType: function canPlayType(type) { 15952 if (HlsSourceHandler.canPlayType(type)) { 15953 return 'maybe'; 15954 } 15955 return ''; 15956 } 15957 }; 15958}; 15959 15960/** 15961 * A comparator function to sort two playlist object by bandwidth. 15962 * 15963 * @param {Object} left a media playlist object 15964 * @param {Object} right a media playlist object 15965 * @return {Number} Greater than zero if the bandwidth attribute of 15966 * left is greater than the corresponding attribute of right. Less 15967 * than zero if the bandwidth of right is greater than left and 15968 * exactly zero if the two are equal. 15969 */ 15970Hls.comparePlaylistBandwidth = function (left, right) { 15971 var leftBandwidth = undefined; 15972 var rightBandwidth = undefined; 15973 15974 if (left.attributes && left.attributes.BANDWIDTH) { 15975 leftBandwidth = left.attributes.BANDWIDTH; 15976 } 15977 leftBandwidth = leftBandwidth || _globalWindow2['default'].Number.MAX_VALUE; 15978 if (right.attributes && right.attributes.BANDWIDTH) { 15979 rightBandwidth = right.attributes.BANDWIDTH; 15980 } 15981 rightBandwidth = rightBandwidth || _globalWindow2['default'].Number.MAX_VALUE; 15982 15983 return leftBandwidth - rightBandwidth; 15984}; 15985 15986/** 15987 * A comparator function to sort two playlist object by resolution (width). 15988 * @param {Object} left a media playlist object 15989 * @param {Object} right a media playlist object 15990 * @return {Number} Greater than zero if the resolution.width attribute of 15991 * left is greater than the corresponding attribute of right. Less 15992 * than zero if the resolution.width of right is greater than left and 15993 * exactly zero if the two are equal. 15994 */ 15995Hls.comparePlaylistResolution = function (left, right) {
15996 var leftWidth = undefined; 15997 var rightWidth = undefined; 15998 15999 if (left.attributes && left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) { 16000 leftWidth = left.attributes.RESOLUTION.width; 16001 } 16002 16003 leftWidth = leftWidth || _globalWindow2['default'].Number.MAX_VALUE; 16004 16005 if (right.attributes && right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) { 16006 rightWidth = right.attributes.RESOLUTION.width; 16007 } 16008 16009 rightWidth = rightWidth || _globalWindow2['default'].Number.MAX_VALUE; 16010 16011 // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions 16012 // have the same media dimensions/ resolution 16013 if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) { 16014 return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH; 16015 } 16016 return leftWidth - rightWidth; 16017}; 16018 16019HlsSourceHandler.canPlayType = function (type) { 16020 var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i; 16021 16022 // favor native HLS support if it's available 16023 if (Hls.supportsNativeHls) { 16024 return false; 16025 } 16026 return mpegurlRE.test(type); 16027}; 16028 16029if (typeof _videoJs2['default'].MediaSource === 'undefined' || typeof _videoJs2['default'].URL === 'undefined') { 16030 _videoJs2['default'].MediaSource = _videojsContribMediaSources.MediaSource; 16031 _videoJs2['default'].URL = _videojsContribMediaSources.URL; 16032} 16033 16034// register source handlers with the appropriate techs 16035if (_videojsContribMediaSources.MediaSource.supportsNativeMediaSources()) { 16036 _videoJs2['default'].getComponent('Html5').registerSourceHandler(HlsSourceHandler('html5')); 16037} 16038if (_globalWindow2['default'].Uint8Array) { 16039 _videoJs2['default'].getComponent('Flash').registerSourceHandler(HlsSourceHandler('flash')); 16040} 16041 16042_videoJs2['default'].HlsHandler = HlsHandler; 16043_videoJs2['default'].HlsSourceHandler = HlsSourceHandler; 16044_videoJs2['default'].Hls = Hls; 16045_videoJs2['default'].m3u8 = _m3u8Parser2['default']; 16046_videoJs2['default'].registerComponent('Hls', Hls); 16047_videoJs2['default'].options.hls = _videoJs2['default'].options.hls || {}; 16048 16049module.exports = { 16050 Hls: Hls, 16051 HlsHandler: HlsHandler, 16052 HlsSourceHandler: HlsSourceHandler 16053}; 16054}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 16055},{"./bin-utils":2,"./config":3,"./gap-skipper":4,"./master-playlist-controller":5,"./playlist":7,"./playlist-loader":6,"./rendition-mixin":9,"./xhr":14,"aes-decrypter":18,"global/document":24,"global/window":25,"m3u8-parser":62,"videojs-contrib-media-sources":93}]},{},[96])(96) 16056});
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.