1/** 2 * videojs-contrib-hls 3 * @version 5.5.3 4 * @copyright 2017 Brightcove, Inc 5 * @license Apache-2.0 6 */ 7(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.videojsContribHls = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ 8 /** 9 * @file ad-cue-tags.js 10 */ 11 'use strict'; 12 13 Object.defineProperty(exports, '__esModule', { 14 value: true 15 }); 16 17 var _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 19 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 20 21 var _globalWindow = require('global/window'); 22 23 var _globalWindow2 = _interopRequireDefault(_globalWindow); 24 25 /** 26 * Searches for an ad cue that overlaps with the given mediaTime 27 */ 28 var 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 41 var 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 115 exports['default'] = { 116 updateAdCues: updateAdCues, 117 findAdCue: findAdCue 118 }; 119 module.exports = exports['default']; 120 },{"global/window":30}],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 133 Object.defineProperty(exports, '__esModule', { 134 value: true 135 }); 136 var 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 */ 146 var formatHexString = function formatHexString(e, i) { 147 var value = e.toString(16); 148 149 return '00'.substring(0, 2 - value.length) + value + (i % 2 ? ' ' : ''); 150 }; 151 var formatAsciiString = function formatAsciiString(e) { 152 if (e >= 0x20 && e < 0x7e) { 153 return String.fromCharCode(e); 154 } 155 return '.'; 156 }; 157 158 /** 159 * Creates an object for sending to a web worker modifying properties that are TypedArrays 160 * into a new object with seperated properties for the buffer, byteOffset, and byteLength. 161 * 162 * @param {Object} message 163 * Object of properties and values to send to the web worker 164 * @return {Object} 165 * Modified message with TypedArray values expanded 166 * @function createTransferableMessage 167 */ 168 var createTransferableMessage = function createTransferableMessage(message) { 169 var transferable = {}; 170 171 Object.keys(message).forEach(function (key) { 172 var value = message[key]; 173 174 if (ArrayBuffer.isView(value)) { 175 transferable[key] = { 176 bytes: value.buffer, 177 byteOffset: value.byteOffset, 178 byteLength: value.byteLength 179 }; 180 } else { 181 transferable[key] = value; 182 } 183 }); 184 185 return transferable; 186 }; 187 188 /** 189 * Returns a unique string identifier for a media initialization 190 * segment. 191 */ 192 var initSegmentId = function initSegmentId(initSegment) { 193 var byterange = initSegment.byterange || { 194 length: Infinity, 195 offset: 0 196 }; 197 198 return [byterange.length, byterange.offset, initSegment.resolvedUri].join(','); 199 }; 200 201 /** 202 * utils to help dump binary data to the console 203 */ 204 var utils = { 205 hexDump: function hexDump(data) { 206 var bytes = Array.prototype.slice.call(data); 207 var step = 16; 208 var result = ''; 209 var hex = undefined; 210 var ascii = undefined; 211 212 for (var j = 0;
212 j < bytes.length / step; j++) { 213 hex = bytes.slice(j * step, j * step + step).map(formatHexString).join(''); 214 ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join(''); 215 result += hex + ' ' + ascii + '\n'; 216 } 217 return result; 218 }, 219 tagDump: function tagDump(tag) { 220 return utils.hexDump(tag.bytes); 221 }, 222 textRanges: function textRanges(ranges) { 223 var result = ''; 224 var i = undefined; 225 226 for (i = 0; i < ranges.length; i++) { 227 result += textRange(ranges, i) + ' '; 228 } 229 return result; 230 }, 231 createTransferableMessage: createTransferableMessage, 232 initSegmentId: initSegmentId 233 }; 234 235 exports['default'] = utils; 236 module.exports = exports['default']; 237 },{}],3:[function(require,module,exports){ 238 "use strict"; 239 240 Object.defineProperty(exports, "__esModule", { 241 value: true 242 }); 243 exports["default"] = { 244 GOAL_BUFFER_LENGTH: 30, 245 // A fudge factor to apply to advertised playlist bitrates to account for 246 // temporary flucations in client bandwidth 247 BANDWIDTH_VARIANCE: 1.2 248 }; 249 module.exports = exports["default"]; 250 },{}],4:[function(require,module,exports){ 251 'use strict'; 252 253 Object.defineProperty(exports, '__esModule', { 254 value: true 255 }); 256 257 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 258 259 var _globalWindow = require('global/window'); 260 261 var _globalWindow2 = _interopRequireDefault(_globalWindow); 262 263 var _aesDecrypter = require('aes-decrypter'); 264 265 var _binUtils = require('./bin-utils'); 266 267 /** 268 * Our web worker interface so that things can talk to aes-decrypter 269 * that will be running in a web worker. the scope is passed to this by 270 * webworkify. 271 * 272 * @param {Object} self 273 * the scope for the web worker 274 */ 275 var DecrypterWorker = function DecrypterWorker(self) { 276 self.onmessage = function (event) { 277 var data = event.data; 278 var encrypted = new Uint8Array(data.encrypted.bytes, data.encrypted.byteOffset, data.encrypted.byteLength); 279 var key = new Uint32Array(data.key.bytes, data.key.byteOffset, data.key.byteLength / 4); 280 var iv = new Uint32Array(data.iv.bytes, data.iv.byteOffset, data.iv.byteLength / 4); 281 282 /* eslint-disable no-new, handle-callback-err */ 283 new _aesDecrypter.Decrypter(encrypted, key, iv, function (err, bytes) { 284 _globalWindow2['default'].postMessage((0, _binUtils.createTransferableMessage)({ 285 source: data.source, 286 decrypted: bytes 287 }), [bytes.buffer]); 288 }); 289 /* eslint-enable */ 290 }; 291 }; 292 293 exports['default'] = function (self) { 294 return new DecrypterWorker(self); 295 }; 296 297 module.exports = exports['default']; 298 },{"./bin-utils":2,"aes-decrypter":23,"global/window":30}],5:[function(require,module,exports){ 299 (function (global){ 300 /** 301 * @file master-playlist-controller.js 302 */ 303 'use strict'; 304 305 Object.defineProperty(exports, '__esModule', { 306 value: true 307 }); 308 309 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 310 311 var _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); } } }; 312 313 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 314 315 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 316 317 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
317onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 318 319 var _playlistLoader = require('./playlist-loader'); 320 321 var _playlistLoader2 = _interopRequireDefault(_playlistLoader); 322 323 var _segmentLoader = require('./segment-loader'); 324 325 var _segmentLoader2 = _interopRequireDefault(_segmentLoader); 326 327 var _vttSegmentLoader = require('./vtt-segment-loader'); 328 329 var _vttSegmentLoader2 = _interopRequireDefault(_vttSegmentLoader); 330 331 var _ranges = require('./ranges'); 332 333 var _ranges2 = _interopRequireDefault(_ranges); 334 335 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 336 337 var _videoJs2 = _interopRequireDefault(_videoJs); 338 339 var _adCueTags = require('./ad-cue-tags'); 340 341 var _adCueTags2 = _interopRequireDefault(_adCueTags); 342 343 var _syncController = require('./sync-controller'); 344 345 var _syncController2 = _interopRequireDefault(_syncController); 346 347 var _videojsContribMediaSourcesEs5CodecUtils = require('videojs-contrib-media-sources/es5/codec-utils'); 348 349 var _webworkify = require('webworkify'); 350 351 var _webworkify2 = _interopRequireDefault(_webworkify); 352 353 var _decrypterWorker = require('./decrypter-worker'); 354 355 var _decrypterWorker2 = _interopRequireDefault(_decrypterWorker); 356 357 var Hls = undefined; 358 359// Default codec parameters if none were provided for video and/or audio 360 var defaultCodecs = { 361 videoCodec: 'avc1', 362 videoObjectTypeIndicator: '.4d400d', 363 // AAC-LC 364 audioProfile: '2' 365 }; 366 367// SegmentLoader stats that need to have each loader's 368// values summed to calculate the final value 369 var loaderStats = ['mediaRequests', 'mediaRequestsAborted', 'mediaRequestsTimedout', 'mediaRequestsErrored', 'mediaTransferDuration', 'mediaBytesTransferred']; 370 var sumLoaderStat = function sumLoaderStat(stat) { 371 return this.audioSegmentLoader_[stat] + this.mainSegmentLoader_[stat]; 372 }; 373 374 /** 375 * determine if an object a is differnt from 376 * and object b. both only having one dimensional 377 * properties 378 * 379 * @param {Object} a object one 380 * @param {Object} b object two 381 * @return {Boolean} if the object has changed or not 382 */ 383 var objectChanged = function objectChanged(a, b) { 384 if (typeof a !== typeof b) { 385 return true; 386 } 387 // if we have a different number of elements 388 // something has changed 389 if (Object.keys(a).length !== Object.keys(b).length) { 390 return true; 391 } 392 393 for (var prop in a) { 394 if (a[prop] !== b[prop]) { 395 return true; 396 } 397 } 398 return false; 399 }; 400 401 /** 402 * Parses a codec string to retrieve the number of codecs specified, 403 * the video codec and object type indicator, and the audio profile. 404 * 405 * @private 406 */ 407 var parseCodecs = function parseCodecs(codecs) { 408 var result = { 409 codecCount: 0 410 }; 411 var parsed = undefined; 412 413 result.codecCount = codecs.split(',').length; 414 result.codecCount = result.codecCount || 2; 415 416 // parse the video codec 417 parsed = /(^|\s|,)+(avc1)([^ ,]*)/i.exec(codecs); 418 if (parsed) { 419 result.videoCodec = parsed[2]; 420 result.videoObjectTypeIndicator = parsed[3]; 421 } 422 423 // parse the last field of the audio codec 424 result.audioProfile = /(^|\s|,)+mp4a.[0-9A-Fa-f]+\.([0-9A-Fa-f]+)/i.exec(codecs); 425 result.audioProfile = result.audioProfile && result.audioProfile[2]; 426 427 return result; 428 }; 429 430 /** 431 * Replace codecs in the codec string with the old apple-style `avc1.<dd>.<dd>` to the 432 * standard `avc1.<hhhhhh>`. 433 *
434 * @param codecString {String} the codec string 435 * @return {String} the codec string with old apple-style codecs replaced 436 * 437 * @private 438 */ 439 var mapLegacyAvcCodecs_ = function mapLegacyAvcCodecs_(codecString) { 440 return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) { 441 return (0, _videojsContribMediaSourcesEs5CodecUtils.translateLegacyCodecs)([match])[0]; 442 }); 443 }; 444 445 exports.mapLegacyAvcCodecs_ = mapLegacyAvcCodecs_; 446 /** 447 * Build a media mime-type string from a set of parameters 448 * @param {String} type either 'audio' or 'video' 449 * @param {String} container either 'mp2t' or 'mp4' 450 * @param {Array} codecs an array of codec strings to add 451 * @return {String} a valid media mime-type 452 */ 453 var makeMimeTypeString = function makeMimeTypeString(type, container, codecs) { 454 // The codecs array is filtered so that falsey values are 455 // dropped and don't cause Array#join to create spurious 456 // commas 457 return type + '/' + container + '; codecs="' + codecs.filter(function (c) { 458 return !!c; 459 }).join(', ') + '"'; 460 }; 461 462 /** 463 * Returns the type container based on information in the playlist 464 * @param {Playlist} media the current media playlist 465 * @return {String} a valid media container type 466 */ 467 var getContainerType = function getContainerType(media) { 468 // An initialization segment means the media playlist is an iframe 469 // playlist or is using the mp4 container. We don't currently 470 // support iframe playlists, so assume this is signalling mp4 471 // fragments. 472 if (media.segments && media.segments.length && media.segments[0].map) { 473 return 'mp4'; 474 } 475 return 'mp2t'; 476 }; 477 478 /** 479 * Returns a set of codec strings parsed from the playlist or the default 480 * codec strings if no codecs were specified in the playlist 481 * @param {Playlist} media the current media playlist 482 * @return {Object} an object with the video and audio codecs 483 */ 484 var getCodecs = function getCodecs(media) { 485 // if the codecs were explicitly specified, use them instead of the 486 // defaults 487 var mediaAttributes = media.attributes || {}; 488 489 if (mediaAttributes.CODECS) { 490 return parseCodecs(mediaAttributes.CODECS); 491 } 492 return defaultCodecs; 493 }; 494 495 /** 496 * Calculates the MIME type strings for a working configuration of 497 * SourceBuffers to play variant streams in a master playlist. If 498 * there is no possible working configuration, an empty array will be 499 * returned. 500 * 501 * @param master {Object} the m3u8 object for the master playlist 502 * @param media {Object} the m3u8 object for the variant playlist 503 * @return {Array} the MIME type strings. If the array has more than 504 * one entry, the first element should be applied to the video 505 * SourceBuffer and the second to the audio SourceBuffer. 506 * 507 * @private 508 */ 509 var mimeTypesForPlaylist_ = function mimeTypesForPlaylist_(master, media) { 510 var containerType = getContainerType(media); 511 var codecInfo = getCodecs(media); 512 var mediaAttributes = media.attributes || {}; 513 // Default condition for a traditional HLS (no demuxed audio/video) 514 var isMuxed = true; 515 var isMaat = false; 516 517 if (!media) { 518 // Not enough information 519 return []; 520 } 521 522 if (master.mediaGroups.AUDIO && mediaAttributes.AUDIO) { 523 var audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; 524 525 // Handle the case where we are in a multiple-audio track scenario 526 if (audioGroup) { 527 isMaat = true; 528 // Start with the everything demuxed then... 529 isMuxed = false;
530 // ...check to see if any audio group tracks are muxed (ie. lacking a uri) 531 for (var groupId in audioGroup) { 532 if (!audioGroup[groupId].uri) { 533 isMuxed = true; 534 break; 535 } 536 } 537 } 538 } 539 540 // HLS with multiple-audio tracks must always get an audio codec. 541 // Put another way, there is no way to have a video-only multiple-audio HLS! 542 if (isMaat && !codecInfo.audioProfile) { 543 _videoJs2['default'].log.warn('Multiple audio tracks present but no audio codec string is specified. ' + 'Attempting to use the default audio codec (mp4a.40.2)'); 544 codecInfo.audioProfile = defaultCodecs.audioProfile; 545 } 546 547 // Generate the final codec strings from the codec object generated above 548 var codecStrings = {}; 549 550 if (codecInfo.videoCodec) { 551 codecStrings.video = '' + codecInfo.videoCodec + codecInfo.videoObjectTypeIndicator; 552 } 553 554 if (codecInfo.audioProfile) { 555 codecStrings.audio = 'mp4a.40.' + codecInfo.audioProfile; 556 } 557 558 // Finally, make and return an array with proper mime-types depending on 559 // the configuration 560 var justAudio = makeMimeTypeString('audio', containerType, [codecStrings.audio]); 561 var justVideo = makeMimeTypeString('video', containerType, [codecStrings.video]); 562 var bothVideoAudio = makeMimeTypeString('video', containerType, [codecStrings.video, codecStrings.audio]); 563 564 if (isMaat) { 565 if (!isMuxed && codecStrings.video) { 566 return [justVideo, justAudio]; 567 } 568 // There exists the possiblity that this will return a `video/container` 569 // mime-type for the first entry in the array even when there is only audio. 570 // This doesn't appear to be a problem and simplifies the code. 571 return [bothVideoAudio, justAudio]; 572 } 573 574 // If there is ano video codec at all, always just return a single 575 // audio/<container> mime-type 576 if (!codecStrings.video) { 577 return [justAudio]; 578 } 579 580 // When not using separate audio media groups, audio and video is 581 // *always* muxed 582 return [bothVideoAudio]; 583 }; 584 585 exports.mimeTypesForPlaylist_ = mimeTypesForPlaylist_; 586 /** 587 * the master playlist controller controller all interactons 588 * between playlists and segmentloaders. At this time this mainly 589 * involves a master playlist and a series of audio playlists 590 * if they are available 591 * 592 * @class MasterPlaylistController 593 * @extends videojs.EventTarget 594 */ 595 596 var MasterPlaylistController = (function (_videojs$EventTarget) { 597 _inherits(MasterPlaylistController, _videojs$EventTarget); 598 599 function MasterPlaylistController(options) { 600 var _this = this; 601 602 _classCallCheck(this, MasterPlaylistController); 603 604 _get(Object.getPrototypeOf(MasterPlaylistController.prototype), 'constructor', this).call(this); 605 606 var url = options.url; 607 var withCredentials = options.withCredentials; 608 var mode = options.mode; 609 var tech = options.tech; 610 var bandwidth = options.bandwidth; 611 var externHls = options.externHls; 612 var useCueTags = options.useCueTags; 613 var blacklistDuration = options.blacklistDuration; 614 615 if (!url) { 616 throw new Error('A non-empty playlist URL is required'); 617 } 618
619 Hls = externHls; 620 621 this.withCredentials = withCredentials; 622 this.tech_ = tech; 623 this.hls_ = tech.hls; 624 this.mode_ = mode; 625 this.useCueTags_ = useCueTags; 626 this.blacklistDuration = blacklistDuration; 627 if (this.useCueTags_) { 628 this.cueTagsTrack_ = this.tech_.addTextTrack('metadata', 'ad-cues'); 629 this.cueTagsTrack_.inBandMetadataTrackDispatchType = ''; 630 } 631 632 this.requestOptions_ = { 633 withCredentials: this.withCredentials, 634 timeout: null 635 }; 636 637 this.audioGroups_ = {}; 638 this.subtitleGroups_ = { groups: {}, tracks: {} }; 639 640 this.mediaSource = new _videoJs2['default'].MediaSource({ mode: mode }); 641 this.audioinfo_ = null; 642 this.mediaSource.on('audioinfo', this.handleAudioinfoUpdate_.bind(this)); 643 644 // load the media source into the player 645 this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen_.bind(this)); 646 647 this.seekable_ = _videoJs2['default'].createTimeRanges(); 648 this.hasPlayed_ = function () { 649 return false; 650 }; 651 652 this.syncController_ = new _syncController2['default'](); 653 this.segmentMetadataTrack_ = tech.addRemoteTextTrack({ 654 kind: 'metadata', 655 label: 'segment-metadata' 656 }, true).track; 657 658 this.decrypter_ = (0, _webworkify2['default'])(_decrypterWorker2['default']); 659 660 var segmentLoaderOptions = { 661 hls: this.hls_, 662 mediaSource: this.mediaSource, 663 currentTime: this.tech_.currentTime.bind(this.tech_), 664 seekable: function seekable() { 665 return _this.seekable(); 666 }, 667 seeking: function seeking() { 668 return _this.tech_.seeking(); 669 }, 670 duration: function duration() { 671 return _this.mediaSource.duration; 672 }, 673 hasPlayed: function hasPlayed() { 674 return _this.hasPlayed_(); 675 }, 676 bandwidth: bandwidth, 677 syncController: this.syncController_, 678 decrypter: this.decrypter_ 679 }; 680 681 // setup playlist loaders 682 this.masterPlaylistLoader_ = new _playlistLoader2['default'](url, this.hls_, this.withCredentials); 683 this.setupMasterPlaylistLoaderListeners_(); 684 this.audioPlaylistLoader_ = null; 685 this.subtitlePlaylistLoader_ = null; 686 687 // setup segment loaders 688 // combined audio/video or just video when alternate audio track is selected 689 this.mainSegmentLoader_ = new _segmentLoader2['default'](_videoJs2['default'].mergeOptions(segmentLoaderOptions, { 690 segmentMetadataTrack: this.segmentMetadataTrack_, 691 loaderType: 'main' 692 })); 693 694 // alternate audio track 695 this.audioSegmentLoader_ = new _segmentLoader2['default'](_videoJs2['default'].mergeOptions(segmentLoaderOptions, { 696 loaderType: 'audio' 697 })); 698 699 this.subtitleSegmentLoader_ = new _vttSegmentLoader2['default'](_videoJs2['default'].mergeOptions(segmentLoaderOptions, { 700 loaderType: 'vtt' 701 })); 702 703 this.setupSegmentLoaderListeners_(); 704 705 // Create SegmentLoader stat-getters
706 loaderStats.forEach(function (stat) { 707 _this[stat + '_'] = sumLoaderStat.bind(_this, stat); 708 }); 709 710 this.masterPlaylistLoader_.load(); 711 } 712 713 /** 714 * Register event handlers on the master playlist loader. A helper 715 * function for construction time. 716 * 717 * @private 718 */ 719 720 _createClass(MasterPlaylistController, [{ 721 key: 'setupMasterPlaylistLoaderListeners_', 722 value: function setupMasterPlaylistLoaderListeners_() { 723 var _this2 = this; 724 725 this.masterPlaylistLoader_.on('loadedmetadata', function () { 726 var media = _this2.masterPlaylistLoader_.media(); 727 var requestTimeout = _this2.masterPlaylistLoader_.targetDuration * 1.5 * 1000; 728 729 // If we don't have any more available playlists, we don't want to 730 // timeout the request. 731 if (_this2.masterPlaylistLoader_.isLowestEnabledRendition_()) { 732 _this2.requestOptions_.timeout = 0; 733 } else { 734 _this2.requestOptions_.timeout = requestTimeout; 735 } 736 737 // if this isn't a live video and preload permits, start 738 // downloading segments 739 if (media.endList && _this2.tech_.preload() !== 'none') { 740 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 741 _this2.mainSegmentLoader_.load(); 742 } 743 744 _this2.fillAudioTracks_(); 745 _this2.setupAudio(); 746 747 _this2.fillSubtitleTracks_(); 748 _this2.setupSubtitles(); 749 750 try { 751 _this2.setupSourceBuffers_(); 752 } catch (e) { 753 _videoJs2['default'].log.warn('Failed to create SourceBuffers', e); 754 return _this2.mediaSource.endOfStream('decode'); 755 } 756 _this2.setupFirstPlay(); 757 758 _this2.trigger('audioupdate'); 759 _this2.trigger('selectedinitialmedia'); 760 }); 761 762 this.masterPlaylistLoader_.on('loadedplaylist', function () { 763 var updatedPlaylist = _this2.masterPlaylistLoader_.media(); 764 765 if (!updatedPlaylist) { 766 // select the initial variant 767 _this2.initialMedia_ = _this2.selectPlaylist(); 768 _this2.masterPlaylistLoader_.media(_this2.initialMedia_); 769 return; 770 } 771 772 if (_this2.useCueTags_) { 773 _this2.updateAdCues_(updatedPlaylist); 774 } 775 776 // TODO: Create a new event on the PlaylistLoader that signals 777 // that the segments have changed in some way and use that to 778 // update the SegmentLoader instead of doing it twice here and 779 // on `mediachange` 780 _this2.mainSegmentLoader_.playlist(updatedPlaylist, _this2.requestOptions_); 781 _this2.updateDuration(); 782 783 // If the player isn't paused, ensure that the segment loader is running, 784 // as it is possible that it was temporarily stopped while waiting for 785 // a playlist (e.g., in case the playlist errored and we re-requested it). 786 if (!_this2.tech_.paused()) { 787 _this2.mainSegmentLoader_.load(); 788 } 789 790 if (!updatedPlaylist.endList) { 791 (function () { 792 var addSeekableRange = function addSeekableRange() { 793 var seekable = _this2.seekable(); 794 795 if (seekable.length !== 0) { 796 _this2.mediaSource.addSeekableRange_(seekable.start(0), seekable.end(0)); 797 } 798 }; 799 800 if (_this2.duration() !== Infinity) { 801 (function () { 802 var onDurationchange = function onDurationchange() { 803 if (_this2.duration() === Infinity) { 804 addSeekableRange(); 805 } else { 806 _this2.tech_.one('durationchange', onDurationchange); 807 } 808 }; 809 810 _this2.tech_.one('durationchange', onDurationchange); 811 })(); 812 } else { 813 addSeekableRange(); 814 } 815 })(); 816 } 817 }); 818 819 this.masterPlaylistLoader_.on('error', function () { 820 _this2.blacklistCurrentPlaylist(_this2.masterPlaylistLoader_.error); 821 }); 822 823 this.masterPlaylistLoader_.on('mediachanging', function () { 824 _this2.mainSegmentLoader_.abort(); 825 _this2.mainSegmentLoader_.pause(); 826 }); 827 828 this.masterPlaylistLoader_.on('mediachange', function () { 829 var media = _this2.masterPlaylistLoader_.media(); 830 var requestTimeout = _this2.masterPlaylistLoader_.targetDuration * 1.5 * 1000; 831 var activeAudioGroup = undefined; 832 var activeTrack = undefined; 833 834 // If we don't have any more available playlists, we don't want to 835 // timeout the request. 836 if (_this2.masterPlaylistLoader_.isLowestEnabledRendition_()) { 837 _this2.requestOptions_.timeout = 0; 838 } else { 839 _this2.requestOptions_.timeout = requestTimeout; 840 } 841 842 // TODO: Create a new event on the PlaylistLoader that signals 843 // that the segments have changed in some way and use that to 844 // update the SegmentLoader instead of doing it twice here and 845 // on `loadedplaylist` 846 _this2.mainSegmentLoader_.playlist(media, _this2.requestOptions_); 847 _this2.mainSegmentLoader_.load(); 848 849 // if the audio group has changed, a new audio track has to be 850 // enabled 851 activeAudioGroup = _this2.activeAudioGroup(); 852 activeTrack = activeAudioGroup.filter(function (track) { 853 return track.enabled; 854 })[0]; 855 if (!activeTrack) { 856 _this2.setupAudio(); 857 _this2.trigger('audioupdate'); 858 } 859 _this2.setupSubtitles(); 860 861 _this2.tech_.trigger({ 862 type: 'mediachange', 863 bubbles: true 864 }); 865 }); 866 867 this.masterPlaylistLoader_.on('playlistunchanged', function () { 868 var updatedPlaylist = _this2.masterPlaylistLoader_.media(); 869 var playlistOutdated = _this2.stuckAtPlaylistEnd_(updatedPlaylist); 870 871 if (playlistOutdated) { 872 // Playlist has stopped updating and we're stuck at its end. Try to 873 // blacklist it and switch to another playlist in the hope that that 874 // one is updating (and give the player a chance to re-adjust to the 875 // safe live point). 876 _this2.blacklistCurrentPlaylist({ 877 message: 'Playlist no longer updating.' 878 }); 879 // useful for monitoring QoS 880 _this2.tech_.trigger('playliststuck'); 881 } 882 }); 883 } 884 885 /** 886 * Register event handlers on the segment loaders. A helper function 887 * for construction time. 888 * 889 * @private 890 */ 891 }, { 892 key: 'setupSegmentLoaderListeners_', 893 value: function setupSegmentLoaderListeners_() { 894 var _this3 = this; 895 896 this.mainSegmentLoader_.on('bandwidthupdate', function () { 897 // figure out what stream the next segment should be downloaded from 898 // with the updated bandwidth information 899 _this3.masterPlaylistLoader_.media(_this3.selectPlaylist()); 900 }); 901 this.mainSegmentLoader_.on('progress', function () { 902 _this3.trigger('progress'); 903 }); 904 905 this.mainSegmentLoader_.on('error', function () { 906 _this3.blacklistCurrentPlaylist(_this3.mainSegmentLoader_.error()); 907 }); 908 909 this.mainSegmentLoader_.on('syncinfoupdate', function () { 910 _this3.onSyncInfoUpdate_(); 911 }); 912 913 this.audioSegmentLoader_.on('syncinfoupdate', function () { 914 _this3.onSyncInfoUpdate_(); 915 }); 916 917 this.audioSegmentLoader_.on('error', function () { 918 _videoJs2['default'].log.warn('Problem encountered with the current alternate audio track' + '. Switching back to default.'); 919 _this3.audioSegmentLoader_.abort(); 920 _this3.audioPlaylistLoader_ = null; 921 _this3.setupAudio(); 922 }); 923 924 this.subtitleSegmentLoader_.on('error', this.handleSubtitleError_.bind(this)); 925 } 926 }, { 927 key: 'handleAudioinfoUpdate_', 928 value: function handleAudioinfoUpdate_(event) { 929 if (Hls.supportsAudioInfoChange_() || !this.audioInfo_ || !
929objectChanged(this.audioInfo_, event.info)) { 930 this.audioInfo_ = event.info; 931 return; 932 } 933 934 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'; 935 936 var enabledIndex = this.activeAudioGroup().map(function (track) { 937 return track.enabled; 938 }).indexOf(true); 939 var enabledTrack = this.activeAudioGroup()[enabledIndex]; 940 var defaultTrack = this.activeAudioGroup().filter(function (track) { 941 return track.properties_ && track.properties_['default']; 942 })[0]; 943 944 // they did not switch audiotracks 945 // blacklist the current playlist 946 if (!this.audioPlaylistLoader_) { 947 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!';
948 this.blacklistCurrentPlaylist(); 949 } else { 950 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!'); 951 defaultTrack.enabled = true; 952 this.activeAudioGroup().splice(enabledIndex, 1); 953 this.trigger('audioupdate'); 954 } 955 956 _videoJs2['default'].log.warn(error); 957 this.setupAudio(); 958 } 959 }, { 960 key: 'mediaSecondsLoaded_', 961 value: function mediaSecondsLoaded_() { 962 return Math.max(this.audioSegmentLoader_.mediaSecondsLoaded + this.mainSegmentLoader_.mediaSecondsLoaded); 963 } 964 965 /** 966 * fill our internal list of HlsAudioTracks with data from 967 * the master playlist or use a default 968 * 969 * @private 970 */ 971 }, { 972 key: 'fillAudioTracks_', 973 value: function fillAudioTracks_() { 974 var master = this.master(); 975 var mediaGroups = master.mediaGroups || {}; 976 977 // force a default if we have none or we are not 978 // in html5 mode (the only mode to support more than one 979 // audio track) 980 if (!mediaGroups || !mediaGroups.AUDIO || Object.keys(mediaGroups.AUDIO).length === 0 || this.mode_ !== 'html5') { 981 // "main" audio group, track name "default" 982 mediaGroups.AUDIO = { main: { 'default': { 'default': true } } }; 983 } 984 985 for (var mediaGroup in mediaGroups.AUDIO) { 986 if (!this.audioGroups_[mediaGroup]) { 987 this.audioGroups_[mediaGroup] = []; 988 } 989 990 for (var label in mediaGroups.AUDIO[mediaGroup]) { 991 var properties = mediaGroups.AUDIO[mediaGroup][label]; 992 var track = new _videoJs2['default'].AudioTrack({ 993 id: label, 994 kind: this.audioTrackKind_(properties), 995 enabled: false, 996 language: properties.language, 997 label: label 998 }); 999 1000 track.properties_ = properties; 1001 this.audioGroups_[mediaGroup].push(track); 1002 } 1003 } 1004 1005 // enable the default active track 1006 (this.activeAudioGroup().filter(function (audioTrack) { 1007 return audioTrack.properties_['default']; 1008 })[0] || this.activeAudioGroup()[0]).enabled = true; 1009 } 1010 1011 /** 1012 * Convert the properties of an HLS track into an audioTrackKind. 1013 * 1014 * @private 1015 */ 1016 }, { 1017 key: 'audioTrackKind_', 1018 value: function audioTrackKind_(properties) { 1019 var kind = properties['default'] ? 'main' : 'alternative'; 1020 1021 if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) { 1022 kind = 'main-desc'; 1023 } 1024 1025 return kind; 1026 } 1027 1028 /** 1029 * fill our internal list of Subtitle Tracks with data from 1030 * the master playlist or use a default 1031 * 1032 * @private 1033 */ 1034 }, { 1035 key: 'fillSubtitleTracks_', 1036 value: function fillSubtitleTracks_() { 1037 var master = this.master(); 1038 var mediaGroups = master.mediaGroups || {}; 1039 1040 for (var mediaGroup in mediaGroups.SUBTITLES) { 1041 if (!this.subtitleGroups_.groups[mediaGroup]) { 1042 this.subtitleGroups_.groups[mediaGroup] = []; 1043 } 1044 1045 for (var label in mediaGroups.SUBTITLES[mediaGroup]) { 1046 var properties = mediaGroups.SUBTITLES[mediaGroup][label]; 1047 1048 if (!properties.forced) { 1049 this.subtitleGroups_.groups[mediaGroup].push(_videoJs2['default'].mergeOptions({ id: label }, properties)); 1050 1051 if (typeof this.subtitleGroups_.tracks[label] === 'undefined') { 1052 var track = this.tech_.addRemoteTextTrack({ 1053 id: label, 1054 kind: 'subtitles', 1055 enabled: false, 1056 language: properties.language, 1057 label: label 1058 }, true).track; 1059 1060 this.subtitleGroups_.tracks[label] = track; 1061 } 1062 } 1063 } 1064 } 1065
1066 // Do not enable a default subtitle track. Wait for user interaction instead. 1067 } 1068 1069 /** 1070 * Call load on our SegmentLoaders 1071 */ 1072 }, { 1073 key: 'load', 1074 value: function load() { 1075 this.mainSegmentLoader_.load(); 1076 if (this.audioPlaylistLoader_) { 1077 this.audioSegmentLoader_.load(); 1078 } 1079 if (this.subtitlePlaylistLoader_) { 1080 this.subtitleSegmentLoader_.load(); 1081 } 1082 } 1083 1084 /** 1085 * Returns the audio group for the currently active primary 1086 * media playlist. 1087 */ 1088 }, { 1089 key: 'activeAudioGroup', 1090 value: function activeAudioGroup() { 1091 var videoPlaylist = this.masterPlaylistLoader_.media(); 1092 var result = undefined; 1093 1094 if (videoPlaylist.attributes && videoPlaylist.attributes.AUDIO) { 1095 result = this.audioGroups_[videoPlaylist.attributes.AUDIO]; 1096 } 1097 1098 return result || this.audioGroups_.main; 1099 } 1100 1101 /** 1102 * Returns the subtitle group for the currently active primary 1103 * media playlist. 1104 */ 1105 }, { 1106 key: 'activeSubtitleGroup_', 1107 value: function activeSubtitleGroup_() { 1108 var videoPlaylist = this.masterPlaylistLoader_.media(); 1109 var result = undefined; 1110 1111 if (!videoPlaylist) { 1112 return null; 1113 } 1114 1115 if (videoPlaylist.attributes && videoPlaylist.attributes.SUBTITLES) { 1116 result = this.subtitleGroups_.groups[videoPlaylist.attributes.SUBTITLES]; 1117 } 1118 1119 return result || this.subtitleGroups_.groups.main; 1120 } 1121 }, { 1122 key: 'activeSubtitleTrack_', 1123 value: function activeSubtitleTrack_() { 1124 for (var trackName in this.subtitleGroups_.tracks) { 1125 if (this.subtitleGroups_.tracks[trackName].mode === 'showing') { 1126 return this.subtitleGroups_.tracks[trackName]; 1127 } 1128 } 1129 1130 return null; 1131 } 1132 }, { 1133 key: 'handleSubtitleError_', 1134 value: function handleSubtitleError_() { 1135 _videoJs2['default'].log.warn('Problem encountered loading the subtitle track' + '. Switching back to default.'); 1136 1137 this.subtitleSegmentLoader_.abort(); 1138 1139 var track = this.activeSubtitleTrack_(); 1140 1141 if (track) { 1142 track.mode = 'disabled'; 1143 } 1144 1145 this.setupSubtitles(); 1146 } 1147 1148 /** 1149 * Determine the correct audio rendition based on the active 1150 * AudioTrack and initialize a PlaylistLoader and SegmentLoader if 1151 * necessary. This method is called once automatically before 1152 * playback begins to enable the default audio track and should be 1153 * invoked again if the track is changed. 1154 */ 1155 }, { 1156 key: 'setupAudio', 1157 value: function setupAudio() { 1158 var _this4 = this; 1159 1160 // determine whether seperate loaders are required for the audio 1161 // rendition 1162 var audioGroup = this.activeAudioGroup(); 1163 var track = audioGroup.filter(function (audioTrack) { 1164 return audioTrack.enabled; 1165 })[0]; 1166 1167 if (!track) { 1168 track = audioGroup.filter(function (audioTrack) { 1169 return audioTrack.properties_['default']; 1170 })[0] || audioGroup[0]; 1171 track.enabled = true; 1172 } 1173 1174 // stop playlist and segment loading for audio 1175 if (this.audioPlaylistLoader_) { 1176 this.audioPlaylistLoader_.dispose(); 1177 this.audioPlaylistLoader_ = null; 1178 } 1179 this.audioSegmentLoader_.pause(); 1180 1181 if (!track.properties_.resolvedUri) { 1182 this.mainSegmentLoader_.resetEverything(); 1183 return; 1184 } 1185 this.audioSegmentLoader_.resetEverything(); 1186 1187 // startup playlist and segment loaders for the enabled audio 1188 // track 1189 this.audioPlaylistLoader_ = new _playlistLoader2['default'](track.properties_.resolvedUri, this.hls_, this.withCredentials); 1190 this.audioPlaylistLoader_.load(); 1191 1192 this.audioPlaylistLoader_.on('loadedmetadata', function () { 1193 var audioPlaylist = _this4.audioPlaylistLoader_.media(); 1194 1195 _this4.audioSegmentLoader_.playlist(audioPlaylist, _this4.requestOptions_); 1196 1197 // if the video is already playing, or if this isn't a live video and preload 1198 // permits, start downloading segments 1199 if (!_this4.tech_.paused() || audioPlaylist.endList && _this4.tech_.preload() !== 'none') { 1200 _this4.audioSegmentLoader_.load(); 1201 } 1202 1203 if (!audioPlaylist.endList) { 1204 _this4.audioPlaylistLoader_.trigger('firstplay'); 1205 } 1206 }); 1207 1208 this.audioPlaylistLoader_.on('loadedplaylist', function () { 1209 var updatedPlaylist = undefined; 1210 1211 if (_this4.audioPlaylistLoader_) { 1212 updatedPlaylist = _this4.audioPlaylistLoader_.media(); 1213 } 1214 1215 if (!updatedPlaylist) { 1216 // only one playlist to select 1217 _this4.audioPlaylistLoader_.media(_this4.audioPlaylistLoader_.playlists.master.playlists[0]); 1218 return; 1219 } 1220 1221 _this4.audioSegmentLoader_.playlist(updatedPlaylist, _this4.requestOptions_); 1222 }); 1223 1224 this.audioPlaylistLoader_.on('error', function () { 1225 _videoJs2['default'].log.warn('Problem encountered loading the alternate audio track' + '. Switching back to default.'); 1226 _this4.audioSegmentLoader_.abort(); 1227 _this4.setupAudio(); 1228 }); 1229 } 1230 1231 /** 1232 * Determine the correct subtitle playlist based on the active
1233 * SubtitleTrack and initialize a PlaylistLoader and SegmentLoader if 1234 * necessary. This method is called once automatically before 1235 * playback begins to enable the default subtitle track and should be 1236 * invoked again if the track is changed. 1237 */ 1238 }, { 1239 key: 'setupSubtitles', 1240 value: function setupSubtitles() { 1241 var _this5 = this; 1242 1243 var subtitleGroup = this.activeSubtitleGroup_(); 1244 var track = this.activeSubtitleTrack_(); 1245 1246 this.subtitleSegmentLoader_.pause(); 1247 1248 if (!track) { 1249 // stop playlist and segment loading for subtitles 1250 if (this.subtitlePlaylistLoader_) { 1251 this.subtitlePlaylistLoader_.dispose(); 1252 this.subtitlePlaylistLoader_ = null; 1253 } 1254 return; 1255 } 1256 1257 var properties = subtitleGroup.filter(function (subtitleProperties) { 1258 return subtitleProperties.id === track.id; 1259 })[0]; 1260 1261 // startup playlist and segment loaders for the enabled subtitle track 1262 if (!this.subtitlePlaylistLoader_ || 1263 // if the media hasn't loaded yet, we don't have the URI to check, so it is 1264 // easiest to simply recreate the playlist loader 1265 !this.subtitlePlaylistLoader_.media() || this.subtitlePlaylistLoader_.media().resolvedUri !== properties.resolvedUri) { 1266 1267 if (this.subtitlePlaylistLoader_) { 1268 this.subtitlePlaylistLoader_.dispose(); 1269 } 1270 1271 // reset the segment loader only when the subtitle playlist is changed instead of 1272 // every time setupSubtitles is called since switching subtitle tracks fires 1273 // multiple `change` events on the TextTrackList 1274 this.subtitleSegmentLoader_.resetEverything(); 1275 1276 // can't reuse playlistloader because we're only using single renditions and not a 1277 // proper master 1278 this.subtitlePlaylistLoader_ = new _playlistLoader2['default'](properties.resolvedUri, this.hls_, this.withCredentials); 1279 1280 this.subtitlePlaylistLoader_.on('loadedmetadata', function () { 1281 var subtitlePlaylist = _this5.subtitlePlaylistLoader_.media(); 1282 1283 _this5.subtitleSegmentLoader_.playlist(subtitlePlaylist, _this5.requestOptions_); 1284 _this5.subtitleSegmentLoader_.track(_this5.activeSubtitleTrack_()); 1285 1286 // if the video is already playing, or if this isn't a live video and preload 1287 // permits, start downloading segments 1288 if (!_this5.tech_.paused() || subtitlePlaylist.endList && _this5.tech_.preload() !== 'none') { 1289 _this5.subtitleSegmentLoader_.load(); 1290 } 1291 }); 1292 1293 this.subtitlePlaylistLoader_.on('loadedplaylist', function () { 1294 var updatedPlaylist = undefined; 1295 1296 if (_this5.subtitlePlaylistLoader_) { 1297 updatedPlaylist = _this5.subtitlePlaylistLoader_.media(); 1298 } 1299 1300 if (!updatedPlaylist) { 1301 return; 1302 } 1303 1304 _this5.subtitleSegmentLoader_.playlist(updatedPlaylist, _this5.requestOptions_); 1305 }); 1306 1307 this.subtitlePlaylistLoader_.on('error', this.handleSubtitleError_.bind(this)); 1308 } 1309 1310 if (this.subtitlePlaylistLoader_.media() && this.subtitlePlaylistLoader_.media().resolvedUri === properties.resolvedUri) { 1311 this.subtitleSegmentLoader_.load(); 1312 } else { 1313 this.subtitlePlaylistLoader_.load(); 1314 } 1315 } 1316 1317 /** 1318 * Re-tune playback quality level for the current player 1319 * conditions. This method may perform destructive actions, like 1320 * removing already buffered content, to readjust the currently 1321 * active playlist quickly. 1322 * 1323 * @private 1324 */ 1325 }, { 1326 key: 'fastQualityChange_', 1327 value: function fastQualityChange_() { 1328 var media = this.selectPlaylist(); 1329 1330 if (media !== this.masterPlaylistLoader_.media()) { 1331 this.masterPlaylistLoader_.media(media); 1332 1333 this.mainSegmentLoader_.resetLoader(); 1334 // don't need to reset audio as it is reset when media changes 1335 } 1336 } 1337 1338 /** 1339 * Begin playback. 1340 */ 1341 }, { 1342 key: 'play', 1343 value: function play() { 1344 if (this.setupFirstPlay()) { 1345 return; 1346 } 1347 1348 if (this.tech_.ended()) { 1349 this.tech_.setCurrentTime(0); 1350 } 1351 1352 if (this.hasPlayed_()) { 1353 this.load(); 1354 } 1355 1356 var seekable = this.tech_.seekable(); 1357 1358 // if the viewer has paused and we fell out of the live window, 1359 // seek forward to the live point 1360 if (this.tech_.duration() === Infinity) { 1361 if (this.tech_.currentTime() < seekable.start(0)) { 1362 return this.tech_.setCurrentTime(seekable.end(seekable.length - 1)); 1363 } 1364 } 1365 } 1366 1367 /** 1368 * Seek to the latest media position if this is a live video and the 1369 * player and video are loaded and initialized. 1370 */ 1371 }, { 1372 key: 'setupFirstPlay', 1373 value: function setupFirstPlay() { 1374 var seekable = undefined; 1375 var media = this.masterPlaylistLoader_.media(); 1376 1377 // check that everything is ready to begin buffering in the live 1378 // scenario 1379 // 1) the active media playlist is available 1380 if (media && 1381 // 2) the player is not paused 1382 !this.tech_.paused() && 1383 // 3) the player has not started playing 1384 !this.hasPlayed_()) { 1385 1386 // when the video is a live stream 1387 if (!media.endList) { 1388 this.trigger('firstplay'); 1389 1390 // seek to the latest media position for live videos 1391 seekable = this.seekable(); 1392 if (seekable.length) { 1393 this.tech_.setCurrentTime(seekable.end(0)); 1394 } 1395 } 1396 this.hasPlayed_ = function () { 1397 return true; 1398 }; 1399 // now that we are ready, load the segment 1400 this.load(); 1401 return true; 1402 } 1403 return false;
1404 } 1405 1406 /** 1407 * handle the sourceopen event on the MediaSource 1408 * 1409 * @private 1410 */ 1411 }, { 1412 key: 'handleSourceOpen_', 1413 value: function handleSourceOpen_() { 1414 // Only attempt to create the source buffer if none already exist. 1415 // handleSourceOpen is also called when we are "re-opening" a source buffer 1416 // after `endOfStream` has been called (in response to a seek for instance) 1417 try { 1418 this.setupSourceBuffers_(); 1419 } catch (e) { 1420 _videoJs2['default'].log.warn('Failed to create Source Buffers', e); 1421 return this.mediaSource.endOfStream('decode'); 1422 } 1423 1424 // if autoplay is enabled, begin playback. This is duplicative of 1425 // code in video.js but is required because play() must be invoked 1426 // *after* the media source has opened. 1427 if (this.tech_.autoplay()) { 1428 this.tech_.play(); 1429 } 1430 1431 this.trigger('sourceopen'); 1432 } 1433 1434 /** 1435 * Check if a playlist has stopped being updated 1436 * @param {Object} playlist the media playlist object 1437 * @return {boolean} whether the playlist has stopped being updated or not 1438 */ 1439 }, { 1440 key: 'stuckAtPlaylistEnd_', 1441 value: function stuckAtPlaylistEnd_(playlist) { 1442 var seekable = this.seekable(); 1443 1444 if (!seekable.length) { 1445 // playlist doesn't have enough information to determine whether we are stuck 1446 return false; 1447 } 1448 1449 var expired = this.syncController_.getExpiredTime(playlist, this.mediaSource.duration); 1450 1451 if (expired === null) { 1452 return false; 1453 } 1454 1455 // does not use the safe live end to calculate playlist end, since we 1456 // don't want to say we are stuck while there is still content 1457 var absolutePlaylistEnd = Hls.Playlist.playlistEnd(playlist, expired); 1458 var currentTime = this.tech_.currentTime(); 1459 var buffered = this.tech_.buffered(); 1460 1461 if (!buffered.length) { 1462 // return true if the playhead reached the absolute end of the playlist 1463 return absolutePlaylistEnd - currentTime <= _ranges2['default'].TIME_FUDGE_FACTOR; 1464 } 1465 var bufferedEnd = buffered.end(buffered.length - 1); 1466 1467 // return true if there is too little buffer left and 1468 // buffer has reached absolute end of playlist 1469 return bufferedEnd - currentTime <= _ranges2['default'].TIME_FUDGE_FACTOR && absolutePlaylistEnd - bufferedEnd <= _ranges2['default'].TIME_FUDGE_FACTOR; 1470 } 1471 1472 /** 1473 * Blacklists a playlist when an error occurs for a set amount of time 1474 * making it unavailable for selection by the rendition selection algorithm 1475 * and then forces a new playlist (rendition) selection. 1476 * 1477 * @param {Object=} error an optional error that may include the playlist 1478 * to blacklist 1479 */ 1480 }, { 1481 key: 'blacklistCurrentPlaylist', 1482 value: function blacklistCurrentPlaylist() { 1483 var error = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 1484 1485 var currentPlaylist = undefined; 1486 var nextPlaylist = undefined; 1487 1488 // If the `error` was generated by the playlist loader, it will contain 1489 // the playlist we were trying to load (but failed) and that should be 1490 // blacklisted instead of the currently selected playlist which is likely 1491 // out-of-date in this scenario 1492 currentPlaylist = error.playlist || this.masterPlaylistLoader_.media(); 1493 1494 // If there is no current playlist, then an error occurred while we were 1495 // trying to load the master OR while we were disposing of the tech 1496 if (!currentPlaylist) { 1497 this.error = error; 1498 1499 try { 1500 return this.mediaSource.endOfStream('network'); 1501 } catch (e) { 1502 return this.trigger('error'); 1503 } 1504 } 1505 1506 var isFinalRendition = this.masterPlaylistLoader_.isFinalRendition_(); 1507 1508 if (isFinalRendition) {
1509 // Never blacklisting this playlist because it's final rendition 1510 _videoJs2['default'].log.warn('Problem encountered with the current ' + 'HLS playlist. Trying again since it is the final playlist.'); 1511 1512 this.tech_.trigger('retryplaylist'); 1513 return this.masterPlaylistLoader_.load(isFinalRendition); 1514 } 1515 // Blacklist this playlist 1516 currentPlaylist.excludeUntil = Date.now() + this.blacklistDuration * 1000; 1517 this.tech_.trigger('blacklistplaylist'); 1518 1519 // Select a new playlist 1520 nextPlaylist = this.selectPlaylist(); 1521 _videoJs2['default'].log.warn('Problem encountered with the current HLS playlist.' + (error.message ? ' ' + error.message : '') + ' Switching to another playlist.'); 1522 1523 return this.masterPlaylistLoader_.media(nextPlaylist); 1524 } 1525 1526 /** 1527 * Pause all segment loaders 1528 */ 1529 }, { 1530 key: 'pauseLoading', 1531 value: function pauseLoading() { 1532 this.mainSegmentLoader_.pause(); 1533 if (this.audioPlaylistLoader_) { 1534 this.audioSegmentLoader_.pause(); 1535 } 1536 if (this.subtitlePlaylistLoader_) { 1537 this.subtitleSegmentLoader_.pause(); 1538 } 1539 } 1540 1541 /** 1542 * set the current time on all segment loaders 1543 * 1544 * @param {TimeRange} currentTime the current time to set 1545 * @return {TimeRange} the current time 1546 */ 1547 }, { 1548 key: 'setCurrentTime', 1549 value: function setCurrentTime(currentTime) { 1550 var buffered = _ranges2['default'].findRange(this.tech_.buffered(), currentTime); 1551 1552 if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) { 1553 // return immediately if the metadata is not ready yet 1554 return 0; 1555 } 1556 1557 // it's clearly an edge-case but don't thrown an error if asked to 1558 // seek within an empty playlist 1559 if (!this.masterPlaylistLoader_.media().segments) { 1560 return 0; 1561 } 1562 1563 // In flash playback, the segment loaders should be reset on every seek, even 1564 // in buffer seeks 1565 var isFlash = this.mode_ === 'flash' || this.mode_ === 'auto' && !_videoJs2['default'].MediaSource.supportsNativeMediaSources(); 1566 1567 // if the seek location is already buffered, continue buffering as 1568 // usual 1569 if (buffered && buffered.length && !isFlash) { 1570 return currentTime; 1571 } 1572 1573 // cancel outstanding requests so we begin buffering at the new 1574 // location 1575 this.mainSegmentLoader_.resetEverything(); 1576 this.mainSegmentLoader_.abort(); 1577 if (this.audioPlaylistLoader_) { 1578 this.audioSegmentLoader_.resetEverything(); 1579 this.audioSegmentLoader_.abort(); 1580 } 1581 if (this.subtitlePlaylistLoader_) { 1582 this.subtitleSegmentLoader_.resetEverything(); 1583 this.subtitleSegmentLoader_.abort(); 1584 } 1585 1586 if (!this.tech_.paused()) { 1587 this.mainSegmentLoader_.load(); 1588 if (this.audioPlaylistLoader_) { 1589 this.audioSegmentLoader_.load(); 1590 } 1591 if (this.subtitlePlaylistLoader_) { 1592 this.subtitleSegmentLoader_.load(); 1593 } 1594 } 1595 } 1596 1597 /** 1598 * get the current duration 1599 * 1600 * @return {TimeRange} the duration 1601 */ 1602 }, { 1603 key: 'duration', 1604 value: function duration() { 1605 if (!this.masterPlaylistLoader_) { 1606 return 0; 1607 } 1608 1609 if (this.mediaSource) { 1610 return this.mediaSource.duration; 1611 } 1612 1613 return Hls.Playlist.duration(this.masterPlaylistLoader_.media()); 1614 } 1615 1616 /** 1617 * check the seekable range 1618 * 1619 * @return {TimeRange} the seekable range 1620 */ 1621 }, { 1622 key: 'seekable', 1623 value: function seekable() { 1624 return this.seekable_; 1625 } 1626 }, { 1627 key: 'onSyncInfoUpdate_', 1628 value: function onSyncInfoUpdate_() { 1629 var mainSeekable = undefined; 1630 var audioSeekable = undefined; 1631 1632 if (!this.masterPlaylistLoader_) { 1633 return; 1634 } 1635 1636 var media = this.masterPlaylistLoader_.media(); 1637 1638 if (!media) { 1639 return; 1640 } 1641 1642 var expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration); 1643 1644 if (expired === null) { 1645 // not enough information to update seekable 1646 return; 1647 } 1648 1649 mainSeekable = Hls.Playlist.seekable(media, expired); 1650 1651 if (mainSeekable.length === 0) { 1652 return; 1653 } 1654 1655 if (this.audioPlaylistLoader_) { 1656 media = this.audioPlaylistLoader_.media(); 1657 expired = this.syncController_.getExpiredTime(media, this.mediaSource.duration); 1658 1659 if (expired === null) { 1660 return; 1661 } 1662 1663 audioSeekable = Hls.Playlist.seekable(media, expired); 1664 1665 if (audioSeekable.length === 0) { 1666 return; 1667 } 1668 } 1669 1670 if (!audioSeekable) { 1671 // seekable has been calculated based on buffering video data so it 1672 // can be returned directly 1673 this.seekable_ = mainSeekable; 1674 } else if (audioSeekable.start(0) > mainSeekable.end(0) || mainSeekable.start(0) > audioSeekable.end(0)) { 1675 // seekables are pretty far off, rely on main 1676 this.seekable_ = mainSeekable; 1677 } else { 1678 this.seekable_ = _videoJs2['default'].createTimeRanges([[audioSeekable.start(0) > mainSeekable.start(0) ? audioSeekable.start(0) : mainSeekable.start(0), audioSeekable.end(0) < mainSeekable.end(0) ? audioSeekable.end(0) : mainSeekable.end(0)]]); 1679 } 1680 1681 this.tech_.trigger('seekablechanged'); 1682 } 1683 1684 /** 1685 * Update the player duration 1686 */ 1687 }, { 1688 key: 'updateDuration', 1689 value: function updateDuration() { 1690 var _this6 = this; 1691 1692 var oldDuration = this.mediaSource.duration; 1693 var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media()); 1694 var buffered = this.tech_.buffered(); 1695 var setDuration = function setDuration() { 1696 _this6.mediaSource.duration = newDuration; 1697 _this6.tech_.trigger('durationchange'); 1698 1699 _this6.mediaSource.removeEventListener('sourceopen', setDuration); 1700 }; 1701 1702 if (buffered.length > 0) { 1703 newDuration = Math.max(newDuration, buffered.end(buffered.length - 1)); 1704 } 1705 1706 // if the duration has changed, invalidate the cached value 1707 if (oldDuration !== newDuration) { 1708 // update the duration 1709 if (this.mediaSource.readyState !== 'open') { 1710 this.mediaSource.addEventListener('sourceopen', setDuration); 1711 } else { 1712 setDuration(); 1713 } 1714 } 1715 } 1716 1717 /** 1718 * dispose of the MasterPlaylistController and everything 1719 * that it controls 1720 */ 1721 }, { 1722 key: 'dispose', 1723 value: function dispose() { 1724 this.decrypter_.terminate(); 1725 this.masterPlaylistLoader_.dispose(); 1726 this.mainSegmentLoader_.dispose(); 1727 1728 if (this.audioPlaylistLoader_) { 1729 this.audioPlaylistLoader_.dispose(); 1730 } 1731 if (this.subtitlePlaylistLoader_) { 1732 this.subtitlePlaylistLoader_.dispose(); 1733 } 1734 this.audioSegmentLoader_.dispose(); 1735 this.subtitleSegmentLoader_.dispose(); 1736 } 1737 1738 /** 1739 * return the master playlist object if we have one 1740 * 1741 * @return {Object} the master playlist object that we parsed 1742 */ 1743 }, { 1744 key: 'master', 1745 value: function master() { 1746 return this.masterPlaylistLoader_.master; 1747 } 1748 1749 /** 1750 * return the currently selected playlist 1751 * 1752 * @return {Object} the currently selected playlist object that we parsed 1753 */ 1754 }, { 1755 key: 'media', 1756 value: function media() { 1757 // playlist loader will not return media if it has not been fully loaded 1758 return this.masterPlaylistLoader_.media() || this.initialMedia_; 1759 } 1760 1761 /** 1762 * setup our internal source buffers on our segment Loaders 1763 * 1764 * @private 1765 */ 1766 }, { 1767 key: 'setupSourceBuffers_', 1768 value: function setupSourceBuffers_() { 1769 var media = this.masterPlaylistLoader_.media(); 1770 var mimeTypes = undefined; 1771 1772 // wait until a media playlist is available and the Media Source is 1773 // attached 1774 if (!media || this.mediaSource.readyState !== 'open') { 1775 return; 1776 } 1777 1778 mimeTypes = mimeTypesForPlaylist_(this.masterPlaylistLoader_.master, media); 1779 if (mimeTypes.length < 1) { 1780 this.error = 'No compatible SourceBuffer configuration for the variant stream:' + media.resolvedUri; 1781 return this.mediaSource.endOfStream('decode'); 1782 } 1783 this.mainSegmentLoader_.mimeType(mimeTypes[0]); 1784 if (mimeTypes[1]) { 1785 this.audioSegmentLoader_.mimeType(mimeTypes[1]); 1786 } 1787 1788 // exclude any incompatible variant streams from future playlist 1789 // selection 1790 this.excludeIncompatibleVariants_(media); 1791 } 1792 1793 /** 1794 * Blacklist playlists that are known to be codec or 1795 * stream-incompatible with the SourceBuffer configuration. For 1796 * instance, Media Source Extensions would cause the video element to 1797 * stall waiting for video data if you switched from a variant with 1798 * video and audio to an audio-only one. 1799 * 1800 * @param {Object} media a media playlist compatible with the current 1801 * set of SourceBuffers. Variants in the current master playlist that
1802 * do not appear to have compatible codec or stream configurations 1803 * will be excluded from the default playlist selection algorithm 1804 * indefinitely. 1805 * @private 1806 */ 1807 }, { 1808 key: 'excludeIncompatibleVariants_', 1809 value: function excludeIncompatibleVariants_(media) { 1810 var master = this.masterPlaylistLoader_.master; 1811 var codecCount = 2; 1812 var videoCodec = null; 1813 var codecs = undefined; 1814 1815 if (media.attributes && media.attributes.CODECS) { 1816 codecs = parseCodecs(media.attributes.CODECS); 1817 videoCodec = codecs.videoCodec; 1818 codecCount = codecs.codecCount; 1819 } 1820 master.playlists.forEach(function (variant) { 1821 var variantCodecs = { 1822 codecCount: 2, 1823 videoCodec: null 1824 }; 1825 1826 if (variant.attributes && variant.attributes.CODECS) { 1827 var codecString = variant.attributes.CODECS; 1828 1829 variantCodecs = parseCodecs(codecString); 1830 1831 if (window.MediaSource && window.MediaSource.isTypeSupported && !window.MediaSource.isTypeSupported('video/mp4; codecs="' + mapLegacyAvcCodecs_(codecString) + '"')) { 1832 variant.excludeUntil = Infinity; 1833 } 1834 } 1835 1836 // if the streams differ in the presence or absence of audio or 1837 // video, they are incompatible 1838 if (variantCodecs.codecCount !== codecCount) { 1839 variant.excludeUntil = Infinity; 1840 } 1841 1842 // if h.264 is specified on the current playlist, some flavor of 1843 // it must be specified on all compatible variants 1844 if (variantCodecs.videoCodec !== videoCodec) { 1845 variant.excludeUntil = Infinity; 1846 } 1847 }); 1848 } 1849 }, { 1850 key: 'updateAdCues_', 1851 value: function updateAdCues_(media) { 1852 var offset = 0; 1853 var seekable = this.seekable(); 1854 1855 if (seekable.length) { 1856 offset = seekable.start(0); 1857 } 1858 1859 _adCueTags2['default'].updateAdCues(media, this.cueTagsTrack_, offset); 1860 } 1861 }]); 1862 1863 return MasterPlaylistController; 1864 })(_videoJs2['default'].EventTarget); 1865 1866 exports.MasterPlaylistController = MasterPlaylistController; 1867 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 1868 },{"./ad-cue-tags":1,"./decrypter-worker":4,"./playlist-loader":8,"./ranges":10,"./segment-loader":14,"./sync-controller":17,"./vtt-segment-loader":18,"videojs-contrib-media-sources/es5/codec-utils":64,"webworkify":75}],6:[function(require,module,exports){ 1869 (function (global){ 1870 'use strict'; 1871 1872 Object.defineProperty(exports, '__esModule', { 1873 value: true 1874 }); 1875 1876 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 1877 1878 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 1879 1880 var _videoJs2 = _interopRequireDefault(_videoJs); 1881 1882 var _binUtils = require('./bin-utils'); 1883 1884 var REQUEST_ERRORS = { 1885 FAILURE: 2, 1886 TIMEOUT: -101, 1887 ABORTED: -102 1888 }; 1889 1890 exports.REQUEST_ERRORS = REQUEST_ERRORS; 1891 /** 1892 * Turns segment byterange into a string suitable for use in 1893 * HTTP Range requests 1894 * 1895 * @param {Object} byterange - an object with two values defining the start and end 1896 * of a byte-range 1897 */ 1898 var byterangeStr = function byterangeStr(byterange) { 1899 var byterangeStart = undefined; 1900 var byterangeEnd = undefined; 1901 1902 // `byterangeEnd` is one less than `offset + length` because the HTTP range 1903 // header uses inclusive ranges 1904 byterangeEnd = byterange.offset + byterange.length - 1; 1905 byterangeStart = byterange.offset; 1906 return 'bytes=' + byterangeStart + '-' + byterangeEnd; 1907 }; 1908 1909 /** 1910 * Defines headers for use in the xhr request for a particular segment. 1911 * 1912 * @param {Object} segment - a simplified copy of the segmentInfo object from SegmentLoader 1913 */ 1914 var segmentXhrHeaders = function segmentXhrHeaders(segment) { 1915 var headers = {}; 1916 1917 if (segment.byterange) { 1918 headers.Range = byterangeStr(segment.byterange); 1919 } 1920 return headers; 1921 }; 1922 1923 /** 1924 * Abort all requests 1925 * 1926 * @param {Object} activeXhrs - an object that tracks all XHR requests 1927 */ 1928 var abortAll = function abortAll(activeXhrs) {
vendor: 6,284 bytes, lines 1929-2075
1929 activeXhrs.forEach(function (xhr) { 1930 xhr.abort(); 1931 }); 1932 }; 1933 1934 /** 1935 * Gather important bandwidth stats once a request has completed 1936 * 1937 * @param {Object} request - the XHR request from which to gather stats 1938 */ 1939 var getRequestStats = function getRequestStats(request) { 1940 return { 1941 bandwidth: request.bandwidth, 1942 bytesReceived: request.bytesReceived || 0, 1943 roundTripTime: request.roundTripTime || 0 1944 }; 1945 }; 1946 1947 /** 1948 * If possible gather bandwidth stats as a request is in 1949 * progress 1950 * 1951 * @param {Event} progressEvent - an event object from an XHR's progress event 1952 */ 1953 var getProgressStats = function getProgressStats(progressEvent) { 1954 var request = progressEvent.target; 1955 var roundTripTime = Date.now() - request.requestTime; 1956 var stats = { 1957 bandwidth: Infinity, 1958 bytesReceived: 0, 1959 roundTripTime: roundTripTime || 0 1960 }; 1961 1962 if (progressEvent.lengthComputable) { 1963 stats.bytesReceived = progressEvent.loaded; 1964 // This can result in Infinity if stats.roundTripTime is 0 but that is ok 1965 // because we should only use bandwidth stats on progress to determine when 1966 // abort a request early due to insufficient bandwidth 1967 stats.bandwidth = Math.floor(stats.bytesReceived / stats.roundTripTime * 8 * 1000); 1968 } 1969 return stats; 1970 }; 1971 1972 /** 1973 * Handle all error conditions in one place and return an object 1974 * with all the information 1975 * 1976 * @param {Error|null} error - if non-null signals an error occured with the XHR 1977 * @param {Object} request - the XHR request that possibly generated the error 1978 */ 1979 var handleErrors = function handleErrors(error, request) { 1980 if (request.timedout) { 1981 return { 1982 status: request.status, 1983 message: 'HLS request timed-out at URL: ' + request.uri, 1984 code: REQUEST_ERRORS.TIMEOUT, 1985 xhr: request 1986 }; 1987 } 1988 1989 if (request.aborted) { 1990 return { 1991 status: request.status, 1992 message: 'HLS request aborted at URL: ' + request.uri, 1993 code: REQUEST_ERRORS.ABORTED, 1994 xhr: request 1995 }; 1996 } 1997 1998 if (error) { 1999 return { 2000 status: request.status, 2001 message: 'HLS request errored at URL: ' + request.uri, 2002 code: REQUEST_ERRORS.FAILURE, 2003 xhr: request 2004 }; 2005 } 2006 2007 return null; 2008 }; 2009 2010 /** 2011 * Handle responses for key data and convert the key data to the correct format 2012 * for the decryption step later 2013 * 2014 * @param {Object} segment - a simplified copy of the segmentInfo object from SegmentLoader 2015 * @param {Function} finishProcessingFn - a callback to execute to continue processing 2016 * this request 2017 */ 2018 var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) { 2019 return function (error, request) { 2020 var response = request.response; 2021 var errorObj = handleErrors(error, request); 2022 2023 if (errorObj) { 2024 return finishProcessingFn(errorObj, segment); 2025 } 2026 2027 if (response.byteLength !== 16) { 2028 return finishProcessingFn({ 2029 status: request.status, 2030 message: 'Invalid HLS key at URL: ' + request.uri, 2031 code: REQUEST_ERRORS.FAILURE, 2032 xhr: request 2033 }, segment); 2034 } 2035 2036 var view = new DataView(response); 2037 2038 segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]); 2039 return finishProcessingFn(null, segment); 2040 }; 2041 }; 2042 2043 /** 2044 * Handle init-segment responses 2045 * 2046 * @param {Object} segment - a simplified copy of the segmentInfo object from SegmentLoader 2047 * @param {Function} finishProcessingFn - a callback to execute to continue processing 2048 * this request 2049 */ 2050 var handleInitSegmentResponse = function handleInitSegmentResponse(segment, finishProcessingFn) { 2051 return function (error, request) { 2052 var response = request.response; 2053 var errorObj = handleErrors(error, request); 2054 2055 if (errorObj) { 2056 return finishProcessingFn(errorObj, segment); 2057 } 2058 2059 // stop processing if received empty content 2060 if (response.byteLength === 0) { 2061 return finishProcessingFn({ 2062 status: request.status, 2063 message: 'Empty HLS segment content at URL: ' + request.uri, 2064 code: REQUEST_ERRORS.FAILURE, 2065 xhr: request 2066 }, segment); 2067 } 2068 2069 segment.map.bytes = new Uint8Array(request.response); 2070 return finishProcessingFn(null, segment); 2071 }; 2072 }; 2073 2074 /** 2075 * Response handler for segment-requests being sure to set the correct
2076 * property depending on whether the segment is encryped or not 2077 * Also records and keeps track of stats that are used for ABR purposes 2078 * 2079 * @param {Object} segment - a simplified copy of the segmentInfo object from SegmentLoader 2080 * @param {Function} finishProcessingFn - a callback to execute to continue processing 2081 * this request 2082 */ 2083 var handleSegmentResponse = function handleSegmentResponse(segment, finishProcessingFn) { 2084 return function (error, request) { 2085 var response = request.response; 2086 var errorObj = handleErrors(error, request); 2087 2088 if (errorObj) { 2089 return finishProcessingFn(errorObj, segment); 2090 } 2091 2092 // stop processing if received empty content 2093 if (response.byteLength === 0) { 2094 return finishProcessingFn({ 2095 status: request.status, 2096 message: 'Empty HLS segment content at URL: ' + request.uri, 2097 code: REQUEST_ERRORS.FAILURE, 2098 xhr: request 2099 }, segment); 2100 } 2101 2102 segment.stats = getRequestStats(request); 2103 2104 if (segment.key) { 2105 segment.encryptedBytes = new Uint8Array(request.response); 2106 } else { 2107 segment.bytes = new Uint8Array(request.response); 2108 } 2109 2110 return finishProcessingFn(null, segment); 2111 }; 2112 }; 2113 2114 /** 2115 * Decrypt the segment via the decryption web worker 2116 * 2117 * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines 2118 * @param {Object} segment - a simplified copy of the segmentInfo object from SegmentLoader 2119 * @param {Function} doneFn - a callback that is executed after decryption has completed 2120 */ 2121 var decryptSegment = function decryptSegment(decrypter, segment, doneFn) { 2122 var decryptionHandler = function decryptionHandler(event) { 2123 if (event.data.source === segment.requestId) { 2124 decrypter.removeEventListener('message', decryptionHandler); 2125 var decrypted = event.data.decrypted; 2126 2127 segment.bytes = new Uint8Array(decrypted.bytes, decrypted.byteOffset, decrypted.byteLength); 2128 return doneFn(null, segment); 2129 } 2130 }; 2131 2132 decrypter.addEventListener('message', decryptionHandler); 2133 2134 // this is an encrypted segment 2135 // incrementally decrypt the segment 2136 decrypter.postMessage((0, _binUtils.createTransferableMessage)({ 2137 source: segment.requestId, 2138 encrypted: segment.encryptedBytes, 2139 key: segment.key.bytes, 2140 iv: segment.key.iv 2141 }), [segment.encryptedBytes.buffer, segment.key.bytes.buffer]); 2142 }; 2143 2144 /** 2145 * The purpose of this function is to get the most pertinent error from the 2146 * array of errors. 2147 * For instance if a timeout and two aborts occur, then the aborts were 2148 * likely triggered by the timeout so return that error object. 2149 */ 2150 var getMostImportantError = function getMostImportantError(errors) { 2151 return errors.reduce(function (prev, err) { 2152 return err.code > prev.code ? err : prev; 2153 }); 2154 }; 2155 2156 /** 2157 * This function waits for all XHRs to finish (with either success or failure) 2158 * before continueing processing via it's callback. The function gathers errors 2159 * from each request into a single errors array so that the error status for 2160 * each request can be examined later. 2161 * 2162 * @param {Object} activeXhrs - an object that tracks all XHR requests 2163 * @param {WebWorker} decrypter - a WebWorker interface to AES-128 decryption routines 2164 * @param {Function} doneFn - a callback that is executed after all resources have been 2165 * downloaded and any decryption completed 2166 */ 2167 var waitForCompletion = function waitForCompletion(activeXhrs, decrypter, doneFn) { 2168 var errors = []; 2169 var count = 0; 2170 2171 return function (error, segment) { 2172 if (error) { 2173 // If there are errors, we have to abort any outstanding requests 2174 abortAll(activeXhrs); 2175 errors.push(error); 2176 } 2177 count += 1; 2178 2179 if (count === activeXhrs.length) { 2180 // Keep track of when *all* of the requests have completed 2181 segment.endOfAllRequests = Date.now(); 2182 2183 if (errors.length > 0) { 2184 var worstError = getMostImportantError(errors); 2185 2186 return doneFn(worstError, segment); 2187 } 2188 if (segment.encryptedBytes) { 2189 return decryptSegment(decrypter, segment, doneFn); 2190 } 2191 // Otherwise, everything is ready just continue 2192 return doneFn(null, segment); 2193 } 2194 }; 2195 }; 2196 2197 /** 2198 * Simple progress event callback handler that gathers some stats before 2199 * executing a provided callback with the `segment` object 2200 * 2201 * @param {Object} segment - a simplified copy of the segmentInfo object from SegmentLoader 2202 * @param {Function} progressFn - a callback that is executed each time a progress event is received 2203 * @param {Event} event - the progress event object from XMLHttpRequest 2204 */ 2205 var handleProgress = function handleProgress(segment, progressFn) { 2206 return function (event) { 2207 segment.stats = getProgressStats(event); 2208 return progressFn(event, segment); 2209 }; 2210 }; 2211 2212 /** 2213 * Load all resources and does any processing necessary for a media-segment 2214 * 2215 * Features: 2216 * decrypts the media-segment if it has a key uri and an iv 2217 * aborts *all* requests if *any* one request fails 2218 * 2219 * The segment object, at minimum, has the following format: 2220 * { 2221 * resolvedUri: String, 2222 * [byterange]: { 2223 * offset: Number, 2224 * length: Number 2225 * }, 2226 * [key]: { 2227 * resolvedUri: String 2228 * [byterange]: { 2229 * offset: Number, 2230 * length: Number 2231 * }, 2232 * iv: { 2233 * bytes: Uint32Array 2234 * } 2235 * }, 2236 * [map]: { 2237 * resolvedUri: String, 2238 * [byterange]: { 2239 * offset: Number, 2240 * length: Number 2241 * }, 2242 * [bytes]: Uint8Array 2243 * } 2244 * } 2245 * ...where [name] denotes optional properties 2246 * 2247 * @param {Function} xhr - an instance of the xhr wrapper in xhr.js 2248 * @param {Object} xhrOptions - the base options to provide to all xhr requests 2249 * @param {WebWorker} decryptionWorker - a WebWorker interface to AES-128 decryption routines 2250 * @param {Object} segment - a simplified copy of the segmentInfo object from SegmentLoader 2251 * @param {Function} progressFn - a callback that receives progress events from the main segment's xhr request 2252 * @param {Function} doneFn - a callback that is executed only once all requests have succeeded or failed 2253 * @returns {Function} a function that, when invoked, immediately aborts all outstanding requests 2254 */ 2255 var mediaSegmentRequest = function mediaSegmentRequest(xhr, xhrOptions, decryptionWorker, segment, progressFn, doneFn) { 2256 var activeXhrs = [];
2257 var finishProcessingFn = waitForCompletion(activeXhrs, decryptionWorker, doneFn); 2258 2259 // optionally, request the decryption key 2260 if (segment.key) { 2261 var keyRequestOptions = _videoJs2['default'].mergeOptions(xhrOptions, { 2262 uri: segment.key.resolvedUri, 2263 responseType: 'arraybuffer' 2264 }); 2265 var keyRequestCallback = handleKeyResponse(segment, finishProcessingFn); 2266 var keyXhr = xhr(keyRequestOptions, keyRequestCallback); 2267 2268 activeXhrs.push(keyXhr); 2269 } 2270 2271 // optionally, request the associated media init segment 2272 if (segment.map && !segment.map.bytes) { 2273 var initSegmentOptions = _videoJs2['default'].mergeOptions(xhrOptions, { 2274 uri: segment.map.resolvedUri, 2275 responseType: 'arraybuffer', 2276 headers: segmentXhrHeaders(segment.map) 2277 }); 2278 var initSegmentRequestCallback = handleInitSegmentResponse(segment, finishProcessingFn); 2279 var initSegmentXhr = xhr(initSegmentOptions, initSegmentRequestCallback); 2280 2281 activeXhrs.push(initSegmentXhr); 2282 } 2283 2284 var segmentRequestOptions = _videoJs2['default'].mergeOptions(xhrOptions, { 2285 uri: segment.resolvedUri, 2286 responseType: 'arraybuffer', 2287 headers: segmentXhrHeaders(segment) 2288 }); 2289 var segmentRequestCallback = handleSegmentResponse(segment, finishProcessingFn); 2290 var segmentXhr = xhr(segmentRequestOptions, segmentRequestCallback); 2291 2292 segmentXhr.addEventListener('progress', handleProgress(segment, progressFn)); 2293 activeXhrs.push(segmentXhr); 2294 2295 return function () { 2296 return abortAll(activeXhrs); 2297 }; 2298 }; 2299 exports.mediaSegmentRequest = mediaSegmentRequest; 2300 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 2301 },{"./bin-utils":2}],7:[function(require,module,exports){ 2302 (function (global){ 2303 /** 2304 * @file playback-watcher.js 2305 * 2306 * Playback starts, and now my watch begins. It shall not end until my death. I shall 2307 * take no wait, hold no uncleared timeouts, father no bad seeks. I shall wear no crowns 2308 * and win no glory. I shall live and die at my post. I am the corrector of the underflow. 2309 * I am the watcher of gaps. I am the shield that guards the realms of seekable. I pledge 2310 * my life and honor to the Playback Watch, for this Player and all the Players to come. 2311 */ 2312 2313 'use strict'; 2314 2315 Object.defineProperty(exports, '__esModule', { 2316 value: true 2317 }); 2318 2319 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 2320 2321 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 2322 2323 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 2324 2325 var _globalWindow = require('global/window'); 2326 2327 var _globalWindow2 = _interopRequireDefault(_globalWindow); 2328 2329 var _ranges = require('./ranges'); 2330 2331 var _ranges2 = _interopRequireDefault(_ranges); 2332 2333 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 2334 2335 var _videoJs2 = _interopRequireDefault(_videoJs); 2336 2337// Set of events that reset the playback-watcher time check logic and clear the timeout 2338 var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error']; 2339 2340 /** 2341 * @class PlaybackWatcher 2342 */ 2343 2344 var PlaybackWatcher = (function () { 2345 /** 2346 * Represents an PlaybackWatcher object. 2347 * @constructor 2348 * @param {object} options an object that includes the tech and settings 2349 */ 2350 2351 function PlaybackWatcher(options) { 2352 var _this = this; 2353 2354 _classCallCheck(this, PlaybackWatcher); 2355 2356 this.tech_ = options.tech; 2357 this.seekable = options.seekable; 2358 2359 this.consecutiveUpdates = 0; 2360 this.lastRecordedTime = null; 2361 this.timer_ = null; 2362 this.checkCurrentTimeTimeout_ = null; 2363 2364 if (options.debug) { 2365 this.logger_ = _videoJs2['default'].log.bind(_videoJs2['default'], 'playback-watcher ->'); 2366 } 2367 this.logger_('initialize'); 2368 2369 var waitingHandler = function waitingHandler() { 2370 return _this.techWaiting_(); 2371 }; 2372 var cancelTimerHandler = function cancelTimerHandler() { 2373 return _this.cancelTimer_(); 2374 }; 2375 var fixesBadSeeksHandler = function fixesBadSeeksHandler() { 2376 return _this.fixesBadSeeks_(); 2377 }; 2378 2379 this.tech_.on('seekablechanged', fixesBadSeeksHandler); 2380 this.tech_.on('waiting', waitingHandler); 2381 this.tech_.on(timerCancelEvents, cancelTimerHandler); 2382 this.monitorCurrentTime_(); 2383 2384 // Define the dispose function to clean up our events 2385 this.dispose = function () { 2386 _this.logger_('dispose'); 2387 _this.tech_.off('seekablechanged', fixesBadSeeksHandler); 2388 _this.tech_.off('waiting', waitingHandler); 2389 _this.tech_.off(timerCancelEvents, cancelTimerHandler); 2390 if (_this.checkCurrentTimeTimeout_) { 2391 _globalWindow2['default'].clearTimeout(_this.checkCurrentTimeTimeout_); 2392 } 2393 _this.cancelTimer_(); 2394 }; 2395 } 2396 2397 /** 2398 * Periodically check current time to see if playback stopped 2399 * 2400 * @private 2401 */ 2402 2403 _createClass(PlaybackWatcher, [{ 2404 key: 'monitorCurrentTime_', 2405 value: function monitorCurrentTime_() { 2406 this.checkCurrentTime_(); 2407 2408 if (this.checkCurrentTimeTimeout_) { 2409 _globalWindow2['default'].clearTimeout(this.checkCurrentTimeTimeout_); 2410 } 2411 2412 // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 2413 this.checkCurrentTimeTimeout_ = _globalWindow2['default'].setTimeout(this.monitorCurrentTime_.bind(this), 250); 2414 } 2415 2416 /** 2417 * The purpose of this function is to emulate the "waiting" event on 2418 * browsers that do not emit it when they are waiting for more 2419 * data to continue playback 2420 * 2421 * @private 2422 */ 2423 }, { 2424 key: 'checkCurrentTime_', 2425 value: function checkCurrentTime_() { 2426 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 2427 this.consecutiveUpdates = 0; 2428 this.lastRecordedTime = this.tech_.currentTime(); 2429 return; 2430 } 2431 2432 if (this.tech_.paused() || this.tech_.seeking()) { 2433 return; 2434 } 2435 2436 var currentTime = this.tech_.currentTime(); 2437 var buffered = this.tech_.buffered(); 2438 2439 if (this.lastRecordedTime === currentTime && (!buffered.length || currentTime + 0.1 >= buffered.end(buffered.length - 1))) { 2440 // If current time is at the end of the final buffered region, then any playback 2441 // stall is most likely caused by buffering in a low bandwidth environment. The tech 2442 // should fire a `waiting` event in this scenario, but due to browser and tech
2443 // inconsistencies (e.g. The Flash tech does not fire a `waiting` event when the end 2444 // of the buffer is reached and has fallen off the live window). Calling 2445 // `techWaiting_` here allows us to simulate responding to a native `waiting` event 2446 // when the tech fails to emit one. 2447 return this.techWaiting_(); 2448 } 2449 2450 if (this.consecutiveUpdates >= 5 && currentTime === this.lastRecordedTime) { 2451 this.consecutiveUpdates++; 2452 this.waiting_(); 2453 } else if (currentTime === this.lastRecordedTime) { 2454 this.consecutiveUpdates++; 2455 } else { 2456 this.consecutiveUpdates = 0; 2457 this.lastRecordedTime = currentTime; 2458 } 2459 } 2460 2461 /** 2462 * Cancels any pending timers and resets the 'timeupdate' mechanism 2463 * designed to detect that we are stalled 2464 * 2465 * @private 2466 */ 2467 }, { 2468 key: 'cancelTimer_', 2469 value: function cancelTimer_() { 2470 this.consecutiveUpdates = 0; 2471 2472 if (this.timer_) { 2473 this.logger_('cancelTimer_'); 2474 clearTimeout(this.timer_); 2475 } 2476 2477 this.timer_ = null; 2478 } 2479 2480 /** 2481 * Fixes situations where there's a bad seek 2482 * 2483 * @return {Boolean} whether an action was taken to fix the seek 2484 * @private 2485 */ 2486 }, { 2487 key: 'fixesBadSeeks_', 2488 value: function fixesBadSeeks_() { 2489 var seekable = this.seekable(); 2490 var currentTime = this.tech_.currentTime(); 2491 2492 if (this.tech_.seeking() && this.outsideOfSeekableWindow_(seekable, currentTime)) { 2493 var seekableEnd = seekable.end(seekable.length - 1); 2494 2495 // sync to live point (if VOD, our seekable was updated and we're simply adjusting) 2496 this.logger_('Trying to seek outside of seekable at time ' + currentTime + ' with ' + ('seekable range ' + _ranges2['default'].printableRange(seekable) + '. Seeking to ') + (seekableEnd + '.')); 2497 this.tech_.setCurrentTime(seekableEnd); 2498 return true; 2499 } 2500 2501 return false; 2502 } 2503 2504 /** 2505 * Handler for situations when we determine the player is waiting. 2506 * 2507 * @private 2508 */ 2509 }, { 2510 key: 'waiting_', 2511 value: function waiting_() { 2512 if (this.techWaiting_()) { 2513 return; 2514 } 2515 2516 // All tech waiting checks failed. Use last resort correction 2517 var currentTime = this.tech_.currentTime(); 2518 var buffered = this.tech_.buffered(); 2519 var currentRange = _ranges2['default'].findRange(buffered, currentTime); 2520 2521 // Sometimes the player can stall for unknown reasons within a contiguous buffered 2522 // region with no indication that anything is amiss (seen in Firefox). Seeking to 2523 // currentTime is usually enough to kickstart the player. This checks that the player 2524 // is currently within a buffered region before attempting a corrective seek. 2525 // Chrome does not appear to continue `timeupdate` events after a `waiting` event 2526 // until there is ~ 3 seconds of forward buffer available. PlaybackWatcher should also 2527 // make sure there is ~3 seconds of forward buffer before taking any corrective action 2528 // to avoid triggering an `unknownwaiting` event when the network is slow. 2529 if (currentRange.length && currentTime + 3 <= currentRange.end(0)) { 2530 this.cancelTimer_(); 2531 this.tech_.setCurrentTime(currentTime); 2532 2533 this.logger_('Stopped at ' + currentTime + ' while inside a buffered region ' + ('[' + currentRange.start(0) + ' -> ' + currentRange.end(0) + ']. Attempting to resume ') + 'playback by seeking to the current time.'); 2534 2535 // unknown waiting corrections may be useful for monitoring QoS 2536 this.tech_.trigger('unknownwaiting'); 2537 return; 2538 } 2539 } 2540 2541 /** 2542 * Handler for situations when the tech fires a `waiting` event 2543 * 2544 * @return {Boolean} 2545 * True if an action (or none) was needed to correct the waiting. False if no 2546 * checks passed 2547 * @private 2548 */ 2549 }, { 2550 key: 'techWaiting_', 2551 value: function techWaiting_() { 2552 var seekable = this.seekable(); 2553 var currentTime = this.tech_.currentTime(); 2554 2555 if (this.tech_.seeking() && this.fixesBadSeeks_()) { 2556 // Tech is seeking or bad seek fixed, no action needed 2557 return true; 2558 } 2559 2560 if (this.tech_.seeking() || this.timer_ !== null) { 2561 // Tech is seeking or already waiting on another action, no action needed 2562 return true; 2563 } 2564 2565 if (this.fellOutOfLiveWindow_(seekable, currentTime)) { 2566 var livePoint = seekable.end(seekable.length - 1); 2567 2568 this.logger_('Fell out of live window at time ' + currentTime + '. Seeking to ' + ('live point (seekable end) ' + livePoint)); 2569 this.cancelTimer_(); 2570 this.tech_.setCurrentTime(livePoint); 2571 2572 // live window resyncs may be useful for monitoring QoS 2573 this.tech_.trigger('liveresync'); 2574 return true; 2575 } 2576 2577 var buffered = this.tech_.buffered(); 2578 var nextRange = _ranges2['default'].findNextRange(buffered, currentTime); 2579 2580 if (this.videoUnderflow_(nextRange, buffered, currentTime)) { 2581 // Even though the video underflowed and was stuck in a gap, the audio overplayed 2582 // the gap, leading currentTime into a buffered range. Seeking to currentTime 2583 // allows the video to catch up to the audio position without losing any audio 2584 // (only suffering ~3 seconds of frozen video and a pause in audio playback). 2585 this.cancelTimer_(); 2586 this.tech_.setCurrentTime(currentTime); 2587 2588 // video underflow may be useful for monitoring QoS 2589 this.tech_.trigger('videounderflow'); 2590 return true; 2591 } 2592 2593 // check for gap 2594 if (nextRange.length > 0) { 2595 var difference = nextRange.start(0) - currentTime; 2596 2597 this.logger_('Stopped at ' + currentTime + ', setting timer for ' + difference + ', seeking ' + ('to ' + nextRange.start(0))); 2598 2599 this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime); 2600 return true; 2601 } 2602 2603 // All checks failed. Returning false to indicate failure to correct waiting 2604 return false;
2605 } 2606 }, { 2607 key: 'outsideOfSeekableWindow_', 2608 value: function outsideOfSeekableWindow_(seekable, currentTime) { 2609 if (!seekable.length) { 2610 // we can't make a solid case if there's no seekable, default to false 2611 return false; 2612 } 2613 2614 // provide a buffer of .1 seconds to handle rounding/imprecise numbers 2615 if (currentTime < seekable.start(0) - 0.1 || currentTime > seekable.end(seekable.length - 1) + 0.1) { 2616 return true; 2617 } 2618 2619 return false; 2620 } 2621 }, { 2622 key: 'fellOutOfLiveWindow_', 2623 value: function fellOutOfLiveWindow_(seekable, currentTime) { 2624 if (seekable.length && 2625 // can't fall before 0 and 0 seekable start identifies VOD stream 2626 seekable.start(0) > 0 && currentTime < seekable.start(0)) { 2627 return true; 2628 } 2629 2630 return false; 2631 } 2632 }, { 2633 key: 'videoUnderflow_', 2634 value: function videoUnderflow_(nextRange, buffered, currentTime) { 2635 if (nextRange.length === 0) { 2636 // Even if there is no available next range, there is still a possibility we are 2637 // stuck in a gap due to video underflow. 2638 var gap = this.gapFromVideoUnderflow_(buffered, currentTime); 2639 2640 if (gap) { 2641 this.logger_('Encountered a gap in video from ' + gap.start + ' to ' + gap.end + '. ' + ('Seeking to current time ' + currentTime)); 2642 2643 return true; 2644 } 2645 } 2646 2647 return false; 2648 } 2649 2650 /** 2651 * Timer callback. If playback still has not proceeded, then we seek 2652 * to the start of the next buffered region. 2653 * 2654 * @private 2655 */ 2656 }, { 2657 key: 'skipTheGap_', 2658 value: function skipTheGap_(scheduledCurrentTime) { 2659 var buffered = this.tech_.buffered(); 2660 var currentTime = this.tech_.currentTime(); 2661 var nextRange = _ranges2['default'].findNextRange(buffered, currentTime); 2662 2663 this.cancelTimer_(); 2664 2665 if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) { 2666 return; 2667 } 2668 2669 this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0)); 2670 2671 // only seek if we still have not played 2672 this.tech_.setCurrentTime(nextRange.start(0) + _ranges2['default'].TIME_FUDGE_FACTOR); 2673 } 2674 }, { 2675 key: 'gapFromVideoUnderflow_', 2676 value: function gapFromVideoUnderflow_(buffered, currentTime) { 2677 // At least in Chrome, if there is a gap in the video buffer, the audio will continue 2678 // playing for ~3 seconds after the video gap starts. This is done to account for 2679 // video buffer underflow/underrun (note that this is not done when there is audio 2680 // buffer underflow/underrun -- in that case the video will stop as soon as it 2681 // encounters the gap, as audio stalls are more noticeable/jarring to a user than 2682 // video stalls). The player's time will reflect the playthrough of audio, so the 2683 // time will appear as if we are in a buffered region, even if we are stuck in a 2684 // "gap." 2685 // 2686 // Example: 2687 // video buffer: 0 => 10.1, 10.2 => 20 2688 // audio buffer: 0 => 20 2689 // overall buffer: 0 => 10.1, 10.2 => 20 2690 // current time: 13 2691 // 2692 // Chrome's video froze at 10 seconds, where the video buffer encountered the gap, 2693 // however, the audio continued playing until it reached ~3 seconds past the gap 2694 // (13 seconds), at which point it stops as well. Since current time is past the 2695 // gap, findNextRange will return no ranges. 2696 // 2697 // To check for this issue, we see if there is a gap that starts somewhere within 2698 // a 3 second range (3 seconds +/- 1 second) back from our current time. 2699 var gaps = _ranges2['default'].findGaps(buffered); 2700 2701 for (var i = 0; i < gaps.length; i++) { 2702 var start = gaps.start(i); 2703 var end = gaps.end(i); 2704 2705 // gap is starts no more than 4 seconds back 2706 if (currentTime - start < 4 && currentTime - start > 2) { 2707 return { 2708 start: start, 2709 end: end 2710 }; 2711 } 2712 } 2713 2714 return null; 2715 } 2716 2717 /** 2718 * A debugging logger noop that is set to console.log only if debugging 2719 * is enabled globally 2720 * 2721 * @private 2722 */ 2723 }, { 2724 key: 'logger_', 2725 value: function logger_() {} 2726 }]); 2727 2728 return PlaybackWatcher; 2729 })(); 2730 2731 exports['default'] = PlaybackWatcher; 2732 module.exports = exports['default']; 2733 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 2734 },{"./ranges":10,"global/window":30}],8:[function(require,module,exports){ 2735 (function (global){ 2736 /** 2737 * @file playlist-loader.js 2738 * 2739 * A state machine that manages the loading, caching, and updating of 2740 * M3U8 playlists. 2741 * 2742 */ 2743 'use strict'; 2744 2745 Object.defineProperty(exports, '__esModule', { 2746 value: true 2747 }); 2748 2749 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 2750 2751 var _resolveUrl = require('./resolve-url'); 2752 2753 var _resolveUrl2 = _interopRequireDefault(_resolveUrl); 2754 2755 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 2756 2757 var _playlistJs = require('./playlist.js'); 2758 2759 var _stream = require('./stream'); 2760 2761 var _stream2 = _interopRequireDefault(_stream); 2762 2763 var _m3u8Parser = require('m3u8-parser'); 2764 2765 var _m3u8Parser2 = _interopRequireDefault(_m3u8Parser); 2766 2767 var _globalWindow = require('global/window'); 2768 2769 var _globalWindow2 = _interopRequireDefault(_globalWindow); 2770 2771 /** 2772 * Returns a new array of segments that is the result of merging 2773 * properties from an older list of segments onto an updated 2774 * list. No properties on the updated playlist will be overridden. 2775 * 2776 * @param {Array} original the outdated list of segments 2777 * @param {Array} update the updated list of segments 2778 * @param {Number=} offset the index of the first update 2779 * segment in the original segment list. For non-live playlists, 2780 * this should always be zero and does not need to be 2781 * specified. For live playlists, it should be the difference 2782 * between the media sequence numbers in the original and updated 2783 * playlists. 2784 * @return a list of merged segment objects 2785 */ 2786 var updateSegments = function updateSegments(original, update, offset) { 2787 var result = update.slice(); 2788 var length = undefined; 2789 var i = undefined; 2790 2791 offset = offset || 0; 2792 length = Math.min(original.length, update.length + offset); 2793 2794 for (i = offset; i < length; i++) { 2795 result[i - offset] = (0, _videoJs.mergeOptions)(original[i], result[i - offset]); 2796 } 2797 return result; 2798 }; 2799 2800 /** 2801 * Returns a new master playlist that is the result of merging an 2802 * updated media playlist into the original version. If the 2803 * updated media playlist does not match any of the playlist 2804 * entries in the original master playlist, null is returned. 2805 * 2806 * @param {Object} master a parsed master M3U8 object 2807 * @param {Object} media a parsed media M3U8 object 2808 * @return {Object} a new object that represents the original 2809 * master playlist with the updated media playlist merged in, or 2810 * null if the merge produced no change. 2811 */ 2812 var updateMaster = function updateMaster(master, media) { 2813 var changed = false;
2814 var result = (0, _videoJs.mergeOptions)(master, {}); 2815 var i = master.playlists.length; 2816 var playlist = undefined; 2817 var segment = undefined; 2818 var j = undefined; 2819 2820 while (i--) { 2821 playlist = result.playlists[i]; 2822 if (playlist.uri === media.uri) { 2823 // consider the playlist unchanged if the number of segments 2824 // are equal and the media sequence number is unchanged 2825 if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.mediaSequence === media.mediaSequence) { 2826 continue; 2827 } 2828 2829 result.playlists[i] = (0, _videoJs.mergeOptions)(playlist, media); 2830 result.playlists[media.uri] = result.playlists[i]; 2831 2832 // if the update could overlap existing segment information, 2833 // merge the two lists 2834 if (playlist.segments) { 2835 result.playlists[i].segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence); 2836 } 2837 // resolve any missing segment and key URIs 2838 j = 0; 2839 if (result.playlists[i].segments) { 2840 j = result.playlists[i].segments.length; 2841 } 2842 while (j--) { 2843 segment = result.playlists[i].segments[j]; 2844 if (!segment.resolvedUri) { 2845 segment.resolvedUri = (0, _resolveUrl2['default'])(playlist.resolvedUri, segment.uri); 2846 } 2847 if (segment.key && !segment.key.resolvedUri) { 2848 segment.key.resolvedUri = (0, _resolveUrl2['default'])(playlist.resolvedUri, segment.key.uri); 2849 } 2850 if (segment.map && !segment.map.resolvedUri) { 2851 segment.map.resolvedUri = (0, _resolveUrl2['default'])(playlist.resolvedUri, segment.map.uri); 2852 } 2853 } 2854 changed = true; 2855 } 2856 } 2857 return changed ? result : null; 2858 }; 2859 2860 /** 2861 * Load a playlist from a remote location 2862 * 2863 * @class PlaylistLoader 2864 * @extends Stream 2865 * @param {String} srcUrl the url to start with 2866 * @param {Boolean} withCredentials the withCredentials xhr option 2867 * @constructor 2868 */ 2869 var PlaylistLoader = function PlaylistLoader(srcUrl, hls, withCredentials) { 2870 var _this = this; 2871 2872 /* eslint-disable consistent-this */ 2873 var loader = this; 2874 /* eslint-enable consistent-this */ 2875 var dispose = undefined; 2876 var mediaUpdateTimeout = undefined; 2877 var request = undefined; 2878 var playlistRequestError = undefined; 2879 var haveMetadata = undefined; 2880 2881 PlaylistLoader.prototype.constructor.call(this); 2882 2883 this.hls_ = hls; 2884 2885 if (!srcUrl) { 2886 throw new Error('A non-empty playlist URL is required'); 2887 } 2888 2889 playlistRequestError = function (xhr, url, startingState) { 2890 loader.setBandwidth(request || xhr); 2891 2892 // any in-flight request is now finished 2893 request = null; 2894 2895 if (startingState) { 2896 loader.state = startingState; 2897 } 2898 2899 loader.error = { 2900 playlist: loader.master.playlists[url], 2901 status: xhr.status, 2902 message: 'HLS playlist request error at URL: ' + url, 2903 responseText: xhr.responseText, 2904 code: xhr.status >= 500 ? 4 : 2 2905 }; 2906 2907 loader.trigger('error'); 2908 }; 2909 2910 // update the playlist loader's state in response to a new or 2911 // updated playlist. 2912 haveMetadata = function (xhr, url) { 2913 var parser = undefined; 2914 var refreshDelay = undefined; 2915 var update = undefined; 2916 2917 loader.setBandwidth(request || xhr); 2918 2919 // any in-flight request is now finished 2920 request = null; 2921 2922 loader.state = 'HAVE_METADATA'; 2923 2924 parser = new _m3u8Parser2['default'].Parser(); 2925 parser.push(xhr.responseText); 2926 parser.end(); 2927 parser.manifest.uri = url; 2928 2929 // merge this playlist into the master 2930 update = updateMaster(loader.master, parser.manifest); 2931 refreshDelay = (parser.manifest.targetDuration || 10) * 1000; 2932 loader.targetDuration = parser.manifest.targetDuration; 2933 if (update) { 2934 loader.master = update; 2935 loader.media_ = loader.master.playlists[parser.manifest.uri]; 2936 } else { 2937 // if the playlist is unchanged since the last reload, 2938 // try again after half the target duration 2939 refreshDelay /= 2; 2940 loader.trigger('playlistunchanged'); 2941 } 2942 2943 // refresh live playlists after a target duration passes 2944 if (!loader.media().endList) { 2945 _globalWindow2['default'].clearTimeout(mediaUpdateTimeout); 2946 mediaUpdateTimeout = _globalWindow2['default'].setTimeout(function () { 2947 loader.trigger('mediaupdatetimeout'); 2948 }, refreshDelay); 2949 } 2950 2951 loader.trigger('loadedplaylist'); 2952 }; 2953 2954 // initialize the loader state 2955 loader.state = 'HAVE_NOTHING'; 2956 2957 // capture the prototype dispose function 2958 dispose = this.dispose; 2959 2960 /** 2961 * Abort any outstanding work and clean up. 2962 */ 2963 loader.dispose = function () { 2964 loader.stopRequest(); 2965 _globalWindow2['default'].clearTimeout(mediaUpdateTimeout); 2966 dispose.call(this); 2967 }; 2968 2969 loader.stopRequest = function () { 2970 if (request) { 2971 var oldRequest = request; 2972 2973 request = null; 2974 oldRequest.onreadystatechange = null; 2975 oldRequest.abort(); 2976 } 2977 }; 2978 2979 /** 2980 * Returns the number of enabled playlists on the master playlist object 2981 * 2982 * @return {Number} number of eneabled playlists 2983 */ 2984 loader.enabledPlaylists_ = function () { 2985 return loader.master.playlists.filter(_playlistJs.isEnabled).length; 2986 }; 2987 2988 /** 2989 * Returns whether the current playlist is the lowest rendition 2990 * 2991 * @return {Boolean} true if on lowest rendition 2992 */ 2993 loader.isLowestEnabledRendition_ = function () { 2994 if (loader.master.playlists.length === 1) { 2995 return true; 2996 } 2997 2998 var media = loader.media(); 2999 3000 var currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE; 3001 3002 return loader.master.playlists.filter(function (playlist) { 3003 var enabled = (0, _playlistJs.isEnabled)(playlist); 3004 3005 if (!enabled) { 3006 return false;
3007 } 3008 3009 var bandwidth = 0; 3010 3011 if (playlist && playlist.attributes) { 3012 bandwidth = playlist.attributes.BANDWIDTH; 3013 } 3014 return bandwidth < currentBandwidth; 3015 }).length === 0; 3016 }; 3017 3018 /** 3019 * Returns whether the current playlist is the final available rendition 3020 * 3021 * @return {Boolean} true if on final rendition 3022 */ 3023 loader.isFinalRendition_ = function () { 3024 return loader.master.playlists.filter(_playlistJs.isEnabled).length === 1; 3025 }; 3026 3027 /** 3028 * When called without any arguments, returns the currently 3029 * active media playlist. When called with a single argument, 3030 * triggers the playlist loader to asynchronously switch to the 3031 * specified media playlist. Calling this method while the 3032 * loader is in the HAVE_NOTHING causes an error to be emitted 3033 * but otherwise has no effect. 3034 * 3035 * @param {Object=} playlist the parsed media playlist 3036 * object to switch to 3037 * @return {Playlist} the current loaded media 3038 */ 3039 loader.media = function (playlist) { 3040 var startingState = loader.state; 3041 var mediaChange = undefined; 3042 3043 // getter 3044 if (!playlist) { 3045 return loader.media_; 3046 } 3047 3048 // setter 3049 if (loader.state === 'HAVE_NOTHING') { 3050 throw new Error('Cannot switch media playlist from ' + loader.state); 3051 } 3052 3053 // find the playlist object if the target playlist has been 3054 // specified by URI 3055 if (typeof playlist === 'string') { 3056 if (!loader.master.playlists[playlist]) { 3057 throw new Error('Unknown playlist URI: ' + playlist); 3058 } 3059 playlist = loader.master.playlists[playlist]; 3060 } 3061 3062 mediaChange = !loader.media_ || playlist.uri !== loader.media_.uri; 3063 3064 // switch to fully loaded playlists immediately 3065 if (loader.master.playlists[playlist.uri].endList) { 3066 // abort outstanding playlist requests 3067 if (request) { 3068 request.onreadystatechange = null; 3069 request.abort(); 3070 request = null; 3071 } 3072 loader.state = 'HAVE_METADATA'; 3073 loader.media_ = playlist; 3074 3075 // trigger media change if the active media has been updated 3076 if (mediaChange) { 3077 loader.trigger('mediachanging'); 3078 loader.trigger('mediachange'); 3079 } 3080 return; 3081 } 3082 3083 // switching to the active playlist is a no-op 3084 if (!mediaChange) { 3085 return; 3086 } 3087 3088 loader.state = 'SWITCHING_MEDIA'; 3089 3090 // there is already an outstanding playlist request 3091 if (request) { 3092 if ((0, _resolveUrl2['default'])(loader.master.uri, playlist.uri) === request.url) { 3093 // requesting to switch to the same playlist multiple times 3094 // has no effect after the first 3095 return; 3096 } 3097 request.onreadystatechange = null; 3098 request.abort(); 3099 request = null; 3100 } 3101 3102 // request the new playlist 3103 if (this.media_) { 3104 this.trigger('mediachanging'); 3105 } 3106 request = this.hls_.xhr({ 3107 uri: (0, _resolveUrl2['default'])(loader.master.uri, playlist.uri), 3108 withCredentials: withCredentials 3109 }, function (error, req) { 3110 // disposed 3111 if (!request) { 3112 return; 3113 } 3114 3115 if (error) { 3116 return playlistRequestError(request, playlist.uri, startingState); 3117 } 3118 3119 haveMetadata(req, playlist.uri); 3120 3121 // fire loadedmetadata the first time a media playlist is loaded 3122 if (startingState === 'HAVE_MASTER') { 3123 loader.trigger('loadedmetadata'); 3124 } else { 3125 loader.trigger('mediachange'); 3126 } 3127 }); 3128 }; 3129 3130 /** 3131 * set the bandwidth on an xhr to the bandwidth on the playlist 3132 */ 3133 loader.setBandwidth = function (xhr) { 3134 loader.bandwidth = xhr.bandwidth; 3135 }; 3136 3137 // live playlist staleness timeout 3138 loader.on('mediaupdatetimeout', function () { 3139 if (loader.state !== 'HAVE_METADATA') { 3140 // only refresh the media playlist if no other activity is going on 3141 return; 3142 } 3143 3144 loader.state = 'HAVE_CURRENT_METADATA'; 3145 request = this.hls_.xhr({ 3146 uri: (0, _resolveUrl2['default'])(loader.master.uri, loader.media().uri), 3147 withCredentials: withCredentials 3148 }, function (error, req) { 3149 // disposed 3150 if (!request) { 3151 return; 3152 } 3153 3154 if (error) { 3155 return playlistRequestError(request, loader.media().uri, 'HAVE_METADATA'); 3156 } 3157 haveMetadata(request, loader.media().uri); 3158 }); 3159 }); 3160 3161 // setup initial sync info 3162 loader.on('firstplay', function () { 3163 var playlist = loader.media(); 3164 3165 if (playlist) { 3166 playlist.syncInfo = { 3167 mediaSequence: playlist.mediaSequence, 3168 time: 0 3169 }; 3170 } 3171 }); 3172 3173 /** 3174 * pause loading of the playlist 3175 */ 3176 loader.pause = function () { 3177 loader.stopRequest(); 3178 _globalWindow2['default'].clearTimeout(mediaUpdateTimeout); 3179 if (loader.state === 'HAVE_NOTHING') { 3180 // If we pause the loader before any data has been retrieved, its as if we never 3181 // started, so reset to an unstarted state. 3182 loader.started = false;
3183 } 3184 }; 3185 3186 /** 3187 * start loading of the playlist 3188 */ 3189 loader.load = function (isFinalRendition) { 3190 var media = loader.media(); 3191 3192 _globalWindow2['default'].clearTimeout(mediaUpdateTimeout); 3193 3194 if (isFinalRendition) { 3195 var refreshDelay = media ? media.targetDuration / 2 * 1000 : 5 * 1000; 3196 3197 mediaUpdateTimeout = _globalWindow2['default'].setTimeout(loader.load.bind(null, false), refreshDelay); 3198 return; 3199 } 3200 3201 if (!loader.started) { 3202 loader.start(); 3203 return; 3204 } 3205 3206 if (media && !media.endList) { 3207 loader.trigger('mediaupdatetimeout'); 3208 } else { 3209 loader.trigger('loadedplaylist'); 3210 } 3211 }; 3212 3213 /** 3214 * start loading of the playlist 3215 */ 3216 loader.start = function () { 3217 loader.started = true; 3218 3219 // request the specified URL 3220 request = _this.hls_.xhr({ 3221 uri: srcUrl, 3222 withCredentials: withCredentials 3223 }, function (error, req) { 3224 var parser = undefined; 3225 var playlist = undefined; 3226 var i = undefined; 3227 3228 // disposed 3229 if (!request) { 3230 return; 3231 } 3232 3233 // clear the loader's request reference 3234 request = null; 3235 3236 if (error) { 3237 loader.error = { 3238 status: req.status, 3239 message: 'HLS playlist request error at URL: ' + srcUrl, 3240 responseText: req.responseText, 3241 // MEDIA_ERR_NETWORK 3242 code: 2 3243 }; 3244 if (loader.state === 'HAVE_NOTHING') { 3245 loader.started = false; 3246 } 3247 return loader.trigger('error'); 3248 } 3249 3250 parser = new _m3u8Parser2['default'].Parser(); 3251 parser.push(req.responseText); 3252 parser.end(); 3253 3254 loader.state = 'HAVE_MASTER'; 3255 3256 parser.manifest.uri = srcUrl; 3257 3258 // loaded a master playlist 3259 if (parser.manifest.playlists) { 3260 loader.master = parser.manifest; 3261 3262 // setup by-URI lookups and resolve media playlist URIs 3263 i = loader.master.playlists.length; 3264 while (i--) { 3265 playlist = loader.master.playlists[i]; 3266 loader.master.playlists[playlist.uri] = playlist; 3267 playlist.resolvedUri = (0, _resolveUrl2['default'])(loader.master.uri, playlist.uri); 3268 } 3269 3270 // resolve any media group URIs 3271 ['AUDIO', 'SUBTITLES'].forEach(function (mediaType) { 3272 for (var groupKey in loader.master.mediaGroups[mediaType]) { 3273 for (var labelKey in loader.master.mediaGroups[mediaType][groupKey]) { 3274 var mediaProperties = loader.master.mediaGroups[mediaType][groupKey][labelKey]; 3275 3276 if (mediaProperties.uri) { 3277 mediaProperties.resolvedUri = (0, _resolveUrl2['default'])(loader.master.uri, mediaProperties.uri); 3278 } 3279 } 3280 } 3281 }); 3282 3283 loader.trigger('loadedplaylist'); 3284 if (!request) { 3285 // no media playlist was specifically selected so start 3286 // from the first listed one 3287 loader.media(parser.manifest.playlists[0]); 3288 } 3289 return; 3290 } 3291 3292 // loaded a media playlist 3293 // infer a master playlist if none was previously requested 3294 loader.master = { 3295 mediaGroups: { 3296 'AUDIO': {}, 3297 'VIDEO': {}, 3298 'CLOSED-CAPTIONS': {}, 3299 'SUBTITLES': {} 3300 }, 3301 uri: _globalWindow2['default'].location.href, 3302 playlists: [{ 3303 uri: srcUrl 3304 }] 3305 }; 3306 loader.master.playlists[srcUrl] = loader.master.playlists[0]; 3307 loader.master.playlists[0].resolvedUri = srcUrl; 3308 haveMetadata(req, srcUrl); 3309 return loader.trigger('loadedmetadata'); 3310 }); 3311 }; 3312 }; 3313 3314 PlaylistLoader.prototype = new _stream2['default'](); 3315 3316 exports['default'] = PlaylistLoader; 3317 module.exports = exports['default']; 3318 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 3319 },{"./playlist.js":9,"./resolve-url":13,"./stream":16,"global/window":30,"m3u8-parser":31}],9:[function(require,module,exports){ 3320 (function (global){ 3321 /** 3322 * @file playlist.js 3323 * 3324 * Playlist related utilities. 3325 */ 3326 'use strict'; 3327 3328 Object.defineProperty(exports, '__esModule', { 3329 value: true 3330 }); 3331 3332 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 3333 3334 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 3335 3336 var _globalWindow = require('global/window'); 3337 3338 var _globalWindow2 = _interopRequireDefault(_globalWindow); 3339 3340 var Playlist = { 3341 /** 3342 * The number of segments that are unsafe to start playback at in 3343 * a live stream. Changing this value can cause playback stalls. 3344 * See HTTP Live Streaming, "Playing the Media Playlist File" 3345 * https://tools.ietf.org/html/draft-pantos-http-live-streaming-18#section-6.3.3 3346 */ 3347 UNSAFE_LIVE_SEGMENTS: 3 3348 }; 3349 3350 /** 3351 * walk backward until we find a duration we can use 3352 * or return a failure 3353 * 3354 * @param {Playlist} playlist the playlist to walk through 3355 * @param {Number} endSequence the mediaSequence to stop walking on 3356 */ 3357 3358 var backwardDuration = function backwardDuration(playlist, endSequence) { 3359 var result = 0; 3360 var i = endSequence - playlist.mediaSequence; 3361 // if a start time is available for segment immediately following 3362 // the interval, use it 3363 var segment = playlist.segments[i]; 3364 3365 // Walk backward until we find the latest segment with timeline 3366 // information that is earlier than endSequence 3367 if (segment) { 3368 if (typeof segment.start !== 'undefined') { 3369 return { result: segment.start, precise: true }; 3370 } 3371 if (typeof segment.end !== 'undefined') { 3372 return { 3373 result: segment.end - segment.duration, 3374 precise: true 3375 }; 3376 } 3377 } 3378 while (i--) { 3379 segment = playlist.segments[i]; 3380 if (typeof segment.end !== 'undefined') { 3381 return { result: result + segment.end, precise: true }; 3382 } 3383 3384 result += segment.duration; 3385 3386 if (typeof segment.start !== 'undefined') { 3387 return { result: result + segment.start, precise: true }; 3388 } 3389 } 3390 return { result: result, precise: false }; 3391 }; 3392 3393 /** 3394 * walk forward until we find a duration we can use 3395 * or return a failure 3396 * 3397 * @param {Playlist} playlist the playlist to walk through 3398 * @param {Number} endSequence the mediaSequence to stop walking on 3399 */ 3400 var forwardDuration = function forwardDuration(playlist, endSequence) { 3401 var result = 0; 3402 var segment = undefined; 3403 var i = endSequence - playlist.mediaSequence; 3404 // Walk forward until we find the earliest segment with timeline 3405 // information 3406 3407 for (; i < playlist.segments.length; i++) { 3408 segment = playlist.segments[i]; 3409 if (typeof segment.start !== 'undefined') { 3410 return { 3411 result: segment.start - result, 3412 precise: true 3413 }; 3414 } 3415 3416 result += segment.duration; 3417 3418 if (typeof segment.end !== 'undefined') { 3419 return { 3420 result: segment.end - result, 3421 precise: true 3422 }; 3423 } 3424 } 3425 // indicate we didn't find a useful duration estimate
3426 return { result: -1, precise: false }; 3427 }; 3428 3429 /** 3430 * Calculate the media duration from the segments associated with a 3431 * playlist. The duration of a subinterval of the available segments 3432 * may be calculated by specifying an end index. 3433 * 3434 * @param {Object} playlist a media playlist object 3435 * @param {Number=} endSequence an exclusive upper boundary 3436 * for the playlist. Defaults to playlist length. 3437 * @param {Number} expired the amount of time that has dropped 3438 * off the front of the playlist in a live scenario 3439 * @return {Number} the duration between the first available segment 3440 * and end index. 3441 */ 3442 var intervalDuration = function intervalDuration(playlist, endSequence, expired) { 3443 var backward = undefined; 3444 var forward = undefined; 3445 3446 if (typeof endSequence === 'undefined') { 3447 endSequence = playlist.mediaSequence + playlist.segments.length; 3448 } 3449 3450 if (endSequence < playlist.mediaSequence) { 3451 return 0; 3452 } 3453 3454 // do a backward walk to estimate the duration 3455 backward = backwardDuration(playlist, endSequence); 3456 if (backward.precise) { 3457 // if we were able to base our duration estimate on timing 3458 // information provided directly from the Media Source, return 3459 // it 3460 return backward.result; 3461 } 3462 3463 // walk forward to see if a precise duration estimate can be made 3464 // that way 3465 forward = forwardDuration(playlist, endSequence); 3466 if (forward.precise) { 3467 // we found a segment that has been buffered and so it's 3468 // position is known precisely 3469 return forward.result; 3470 } 3471 3472 // return the less-precise, playlist-based duration estimate 3473 return backward.result + expired; 3474 }; 3475 3476 /** 3477 * Calculates the duration of a playlist. If a start and end index 3478 * are specified, the duration will be for the subset of the media 3479 * timeline between those two indices. The total duration for live 3480 * playlists is always Infinity. 3481 * 3482 * @param {Object} playlist a media playlist object 3483 * @param {Number=} endSequence an exclusive upper 3484 * boundary for the playlist. Defaults to the playlist media 3485 * sequence number plus its length. 3486 * @param {Number=} expired the amount of time that has 3487 * dropped off the front of the playlist in a live scenario 3488 * @return {Number} the duration between the start index and end 3489 * index. 3490 */ 3491 var duration = function duration(playlist, endSequence, expired) { 3492 if (!playlist) { 3493 return 0; 3494 } 3495 3496 if (typeof expired !== 'number') { 3497 expired = 0; 3498 } 3499 3500 // if a slice of the total duration is not requested, use 3501 // playlist-level duration indicators when they're present 3502 if (typeof endSequence === 'undefined') { 3503 // if present, use the duration specified in the playlist 3504 if (playlist.totalDuration) { 3505 return playlist.totalDuration; 3506 } 3507 3508 // duration should be Infinity for live playlists 3509 if (!playlist.endList) { 3510 return _globalWindow2['default'].Infinity; 3511 } 3512 } 3513 3514 // calculate the total duration based on the segment durations 3515 return intervalDuration(playlist, endSequence, expired); 3516 }; 3517 3518 exports.duration = duration; 3519 /** 3520 * Calculate the time between two indexes in the current playlist 3521 * neight the start- nor the end-index need to be within the current 3522 * playlist in which case, the targetDuration of the playlist is used 3523 * to approximate the durations of the segments 3524 * 3525 * @param {Object} playlist a media playlist object 3526 * @param {Number} startIndex 3527 * @param {Number} endIndex 3528 * @return {Number} the number of seconds between startIndex and endIndex 3529 */ 3530 var sumDurations = function sumDurations(playlist, startIndex, endIndex) { 3531 var durations = 0; 3532 3533 if (startIndex > endIndex) { 3534 var _ref = [endIndex, startIndex]; 3535 startIndex = _ref[0]; 3536 endIndex = _ref[1]; 3537 } 3538 3539 if (startIndex < 0) { 3540 for (var i = startIndex; i < Math.min(0, endIndex); i++) { 3541 durations += playlist.targetDuration; 3542 } 3543 startIndex = 0; 3544 } 3545 3546 for (var i = startIndex; i < endIndex; i++) { 3547 durations += playlist.segments[i].duration; 3548 } 3549 3550 return durations; 3551 }; 3552 3553 exports.sumDurations = sumDurations; 3554 /** 3555 * Calculates the playlist end time 3556 * 3557 * @param {Object} playlist a media playlist object 3558 * @param {Number=} expired the amount of time that has 3559 * dropped off the front of the playlist in a live scenario 3560 * @param {Boolean|false} useSafeLiveEnd a boolean value indicating whether or not the playlist 3561 * end calculation should consider the safe live end (truncate the playlist 3562 * end by three segments). This is normally used for calculating the end of 3563 * the playlist's seekable range. 3564 * @returns {Number} the end time of playlist 3565 * @function playlistEnd 3566 */ 3567 var playlistEnd = function playlistEnd(playlist, expired, useSafeLiveEnd) { 3568 if (!playlist || !playlist.segments) { 3569 return null; 3570 } 3571 if (playlist.endList) { 3572 return duration(playlist); 3573 } 3574 3575 if (expired === null) { 3576 return null; 3577 } 3578 3579 expired = expired || 0; 3580 3581 var endSequence = useSafeLiveEnd ? Math.max(0, playlist.segments.length - Playlist.UNSAFE_LIVE_SEGMENTS) : Math.max(0, playlist.segments.length); 3582 3583 return intervalDuration(playlist, playlist.mediaSequence + endSequence, expired); 3584 }; 3585 3586 exports.playlistEnd = playlistEnd; 3587 /** 3588 * Calculates the interval of time that is currently seekable in a 3589 * playlist. The returned time ranges are relative to the earliest 3590 * moment in the specified playlist that is still available. A full 3591 * seekable implementation for live streams would need to offset 3592 * these values by the duration of content that has expired from the 3593 * stream. 3594 * 3595 * @param {Object} playlist a media playlist object 3596 * dropped off the front of the playlist in a live scenario 3597 * @param {Number=} expired the amount of time that has 3598 * dropped off the front of the playlist in a live scenario 3599 * @return {TimeRanges}
3599 the periods of time that are valid targets 3600 * for seeking 3601 */ 3602 var seekable = function seekable(playlist, expired) { 3603 var useSafeLiveEnd = true; 3604 var seekableStart = expired || 0; 3605 var seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd); 3606 3607 if (seekableEnd === null) { 3608 return (0, _videoJs.createTimeRange)(); 3609 } 3610 return (0, _videoJs.createTimeRange)(seekableStart, seekableEnd); 3611 }; 3612 3613 exports.seekable = seekable; 3614 var isWholeNumber = function isWholeNumber(num) { 3615 return num - Math.floor(num) === 0; 3616 }; 3617 3618 var roundSignificantDigit = function roundSignificantDigit(increment, num) { 3619 // If we have a whole number, just add 1 to it 3620 if (isWholeNumber(num)) { 3621 return num + increment * 0.1; 3622 } 3623 3624 var numDecimalDigits = num.toString().split('.')[1].length; 3625 3626 for (var i = 1; i <= numDecimalDigits; i++) { 3627 var scale = Math.pow(10, i); 3628 var temp = num * scale; 3629 3630 if (isWholeNumber(temp) || i === numDecimalDigits) { 3631 return (temp + increment) / scale; 3632 } 3633 } 3634 }; 3635 3636 var ceilLeastSignificantDigit = roundSignificantDigit.bind(null, 1); 3637 var floorLeastSignificantDigit = roundSignificantDigit.bind(null, -1); 3638 3639 /** 3640 * Determine the index and estimated starting time of the segment that 3641 * contains a specified playback position in a media playlist. 3642 * 3643 * @param {Object} playlist the media playlist to query 3644 * @param {Number} currentTime The number of seconds since the earliest 3645 * possible position to determine the containing segment for 3646 * @param {Number} startIndex 3647 * @param {Number} startTime 3648 * @return {Object} 3649 */ 3650 var getMediaInfoForTime_ = function getMediaInfoForTime_(playlist, currentTime, startIndex, startTime) { 3651 var i = undefined; 3652 var segment = undefined; 3653 var numSegments = playlist.segments.length; 3654 3655 var time = currentTime - startTime; 3656 3657 if (time < 0) { 3658 // Walk backward from startIndex in the playlist, adding durations 3659 // until we find a segment that contains `time` and return it 3660 if (startIndex > 0) { 3661 for (i = startIndex - 1; i >= 0; i--) { 3662 segment = playlist.segments[i]; 3663 time += floorLeastSignificantDigit(segment.duration); 3664 if (time > 0) { 3665 return { 3666 mediaIndex: i, 3667 startTime: startTime - sumDurations(playlist, startIndex, i) 3668 }; 3669 } 3670 } 3671 } 3672 // We were unable to find a good segment within the playlist 3673 // so select the first segment 3674 return { 3675 mediaIndex: 0, 3676 startTime: currentTime 3677 }; 3678 } 3679 3680 // When startIndex is negative, we first walk forward to first segment 3681 // adding target durations. If we "run out of time" before getting to 3682 // the first segment, return the first segment 3683 if (startIndex < 0) { 3684 for (i = startIndex; i < 0; i++) { 3685 time -= playlist.targetDuration; 3686 if (time < 0) { 3687 return { 3688 mediaIndex: 0, 3689 startTime: currentTime 3690 }; 3691 } 3692 } 3693 startIndex = 0; 3694 } 3695 3696 // Walk forward from startIndex in the playlist, subtracting durations 3697 // until we find a segment that contains `time` and return it 3698 for (i = startIndex; i < numSegments; i++) { 3699 segment = playlist.segments[i]; 3700 time -= ceilLeastSignificantDigit(segment.duration); 3701 if (time < 0) { 3702 return { 3703 mediaIndex: i, 3704 startTime: startTime + sumDurations(playlist, startIndex, i) 3705 }; 3706 } 3707 } 3708 3709 // We are out of possible candidates so load the last one... 3710 return { 3711 mediaIndex: numSegments - 1, 3712 startTime: currentTime 3713 }; 3714 }; 3715 3716 exports.getMediaInfoForTime_ = getMediaInfoForTime_; 3717 /** 3718 * Check whether the playlist is blacklisted or not. 3719 * 3720 * @param {Object} playlist the media playlist object 3721 * @return {boolean} whether the playlist is blacklisted or not 3722 * @function isBlacklisted 3723 */ 3724 var isBlacklisted = function isBlacklisted(playlist) { 3725 return playlist.excludeUntil && playlist.excludeUntil > Date.now(); 3726 }; 3727 3728 exports.isBlacklisted = isBlacklisted; 3729 /** 3730 * Check whether the playlist is enabled or not. 3731 * 3732 * @param {Object} playlist the media playlist object 3733 * @return {boolean} whether the playlist is enabled or not 3734 * @function isEnabled 3735 */ 3736 var isEnabled = function isEnabled(playlist) { 3737 var blacklisted = isBlacklisted(playlist); 3738 3739 return !playlist.disabled && !blacklisted; 3740 }; 3741 3742 exports.isEnabled = isEnabled; 3743 Playlist.duration = duration; 3744 Playlist.seekable = seekable; 3745 Playlist.getMediaInfoForTime_ = getMediaInfoForTime_; 3746 Playlist.isEnabled = isEnabled; 3747 Playlist.isBlacklisted = isBlacklisted; 3748 Playlist.playlistEnd = playlistEnd; 3749
3750// exports 3751 exports['default'] = Playlist; 3752 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 3753 },{"global/window":30}],10:[function(require,module,exports){ 3754 (function (global){ 3755 /** 3756 * ranges 3757 * 3758 * Utilities for working with TimeRanges. 3759 * 3760 */ 3761 3762 'use strict'; 3763 3764 Object.defineProperty(exports, '__esModule', { 3765 value: true 3766 }); 3767 3768 var _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'); } }; })(); 3769 3770 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 3771 3772 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 3773 3774 var _videoJs2 = _interopRequireDefault(_videoJs); 3775 3776// Fudge factor to account for TimeRanges rounding 3777 var TIME_FUDGE_FACTOR = 1 / 30; 3778 3779 /** 3780 * Clamps a value to within a range 3781 * @param {Number} num - the value to clamp 3782 * @param {Number} start - the start of the range to clamp within, inclusive 3783 * @param {Number} end - the end of the range to clamp within, inclusive 3784 * @return {Number} 3785 */ 3786 var clamp = function clamp(num, _ref) { 3787 var _ref2 = _slicedToArray(_ref, 2); 3788 3789 var start = _ref2[0]; 3790 var end = _ref2[1]; 3791 3792 return Math.min(Math.max(start, num), end); 3793 }; 3794 var filterRanges = function filterRanges(timeRanges, predicate) { 3795 var results = []; 3796 var i = undefined; 3797 3798 if (timeRanges && timeRanges.length) { 3799 // Search for ranges that match the predicate 3800 for (i = 0; i < timeRanges.length; i++) { 3801 if (predicate(timeRanges.start(i), timeRanges.end(i))) { 3802 results.push([timeRanges.start(i), timeRanges.end(i)]); 3803 } 3804 } 3805 } 3806 3807 return _videoJs2['default'].createTimeRanges(results); 3808 }; 3809 3810 /** 3811 * Attempts to find the buffered TimeRange that contains the specified 3812 * time. 3813 * @param {TimeRanges} buffered - the TimeRanges object to query 3814 * @param {number} time - the time to filter on. 3815 * @returns {TimeRanges} a new TimeRanges object 3816 */ 3817 var findRange = function findRange(buffered, time) { 3818 return filterRanges(buffered, function (start, end) { 3819 return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time; 3820 }); 3821 }; 3822 3823 /** 3824 * Returns the TimeRanges that begin later than the specified time. 3825 * @param {TimeRanges} timeRanges - the TimeRanges object to query 3826 * @param {number} time - the time to filter on. 3827 * @returns {TimeRanges} a new TimeRanges object. 3828 */ 3829 var findNextRange = function findNextRange(timeRanges, time) { 3830 return filterRanges(timeRanges, function (start) { 3831 return start - TIME_FUDGE_FACTOR >= time; 3832 }); 3833 }; 3834 3835 /** 3836 * Returns gaps within a list of TimeRanges 3837 * @param {TimeRanges} buffered - the TimeRanges object 3838 * @return {TimeRanges} a TimeRanges object of gaps 3839 */ 3840 var findGaps = function findGaps(buffered) { 3841 if (buffered.length < 2) { 3842 return _videoJs2['default'].createTimeRanges(); 3843 } 3844 3845 var ranges = []; 3846 3847 for (var i = 1; i < buffered.length; i++) { 3848 var start = buffered.end(i - 1); 3849 var end = buffered.start(i); 3850 3851 ranges.push([start, end]); 3852 } 3853 3854 return _videoJs2['default'].createTimeRanges(ranges); 3855 }; 3856 3857 /** 3858 * Search for a likely end time for the segment that was just appened 3859 * based on the state of the `buffered` property before and after the 3860 * append. If we fin only one such uncommon end-point return it. 3861 * @param {TimeRanges} original - the buffered time ranges before the update 3862 * @param {TimeRanges} update - the buffered time ranges after the update 3863 * @returns {Number|null} the end time added between `original` and `update`,
3864 * or null if one cannot be unambiguously determined. 3865 */ 3866 var findSoleUncommonTimeRangesEnd = function findSoleUncommonTimeRangesEnd(original, update) { 3867 var i = undefined; 3868 var start = undefined; 3869 var end = undefined; 3870 var result = []; 3871 var edges = []; 3872 3873 // In order to qualify as a possible candidate, the end point must: 3874 // 1) Not have already existed in the `original` ranges 3875 // 2) Not result from the shrinking of a range that already existed 3876 // in the `original` ranges 3877 // 3) Not be contained inside of a range that existed in `original` 3878 var overlapsCurrentEnd = function overlapsCurrentEnd(span) { 3879 return span[0] <= end && span[1] >= end; 3880 }; 3881 3882 if (original) { 3883 // Save all the edges in the `original` TimeRanges object 3884 for (i = 0; i < original.length; i++) { 3885 start = original.start(i); 3886 end = original.end(i); 3887 3888 edges.push([start, end]); 3889 } 3890 } 3891 3892 if (update) { 3893 // Save any end-points in `update` that are not in the `original` 3894 // TimeRanges object 3895 for (i = 0; i < update.length; i++) { 3896 start = update.start(i); 3897 end = update.end(i); 3898 3899 if (edges.some(overlapsCurrentEnd)) { 3900 continue; 3901 } 3902 3903 // at this point it must be a unique non-shrinking end edge 3904 result.push(end); 3905 } 3906 } 3907 3908 // we err on the side of caution and return null if didn't find 3909 // exactly *one* differing end edge in the search above 3910 if (result.length !== 1) { 3911 return null; 3912 } 3913 3914 return result[0]; 3915 }; 3916 3917 /** 3918 * Calculate the intersection of two TimeRanges 3919 * @param {TimeRanges} bufferA 3920 * @param {TimeRanges} bufferB 3921 * @returns {TimeRanges} The interesection of `bufferA` with `bufferB` 3922 */ 3923 var bufferIntersection = function bufferIntersection(bufferA, bufferB) { 3924 var start = null; 3925 var end = null; 3926 var arity = 0; 3927 var extents = []; 3928 var ranges = []; 3929 3930 if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) { 3931 return _videoJs2['default'].createTimeRange(); 3932 } 3933 3934 // Handle the case where we have both buffers and create an 3935 // intersection of the two 3936 var count = bufferA.length; 3937 3938 // A) Gather up all start and end times 3939 while (count--) { 3940 extents.push({ time: bufferA.start(count), type: 'start' }); 3941 extents.push({ time: bufferA.end(count), type: 'end' }); 3942 } 3943 count = bufferB.length; 3944 while (count--) { 3945 extents.push({ time: bufferB.start(count), type: 'start' }); 3946 extents.push({ time: bufferB.end(count), type: 'end' }); 3947 } 3948 // B) Sort them by time 3949 extents.sort(function (a, b) { 3950 return a.time - b.time; 3951 }); 3952 3953 // C) Go along one by one incrementing arity for start and decrementing 3954 // arity for ends 3955 for (count = 0; count < extents.length; count++) { 3956 if (extents[count].type === 'start') { 3957 arity++; 3958 3959 // D) If arity is ever incremented to 2 we are entering an 3960 // overlapping range 3961 if (arity === 2) { 3962 start = extents[count].time; 3963 } 3964 } else if (extents[count].type === 'end') { 3965 arity--; 3966 3967 // E) If arity is ever decremented to 1 we leav
3967ing an 3968 // overlapping range 3969 if (arity === 1) { 3970 end = extents[count].time; 3971 } 3972 } 3973 3974 // F) Record overlapping ranges 3975 if (start !== null && end !== null) { 3976 ranges.push([start, end]); 3977 start = null; 3978 end = null; 3979 } 3980 } 3981 3982 return _videoJs2['default'].createTimeRanges(ranges); 3983 }; 3984 3985 /** 3986 * Calculates the percentage of `segmentRange` that overlaps the 3987 * `buffered` time ranges. 3988 * @param {TimeRanges} segmentRange - the time range that the segment 3989 * covers adjusted according to currentTime 3990 * @param {TimeRanges} referenceRange - the original time range that the 3991 * segment covers 3992 * @param {Number} currentTime - time in seconds where the current playback 3993 * is at 3994 * @param {TimeRanges} buffered - the currently buffered time ranges 3995 * @returns {Number} percent of the segment currently buffered 3996 */ 3997 var calculateBufferedPercent = function calculateBufferedPercent(adjustedRange, referenceRange, currentTime, buffered) { 3998 var referenceDuration = referenceRange.end(0) - referenceRange.start(0); 3999 var adjustedDuration = adjustedRange.end(0) - adjustedRange.start(0); 4000 var bufferMissingFromAdjusted = referenceDuration - adjustedDuration; 4001 var adjustedIntersection = bufferIntersection(adjustedRange, buffered); 4002 var referenceIntersection = bufferIntersection(referenceRange, buffered); 4003 var adjustedOverlap = 0; 4004 var referenceOverlap = 0; 4005 4006 var count = adjustedIntersection.length; 4007 4008 while (count--) { 4009 adjustedOverlap += adjustedIntersection.end(count) - adjustedIntersection.start(count); 4010 4011 // If the current overlap segment starts at currentTime, then increase the 4012 // overlap duration so that it actually starts at the beginning of referenceRange 4013 // by including the difference between the two Range's durations 4014 // This is a work around for the way Flash has no buffer before currentTime 4015 if (adjustedIntersection.start(count) === currentTime) { 4016 adjustedOverlap += bufferMissingFromAdjusted; 4017 } 4018 } 4019 4020 count = referenceIntersection.length; 4021 4022 while (count--) { 4023 referenceOverlap += referenceIntersection.end(count) - referenceIntersection.start(count); 4024 } 4025 4026 // Use whichever value is larger for the percentage-buffered since that value 4027 // is likely more accurate because the only way 4028 return Math.max(adjustedOverlap, referenceOverlap) / referenceDuration * 100; 4029 }; 4030 4031 /** 4032 * Return the amount of a range specified by the startOfSegment and segmentDuration 4033 * overlaps the current buffered content. 4034 * 4035 * @param {Number} startOfSegment - the time where the segment begins 4036 * @param {Number} segmentDuration - the duration of the segment in seconds 4037 * @param {Number} currentTime - time in seconds where the current playback 4038 * is at 4039 * @param {TimeRanges} buffered - the state of the buffer 4040 * @returns {Number} percentage of the segment's time range that is 4041 * already in `buffered` 4042 */ 4043 var getSegmentBufferedPercent = function getSegmentBufferedPercent(startOfSegment, segmentDuration, currentTime, buffered) { 4044 var endOfSegment = startOfSegment + segmentDuration; 4045 4046 // The entire time range of the segment 4047 var originalSegmentRange = _videoJs2['default'].createTimeRanges([[startOfSegment, endOfSegment]]); 4048 4049 // The adjusted segment time range that is setup such that it starts 4050 // no earlier than currentTime 4051 // Flash has no notion of a back-buffer so adjustedSegmentRange adjusts 4052 // for that and the function will still return 100% if a only half of a 4053 // segment is actually in the buffer as long as the currentTime is also 4054 // half-way through the segment 4055 var adjustedSegmentRange = _videoJs2['default'].createTimeRanges([[clamp(startOfSegment, [currentTime, endOfSegment]), endOfSegment]]); 4056 4057 // This condition happens when the currentTime is beyond the segment's 4058 // end time 4059 if (adjustedSegmentRange.start(0) === adjustedSegmentRange.end(0)) { 4060 return 0; 4061 } 4062 4063 var percent = calculateBufferedPercent(adjustedSegmentRange, originalSegmentRange, currentTime, buffered); 4064 4065 // If the segment is reported as having a zero duration, return 0% 4066 // since it is likely that we will need to fetch the segment 4067 if (isNaN(percent) || percent === Infinity || percent === -Infinity) { 4068 return 0; 4069 } 4070 4071 return percent; 4072 }; 4073 4074 /** 4075 * Gets a human readable string for a TimeRange 4076 * 4077 * @param {TimeRange} range 4078 * @returns {String} a human readable string 4079 */ 4080 var printableRange = function printableRange(range) { 4081 var strArr = []; 4082 4083 if (!range || !range.length) { 4084 return ''; 4085 } 4086 4087 for (var i = 0; i < range.length; i++) { 4088 strArr.push(range.start(i) + ' => ' + range.end(i)); 4089 } 4090 4091 return strArr.join(', '); 4092 }; 4093 4094 exports['default'] = { 4095 findRange: findRange, 4096 findNextRange: findNextRange, 4097 findGaps: findGaps, 4098 findSoleUncommonTimeRangesEnd: findSoleUncommonTimeRangesEnd, 4099 getSegmentBufferedPercent: getSegmentBufferedPercent, 4100 TIME_FUDGE_FACTOR: TIME_FUDGE_FACTOR, 4101 printableRange: printableRange 4102 }; 4103 module.exports = exports['default']; 4104 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 4105 },{}],11:[function(require,module,exports){ 4106 (function (global){ 4107 'use strict'; 4108 4109 Object.defineProperty(exports, '__esModule', { 4110 value: true 4111 }); 4112 4113 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 4114 4115 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 4116 4117 var _videoJs2 = _interopRequireDefault(_videoJs); 4118 4119 var defaultOptions = { 4120 errorInterval: 30, 4121 getSource: function getSource(next) { 4122 var tech = this.tech({ IWillNotUseThisInPlugins: true }); 4123 var sourceObj = tech.currentSource_; 4124 4125 return next(sourceObj); 4126 } 4127 }; 4128 4129 /** 4130 * Main entry point for the plugin 4131 * 4132 * @param {Player} player a reference to a videojs Player instance 4133 * @param {Object} [options] an object with plugin options 4134 * @private 4135 */ 4136 var initPlugin = function initPlugin(player, options) { 4137 var lastCalled = 0; 4138 var seekTo = 0; 4139 var localOptions = _videoJs2['default'].mergeOptions(defaultOptions, options); 4140 4141 /** 4142 * Player modifications to perform that must wait until `loadedmetadata` 4143 * has been triggered 4144 * 4145 * @private 4146 */ 4147 var loadedMetadataHandler = function loadedMetadataHandler() { 4148 if (seekTo) { 4149 player.currentTime(seekTo); 4150 } 4151 }; 4152 4153 /** 4154 * Set the source on the player element, play, and seek if necessary 4155 * 4156 * @param {Object} sourceObj An object specifying the source url and mime-type to play 4157 * @private 4158 */ 4159 var setSource = function setSource(sourceObj) { 4160 if (sourceObj === null || sourceObj === undefined) { 4161 return; 4162 } 4163 seekTo = player.duration() !== Infinity && player.currentTime() || 0; 4164 4165 player.one('loadedmetadata', loadedMetadataHandler); 4166 4167 player.src(sourceObj); 4168 player.play(); 4169 }; 4170 4171 /** 4172 * Attempt to get a source from either the built-in getSource function 4173 * or a custom function provided via the options 4174 * 4175 * @private 4176 */ 4177 var errorHandler = function errorHandler() {
4178 // Do not attempt to reload the source if a source-reload occurred before 4179 // 'errorInterval' time has elapsed since the last source-reload 4180 if (Date.now() - lastCalled < localOptions.errorInterval * 1000) { 4181 return; 4182 } 4183 4184 if (!localOptions.getSource || typeof localOptions.getSource !== 'function') { 4185 _videoJs2['default'].log.error('ERROR: reloadSourceOnError - The option getSource must be a function!'); 4186 return; 4187 } 4188 lastCalled = Date.now(); 4189 4190 return localOptions.getSource.call(player, setSource); 4191 }; 4192 4193 /** 4194 * Unbind any event handlers that were bound by the plugin 4195 * 4196 * @private 4197 */ 4198 var cleanupEvents = function cleanupEvents() { 4199 player.off('loadedmetadata', loadedMetadataHandler); 4200 player.off('error', errorHandler); 4201 player.off('dispose', cleanupEvents); 4202 }; 4203 4204 /** 4205 * Cleanup before re-initializing the plugin 4206 * 4207 * @param {Object} [newOptions] an object with plugin options 4208 * @private 4209 */ 4210 var reinitPlugin = function reinitPlugin(newOptions) { 4211 cleanupEvents(); 4212 initPlugin(player, newOptions); 4213 }; 4214 4215 player.on('error', errorHandler); 4216 player.on('dispose', cleanupEvents); 4217 4218 // Overwrite the plugin function so that we can correctly cleanup before 4219 // initializing the plugin 4220 player.reloadSourceOnError = reinitPlugin; 4221 }; 4222 4223 /** 4224 * Reload the source when an error is detected as long as there 4225 * wasn't an error previously within the last 30 seconds 4226 * 4227 * @param {Object} [options] an object with plugin options 4228 */ 4229 var reloadSourceOnError = function reloadSourceOnError(options) { 4230 initPlugin(this, options); 4231 }; 4232 4233 exports['default'] = reloadSourceOnError; 4234 module.exports = exports['default']; 4235 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 4236 },{}],12:[function(require,module,exports){ 4237 'use strict'; 4238 4239 Object.defineProperty(exports, '__esModule', { 4240 value: true 4241 }); 4242 4243 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 4244 4245 var _playlistJs = require('./playlist.js'); 4246 4247 /** 4248 * Enable/disable playlist function. It is intended to have the first two 4249 * arguments partially-applied in order to create the final per-playlist 4250 * function. 4251 * 4252 * @param {PlaylistLoader} playlist - The rendition or media-playlist 4253 * @param {Function} changePlaylistFn - A function to be called after a 4254 * playlist's enabled-state has been changed. Will NOT be called if a 4255 * playlist's enabled-state is unchanged 4256 * @param {Boolean=} enable - Value to set the playlist enabled-state to 4257 * or if undefined returns the current enabled-state for the playlist 4258 * @return {Boolean} The current enabled-state of the playlist 4259 */ 4260 var enableFunction = function enableFunction(loader, playlistUri, changePlaylistFn, enable) { 4261 var playlist = loader.master.playlists[playlistUri]; 4262 var blacklisted = (0, _playlistJs.isBlacklisted)(playlist); 4263 var currentlyEnabled = (0, _playlistJs.isEnabled)(playlist); 4264 4265 if (typeof enable === 'undefined') { 4266 return currentlyEnabled; 4267 } 4268 4269 if (enable) { 4270 delete playlist.disabled; 4271 } else { 4272 playlist.disabled = true; 4273 } 4274 4275 if (enable !== currentlyEnabled && !blacklisted) { 4276 // Ensure the outside world knows about our changes 4277 changePlaylistFn(); 4278 } 4279 4280 return enable; 4281 }; 4282 4283 /** 4284 * The representation object encapsulates the publicly visible information 4285 * in a media playlist along with a setter/getter-type function (enabled) 4286 * for changing the enabled-state of a particular playlist entry 4287 * 4288 * @class Representation 4289 */ 4290 4291 var Representation = function Representation(hlsHandler, playlist, id) { 4292 _classCallCheck(this, Representation); 4293 4294 // Get a reference to a bound version of fastQualityChange_ 4295 var fastChangeFunction = hlsHandler.masterPlaylistController_.fastQualityChange_.bind(hlsHandler.masterPlaylistController_); 4296
4297 // Carefully descend into the playlist's attributes since most 4298 // properties are optional 4299 if (playlist.attributes) { 4300 var attributes = playlist.attributes; 4301 4302 if (attributes.RESOLUTION) { 4303 var resolution = attributes.RESOLUTION; 4304 4305 this.width = resolution.width; 4306 this.height = resolution.height; 4307 } 4308 4309 this.bandwidth = attributes.BANDWIDTH; 4310 } 4311 4312 // The id is simply the ordinality of the media playlist 4313 // within the master playlist 4314 this.id = id; 4315 4316 // Partially-apply the enableFunction to create a playlist- 4317 // specific variant 4318 this.enabled = enableFunction.bind(this, hlsHandler.playlists, playlist.uri, fastChangeFunction); 4319 } 4320 4321 /** 4322 * A mixin function that adds the `representations` api to an instance 4323 * of the HlsHandler class 4324 * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the 4325 * representation API into 4326 */ 4327 ; 4328 4329 var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) { 4330 var playlists = hlsHandler.playlists; 4331 4332 // Add a single API-specific function to the HlsHandler instance 4333 hlsHandler.representations = function () { 4334 return playlists.master.playlists.filter(function (media) { 4335 return !(0, _playlistJs.isBlacklisted)(media); 4336 }).map(function (e, i) { 4337 return new Representation(hlsHandler, e, e.uri); 4338 }); 4339 }; 4340 }; 4341 4342 exports['default'] = renditionSelectionMixin; 4343 module.exports = exports['default']; 4344 },{"./playlist.js":9}],13:[function(require,module,exports){ 4345 /** 4346 * @file resolve-url.js 4347 */ 4348 4349 'use strict'; 4350 4351 Object.defineProperty(exports, '__esModule', { 4352 value: true 4353 }); 4354 4355 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 4356 4357 var _urlToolkit = require('url-toolkit'); 4358 4359 var _urlToolkit2 = _interopRequireDefault(_urlToolkit); 4360 4361 var _globalWindow = require('global/window'); 4362 4363 var _globalWindow2 = _interopRequireDefault(_globalWindow); 4364 4365 var resolveUrl = function resolveUrl(baseURL, relativeURL) { 4366 // return early if we don't need to resolve 4367 if (/^[a-z]+:/i.test(relativeURL)) { 4368 return relativeURL; 4369 } 4370 4371 // if the base URL is relative then combine with the current location 4372 if (!/\/\//i.test(baseURL)) { 4373 baseURL = _urlToolkit2['default'].buildAbsoluteURL(_globalWindow2['default'].location.href, baseURL); 4374 } 4375 4376 return _urlToolkit2['default'].buildAbsoluteURL(baseURL, relativeURL); 4377 }; 4378 4379 exports['default'] = resolveUrl; 4380 module.exports = exports['default']; 4381 },{"global/window":30,"url-toolkit":61}],14:[function(require,module,exports){ 4382 (function (global){ 4383 /** 4384 * @file segment-loader.js 4385 */ 4386 'use strict'; 4387 4388 Object.defineProperty(exports, '__esModule', { 4389 value: true 4390 }); 4391 4392 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 4393 4394 var _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); } } }; 4395 4396 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 4397 4398 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 4399 4400 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
4400onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 4401 4402 var _playlist = require('./playlist'); 4403 4404 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 4405 4406 var _videoJs2 = _interopRequireDefault(_videoJs); 4407 4408 var _sourceUpdater = require('./source-updater'); 4409 4410 var _sourceUpdater2 = _interopRequireDefault(_sourceUpdater); 4411 4412 var _config = require('./config'); 4413 4414 var _config2 = _interopRequireDefault(_config); 4415 4416 var _globalWindow = require('global/window'); 4417 4418 var _globalWindow2 = _interopRequireDefault(_globalWindow); 4419 4420 var _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs = require('videojs-contrib-media-sources/es5/remove-cues-from-track.js'); 4421 4422 var _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs2 = _interopRequireDefault(_videojsContribMediaSourcesEs5RemoveCuesFromTrackJs); 4423 4424 var _binUtils = require('./bin-utils'); 4425 4426 var _mediaSegmentRequest = require('./media-segment-request'); 4427 4428// in ms 4429 var CHECK_BUFFER_DELAY = 500; 4430 4431 /** 4432 * Determines if we should call endOfStream on the media source based 4433 * on the state of the buffer or if appened segment was the final 4434 * segment in the playlist. 4435 * 4436 * @param {Object} playlist a media playlist object 4437 * @param {Object} mediaSource the MediaSource object 4438 * @param {Number} segmentIndex the index of segment we last appended 4439 * @returns {Boolean} do we need to call endOfStream on the MediaSource 4440 */ 4441 var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex) { 4442 if (!playlist || !mediaSource) { 4443 return false; 4444 } 4445 4446 var segments = playlist.segments; 4447 4448 // determine a few boolean values to help make the branch below easier 4449 // to read 4450 var appendedLastSegment = segmentIndex === segments.length; 4451 4452 // if we've buffered to the end of the video, we need to call endOfStream 4453 // so that MediaSources can trigger the `ended` event when it runs out of 4454 // buffered data instead of waiting for me 4455 return playlist.endList && mediaSource.readyState === 'open' && appendedLastSegment; 4456 }; 4457 4458 /** 4459 * An object that manages segment loading and appending. 4460 * 4461 * @class SegmentLoader 4462 * @param {Object} options required and optional options 4463 * @extends videojs.EventTarget 4464 */ 4465 4466 var SegmentLoader = (function (_videojs$EventTarget) { 4467 _inherits(SegmentLoader, _videojs$EventTarget); 4468 4469 function SegmentLoader(options) { 4470 var _this = this; 4471 4472 _classCallCheck(this, SegmentLoader); 4473 4474 _get(Object.getPrototypeOf(SegmentLoader.prototype), 'constructor', this).call(this); 4475 // check pre-conditions 4476 if (!options) { 4477 throw new TypeError('Initialization options are required'); 4478 } 4479 if (typeof options.currentTime !== 'function') { 4480 throw new TypeError('No currentTime getter specified'); 4481 } 4482 if (!options.mediaSource) { 4483 throw new TypeError('No MediaSource specified'); 4484 } 4485 var settings = _videoJs2['default'].mergeOptions(_videoJs2['default'].options.hls, options); 4486 4487 // public properties 4488 this.state = 'INIT'; 4489 this.bandwidth = settings.bandwidth; 4490 this.throughput = { rate: 0, count: 0 }; 4491 this.roundTrip = NaN; 4492 this.resetStats_(); 4493 this.mediaIndex = null; 4494 4495 // private settings 4496 this.hasPlayed_ = settings.hasPlayed; 4497 this.currentTime_ = settings.currentTime; 4498 this.seekable_ = settings.seekable; 4499 this.seeking_ = settings.seeking; 4500 this.duration_ = settings.duration; 4501 this.mediaSource_ = settings.mediaSource; 4502 this.hls_ = settings.hls; 4503 this.loaderType_ = settings.loaderType; 4504 this.segmentMetadataTrack_ = settings.segmentMetadataTrack; 4505 4506 // private instance variables 4507 this.checkBufferTimeout_ = null; 4508 this.error_ = void 0; 4509 this.currentTimeline_ = -1; 4510 this.pendingSegment_ = null; 4511 this.mimeType_ = null; 4512 this.sourceUpdater_ = null; 4513 this.xhrOptions_ = null; 4514 4515 // Fragmented mp4 playback 4516 this.activeInitSegmentId_ = null; 4517 this.initSegments_ = {}; 4518 4519 this.decrypter_ = settings.decrypter; 4520 4521 // Manages the tracking and generation of sync-points, mappings 4522 // between a time in the display time and a segment index within 4523 // a playlist 4524 this.syncController_ = settings.syncController; 4525 this.syncPoint_ = { 4526 segmentIndex: 0, 4527 time: 0 4528 }; 4529 4530 this.syncController_.on('syncinfoupdate', function () { 4531 return _this.trigger('syncinfoupdate'); 4532 }); 4533 4534 // ...for determining the fetch location 4535 this.fetchAtBuffer_ = false;
4536 4537 if (settings.debug) { 4538 this.logger_ = _videoJs2['default'].log.bind(_videoJs2['default'], 'segment-loader', this.loaderType_, '->'); 4539 } 4540 } 4541 4542 /** 4543 * reset all of our media stats 4544 * 4545 * @private 4546 */ 4547 4548 _createClass(SegmentLoader, [{ 4549 key: 'resetStats_', 4550 value: function resetStats_() { 4551 this.mediaBytesTransferred = 0; 4552 this.mediaRequests = 0; 4553 this.mediaRequestsAborted = 0; 4554 this.mediaRequestsTimedout = 0; 4555 this.mediaRequestsErrored = 0; 4556 this.mediaTransferDuration = 0; 4557 this.mediaSecondsLoaded = 0; 4558 } 4559 4560 /** 4561 * dispose of the SegmentLoader and reset to the default state 4562 */ 4563 }, { 4564 key: 'dispose', 4565 value: function dispose() { 4566 this.state = 'DISPOSED'; 4567 this.abort_(); 4568 if (this.sourceUpdater_) { 4569 this.sourceUpdater_.dispose(); 4570 } 4571 this.resetStats_(); 4572 } 4573 4574 /** 4575 * abort anything that is currently doing on with the SegmentLoader 4576 * and reset to a default state 4577 */ 4578 }, { 4579 key: 'abort', 4580 value: function abort() { 4581 if (this.state !== 'WAITING') { 4582 if (this.pendingSegment_) { 4583 this.pendingSegment_ = null; 4584 } 4585 return; 4586 } 4587 4588 this.abort_(); 4589 4590 // don't wait for buffer check timeouts to begin fetching the 4591 // next segment 4592 if (!this.paused()) { 4593 this.state = 'READY'; 4594 this.monitorBuffer_(); 4595 } 4596 } 4597 4598 /** 4599 * abort all pending xhr requests and null any pending segements 4600 * 4601 * @private 4602 */ 4603 }, { 4604 key: 'abort_', 4605 value: function abort_() { 4606 if (this.pendingSegment_) { 4607 this.pendingSegment_.abortRequests(); 4608 } 4609 4610 // clear out the segment being processed 4611 this.pendingSegment_ = null; 4612 } 4613 4614 /** 4615 * set an error on the segment loader and null out any pending segements 4616 * 4617 * @param {Error} error the error to set on the SegmentLoader 4618 * @return {Error} the error that was set or that is currently set 4619 */ 4620 }, { 4621 key: 'error', 4622 value: function error(_error) { 4623 if (typeof _error !== 'undefined') { 4624 this.error_ = _error; 4625 } 4626 4627 this.pendingSegment_ = null; 4628 return this.error_; 4629 } 4630 4631 /** 4632 * Indicates which time ranges are buffered 4633 * 4634 * @return {TimeRange} 4635 * TimeRange object representing the current buffered ranges 4636 */ 4637 }, { 4638 key: 'buffered_', 4639 value: function buffered_() { 4640 if (!this.sourceUpdater_) { 4641 return _videoJs2['default'].createTimeRanges(); 4642 } 4643 4644 return this.sourceUpdater_.buffered(); 4645 } 4646 4647 /** 4648 * Gets and sets init segment for the provided map 4649 * 4650 * @param {Object} map 4651 * The map object representing the init segment to get or set 4652 * @param {Boolean=} set 4653 * If true, the init segment for the provided map should be saved 4654 * @return {Object} 4655 * map object for desired init segment 4656 */ 4657 }, { 4658 key: 'initSegment', 4659 value: function initSegment(map) { 4660 var set = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 4661 4662 if (!map) { 4663 return null; 4664 } 4665 4666 var id = (0, _binUtils.initSegmentId)(map); 4667 var storedMap = this.initSegments_[id]; 4668 4669 if (set && !storedMap && map.bytes) { 4670 this.initSegments_[id] = storedMap = { 4671 resolvedUri: map.resolvedUri, 4672 byterange: map.byterange, 4673 bytes: map.bytes 4674 }; 4675 } 4676 4677 return storedMap || map; 4678 } 4679 4680 /**
4681 * Returns true if all configuration required for loading is present, otherwise false. 4682 * 4683 * @return {Boolean} True if the all configuration is ready for loading 4684 * @private 4685 */ 4686 }, { 4687 key: 'couldBeginLoading_', 4688 value: function couldBeginLoading_() { 4689 return this.playlist_ && ( 4690 // the source updater is created when init_ is called, so either having a 4691 // source updater or being in the INIT state with a mimeType is enough 4692 // to say we have all the needed configuration to start loading. 4693 this.sourceUpdater_ || this.mimeType_ && this.state === 'INIT') && !this.paused(); 4694 } 4695 4696 /** 4697 * load a playlist and start to fill the buffer 4698 */ 4699 }, { 4700 key: 'load', 4701 value: function load() { 4702 // un-pause 4703 this.monitorBuffer_(); 4704 4705 // if we don't have a playlist yet, keep waiting for one to be 4706 // specified 4707 if (!this.playlist_) { 4708 return; 4709 } 4710 4711 // not sure if this is the best place for this 4712 this.syncController_.setDateTimeMapping(this.playlist_); 4713 4714 // if all the configuration is ready, initialize and begin loading 4715 if (this.state === 'INIT' && this.couldBeginLoading_()) { 4716 return this.init_(); 4717 } 4718 4719 // if we're in the middle of processing a segment already, don't 4720 // kick off an additional segment request 4721 if (!this.couldBeginLoading_() || this.state !== 'READY' && this.state !== 'INIT') { 4722 return; 4723 } 4724 4725 this.state = 'READY'; 4726 } 4727 4728 /** 4729 * Once all the starting parameters have been specified, begin 4730 * operation. This method should only be invoked from the INIT 4731 * state. 4732 * 4733 * @private 4734 */ 4735 }, { 4736 key: 'init_', 4737 value: function init_() { 4738 this.state = 'READY'; 4739 this.sourceUpdater_ = new _sourceUpdater2['default'](this.mediaSource_, this.mimeType_); 4740 this.resetEverything(); 4741 return this.monitorBuffer_(); 4742 } 4743 4744 /** 4745 * set a playlist on the segment loader 4746 * 4747 * @param {PlaylistLoader} media the playlist to set on the segment loader 4748 */ 4749 }, { 4750 key: 'playlist', 4751 value: function playlist(newPlaylist) { 4752 var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; 4753 4754 if (!newPlaylist) { 4755 return; 4756 } 4757 4758 var oldPlaylist = this.playlist_; 4759 var segmentInfo = this.pendingSegment_; 4760 4761 this.playlist_ = newPlaylist; 4762 this.xhrOptions_ = options; 4763 4764 // when we haven't started playing yet, the start of a live playlist 4765 // is always our zero-time so force a sync update each time the playlist 4766 // is refreshed from the server 4767 if (!this.hasPlayed_()) { 4768 newPlaylist.syncInfo = { 4769 mediaSequence: newPlaylist.mediaSequence, 4770 time: 0 4771 }; 4772 } 4773 4774 // in VOD, this is always a rendition switch (or we updated our syncInfo above) 4775 // in LIVE, we always want to update with new playlists (including refreshes) 4776 this.trigger('syncinfoupdate'); 4777 4778 // if we were unpaused but waiting for a playlist, start 4779 // buffering now 4780 if (this.state === 'INIT' && this.couldBeginLoading_()) { 4781 return this.init_(); 4782 } 4783 4784 if (!oldPlaylist || oldPlaylist.uri !== newPlaylist.uri) { 4785 if (this.mediaIndex !== null) { 4786 // we must "resync" the segment loader when we switch renditions and 4787 // the segment loader is already synced to the previous rendition 4788 this.resyncLoader(); 4789 } 4790 4791 // the rest of this function depends on `oldPlaylist` being defined 4792 return; 4793 } 4794 4795 // we reloaded the same playlist so we are in a live scenario 4796 // and we will likely need to adjust the mediaIndex 4797 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; 4798 4799 this.logger_('mediaSequenceDiff', mediaSequenceDiff); 4800 4801 // update the mediaIndex on the SegmentLoader 4802 // this is important because we can abort a request and this value must be 4803 // equal to the last appended mediaIndex 4804 if (this.mediaIndex !== null) { 4805 this.mediaIndex -= mediaSequenceDiff; 4806 } 4807 4808 // update the mediaIndex on the SegmentInfo object 4809 // this is important because we will update this.mediaIndex with this value 4810 // in `handleUpdateEnd_` after the segment has been successfully appended 4811 if (segmentInfo) { 4812 segmentInfo.mediaIndex -= mediaSequenceDiff; 4813 4814 // we need to update the referenced segment so that timing information is 4815 // saved for the new playlist's segment, however, if the segment fell off the 4816 // playlist, we can leave the old reference and just lose the timing info 4817 if (segmentInfo.mediaIndex >= 0) { 4818 segmentInfo.segment = newPlaylist.segments[segmentInfo.mediaIndex]; 4819 } 4820 } 4821 4822 this.syncController_.saveExpiredSegmentInfo(oldPlaylist, newPlaylist); 4823 } 4824 4825 /** 4826 * Prevent the loader from fetching additional segments. If there 4827 * is a segment request outstanding, it will finish processing 4828 * before the loader halts. A segment loader can be unpaused by 4829 * calling load(). 4830 */ 4831 }, { 4832 key: 'pause', 4833 value: function pause() { 4834 if (this.checkBufferTimeout_) { 4835 _globalWindow2['default'].clearTimeout(this.checkBufferTimeout_); 4836 4837 this.checkBufferTimeout_ = null; 4838 } 4839 } 4840 4841 /** 4842 * Returns whether the segment loader is fetching additional 4843 * segments when given the opportunity. This property can be 4844 * modified through calls to pause() and load(). 4845 */ 4846 }, { 4847 key: 'paused', 4848 value: function paused() { 4849 return this.checkBufferTimeout_ === null; 4850 } 4851 4852 /** 4853 * create/set the following mimetype on the SourceBuffer through a 4854 * SourceUpdater 4855 * 4856 * @param {String} mimeType the mime type string to use 4857 */ 4858 }, { 4859 key: 'mimeType', 4860 value: function mimeType(_mimeType) { 4861 if (this.mimeType_) { 4862 return; 4863 } 4864 4865 this.mimeType_ = _mimeType; 4866 // if we were unpaused but waiting for a sourceUpdater, start 4867 // buffering now 4868 if (this.state === 'INIT' && this.couldBeginLoading_()) { 4869 this.init_(); 4870 } 4871 } 4872 4873 /** 4874 * Delete all the buffered data and reset the SegmentLoader 4875 */ 4876 }, { 4877 key: 'resetEverything', 4878 value: function resetEverything() { 4879 this.resetLoader(); 4880 this.remove(0, Infinity); 4881 } 4882 4883 /** 4884 * Force the SegmentLoader to resync and start loading around the currentTime instead 4885 * of starting at the end of the buffer 4886 * 4887 * Useful for fast quality changes 4888 */ 4889 }, { 4890 key: 'resetLoader', 4891 value: function resetLoader() { 4892 this.fetchAtBuffer_ = false;
4893 this.resyncLoader(); 4894 } 4895 4896 /** 4897 * Force the SegmentLoader to restart synchronization and make a conservative guess 4898 * before returning to the simple walk-forward method 4899 */ 4900 }, { 4901 key: 'resyncLoader', 4902 value: function resyncLoader() { 4903 this.mediaIndex = null; 4904 this.syncPoint_ = null; 4905 } 4906 4907 /** 4908 * Remove any data in the source buffer between start and end times 4909 * @param {Number} start - the start time of the region to remove from the buffer 4910 * @param {Number} end - the end time of the region to remove from the buffer 4911 */ 4912 }, { 4913 key: 'remove', 4914 value: function remove(start, end) { 4915 if (this.sourceUpdater_) { 4916 this.sourceUpdater_.remove(start, end); 4917 } 4918 (0, _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs2['default'])(start, end, this.segmentMetadataTrack_); 4919 } 4920 4921 /** 4922 * (re-)schedule monitorBufferTick_ to run as soon as possible 4923 * 4924 * @private 4925 */ 4926 }, { 4927 key: 'monitorBuffer_', 4928 value: function monitorBuffer_() { 4929 if (this.checkBufferTimeout_) { 4930 _globalWindow2['default'].clearTimeout(this.checkBufferTimeout_); 4931 } 4932 4933 this.checkBufferTimeout_ = _globalWindow2['default'].setTimeout(this.monitorBufferTick_.bind(this), 1); 4934 } 4935 4936 /** 4937 * As long as the SegmentLoader is in the READY state, periodically 4938 * invoke fillBuffer_(). 4939 * 4940 * @private 4941 */ 4942 }, { 4943 key: 'monitorBufferTick_', 4944 value: function monitorBufferTick_() { 4945 if (this.state === 'READY') { 4946 this.fillBuffer_(); 4947 } 4948 4949 if (this.checkBufferTimeout_) { 4950 _globalWindow2['default'].clearTimeout(this.checkBufferTimeout_); 4951 } 4952 4953 this.checkBufferTimeout_ = _globalWindow2['default'].setTimeout(this.monitorBufferTick_.bind(this), CHECK_BUFFER_DELAY); 4954 } 4955 4956 /** 4957 * fill the buffer with segements unless the sourceBuffers are 4958 * currently updating 4959 * 4960 * Note: this function should only ever be called by monitorBuffer_ 4961 * and never directly 4962 * 4963 * @private 4964 */ 4965 }, { 4966 key: 'fillBuffer_', 4967 value: function fillBuffer_() { 4968 if (this.sourceUpdater_.updating()) { 4969 return; 4970 } 4971 4972 if (!this.syncPoint_) { 4973 this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_()); 4974 } 4975 4976 // see if we need to begin loading immediately 4977 var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_); 4978 4979 if (!segmentInfo) { 4980 return; 4981 } 4982 4983 var isEndOfStream = detectEndOfStream(this.playlist_, this.mediaSource_, segmentInfo.mediaIndex); 4984
4985 if (isEndOfStream) { 4986 this.mediaSource_.endOfStream(); 4987 return; 4988 } 4989 4990 if (segmentInfo.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) { 4991 return; 4992 } 4993 4994 // We will need to change timestampOffset of the sourceBuffer if either of 4995 // the following conditions are true: 4996 // - The segment.timeline !== this.currentTimeline 4997 // (we are crossing a discontinuity somehow) 4998 // - The "timestampOffset" for the start of this segment is less than 4999 // the currently set timestampOffset 5000 if (segmentInfo.timeline !== this.currentTimeline_ || segmentInfo.startOfSegment !== null && segmentInfo.startOfSegment < this.sourceUpdater_.timestampOffset()) { 5001 this.syncController_.reset(); 5002 segmentInfo.timestampOffset = segmentInfo.startOfSegment; 5003 } 5004 5005 this.loadSegment_(segmentInfo); 5006 } 5007 5008 /** 5009 * Determines what segment request should be made, given current playback 5010 * state. 5011 * 5012 * @param {TimeRanges} buffered - the state of the buffer 5013 * @param {Object} playlist - the playlist object to fetch segments from 5014 * @param {Number} mediaIndex - the previous mediaIndex fetched or null 5015 * @param {Boolean} hasPlayed - a flag indicating whether we have played or not 5016 * @param {Number} currentTime - the playback position in seconds 5017 * @param {Object} syncPoint - a segment info object that describes the 5018 * @returns {Object} a segment request object that describes the segment to load 5019 */ 5020 }, { 5021 key: 'checkBuffer_', 5022 value: function checkBuffer_(buffered, playlist, mediaIndex, hasPlayed, currentTime, syncPoint) { 5023 var lastBufferedEnd = 0; 5024 var startOfSegment = undefined; 5025 5026 if (buffered.length) { 5027 lastBufferedEnd = buffered.end(buffered.length - 1); 5028 } 5029 5030 var bufferedTime = Math.max(0, lastBufferedEnd - currentTime); 5031 5032 if (!playlist.segments.length) { 5033 return null; 5034 } 5035 5036 // if there is plenty of content buffered, and the video has 5037 // been played before relax for awhile 5038 if (bufferedTime >= _config2['default'].GOAL_BUFFER_LENGTH) { 5039 return null; 5040 } 5041 5042 // if the video has not yet played once, and we already have 5043 // one segment downloaded do nothing 5044 if (!hasPlayed && bufferedTime >= 1) { 5045 return null; 5046 } 5047 5048 this.logger_('checkBuffer_', 'mediaIndex:', mediaIndex, 'hasPlayed:', hasPlayed, 'currentTime:', currentTime, 'syncPoint:', syncPoint, 'fetchAtBuffer:', this.fetchAtBuffer_, 'bufferedTime:', bufferedTime); 5049 5050 // When the syncPoint is null, there is no way of determining a good 5051 // conservative segment index to fetch from 5052 // The best thing to do here is to get the kind of sync-point data by 5053 // making a request 5054 if (syncPoint === null) { 5055 mediaIndex = this.getSyncSegmentCandidate_(playlist); 5056 this.logger_('getSync', 'mediaIndex:', mediaIndex); 5057 return this.generateSegmentInfo_(playlist, mediaIndex, null, true); 5058 } 5059 5060 // Under normal playback conditions fetching is a simple walk forward 5061 if (mediaIndex !== null) { 5062 this.logger_('walkForward', 'mediaIndex:', mediaIndex + 1); 5063 var segment = playlist.segments[mediaIndex]; 5064
5065 if (segment && segment.end) { 5066 startOfSegment = segment.end; 5067 } else { 5068 startOfSegment = lastBufferedEnd; 5069 } 5070 return this.generateSegmentInfo_(playlist, mediaIndex + 1, startOfSegment, false); 5071 } 5072 5073 // There is a sync-point but the lack of a mediaIndex indicates that 5074 // we need to make a good conservative guess about which segment to 5075 // fetch 5076 if (this.fetchAtBuffer_) { 5077 // Find the segment containing the end of the buffer 5078 var mediaSourceInfo = (0, _playlist.getMediaInfoForTime_)(playlist, lastBufferedEnd, syncPoint.segmentIndex, syncPoint.time); 5079 5080 mediaIndex = mediaSourceInfo.mediaIndex; 5081 startOfSegment = mediaSourceInfo.startTime; 5082 } else { 5083 // Find the segment containing currentTime 5084 var mediaSourceInfo = (0, _playlist.getMediaInfoForTime_)(playlist, currentTime, syncPoint.segmentIndex, syncPoint.time); 5085 5086 mediaIndex = mediaSourceInfo.mediaIndex; 5087 startOfSegment = mediaSourceInfo.startTime; 5088 } 5089 this.logger_('getMediaIndexForTime', 'mediaIndex:', mediaIndex, 'startOfSegment:', startOfSegment); 5090 5091 return this.generateSegmentInfo_(playlist, mediaIndex, startOfSegment, false); 5092 } 5093 5094 /** 5095 * The segment loader has no recourse except to fetch a segment in the 5096 * current playlist and use the internal timestamps in that segment to 5097 * generate a syncPoint. This function returns a good candidate index 5098 * for that process. 5099 * 5100 * @param {Object} playlist - the playlist object to look for a 5101 * @returns {Number} An index of a segment from the playlist to load 5102 */ 5103 }, { 5104 key: 'getSyncSegmentCandidate_', 5105 value: function getSyncSegmentCandidate_(playlist) { 5106 var _this2 = this; 5107 5108 if (this.currentTimeline_ === -1) { 5109 return 0; 5110 } 5111 5112 var segmentIndexArray = playlist.segments.map(function (s, i) { 5113 return { 5114 timeline: s.timeline, 5115 segmentIndex: i 5116 }; 5117 }).filter(function (s) { 5118 return s.timeline === _this2.currentTimeline_; 5119 }); 5120 5121 if (segmentIndexArray.length) { 5122 return segmentIndexArray[Math.min(segmentIndexArray.length - 1, 1)].segmentIndex; 5123 } 5124 5125 return Math.max(playlist.segments.length - 1, 0); 5126 } 5127 }, { 5128 key: 'generateSegmentInfo_', 5129 value: function generateSegmentInfo_(playlist, mediaIndex, startOfSegment, isSyncRequest) { 5130 if (mediaIndex < 0 || mediaIndex >= playlist.segments.length) { 5131 return null; 5132 } 5133 5134 var segment = playlist.segments[mediaIndex]; 5135 5136 return { 5137 requestId: 'segment-loader-' + Math.random(), 5138 // resolve the segment URL relative to the playlist 5139 uri: segment.resolvedUri, 5140 // the segment's mediaIndex at the time it was requested 5141 mediaIndex: mediaIndex, 5142 // whether or not to update the SegmentLoader's state with this 5143 // segment's mediaIndex 5144 isSyncRequest: isSyncRequest, 5145 startOfSegment: startOfSegment, 5146 // the segment's playlist 5147 playlist: playlist, 5148 // unencrypted bytes of the segment 5149 bytes: null, 5150 // when a key is defined for this segment, the encrypted bytes 5151 encryptedBytes: null, 5152 // The target timestampOffset for this segment when we append it 5153 // to the source buffer 5154 timestampOffset: null, 5155 // The timeline that the segment is in 5156 timeline: segment.timeline, 5157 // The expected duration of the segment in seconds 5158 duration: segment.duration, 5159 // retain the segment in case the playlist updates while doing an async process 5160 segment: segment 5161 }; 5162 } 5163 5164 /** 5165 * load a specific segment from a request into the buffer 5166 * 5167 * @private 5168 */ 5169 }, { 5170 key: 'loadSegment_', 5171 value: function loadSegment_(segmentInfo) { 5172 var _this3 = this; 5173 5174 this.state = 'WAITING'; 5175 this.pendingSegment_ = segmentInfo; 5176 this.trimBackBuffer_(segmentInfo); 5177 5178 segmentInfo.abortRequests = (0, _mediaSegmentRequest.mediaSegmentRequest)(this.hls_.xhr, this.xhrOptions_, this.decrypter_, this.createSimplifiedSegmentObj_(segmentInfo), 5179 // progress callback 5180 function (event, segment) { 5181 if (!_this3.pendingSegment_ || segment.requestId !== _this3.pendingSegment_.requestId) { 5182 return; 5183 } 5184 // TODO: Use progress-based bandwidth to early abort low-bandwidth situations 5185 _this3.trigger('progress'); 5186 }, this.segmentRequestFinished_.bind(this)); 5187 } 5188 5189 /** 5190 * trim the back buffer so that we don't have too much data 5191 * in the source buffer 5192 * 5193 * @private 5194 * 5195 * @param {Object} segmentInfo - the current segment 5196 */ 5197 }, { 5198 key: 'trimBackBuffer_', 5199 value: function trimBackBuffer_(segmentInfo) { 5200 var seekable = this.seekable_(); 5201 var currentTime = this.currentTime_(); 5202 var removeToTime = 0; 5203 5204 // Chrome has a hard limit of 150mb of 5205 // buffer and a very conservative "garbage collector" 5206 // We manually clear out the old buffer to ensure 5207 // we don't trigger the QuotaExceeded error 5208 // on the source buffer during subsequent appends 5209 5210 // If we have a seekable range use that as the limit for what can be removed safely 5211 // otherwise remove anything older than 1 minute before the current play head 5212 if (seekable.length && seekable.start(0) > 0 && seekable.start(0) < currentTime) { 5213 removeToTime = seekable.start(0); 5214 } else { 5215 removeToTime = currentTime - 60; 5216 } 5217 5218 if (removeToTime > 0) { 5219 this.remove(0, removeToTime); 5220 } 5221 } 5222 5223 /** 5224 * created a simplified copy of the segment object with just the 5225 * information necessary to perform the XHR and decryption 5226 * 5227 * @private 5228 * 5229 * @param {Object} segmentInfo - the current segment 5230 * @returns {Object}
5230 a simplified segment object copy 5231 */ 5232 }, { 5233 key: 'createSimplifiedSegmentObj_', 5234 value: function createSimplifiedSegmentObj_(segmentInfo) { 5235 var segment = segmentInfo.segment; 5236 var simpleSegment = { 5237 resolvedUri: segment.resolvedUri, 5238 byterange: segment.byterange, 5239 requestId: segmentInfo.requestId 5240 }; 5241 5242 if (segment.key) { 5243 // if the media sequence is greater than 2^32, the IV will be incorrect 5244 // assuming 10s segments, that would be about 1300 years 5245 var iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]); 5246 5247 simpleSegment.key = { 5248 resolvedUri: segment.key.resolvedUri, 5249 iv: iv 5250 }; 5251 } 5252 5253 if (segment.map) { 5254 simpleSegment.map = this.initSegment(segment.map); 5255 } 5256 5257 return simpleSegment; 5258 } 5259 5260 /** 5261 * Handle the callback from the segmentRequest function and set the 5262 * associated SegmentLoader state and errors if necessary 5263 * 5264 * @private 5265 */ 5266 }, { 5267 key: 'segmentRequestFinished_', 5268 value: function segmentRequestFinished_(error, simpleSegment) { 5269 // every request counts as a media request even if it has been aborted 5270 // or canceled due to a timeout 5271 this.mediaRequests += 1; 5272 5273 if (simpleSegment.stats) { 5274 this.mediaBytesTransferred += simpleSegment.stats.bytesReceived; 5275 this.mediaTransferDuration += simpleSegment.stats.roundTripTime; 5276 } 5277 5278 // The request was aborted and the SegmentLoader has already been reset 5279 if (!this.pendingSegment_) { 5280 this.mediaRequestsAborted += 1; 5281 return; 5282 } 5283 5284 // the request was aborted and the SegmentLoader has already started 5285 // another request. this can happen when the timeout for an aborted 5286 // request triggers due to a limitation in the XHR library 5287 // do not count this as any sort of request or we risk double-counting 5288 if (simpleSegment.requestId !== this.pendingSegment_.requestId) { 5289 return; 5290 } 5291 5292 // an error occurred from the active pendingSegment_ so reset everything 5293 if (error) { 5294 this.pendingSegment_ = null; 5295 5296 // the requests were aborted just record the aborted stat and exit 5297 // this is not a true error condition and nothing corrective needs 5298 // to be done 5299 if (error.code === _mediaSegmentRequest.REQUEST_ERRORS.ABORTED) { 5300 this.mediaRequestsAborted += 1; 5301 return; 5302 } 5303 5304 this.state = 'READY'; 5305 this.pause(); 5306 5307 // the error is really just that at least one of the requests timed-out 5308 // set the bandwidth to a very low value and trigger an ABR switch to 5309 // take emergency action 5310 if (error.code === _mediaSegmentRequest.REQUEST_ERRORS.TIMEOUT) { 5311 this.mediaRequestsTimedout += 1; 5312 this.bandwidth = 1; 5313 this.roundTrip = NaN; 5314 this.trigger('bandwidthupdate'); 5315 return; 5316 } 5317 5318 // if control-flow has arrived here, then the error is real 5319 // emit an error event to blacklist the current playlist 5320 this.mediaRequestsErrored += 1; 5321 this.error(error); 5322 this.trigger('error'); 5323 return; 5324 } 5325 5326 // the response was a success so set any bandwidth stats the request 5327 // generated for ABR purposes 5328 this.bandwidth = simpleSegment.stats.bandwidth; 5329 this.roundTrip = simpleSegment.stats.roundTripTime; 5330 5331 // if this request included an initialization segment, save that data 5332 // to the initSegment cache 5333 if (simpleSegment.map) { 5334 simpleSegment.map = this.initSegment(simpleSegment.map, true); 5335 } 5336 5337 this.processSegmentResponse_(simpleSegment); 5338 } 5339 5340 /** 5341 * Move any important data from the simplified segment object 5342 * back to the real segment object for future phases 5343 * 5344 * @private 5345 */ 5346 }, { 5347 key: 'processSegmentResponse_', 5348 value: function processSegmentResponse_(simpleSegment) { 5349 var segmentInfo = this.pendingSegment_; 5350 5351 segmentInfo.bytes = simpleSegment.bytes; 5352 if (simpleSegment.map) { 5353 segmentInfo.segment.map.bytes = simpleSegment.map.bytes; 5354 } 5355 5356 segmentInfo.endOfAllRequests = simpleSegment.endOfAllRequests; 5357 this.handleSegment_(); 5358 } 5359 5360 /** 5361 * append a decrypted segement to the SourceBuffer through a SourceUpdater 5362 * 5363 * @private 5364 */ 5365 }, { 5366 key: 'handleSegment_', 5367 value: function handleSegment_() { 5368 var _this4 = this; 5369 5370 if (!this.pendingSegment_) { 5371 this.state = 'READY'; 5372 return; 5373 } 5374 5375 this.state = 'APPENDING'; 5376 5377 var segmentInfo = this.pendingSegment_; 5378 var segment = segmentInfo.segment; 5379 5380 this.syncController_.probeSegmentInfo(segmentInfo); 5381 5382 if (segmentInfo.isSyncRequest) { 5383 this.trigger('syncinfoupdate'); 5384 this.pendingSegment_ = null; 5385 this.state = 'READY'; 5386 return; 5387 } 5388 5389 if (segmentInfo.timestampOffset !== null && segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) { 5390 this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset); 5391 } 5392 5393 // if the media initialization segment is changing, append it 5394 // before the content segment 5395 if (segment.map) { 5396 (function () { 5397 var initId = (0, _binUtils.initSegmentId)(segment.map); 5398 5399 if (!_this4.activeInitSegmentId_ || _this4.activeInitSegmentId_ !== initId) { 5400 var initSegment = _this4.initSegment(segment.map); 5401 5402 _this4.sourceUpdater_.appendBuffer(initSegment.bytes, function () { 5403 _this4.activeInitSegmentId_ = initId; 5404 }); 5405 } 5406 })(); 5407 } 5408 5409 segmentInfo.byteLength = segmentInfo.bytes.byteLength; 5410 if (typeof segment.start === 'number' && typeof segment.end === 'number') { 5411 this.mediaSecondsLoaded += segment.end - segment.start; 5412 } else { 5413 this.mediaSecondsLoaded += segment.duration; 5414 } 5415 5416 this.sourceUpdater_.appendBuffer(segmentInfo.bytes, this.handleUpdateEnd_.bind(this)); 5417 } 5418 5419 /** 5420 * callback to run when appendBuffer is finished. detects if we are 5421 * in a good state to do things with the data we got, or if we need 5422 * to wait for more 5423 * 5424 * @private 5425 */ 5426 }, { 5427 key: 'handleUpdateEnd_', 5428 value: function handleUpdateEnd_() { 5429 this.logger_('handleUpdateEnd_', 'segmentInfo:', this.pendingSegment_); 5430 5431 if (!this.pendingSegment_) { 5432 this.state = 'READY'; 5433 if (!this.paused()) { 5434 this.monitorBuffer_(); 5435 } 5436 return; 5437 } 5438 5439 var segmentInfo = this.pendingSegment_; 5440 var segment = segmentInfo.segment; 5441 var isWalkingForward = this.mediaIndex !== null; 5442 5443 this.pendingSegment_ = null; 5444 this.recordThroughput_(segmentInfo); 5445 this.addSegmentMetadataCue_(segmentInfo); 5446 5447 this.state = 'READY'; 5448 5449 this.mediaIndex = segmentInfo.mediaIndex; 5450 this.fetchAtBuffer_ = true; 5451 this.currentTimeline_ = segmentInfo.timeline; 5452 5453 // We must update the syncinfo to recalculate the seekable range before 5454 // the following conditional otherwise it may consider this a bad "guess" 5455 // and attempt to resync when the post-update seekable window and live 5456 // point would mean that this was the perfect segment to fetch 5457 this.trigger('syncinfoupdate'); 5458 5459 // If we previously appended a segment that ends more than 3 targetDurations before 5460 // the currentTime_ that means that our conservative guess was too conservative. 5461 // In that case, reset the loader state so that we try to use any information gained 5462 // from the previous request to create a new, more accurate, sync-point. 5463 if (segment.end && this.currentTime_() - segment.end > segmentInfo.playlist.targetDuration * 3) { 5464 this.resetEverything(); 5465 return; 5466 } 5467 5468 // Don't do a rendition switch unless we have enough time to get a sync segment 5469 // and conservatively guess 5470 if (isWalkingForward) { 5471 this.trigger('bandwidthupdate'); 5472 } 5473 this.trigger('progress'); 5474 5475 // any time an update finishes and the last segment is in the 5476 // buffer, end the stream. this ensures the "ended" event will 5477 // fire if playback reaches that point. 5478 var isEndOfStream = detectEndOfStream(segmentInfo.playlist, this.mediaSource_, segmentInfo.mediaIndex + 1); 5479
5480 if (isEndOfStream) { 5481 this.mediaSource_.endOfStream(); 5482 } 5483 5484 if (!this.paused()) { 5485 this.monitorBuffer_(); 5486 } 5487 } 5488 5489 /** 5490 * Records the current throughput of the decrypt, transmux, and append 5491 * portion of the semgment pipeline. `throughput.rate` is a the cumulative 5492 * moving average of the throughput. `throughput.count` is the number of 5493 * data points in the average. 5494 * 5495 * @private 5496 * @param {Object} segmentInfo the object returned by loadSegment 5497 */ 5498 }, { 5499 key: 'recordThroughput_', 5500 value: function recordThroughput_(segmentInfo) { 5501 var rate = this.throughput.rate; 5502 // Add one to the time to ensure that we don't accidentally attempt to divide 5503 // by zero in the case where the throughput is ridiculously high 5504 var segmentProcessingTime = Date.now() - segmentInfo.endOfAllRequests + 1; 5505 // Multiply by 8000 to convert from bytes/millisecond to bits/second 5506 var segmentProcessingThroughput = Math.floor(segmentInfo.byteLength / segmentProcessingTime * 8 * 1000); 5507 5508 // This is just a cumulative moving average calculation: 5509 // newAvg = oldAvg + (sample - oldAvg) / (sampleCount + 1) 5510 this.throughput.rate += (segmentProcessingThroughput - rate) / ++this.throughput.count; 5511 } 5512 5513 /** 5514 * A debugging logger noop that is set to console.log only if debugging 5515 * is enabled globally 5516 * 5517 * @private 5518 */ 5519 }, { 5520 key: 'logger_', 5521 value: function logger_() {} 5522 5523 /** 5524 * Adds a cue to the segment-metadata track with some metadata information about the 5525 * segment 5526 * 5527 * @private 5528 * @param {Object} segmentInfo 5529 * the object returned by loadSegment 5530 * @method addSegmentMetadataCue_ 5531 */ 5532 }, { 5533 key: 'addSegmentMetadataCue_', 5534 value: function addSegmentMetadataCue_(segmentInfo) { 5535 if (!this.segmentMetadataTrack_) { 5536 return; 5537 } 5538 5539 var segment = segmentInfo.segment; 5540 var start = segment.start; 5541 var end = segment.end; 5542 5543 (0, _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs2['default'])(start, end, this.segmentMetadataTrack_); 5544 5545 var Cue = _globalWindow2['default'].WebKitDataCue || _globalWindow2['default'].VTTCue; 5546 var value = { 5547 uri: segmentInfo.uri, 5548 timeline: segmentInfo.timeline, 5549 playlist: segmentInfo.playlist.uri, 5550 start: start, 5551 end: end 5552 }; 5553 var data = JSON.stringify(value); 5554 var cue = new Cue(start, end, data); 5555 5556 // Attach the metadata to the value property of the cue to keep consistency between 5557 // the differences of WebKitDataCue in safari and VTTCue in other browsers 5558 cue.value = value; 5559 5560 this.segmentMetadataTrack_.addCue(cue); 5561 } 5562 }]); 5563 5564 return SegmentLoader; 5565 })(_videoJs2['default'].EventTarget); 5566 5567 exports['default'] = SegmentLoader; 5568 module.exports = exports['default']; 5569 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 5570 },{"./bin-utils":2,"./config":3,"./media-segment-request":6,"./playlist":9,"./source-updater":15,"global/window":30,"videojs-contrib-media-sources/es5/remove-cues-from-track.js":71}],15:[function(require,module,exports){ 5571 (function (global){ 5572 /** 5573 * @file source-updater.js 5574 */ 5575 'use strict'; 5576 5577 Object.defineProperty(exports, '__esModule', { 5578 value: true 5579 }); 5580 5581 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 5582 5583 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 5584 5585 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 5586 5587 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 5588 5589 var _videoJs2 = _interopRequireDefault(_videoJs); 5590 5591 var noop = function noop() {}; 5592 5593 /** 5594 * A queue of callbacks to be serialized and applied when a
5595 * MediaSource and its associated SourceBuffers are not in the 5596 * updating state. It is used by the segment loader to update the 5597 * underlying SourceBuffers when new data is loaded, for instance. 5598 * 5599 * @class SourceUpdater 5600 * @param {MediaSource} mediaSource the MediaSource to create the 5601 * SourceBuffer from 5602 * @param {String} mimeType the desired MIME type of the underlying 5603 * SourceBuffer 5604 */ 5605 5606 var SourceUpdater = (function () { 5607 function SourceUpdater(mediaSource, mimeType) { 5608 var _this = this; 5609 5610 _classCallCheck(this, SourceUpdater); 5611 5612 var createSourceBuffer = function createSourceBuffer() { 5613 _this.sourceBuffer_ = mediaSource.addSourceBuffer(mimeType); 5614 5615 // run completion handlers and process callbacks as updateend 5616 // events fire 5617 _this.onUpdateendCallback_ = function () { 5618 var pendingCallback = _this.pendingCallback_; 5619 5620 _this.pendingCallback_ = null; 5621 5622 if (pendingCallback) { 5623 pendingCallback(); 5624 } 5625 5626 _this.runCallback_(); 5627 }; 5628 5629 _this.sourceBuffer_.addEventListener('updateend', _this.onUpdateendCallback_); 5630 5631 _this.runCallback_(); 5632 }; 5633 5634 this.callbacks_ = []; 5635 this.pendingCallback_ = null; 5636 this.timestampOffset_ = 0; 5637 this.mediaSource = mediaSource; 5638 this.processedAppend_ = false; 5639 5640 if (mediaSource.readyState === 'closed') { 5641 mediaSource.addEventListener('sourceopen', createSourceBuffer); 5642 } else { 5643 createSourceBuffer(); 5644 } 5645 } 5646 5647 /** 5648 * Aborts the current segment and resets the segment parser. 5649 * 5650 * @param {Function} done function to call when done 5651 * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void 5652 */ 5653 5654 _createClass(SourceUpdater, [{ 5655 key: 'abort', 5656 value: function abort(done) { 5657 var _this2 = this; 5658 5659 if (this.processedAppend_) { 5660 this.queueCallback_(function () { 5661 _this2.sourceBuffer_.abort(); 5662 }, done); 5663 } 5664 } 5665 5666 /** 5667 * Queue an update to append an ArrayBuffer. 5668 * 5669 * @param {ArrayBuffer} bytes 5670 * @param {Function} done the function to call when done 5671 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data 5672 */ 5673 }, { 5674 key: 'appendBuffer', 5675 value: function appendBuffer(bytes, done) { 5676 var _this3 = this; 5677 5678 this.processedAppend_ = true; 5679 5680 this.queueCallback_(function () { 5681 _this3.sourceBuffer_.appendBuffer(bytes); 5682 }, done); 5683 } 5684 5685 /** 5686 * Indicates what TimeRanges are buffered in the managed SourceBuffer. 5687 * 5688 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered 5689 */ 5690 }, { 5691 key: 'buffered', 5692 value: function buffered() { 5693 if (!this.sourceBuffer_) { 5694 return _videoJs2['default'].createTimeRanges(); 5695 } 5696 return this.sourceBuffer_.buffered; 5697 } 5698 5699 /** 5700 * Queue an update to remove a time range from the buffer. 5701 * 5702 * @param {Number} start where to start the removal 5703 * @param {Number} end where to end the removal 5704 * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end 5705 */ 5706 }, { 5707 key: 'remove', 5708 value: function remove(start, end) { 5709 var _this4 = this; 5710 5711 if (this.processedAppend_) { 5712 this.queueCallback_(function () { 5713 _this4.sourceBuffer_.remove(start, end); 5714 }, noop); 5715 } 5716 } 5717 5718 /** 5719 * Whether the underlying sourceBuffer is updating or not 5720 * 5721 * @return {Boolean} the updating status of the SourceBuffer 5722 */ 5723 }, { 5724 key: 'updating', 5725 value: function updating() { 5726 return !this.sourceBuffer_ || this.sourceBuffer_.updating || this.pendingCallback_; 5727 } 5728 5729 /** 5730 * Set/get the timestampoffset on the SourceBuffer 5731 * 5732 * @return {Number} the timestamp offset 5733 */ 5734 }, { 5735 key: 'timestampOffset', 5736 value: function timestampOffset(offset) { 5737 var _this5 = this; 5738 5739 if (typeof offset !== 'undefined') { 5740 this.queueCallback_(function () { 5741 _this5.sourceBuffer_.timestampOffset = offset; 5742 }); 5743 this.timestampOffset_ = offset; 5744 } 5745 return this.timestampOffset_; 5746 } 5747 5748 /** 5749 * Queue a callback to run 5750 */ 5751 }, { 5752 key: 'queueCallback_', 5753 value: function queueCallback_(callback, done) { 5754 this.callbacks_.push([callback.bind(this), done]); 5755 this.runCallback_(); 5756 } 5757 5758 /** 5759 * Run a queued callback 5760 */ 5761 }, { 5762 key: 'runCallback_', 5763 value: function runCallback_() { 5764 var callbacks = undefined; 5765 5766 if (!this.updating() && this.callbacks_.length) { 5767 callbacks = this.callbacks_.shift(); 5768 this.pendingCallback_ = callbacks[1]; 5769 callbacks[0](); 5770 } 5771 } 5772 5773 /** 5774 * dispose of the source updater and the underlying sourceBuffer 5775 */ 5776 }, { 5777 key: 'dispose', 5778 value: function dispose() { 5779 this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_); 5780 if (this.sourceBuffer_ && this.mediaSource.readyState === 'open') { 5781 this.sourceBuffer_.abort(); 5782 } 5783 } 5784 }]); 5785 5786 return SourceUpdater; 5787 })(); 5788 5789 exports['default'] = SourceUpdater; 5790 module.exports = exports['default']; 5791 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 5792 },{}],16:[function(require,module,exports){ 5793 /** 5794 * @file stream.js 5795 */ 5796 /** 5797 * A lightweight readable stream implemention that handles event dispatching. 5798 * 5799 * @class Stream 5800 */ 5801 'use strict'; 5802 5803 Object.defineProperty(exports, '__esModule', { 5804 value: true 5805 }); 5806 5807 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 5808 5809 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 5810 5811 var Stream = (function () { 5812 function Stream() { 5813 _classCallCheck(this, Stream); 5814 5815 this.listeners = {}; 5816 } 5817 5818 /** 5819 * Add a listener for a specified event type. 5820 * 5821 * @param {String} type the event name 5822 * @param {Function} listener the callback to be invoked when an event of 5823 * the specified type occurs 5824 */ 5825 5826 _createClass(Stream, [{ 5827 key: 'on', 5828 value: function on(type, listener) { 5829 if (!this.listeners[type]) { 5830 this.listeners[type] = []; 5831 } 5832 this.listeners[type].push(listener); 5833 } 5834 5835 /** 5836 * Remove a listener for a specified event type. 5837 * 5838 * @param {String} type the event name 5839 * @param {Function} listener a function previously registered for this 5840 * type of event through `on` 5841 * @return {Boolean} if we could turn it off or not 5842 */ 5843 }, { 5844 key: 'off', 5845 value: function off(type, listener) { 5846 var index = undefined; 5847 5848 if (!this.listeners[type]) { 5849 return false;
5850 } 5851 index = this.listeners[type].indexOf(listener); 5852 this.listeners[type].splice(index, 1); 5853 return index > -1; 5854 } 5855 5856 /** 5857 * Trigger an event of the specified type on this stream. Any additional 5858 * arguments to this function are passed as parameters to event listeners. 5859 * 5860 * @param {String} type the event name 5861 */ 5862 }, { 5863 key: 'trigger', 5864 value: function trigger(type) { 5865 var callbacks = undefined; 5866 var i = undefined; 5867 var length = undefined; 5868 var args = undefined; 5869 5870 callbacks = this.listeners[type]; 5871 if (!callbacks) { 5872 return; 5873 } 5874 // Slicing the arguments on every invocation of this method 5875 // can add a significant amount of overhead. Avoid the 5876 // intermediate object creation for the common case of a 5877 // single callback argument 5878 if (arguments.length === 2) { 5879 length = callbacks.length; 5880 for (i = 0; i < length; ++i) { 5881 callbacks[i].call(this, arguments[1]); 5882 } 5883 } else { 5884 args = Array.prototype.slice.call(arguments, 1); 5885 length = callbacks.length; 5886 for (i = 0; i < length; ++i) { 5887 callbacks[i].apply(this, args); 5888 } 5889 } 5890 } 5891 5892 /** 5893 * Destroys the stream and cleans up. 5894 */ 5895 }, { 5896 key: 'dispose', 5897 value: function dispose() { 5898 this.listeners = {}; 5899 } 5900 5901 /** 5902 * Forwards all `data` events on this stream to the destination stream. The 5903 * destination stream should provide a method `push` to receive the data 5904 * events as they arrive. 5905 * 5906 * @param {Stream} destination the stream that will receive all `data` events 5907 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 5908 */ 5909 }, { 5910 key: 'pipe', 5911 value: function pipe(destination) { 5912 this.on('data', function (data) { 5913 destination.push(data); 5914 }); 5915 } 5916 }]); 5917 5918 return Stream; 5919 })(); 5920 5921 exports['default'] = Stream; 5922 module.exports = exports['default']; 5923 },{}],17:[function(require,module,exports){ 5924 (function (global){ 5925 /** 5926 * @file sync-controller.js 5927 */ 5928 5929 'use strict'; 5930 5931 Object.defineProperty(exports, '__esModule', { 5932 value: true 5933 }); 5934 5935 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 5936 5937 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 5938 5939 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 5940 5941 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 5942 5943 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
5943onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 5944 5945 var _muxJsLibMp4Probe = require('mux.js/lib/mp4/probe'); 5946 5947 var _muxJsLibMp4Probe2 = _interopRequireDefault(_muxJsLibMp4Probe); 5948 5949 var _muxJsLibToolsTsInspectorJs = require('mux.js/lib/tools/ts-inspector.js'); 5950 5951 var _playlist = require('./playlist'); 5952 5953 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 5954 5955 var _videoJs2 = _interopRequireDefault(_videoJs); 5956 5957 var syncPointStrategies = [ 5958// Stategy "VOD": Handle the VOD-case where the sync-point is *always* 5959// the equivalence display-time 0 === segment-index 0 5960 { 5961 name: 'VOD', 5962 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 5963 if (duration !== Infinity) { 5964 var syncPoint = { 5965 time: 0, 5966 segmentIndex: 0 5967 }; 5968 5969 return syncPoint; 5970 } 5971 return null; 5972 } 5973 }, 5974// Stategy "ProgramDateTime": We have a program-date-time tag in this playlist 5975 { 5976 name: 'ProgramDateTime', 5977 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 5978 if (syncController.datetimeToDisplayTime && playlist.dateTimeObject) { 5979 var playlistTime = playlist.dateTimeObject.getTime() / 1000; 5980 var playlistStart = playlistTime + syncController.datetimeToDisplayTime; 5981 var syncPoint = { 5982 time: playlistStart, 5983 segmentIndex: 0 5984 }; 5985 5986 return syncPoint; 5987 } 5988 return null; 5989 } 5990 }, 5991// Stategy "Segment": We have a known time mapping for a timeline and a 5992// segment in the current timeline with timing data 5993 { 5994 name: 'Segment', 5995 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 5996 var segments = playlist.segments; 5997 var syncPoint = null; 5998 var lastDistance = null; 5999 6000 currentTime = currentTime || 0; 6001 6002 for (var i = 0; i < segments.length; i++) { 6003 var segment = segments[i]; 6004 6005 if (segment.timeline === currentTimeline && typeof segment.start !== 'undefined') { 6006 var distance = Math.abs(currentTime - segment.start); 6007 6008 // Once the distance begins to increase, we have passed 6009 // currentTime and can stop looking for better candidates 6010 if (lastDistance !== null && lastDistance < distance) { 6011 break; 6012 } 6013 6014 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 6015 lastDistance = distance; 6016 syncPoint = { 6017 time: segment.start, 6018 segmentIndex: i 6019 }; 6020 } 6021 } 6022 } 6023 return syncPoint; 6024 } 6025 }, 6026// Stategy "Discontinuity": We have a discontinuity with a known 6027// display-time 6028 { 6029 name: 'Discontinuity', 6030 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 6031 var syncPoint = null; 6032 6033 currentTime = currentTime || 0; 6034 6035 if (playlist.discontinuityStarts.length) { 6036 var lastDistance = null; 6037 6038 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 6039 var segmentIndex = playlist.discontinuityStarts[i]; 6040 var discontinuity = playlist.discontinuitySequence + i + 1; 6041 var discontinuitySync = syncController.discontinuities[discontinuity]; 6042 6043 if (discontinuitySync) { 6044 var distance = Math.abs(currentTime - discontinuitySync.time); 6045 6046 // Once the distance begins to increase, we have passed 6047 // currentTime and can stop looking for better candidates 6048 if (lastDistance !== null && lastDistance < distance) { 6049 break; 6050 } 6051 6052 if (!syncPoint || lastDistance === null || lastDistance >= distance) { 6053 lastDistance = distance; 6054 syncPoint = { 6055 time: discontinuitySync.time, 6056 segmentIndex: segmentIndex 6057 }; 6058 } 6059 } 6060 } 6061 } 6062 return syncPoint; 6063 } 6064 }, 6065// Stategy "Playlist": We have a playlist with a known mapping of 6066// segment index to display time 6067 { 6068 name: 'Playlist', 6069 run: function run(syncController, playlist, duration, currentTimeline, currentTime) { 6070 if (playlist.syncInfo) { 6071 var syncPoint = { 6072 time: playlist.syncInfo.time, 6073 segmentIndex: playlist.syncInfo.mediaSequence - playlist.mediaSequence 6074 }; 6075 6076 return syncPoint; 6077 } 6078 return null; 6079 } 6080 }]; 6081 6082 exports.syncPointStrategies = syncPointStrategies; 6083 6084 var SyncController = (function (_videojs$EventTarget) { 6085 _inherits(SyncController, _videojs$EventTarget); 6086 6087 function SyncController() { 6088 _classCallCheck(this, SyncController); 6089 6090 _get(Object.getPrototypeOf(SyncController.prototype), 'constructor', this).call(this); 6091 // Segment Loader state variables... 6092 // ...for synching across variants 6093 this.inspectCache_ = undefined; 6094 6095 // ...for synching across variants 6096 this.timelines = []; 6097 this.discontinuities = []; 6098 this.datetimeToDisplayTime = null; 6099 6100 if (_videoJs2['default'].options.hls && _videoJs2['default'].options.hls.debug) { 6101 this.logger_ = _videoJs2['default'].log.bind(_videoJs2['default'], 'sync-controller ->'); 6102 } 6103 } 6104 6105 /** 6106 * Find a sync-point for the playlist specified 6107 * 6108 * A sync-point is defined as a known mapping from display-time to 6109 * a segment-index in the current playlist. 6110 * 6111 * @param {Playlist} playlist 6112 * The playlist that needs a sync-point 6113 * @param {Number} duration 6114 * Duration of the MediaSource (Infinite if playing a live source) 6115 * @param {Number} currentTimeline 6116 * The last timeline from which a segment was loaded 6117 * @returns {Object} 6118 * A sync-point object 6119 */ 6120 6121 _createClass(SyncController, [{ 6122 key: 'getSyncPoint', 6123 value: function getSyncPoint(playlist, duration, currentTimeline, currentTime) { 6124 var syncPoints = this.runStrategies_(playlist, duration, currentTimeline, currentTime); 6125 6126 if (!syncPoints.length) { 6127 // Signal that we need to attempt to get a sync-point manually 6128 // by fetching a segment in the playlist and constructing 6129 // a sync-point from that information 6130 return null; 6131 } 6132 6133 // Now find the sync-point that is closest to the currentTime because 6134 // that should result in the most accurate guess about which segment 6135 // to fetch 6136 return this.selectSyncPoint_(syncPoints, { key: 'time', value: currentTime }); 6137 } 6138 6139 /** 6140 * Calculate the amount of time that has expired off the playlist during playback 6141 * 6142 * @param {Playlist} playlist 6143 * Playlist object to calculate expired from 6144 * @param {Number} duration 6145 * Duration of the MediaSource (Infinity if playling a live source) 6146 * @returns {Number|null} 6147 * The amount of time that has expired off the playlist during playback. Null 6148 * if no sync-points for the playlist can be found. 6149 */ 6150 }, { 6151 key: 'getExpiredTime', 6152 value: function getExpiredTime(playlist, duration) { 6153 if (!playlist || !playlist.segments) { 6154 return null; 6155 } 6156 6157 var syncPoints = this.runStrategies_(playlist, duration, playlist.discontinuitySequence, 0); 6158
6159 // Without sync-points, there is not enough information to determine the expired time 6160 if (!syncPoints.length) { 6161 return null; 6162 } 6163 6164 var syncPoint = this.selectSyncPoint_(syncPoints, { 6165 key: 'segmentIndex', 6166 value: 0 6167 }); 6168 6169 // If the sync-point is beyond the start of the playlist, we want to subtract the 6170 // duration from index 0 to syncPoint.segmentIndex instead of adding. 6171 if (syncPoint.segmentIndex > 0) { 6172 syncPoint.time *= -1; 6173 } 6174 6175 return Math.abs(syncPoint.time + (0, _playlist.sumDurations)(playlist, syncPoint.segmentIndex, 0)); 6176 } 6177 6178 /** 6179 * Runs each sync-point strategy and returns a list of sync-points returned by the 6180 * strategies 6181 * 6182 * @private 6183 * @param {Playlist} playlist 6184 * The playlist that needs a sync-point 6185 * @param {Number} duration 6186 * Duration of the MediaSource (Infinity if playing a live source) 6187 * @param {Number} currentTimeline 6188 * The last timeline from which a segment was loaded 6189 * @returns {Array} 6190 * A list of sync-point objects 6191 */ 6192 }, { 6193 key: 'runStrategies_', 6194 value: function runStrategies_(playlist, duration, currentTimeline, currentTime) { 6195 var syncPoints = []; 6196 6197 // Try to find a sync-point in by utilizing various strategies... 6198 for (var i = 0; i < syncPointStrategies.length; i++) { 6199 var strategy = syncPointStrategies[i]; 6200 var syncPoint = strategy.run(this, playlist, duration, currentTimeline, currentTime); 6201 6202 if (syncPoint) { 6203 syncPoint.strategy = strategy.name; 6204 syncPoints.push({ 6205 strategy: strategy.name, 6206 syncPoint: syncPoint 6207 }); 6208 this.logger_('syncPoint found via <' + strategy.name + '>:', syncPoint); 6209 } 6210 } 6211 6212 return syncPoints; 6213 } 6214 6215 /** 6216 * Selects the sync-point nearest the specified target 6217 * 6218 * @private 6219 * @param {Array} syncPoints 6220 * List of sync-points to select from 6221 * @param {Object} target 6222 * Object specifying the property and value we are targeting 6223 * @param {String} target.key 6224 * Specifies the property to target. Must be either 'time' or 'segmentIndex' 6225 * @param {Number} target.value 6226 * The value to target for the specified key. 6227 * @returns {Object} 6228 * The sync-point nearest the target 6229 */ 6230 }, { 6231 key: 'selectSyncPoint_', 6232 value: function selectSyncPoint_(syncPoints, target) { 6233 var bestSyncPoint = syncPoints[0].syncPoint; 6234 var bestDistance = Math.abs(syncPoints[0].syncPoint[target.key] - target.value); 6235 var bestStrategy = syncPoints[0].strategy; 6236 6237 for (var i = 1; i < syncPoints.length; i++) { 6238 var newDistance = Math.abs(syncPoints[i].syncPoint[target.key] - target.value); 6239 6240 if (newDistance < bestDistance) { 6241 bestDistance = newDistance; 6242 bestSyncPoint = syncPoints[i].syncPoint; 6243 bestStrategy = syncPoints[i].strategy; 6244 } 6245 } 6246 6247 this.logger_('syncPoint with strategy <' + bestStrategy + '> chosen: ', bestSyncPoint); 6248 return bestSyncPoint; 6249 } 6250 6251 /** 6252 * Save any meta-data present on the segments when segments leave 6253 * the live window to the playlist to allow for synchronization at the 6254 * playlist level later. 6255 * 6256 * @param {Playlist} oldPlaylist - The previous active playlist 6257 * @param {Playlist} newPlaylist - The updated and most current playlist 6258 */ 6259 }, { 6260 key: 'saveExpiredSegmentInfo', 6261 value: function saveExpiredSegmentInfo(oldPlaylist, newPlaylist) { 6262 var mediaSequenceDiff = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; 6263 6264 // When a segment expires from the playlist and it has a start time 6265 // save that information as a possible sync-point reference in future 6266 for (var i = mediaSequenceDiff - 1; i >= 0; i--) { 6267 var lastRemovedSegment = oldPlaylist.segments[i]; 6268 6269 if (lastRemovedSegment && typeof lastRemovedSegment.start !== 'undefined') { 6270 newPlaylist.syncInfo = { 6271 mediaSequence: oldPlaylist.mediaSequence + i, 6272 time: lastRemovedSegment.start 6273 }; 6274 this.logger_('playlist sync:', newPlaylist.syncInfo); 6275 this.trigger('syncinfoupdate'); 6276 break; 6277 } 6278 } 6279 } 6280 6281 /** 6282 * Save the mapping from playlist's ProgramDateTime to display. This should 6283 * only ever happen once at the start of playback. 6284 * 6285 * @param {Playlist} playlist - The currently active playlist 6286 */ 6287 }, { 6288 key: 'setDateTimeMapping', 6289 value: function setDateTimeMapping(playlist) { 6290 if (!this.datetimeToDisplayTime && playlist.dateTimeObject) { 6291 var playlistTimestamp = playlist.dateTimeObject.getTime() / 1000; 6292 6293 this.datetimeToDisplayTime = -playlistTimestamp; 6294 } 6295 } 6296 6297 /** 6298 * Reset the state of the inspection cache when we do a rendition 6299 * switch 6300 */ 6301 }, { 6302 key: 'reset', 6303 value: function reset() { 6304 this.inspectCache_ = undefined; 6305 } 6306 6307 /** 6308 * Probe or inspect a fmp4 or an mpeg2-ts segment to determine the start 6309 * and end of the segment in it's internal "media time". Used to generate 6310 * mappings from that internal "media time" to the display time that is 6311 * shown on the player. 6312 * 6313 * @param {SegmentInfo} segmentInfo - The current active request information 6314 */ 6315 }, { 6316 key: 'probeSegmentInfo', 6317 value: function probeSegmentInfo(segmentInfo) { 6318 var segment = segmentInfo.segment; 6319 var timingInfo = undefined; 6320 6321 if (segment.map) { 6322 timingInfo = this.probeMp4Segment_(segmentInfo); 6323 } else { 6324 timingInfo = this.probeTsSegment_(segmentInfo); 6325 } 6326 6327 if (timingInfo) { 6328 if (this.calculateSegmentTimeMapping_(segmentInfo, timingInfo)) { 6329 this.saveDiscontinuitySyncInfo_(segmentInfo); 6330 } 6331 } 6332 } 6333 6334 /** 6335 * Probe an fmp4 or an mpeg2-ts segment to determine the start of the segment 6336 * in it's internal "media time". 6337 * 6338 * @private 6339 * @param {SegmentInfo} segmentInfo - The current active request information 6340 * @return {object} The start and end time of the current segment in "media time" 6341 */ 6342 }, { 6343 key: 'probeMp4Segment_', 6344 value: function probeMp4Segment_(segmentInfo) { 6345 var segment = segmentInfo.segment; 6346 var timescales = _muxJsLibMp4Probe2['default'].timescale(segment.map.bytes); 6347 var startTime = _muxJsLibMp4Probe2['default'].startTime(timescales, segmentInfo.bytes); 6348 6349 if (segmentInfo.timestampOffset !== null) { 6350 segmentInfo.timestampOffset -= startTime; 6351 } 6352 6353 return { 6354 start: startTime, 6355 end: startTime + segment.duration 6356 }; 6357 } 6358 6359 /** 6360 * Probe an mpeg2-ts segment to determine the start and end of the segment 6361 * in it's internal "media time". 6362 * 6363 * @private 6364 * @param {SegmentInfo} segmentInfo - The current active request information 6365 * @return {object} The start and end time of the current segment in "media time" 6366 */ 6367 }, { 6368 key: 'probeTsSegment_', 6369 value: function probeTsSegment_(segmentInfo) { 6370 var timeInfo = (0, _muxJsLibToolsTsInspectorJs.inspect)(segmentInfo.bytes, this.inspectCache_); 6371 var segmentStartTime = undefined; 6372 var segmentEndTime = undefined; 6373 6374 if (!timeInfo) { 6375 return null; 6376 } 6377 6378 if (timeInfo.video && timeInfo.video.length === 2) { 6379 this.inspectCache_ = timeInfo.video[1].dts; 6380 segmentStartTime = timeInfo.video[0].dtsTime; 6381 segmentEndTime = timeInfo.video[1].dtsTime; 6382 } else if (timeInfo.audio && timeInfo.audio.length === 2) { 6383 this.inspectCache_ = timeInfo.audio[1].dts; 6384 segmentStartTime = timeInfo.audio[0].dtsTime; 6385 segmentEndTime = timeInfo.audio[1].dtsTime; 6386 } 6387 6388 return { 6389 start: segmentStartTime, 6390 end: segmentEndTime 6391 }; 6392 } 6393 }, { 6394 key: 'timestampOffsetForTimeline', 6395 value: function timestampOffsetForTimeline(timeline) { 6396 if (typeof this.timelines[timeline] === 'undefined') { 6397 return null; 6398 } 6399 return this.timelines[timeline].time; 6400 } 6401 6402 /** 6403 * Use the "media time" for a segment to generate a mapping to "display time" and 6404 * save that display time to the segment. 6405 * 6406 * @private 6407 * @param {SegmentInfo} segmentInfo 6408 * The current active request information 6409 * @param {object} timingInfo 6410 * The start and end time of the current segment in "media time" 6411 * @returns {Boolean} 6412 * Returns false if segment time mapping could not be calculated 6413 */ 6414 }, { 6415 key: 'calculateSegmentTimeMapping_', 6416 value: function calculateSegmentTimeMapping_(segmentInfo, timingInfo) { 6417 var segment = segmentInfo.segment; 6418 var mappingObj = this.timelines[segmentInfo.timeline]; 6419 6420 if (segmentInfo.timestampOffset !== null) { 6421 this.logger_('tsO:', segmentInfo.timestampOffset); 6422 6423 mappingObj = { 6424 time: segmentInfo.startOfSegment, 6425 mapping: segmentInfo.startOfSegment - timingInfo.start 6426 }; 6427 this.timelines[segmentInfo.timeline] = mappingObj; 6428 this.trigger('timestampoffset'); 6429 6430 segment.start = segmentInfo.startOfSegment; 6431 segment.end = timingInfo.end + mappingObj.mapping; 6432 } else if (mappingObj) { 6433 segment.start = timingInfo.start + mappingObj.mapping; 6434 segment.end = timingInfo.end + mappingObj.mapping; 6435 } else { 6436 return false;
6437 } 6438 6439 return true; 6440 } 6441 6442 /** 6443 * Each time we have discontinuity in the playlist, attempt to calculate the location 6444 * in display of the start of the discontinuity and save that. We also save an accuracy 6445 * value so that we save values with the most accuracy (closest to 0.) 6446 * 6447 * @private 6448 * @param {SegmentInfo} segmentInfo - The current active request information 6449 */ 6450 }, { 6451 key: 'saveDiscontinuitySyncInfo_', 6452 value: function saveDiscontinuitySyncInfo_(segmentInfo) { 6453 var playlist = segmentInfo.playlist; 6454 var segment = segmentInfo.segment; 6455 6456 // If the current segment is a discontinuity then we know exactly where 6457 // the start of the range and it's accuracy is 0 (greater accuracy values 6458 // mean more approximation) 6459 if (segment.discontinuity) { 6460 this.discontinuities[segment.timeline] = { 6461 time: segment.start, 6462 accuracy: 0 6463 }; 6464 } else if (playlist.discontinuityStarts.length) { 6465 // Search for future discontinuities that we can provide better timing 6466 // information for and save that information for sync purposes 6467 for (var i = 0; i < playlist.discontinuityStarts.length; i++) { 6468 var segmentIndex = playlist.discontinuityStarts[i]; 6469 var discontinuity = playlist.discontinuitySequence + i + 1; 6470 var mediaIndexDiff = segmentIndex - segmentInfo.mediaIndex; 6471 var accuracy = Math.abs(mediaIndexDiff); 6472 6473 if (!this.discontinuities[discontinuity] || this.discontinuities[discontinuity].accuracy > accuracy) { 6474 var time = undefined; 6475 6476 if (mediaIndexDiff < 0) { 6477 time = segment.start - (0, _playlist.sumDurations)(playlist, segmentInfo.mediaIndex, segmentIndex); 6478 } else { 6479 time = segment.end + (0, _playlist.sumDurations)(playlist, segmentInfo.mediaIndex + 1, segmentIndex); 6480 } 6481 6482 this.discontinuities[discontinuity] = { 6483 time: time, 6484 accuracy: accuracy 6485 }; 6486 } 6487 } 6488 } 6489 } 6490 6491 /** 6492 * A debugging logger noop that is set to console.log only if debugging 6493 * is enabled globally 6494 * 6495 * @private 6496 */ 6497 }, { 6498 key: 'logger_', 6499 value: function logger_() {} 6500 }]); 6501 6502 return SyncController; 6503 })(_videoJs2['default'].EventTarget); 6504 6505 exports['default'] = SyncController; 6506 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 6507 },{"./playlist":9,"mux.js/lib/mp4/probe":55,"mux.js/lib/tools/ts-inspector.js":57}],18:[function(require,module,exports){ 6508 (function (global){ 6509 /** 6510 * @file vtt-segment-loader.js 6511 */ 6512 'use strict'; 6513 6514 Object.defineProperty(exports, '__esModule', { 6515 value: true 6516 }); 6517 6518 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 6519 6520 var _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); } } }; 6521 6522 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 6523 6524 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 6525 6526 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
6526onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 6527 6528 var _segmentLoader = require('./segment-loader'); 6529 6530 var _segmentLoader2 = _interopRequireDefault(_segmentLoader); 6531 6532 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 6533 6534 var _videoJs2 = _interopRequireDefault(_videoJs); 6535 6536 var _globalWindow = require('global/window'); 6537 6538 var _globalWindow2 = _interopRequireDefault(_globalWindow); 6539 6540 var _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs = require('videojs-contrib-media-sources/es5/remove-cues-from-track.js'); 6541 6542 var _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs2 = _interopRequireDefault(_videojsContribMediaSourcesEs5RemoveCuesFromTrackJs); 6543 6544 var _binUtils = require('./bin-utils'); 6545 6546 var VTT_LINE_TERMINATORS = new Uint8Array('\n\n'.split('').map(function (char) { 6547 return char.charCodeAt(0); 6548 })); 6549 6550 var uintToString = function uintToString(uintArray) { 6551 return String.fromCharCode.apply(null, uintArray); 6552 }; 6553 6554 /** 6555 * An object that manages segment loading and appending. 6556 * 6557 * @class VTTSegmentLoader 6558 * @param {Object} options required and optional options 6559 * @extends videojs.EventTarget 6560 */ 6561 6562 var VTTSegmentLoader = (function (_SegmentLoader) { 6563 _inherits(VTTSegmentLoader, _SegmentLoader); 6564 6565 function VTTSegmentLoader(options) { 6566 _classCallCheck(this, VTTSegmentLoader); 6567 6568 _get(Object.getPrototypeOf(VTTSegmentLoader.prototype), 'constructor', this).call(this, options); 6569 6570 // SegmentLoader requires a MediaSource be specified or it will throw an error; 6571 // however, VTTSegmentLoader has no need of a media source, so delete the reference 6572 this.mediaSource_ = null; 6573 6574 this.subtitlesTrack_ = null; 6575 } 6576 6577 /** 6578 * Indicates which time ranges are buffered 6579 * 6580 * @return {TimeRange} 6581 * TimeRange object representing the current buffered ranges 6582 */ 6583 6584 _createClass(VTTSegmentLoader, [{ 6585 key: 'buffered_', 6586 value: function buffered_() { 6587 if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues.length) { 6588 return _videoJs2['default'].createTimeRanges(); 6589 } 6590 6591 var cues = this.subtitlesTrack_.cues; 6592 var start = cues[0].startTime; 6593 var end = cues[cues.length - 1].startTime; 6594 6595 return _videoJs2['default'].createTimeRanges([[start, end]]); 6596 } 6597 6598 /** 6599 * Gets and sets init segment for the provided map 6600 * 6601 * @param {Object} map 6602 * The map object representing the init segment to get or set 6603 * @param {Boolean=} set 6604 * If true, the init segment for the provided map should be saved 6605 * @return {Object} 6606 * map object for desired init segment 6607 */ 6608 }, { 6609 key: 'initSegment', 6610 value: function initSegment(map) { 6611 var set = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; 6612 6613 if (!map) { 6614 return null; 6615 } 6616 6617 var id = (0, _binUtils.initSegmentId)(map); 6618 var storedMap = this.initSegments_[id]; 6619 6620 if (set && !storedMap && map.bytes) { 6621 // append WebVTT line terminators to the media initialization segment if it exists 6622 // to follow the WebVTT spec (https://w3c.github.io/webvtt/#file-structure) that 6623 // requires two or more WebVTT line terminators between the WebVTT header and the rest 6624 // of the file 6625 var combinedByteLength = VTT_LINE_TERMINATORS.byteLength + map.bytes.byteLength; 6626 var combinedSegment = new Uint8Array(combinedByteLength); 6627 6628 combinedSegment.set(map.bytes); 6629 combinedSegment.set(VTT_LINE_TERMINATORS, map.b
vendor: 5,269 bytes, lines 6629-6745
6629ytes.byteLength); 6630 6631 this.initSegments_[id] = storedMap = { 6632 resolvedUri: map.resolvedUri, 6633 byterange: map.byterange, 6634 bytes: combinedSegment 6635 }; 6636 } 6637 6638 return storedMap || map; 6639 } 6640 6641 /** 6642 * Returns true if all configuration required for loading is present, otherwise false. 6643 * 6644 * @return {Boolean} True if the all configuration is ready for loading 6645 * @private 6646 */ 6647 }, { 6648 key: 'couldBeginLoading_', 6649 value: function couldBeginLoading_() { 6650 return this.playlist_ && this.subtitlesTrack_ && !this.paused(); 6651 } 6652 6653 /** 6654 * Once all the starting parameters have been specified, begin 6655 * operation. This method should only be invoked from the INIT 6656 * state. 6657 * 6658 * @private 6659 */ 6660 }, { 6661 key: 'init_', 6662 value: function init_() { 6663 this.state = 'READY'; 6664 this.resetEverything(); 6665 return this.monitorBuffer_(); 6666 } 6667 6668 /** 6669 * Set a subtitle track on the segment loader to add subtitles to 6670 * 6671 * @param {TextTrack} track 6672 * The text track to add loaded subtitles to 6673 */ 6674 }, { 6675 key: 'track', 6676 value: function track(_track) { 6677 this.subtitlesTrack_ = _track; 6678 6679 // if we were unpaused but waiting for a sourceUpdater, start 6680 // buffering now 6681 if (this.state === 'INIT' && this.couldBeginLoading_()) { 6682 this.init_(); 6683 } 6684 } 6685 6686 /** 6687 * Remove any data in the source buffer between start and end times 6688 * @param {Number} start - the start time of the region to remove from the buffer 6689 * @param {Number} end - the end time of the region to remove from the buffer 6690 */ 6691 }, { 6692 key: 'remove', 6693 value: function remove(start, end) { 6694 (0, _videojsContribMediaSourcesEs5RemoveCuesFromTrackJs2['default'])(start, end, this.subtitlesTrack_); 6695 } 6696 6697 /** 6698 * fill the buffer with segements unless the sourceBuffers are 6699 * currently updating 6700 * 6701 * Note: this function should only ever be called by monitorBuffer_ 6702 * and never directly 6703 * 6704 * @private 6705 */ 6706 }, { 6707 key: 'fillBuffer_', 6708 value: function fillBuffer_() { 6709 var _this = this; 6710 6711 if (!this.syncPoint_) { 6712 this.syncPoint_ = this.syncController_.getSyncPoint(this.playlist_, this.duration_(), this.currentTimeline_, this.currentTime_()); 6713 } 6714 6715 // see if we need to begin loading immediately 6716 var segmentInfo = this.checkBuffer_(this.buffered_(), this.playlist_, this.mediaIndex, this.hasPlayed_(), this.currentTime_(), this.syncPoint_); 6717 6718 segmentInfo = this.skipEmptySegments_(segmentInfo); 6719 6720 if (!segmentInfo) { 6721 return; 6722 } 6723 6724 if (this.syncController_.timestampOffsetForTimeline(segmentInfo.timeline) === null) { 6725 // We don't have the timestamp offset that we need to sync subtitles. 6726 // Rerun on a timestamp offset or user interaction. 6727 var checkTimestampOffset = function checkTimestampOffset() { 6728 _this.state = 'READY'; 6729 if (!_this.paused()) { 6730 // if not paused, queue a buffer check as soon as possible 6731 _this.monitorBuffer_(); 6732 } 6733 }; 6734 6735 this.syncController_.one('timestampoffset', checkTimestampOffset); 6736 this.state = 'WAITING_ON_TIMELINE'; 6737 return; 6738 } 6739 6740 this.loadSegment_(segmentInfo); 6741 } 6742 6743 /** 6744 * Prevents the segment loader from requesting segments we know contain no subtitles 6745 * by walking forward until we find the next segment that we don't know
6745whether it is 6746 * empty or not. 6747 * 6748 * @param {Object} segmentInfo 6749 * a segment info object that describes the current segment 6750 * @return {Object} 6751 * a segment info object that describes the current segment 6752 */ 6753 }, { 6754 key: 'skipEmptySegments_', 6755 value: function skipEmptySegments_(segmentInfo) { 6756 while (segmentInfo && segmentInfo.segment.empty) { 6757 segmentInfo = this.generateSegmentInfo_(segmentInfo.playlist, segmentInfo.mediaIndex + 1, segmentInfo.startOfSegment + segmentInfo.duration, segmentInfo.isSyncRequest); 6758 } 6759 return segmentInfo; 6760 } 6761 6762 /** 6763 * append a decrypted segement to the SourceBuffer through a SourceUpdater 6764 * 6765 * @private 6766 */ 6767 }, { 6768 key: 'handleSegment_', 6769 value: function handleSegment_() { 6770 var _this2 = this; 6771 6772 if (!this.pendingSegment_) { 6773 this.state = 'READY'; 6774 return; 6775 } 6776 6777 this.state = 'APPENDING'; 6778 6779 var segmentInfo = this.pendingSegment_; 6780 var segment = segmentInfo.segment; 6781 6782 // Make sure that vttjs has loaded, otherwise, wait till it finished loading 6783 if (typeof _globalWindow2['default'].WebVTT !== 'function' && this.subtitlesTrack_ && this.subtitlesTrack_.tech_) { 6784 var _ret = (function () { 6785 6786 var loadHandler = function loadHandler() { 6787 _this2.handleSegment_(); 6788 }; 6789 6790 _this2.state = 'WAITING_ON_VTTJS'; 6791 _this2.subtitlesTrack_.tech_.one('vttjsloaded', loadHandler); 6792 _this2.subtitlesTrack_.tech_.one('vttjserror', function () { 6793 _this2.subtitlesTrack_.tech_.off('vttjsloaded', loadHandler); 6794 _this2.error({ 6795 message: 'Error loading vtt.js' 6796 }); 6797 _this2.state = 'READY'; 6798 _this2.pause(); 6799 _this2.trigger('error'); 6800 }); 6801 6802 return { 6803 v: undefined 6804 }; 6805 })(); 6806 6807 if (typeof _ret === 'object') return _ret.v; 6808 } 6809 6810 segment.requested = true; 6811 6812 try { 6813 this.parseVTTCues_(segmentInfo); 6814 } catch (e) { 6815 this.error({ 6816 message: e.message 6817 }); 6818 this.state = 'READY'; 6819 this.pause(); 6820 return this.trigger('error'); 6821 } 6822 6823 this.updateTimeMapping_(segmentInfo, this.syncController_.timelines[segmentInfo.timeline], this.playlist_); 6824 6825 if (segmentInfo.isSyncRequest) { 6826 this.trigger('syncinfoupdate'); 6827 this.pendingSegment_ = null; 6828 this.state = 'READY'; 6829 return; 6830 } 6831 6832 segmentInfo.byteLength = segmentInfo.bytes.byteLength; 6833 6834 this.mediaSecondsLoaded += segment.duration; 6835
6836 segmentInfo.cues.forEach(function (cue) { 6837 _this2.subtitlesTrack_.addCue(cue); 6838 }); 6839 6840 this.handleUpdateEnd_(); 6841 } 6842 6843 /** 6844 * Uses the WebVTT parser to parse the segment response 6845 * 6846 * @param {Object} segmentInfo 6847 * a segment info object that describes the current segment 6848 * @private 6849 */ 6850 }, { 6851 key: 'parseVTTCues_', 6852 value: function parseVTTCues_(segmentInfo) { 6853 var decoder = undefined; 6854 var decodeBytesToString = false; 6855 6856 if (typeof _globalWindow2['default'].TextDecoder === 'function') { 6857 decoder = new _globalWindow2['default'].TextDecoder('utf8'); 6858 } else { 6859 decoder = _globalWindow2['default'].WebVTT.StringDecoder(); 6860 decodeBytesToString = true; 6861 } 6862 6863 var parser = new _globalWindow2['default'].WebVTT.Parser(_globalWindow2['default'], _globalWindow2['default'].vttjs, decoder); 6864 6865 segmentInfo.cues = []; 6866 segmentInfo.timestampmap = { MPEGTS: 0, LOCAL: 0 }; 6867 6868 parser.oncue = segmentInfo.cues.push.bind(segmentInfo.cues); 6869 parser.ontimestampmap = function (map) { 6870 return segmentInfo.timestampmap = map; 6871 }; 6872 parser.onparsingerror = function (error) { 6873 _videoJs2['default'].log.warn('Error encountered when parsing cues: ' + error.message); 6874 }; 6875 6876 if (segmentInfo.segment.map) { 6877 var mapData = segmentInfo.segment.map.bytes; 6878 6879 if (decodeBytesToString) { 6880 mapData = uintToString(mapData); 6881 } 6882 6883 parser.parse(mapData); 6884 } 6885 6886 var segmentData = segmentInfo.bytes; 6887 6888 if (decodeBytesToString) { 6889 segmentData = uintToString(segmentData); 6890 } 6891 6892 parser.parse(segmentData); 6893 parser.flush(); 6894 } 6895 6896 /** 6897 * Updates the start and end times of any cues parsed by the WebVTT parser using 6898 * the information parsed from the X-TIMESTAMP-MAP header and a TS to media time mapping 6899 * from the SyncController 6900 * 6901 * @param {Object} segmentInfo 6902 * a segment info object that describes the current segment 6903 * @param {Object} mappingObj 6904 * object containing a mapping from TS to media time 6905 * @param {Object} playlist 6906 * the playlist object containing the segment 6907 * @private 6908 */ 6909 }, { 6910 key: 'updateTimeMapping_', 6911 value: function updateTimeMapping_(segmentInfo, mappingObj, playlist) { 6912 var segment = segmentInfo.segment; 6913 6914 if (!mappingObj) { 6915 // If the sync controller does not have a mapping of TS to Media Time for the 6916 // timeline, then we don't have enough information to update the cue 6917 // start/end times 6918 return; 6919 } 6920 6921 if (!segmentInfo.cues.length) { 6922 // If there are no cues, we also do not have enough information to figure out 6923 // segment timing. Mark that the segment contains
6923no cues so we don't re-request 6924 // an empty segment. 6925 segment.empty = true; 6926 return; 6927 } 6928 6929 var timestampmap = segmentInfo.timestampmap; 6930 var diff = timestampmap.MPEGTS / 90000 - timestampmap.LOCAL + mappingObj.mapping; 6931 6932 segmentInfo.cues.forEach(function (cue) { 6933 // First convert cue time to TS time using the timestamp-map provided within the vtt 6934 cue.startTime += diff; 6935 cue.endTime += diff; 6936 }); 6937 6938 if (!playlist.syncInfo) { 6939 var firstStart = segmentInfo.cues[0].startTime; 6940 var lastStart = segmentInfo.cues[segmentInfo.cues.length - 1].startTime; 6941 6942 playlist.syncInfo = { 6943 mediaSequence: playlist.mediaSequence + segmentInfo.mediaIndex, 6944 time: Math.min(firstStart, lastStart - segment.duration) 6945 }; 6946 } 6947 } 6948 }]); 6949 6950 return VTTSegmentLoader; 6951 })(_segmentLoader2['default']); 6952 6953 exports['default'] = VTTSegmentLoader; 6954 module.exports = exports['default']; 6955 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 6956 },{"./bin-utils":2,"./segment-loader":14,"global/window":30,"videojs-contrib-media-sources/es5/remove-cues-from-track.js":71}],19:[function(require,module,exports){ 6957 (function (global){ 6958 /** 6959 * @file xhr.js 6960 */ 6961 6962 /** 6963 * A wrapper for videojs.xhr that tracks bandwidth. 6964 * 6965 * @param {Object} options options for the XHR 6966 * @param {Function} callback the callback to call when done 6967 * @return {Request} the xhr request that is going to be made 6968 */ 6969 'use strict'; 6970 6971 Object.defineProperty(exports, '__esModule', { 6972 value: true 6973 }); 6974 6975 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 6976 6977 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 6978 6979 var _videoJs2 = _interopRequireDefault(_videoJs); 6980 6981 var xhrFactory = function xhrFactory() { 6982 var xhr = function XhrFunction(options, callback) { 6983 // Add a default timeout for all hls requests 6984 options = (0, _videoJs.mergeOptions)({ 6985 timeout: 45e3 6986 }, options); 6987 6988 // Allow an optional user-specified function to modify the option 6989 // object before we construct the xhr request 6990 var beforeRequest = XhrFunction.beforeRequest || _videoJs2['default'].Hls.xhr.beforeRequest; 6991 6992 if (beforeRequest && typeof beforeRequest === 'function') { 6993 var newOptions = beforeRequest(options); 6994 6995 if (newOptions) { 6996 options = newOptions; 6997 } 6998 } 6999 7000 var request = (0, _videoJs.xhr)(options, function (error, response) { 7001 var reqResponse = request.response; 7002 7003 if (!error && reqResponse) { 7004 request.responseTime = Date.now(); 7005 request.roundTripTime = request.responseTime - request.requestTime; 7006 request.bytesReceived = reqResponse.byteLength || reqResponse.length; 7007 if (!request.bandwidth) { 7008 request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000); 7009 } 7010 } 7011 7012 // videojs.xhr now uses a specific code on the error 7013 // object to signal that a request has t
7013imed out instead 7014 // of setting a boolean on the request object 7015 if (error && error.code === 'ETIMEDOUT') { 7016 request.timedout = true; 7017 } 7018 7019 // videojs.xhr no longer considers status codes outside of 200 and 0 7020 // (for file uris) to be errors, but the old XHR did, so emulate that 7021 // behavior. Status 206 may be used in response to byterange requests. 7022 if (!error && !request.aborted && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) { 7023 error = new Error('XHR Failed with a response of: ' + (request && (reqResponse || request.responseText))); 7024 } 7025 7026 callback(error, request); 7027 }); 7028 var originalAbort = request.abort; 7029 7030 request.abort = function () { 7031 request.aborted = true; 7032 return originalAbort.apply(request, arguments); 7033 }; 7034 request.uri = options.uri; 7035 request.requestTime = Date.now(); 7036 return request; 7037 }; 7038 7039 return xhr; 7040 }; 7041 7042 exports['default'] = xhrFactory; 7043 module.exports = exports['default']; 7044 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 7045 },{}],20:[function(require,module,exports){ 7046 /** 7047 * @file aes.js 7048 * 7049 * This file contains an adaptation of the AES decryption algorithm 7050 * from the Standford Javascript Cryptography Library. That work is 7051 * covered by the following copyright and permissions notice: 7052 * 7053 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh. 7054 * All rights reserved. 7055 * 7056 * Redistribution and use in source and binary forms, with or without 7057 * modification, are permitted provided that the following conditions are 7058 * met: 7059 * 7060 * 1. Redistributions of source code must retain the above copyright 7061 * notice, this list of conditions and the following disclaimer. 7062 * 7063 * 2. Redistributions in binary form must reproduce the above 7064 * copyright notice, this list of conditions and the following 7065 * disclaimer in the documentation and/or other materials provided 7066 * with the distribution. 7067 * 7068 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 7069 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 7070 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 7071 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE 7072 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 7073 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 7074 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 7075 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 7076 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 7077 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 7078 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 7079 * 7080 * The views and conclusions contained in the software and documentation 7081 * are those of the authors and should not be interpreted as representing 7082 * official policies, either expressed or implied, of the authors. 7083 */ 7084 7085 /** 7086 * Expand the S-box tables. 7087 * 7088 * @private 7089 */ 7090 'use strict'; 7091 7092 Object.defineProperty(exports, '__esModule', { 7093 value: true 7094 }); 7095 7096 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 7097 7098 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 7099 7100 var precompute = function precompute() { 7101 var tables = [[[], [], [], [], []], [[], [], [], [], []]]; 7102 var encTable = tables[0]; 7103 var decTable = tables[1]; 7104 var sbox = encTable[4]; 7105 var sboxInv = decTable[4]; 7106 var i = undefined; 7107 var x = undefined; 7108 var xInv = undefined; 7109 var d = []; 7110 var th = []; 7111 var x2 = undefined; 7112 var x4 = undefined; 7113 var x8 = undefined; 7114 var s = undefined; 7115 var tEnc = undefined; 7116 var tDec = undefined; 7117 7118 // Compute double and third tables 7119 for (i = 0; i < 256; i++) { 7120 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i; 7121 } 7122 7123 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 7124 // Compute sbox 7125 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4; 7126 s = s >> 8 ^ s & 255 ^ 99; 7127 sbox[x] = s; 7128 sboxInv[s] = x; 7129 7130 // Compute MixColumns 7131 x8 = d[x4 = d[x2 = d[x]]];
7132 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; 7133 tEnc = d[s] * 0x101 ^ s * 0x1010100; 7134 7135 for (i = 0; i < 4; i++) { 7136 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8; 7137 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8; 7138 } 7139 } 7140 7141 // Compactify. Considerable speedup on Firefox. 7142 for (i = 0; i < 5; i++) { 7143 encTable[i] = encTable[i].slice(0); 7144 decTable[i] = decTable[i].slice(0); 7145 } 7146 return tables; 7147 }; 7148 var aesTables = null; 7149 7150 /** 7151 * Schedule out an AES key for both encryption and decryption. This 7152 * is a low-level class. Use a cipher mode to do bulk encryption. 7153 * 7154 * @class AES 7155 * @param key {Array} The key as an array of 4, 6 or 8 words. 7156 */ 7157 7158 var AES = (function () { 7159 function AES(key) { 7160 _classCallCheck(this, AES); 7161 7162 /** 7163 * The expanded S-box and inverse S-box tables. These will be computed 7164 * on the client so that we don't have to send them down the wire. 7165 * 7166 * There are two tables, _tables[0] is for encryption and 7167 * _tables[1] is for decryption. 7168 * 7169 * The first 4 sub-tables are the expanded S-box with MixColumns. The 7170 * last (_tables[01][4]) is the S-box itself. 7171 * 7172 * @private 7173 */ 7174 // if we have yet to precompute the S-box tables 7175 // do so now 7176 if (!aesTables) { 7177 aesTables = precompute(); 7178 } 7179 // then make a copy of that object for use 7180 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()]]; 7181 var i = undefined; 7182 var j = undefined; 7183 var tmp = undefined; 7184 var encKey = undefined; 7185 var decKey = undefined; 7186 var sbox = this._tables[0][4]; 7187 var decTable = this._tables[1]; 7188 var keyLen = key.length; 7189 var rcon = 1; 7190 7191 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 7192 throw new Error('Invalid aes key size'); 7193 } 7194 7195 encKey = key.slice(0); 7196 decKey = []; 7197 this._key = [encKey, decKey]; 7198 7199 // schedule encryption keys 7200 for (i = keyLen; i < 4 * keyLen + 28; i++) { 7201 tmp = encKey[i - 1]; 7202 7203 // apply sbox 7204 if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) { 7205 tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255]; 7206 7207 // shift rows and add rcon 7208 if (i % keyLen === 0) { 7209 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24; 7210 rcon = rcon << 1 ^ (rcon >> 7) * 283; 7211 } 7212 } 7213 7214 encKey[i] = encKey[i - keyLen] ^ tmp; 7215 } 7216 7217 // schedule decryption keys 7218 for (j = 0; i; j++, i--) { 7219 tmp = encKey[j & 3 ? i : i - 4]; 7220 if (i <= 4 || j < 4) { 7221 decKey[j] = tmp; 7222 } else { 7223 decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]]; 7224 } 7225 } 7226 } 7227 7228 /** 7229 * Decrypt 16 bytes, specified as four 32-bit words. 7230 * 7231 * @param {Number} encrypted0 the first word to decrypt 7232 * @param {Number} encrypted1 the second word to decrypt 7233 * @param {Number} encrypted2 the third word to decrypt 7234 * @param {Number} encrypted3 the fourth word to decrypt 7235 * @param {Int32Array} out the array to write the decrypted words 7236 * into 7237 * @param {Number} offset the offset into the output array to start 7238 * writing results 7239 * @return {Array} The plaintext. 7240 */ 7241 7242 _createClass(AES, [{ 7243 key: 'decrypt', 7244 value: function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) { 7245 var key = this._key[1]; 7246 // state variables a,b,c,d are loaded with pre-whitened data 7247 var a = encrypted0 ^ key[0]; 7248 var b = encrypted3 ^ key[1]; 7249 var c = encrypted2 ^ key[2]; 7250 var d = encrypted1 ^ key[3]; 7251 var a2 = undefined; 7252 var b2 = undefined; 7253 var c2 = undefined; 7254 7255 // key.length === 2 ? 7256 var nInnerRounds = key.length / 4 - 2; 7257 var i = undefined; 7258 var kIndex = 4; 7259 var table = this._tables[1]; 7260 7261 // load up the tables 7262 var table0 = table[0]; 7263 var table1 = table[1]; 7264 var table2 = table[2]; 7265 var table3 = table[3]; 7266 var sbox = table[4]; 7267 7268 // Inner rounds. Cribbed from OpenSSL. 7269 for (i = 0; i < nInnerRounds; i++) { 7270 a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex]; 7271 b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1]; 7272 c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2]; 7273 d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3]; 7274 kIndex += 4; 7275 a = a2;b = b2;c = c2; 7276 } 7277 7278 // Last round. 7279 for (i = 0; i < 4; i++) { 7280 out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++]; 7281 a2 = a;a = b;b = c;c = d;d = a2; 7282 } 7283 } 7284 }]); 7285 7286 return AES; 7287 })(); 7288 7289 exports['default'] = AES; 7290 module.exports = exports['default']; 7291 },{}],21:[function(require,module,exports){ 7292 /** 7293 * @file async-stream.js 7294 */ 7295 'use strict'; 7296 7297 Object.defineProperty(exports, '__esModule', { 7298 value: true 7299 }); 7300 7301 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 7302 7303 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 7304 7305 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 7306 7307 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 7308 7309 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
7309onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 7310 7311 var _stream = require('./stream'); 7312 7313 var _stream2 = _interopRequireDefault(_stream); 7314 7315 /** 7316 * A wrapper around the Stream class to use setTiemout 7317 * and run stream "jobs" Asynchronously 7318 * 7319 * @class AsyncStream 7320 * @extends Stream 7321 */ 7322 7323 var AsyncStream = (function (_Stream) { 7324 _inherits(AsyncStream, _Stream); 7325 7326 function AsyncStream() { 7327 _classCallCheck(this, AsyncStream); 7328 7329 _get(Object.getPrototypeOf(AsyncStream.prototype), 'constructor', this).call(this, _stream2['default']); 7330 this.jobs = []; 7331 this.delay = 1; 7332 this.timeout_ = null; 7333 } 7334 7335 /** 7336 * process an async job 7337 * 7338 * @private 7339 */ 7340 7341 _createClass(AsyncStream, [{ 7342 key: 'processJob_', 7343 value: function processJob_() { 7344 this.jobs.shift()(); 7345 if (this.jobs.length) { 7346 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 7347 } else { 7348 this.timeout_ = null; 7349 } 7350 } 7351 7352 /** 7353 * push a job into the stream 7354 * 7355 * @param {Function} job the job to push into the stream 7356 */ 7357 }, { 7358 key: 'push', 7359 value: function push(job) { 7360 this.jobs.push(job); 7361 if (!this.timeout_) { 7362 this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay); 7363 } 7364 } 7365 }]); 7366 7367 return AsyncStream; 7368 })(_stream2['default']); 7369 7370 exports['default'] = AsyncStream; 7371 module.exports = exports['default']; 7372 },{"./stream":24}],22:[function(require,module,exports){ 7373 /** 7374 * @file decrypter.js 7375 * 7376 * An asynchronous implementation of AES-128 CBC decryption with 7377 * PKCS#7 padding. 7378 */ 7379 7380 'use strict'; 7381 7382 Object.defineProperty(exports, '__esModule', { 7383 value: true 7384 }); 7385 7386 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 7387 7388 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 7389 7390 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 7391 7392 var _aes = require('./aes'); 7393 7394 var _aes2 = _interopRequireDefault(_aes); 7395 7396 var _asyncStream = require('./async-stream'); 7397 7398 var _asyncStream2 = _interopRequireDefault(_asyncStream); 7399 7400 var _pkcs7 = require('pkcs7'); 7401 7402 /** 7403 * Convert network-order (big-endian) bytes into their little-endian 7404 * representation. 7405 */ 7406 var ntoh = function ntoh(word) { 7407 return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; 7408 }; 7409 7410 /** 7411 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding. 7412 * 7413 * @param {Uint8Array} encrypted the encrypted bytes 7414 * @param {Uint32Array} key the bytes of the decryption key 7415 * @param {Uint32Array} initVector the initialization vector (IV) to
7416 * use for the first round of CBC. 7417 * @return {Uint8Array} the decrypted bytes 7418 * 7419 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 7420 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 7421 * @see https://tools.ietf.org/html/rfc2315 7422 */ 7423 var decrypt = function decrypt(encrypted, key, initVector) { 7424 // word-level access to the encrypted bytes 7425 var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2); 7426 7427 var decipher = new _aes2['default'](Array.prototype.slice.call(key)); 7428 7429 // byte and word-level access for the decrypted output 7430 var decrypted = new Uint8Array(encrypted.byteLength); 7431 var decrypted32 = new Int32Array(decrypted.buffer); 7432 7433 // temporary variables for working with the IV, encrypted, and 7434 // decrypted data 7435 var init0 = undefined; 7436 var init1 = undefined; 7437 var init2 = undefined; 7438 var init3 = undefined; 7439 var encrypted0 = undefined; 7440 var encrypted1 = undefined; 7441 var encrypted2 = undefined; 7442 var encrypted3 = undefined; 7443 7444 // iteration variable 7445 var wordIx = undefined; 7446 7447 // pull out the words of the IV to ensure we don't modify the 7448 // passed-in reference and easier access 7449 init0 = initVector[0]; 7450 init1 = initVector[1]; 7451 init2 = initVector[2]; 7452 init3 = initVector[3]; 7453 7454 // decrypt four word sequences, applying cipher-block chaining (CBC) 7455 // to each decrypted block 7456 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) { 7457 // convert big-endian (network order) words into little-endian 7458 // (javascript order) 7459 encrypted0 = ntoh(encrypted32[wordIx]); 7460 encrypted1 = ntoh(encrypted32[wordIx + 1]); 7461 encrypted2 = ntoh(encrypted32[wordIx + 2]); 7462 encrypted3 = ntoh(encrypted32[wordIx + 3]); 7463 7464 // decrypt the block 7465 decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx); 7466 7467 // XOR with the IV, and restore network byte-order to obtain the 7468 // plaintext 7469 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0); 7470 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1); 7471 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2); 7472 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3); 7473 7474 // setup the IV for the next round 7475 init0 = encrypted0; 7476 init1 = encrypted1; 7477 init2 = encrypted2; 7478 init3 = encrypted3; 7479 } 7480 7481 return decrypted; 7482 }; 7483 7484 exports.decrypt = decrypt; 7485 /** 7486 * The `Decrypter` class that manages decryption of AES 7487 * data through `AsyncStream` objects and the `decrypt` 7488 * function 7489 * 7490 * @param {Uint8Array} encrypted the encrypted bytes 7491 * @param {Uint32Array} key the bytes of the decryption key 7492 * @param {Uint32Array} initVector the initialization vector (IV) to 7493 * @param {Function} done the function to run when done 7494 * @class Decrypter 7495 */ 7496 7497 var Decrypter = (function () { 7498 function Decrypter(encrypted, key, initVector, done) { 7499 _classCallCheck(this, Decrypter); 7500 7501 var step = Decrypter.STEP; 7502 var encrypted32 = new Int32Array(encrypted.buffer); 7503 var decrypted = new Uint8Array(encrypted.byteLength); 7504 var i = 0; 7505 7506 this.asyncStream_ = new _asyncStream2['default'](); 7507 7508 // split up the encryption job and do the individual chunks asynchronously 7509 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 7510 for (i = step; i < encrypted32.length; i += step) { 7511 initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]); 7512 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted)); 7513 } 7514 // invoke the done() callback when everything is finished 7515 this.asyncStream_.push(function () { 7516 // remove pkcs#7 padding from the decrypted bytes 7517 done(null, (0, _pkcs7.unpad)(decrypted)); 7518 }); 7519 } 7520 7521 /** 7522 * a getter for step the maximum number of bytes to process at one time 7523 * 7524 * @return {Number} the value of step 32000 7525 */ 7526 7527 _createClass(Decrypter, [{ 7528 key: 'decryptChunk_', 7529 7530 /** 7531 * @private 7532 */ 7533 value: function decryptChunk_(encrypted, key, initVector, decrypted) { 7534 return function () { 7535 var bytes = decrypt(encrypted, key, initVector); 7536 7537 decrypted.set(bytes, encrypted.byteOffset); 7538 }; 7539 } 7540 }], [{ 7541 key: 'STEP', 7542 get: function get() { 7543 // 4 * 8000; 7544 return 32000; 7545 } 7546 }]); 7547 7548 return Decrypter; 7549 })(); 7550 7551 exports.Decrypter = Decrypter; 7552 exports['default'] = { 7553 Decrypter: Decrypter, 7554 decrypt: decrypt 7555 }; 7556 },{"./aes":20,"./async-stream":21,"pkcs7":26}],23:[function(require,module,exports){ 7557 /** 7558 * @file index.js 7559 * 7560 * Index module to easily import the primary components of AES-128 7561 * decryption. Like this: 7562 * 7563 * ```js 7564 * import {Decrypter, decrypt, AsyncStream} from 'aes-decrypter'; 7565 * ``` 7566 */ 7567 'use strict'; 7568 7569 Object.defineProperty(exports, '__esModule', { 7570 value: true 7571 }); 7572 7573 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 7574 7575 var _decrypter = require('./decrypter'); 7576 7577 var _asyncStream = require('./async-stream'); 7578 7579 var _asyncStream2 = _interopRequireDefault(_asyncStream); 7580 7581 exports['default'] = { 7582 decrypt: _decrypter.decrypt, 7583 Decrypter: _decrypter.Decrypter, 7584 AsyncStream: _asyncStream2['default'] 7585 }; 7586 module.exports = exports['default']; 7587 },{"./async-stream":21,"./decrypter":22}],24:[function(require,module,exports){ 7588 arguments[4][16][0].apply(exports,arguments) 7589 },{"dup":16}],25:[function(require,module,exports){ 7590 /* 7591 * pkcs7.pad 7592 * https://github.com/brightcove/pkcs7 7593 * 7594 * Copyright (c) 2014 Brightcove
7595 * Licensed under the apache2 license. 7596 */ 7597 7598 'use strict'; 7599 7600 var PADDING; 7601 7602 /** 7603 * Returns a new Uint8Array that is padded with PKCS#7 padding. 7604 * @param plaintext {Uint8Array} the input bytes before encryption 7605 * @return {Uint8Array} the padded bytes 7606 * @see http://tools.ietf.org/html/rfc5652 7607 */ 7608 module.exports = function pad(plaintext) { 7609 var padding = PADDING[(plaintext.byteLength % 16) || 0], 7610 result = new Uint8Array(plaintext.byteLength + padding.length); 7611 result.set(plaintext); 7612 result.set(padding, plaintext.byteLength); 7613 return result; 7614 }; 7615 7616// pre-define the padding values 7617 PADDING = [ 7618 [16, 16, 16, 16, 7619 16, 16, 16, 16, 7620 16, 16, 16, 16, 7621 16, 16, 16, 16], 7622 7623 [15, 15, 15, 15, 7624 15, 15, 15, 15, 7625 15, 15, 15, 15, 7626 15, 15, 15], 7627 7628 [14, 14, 14, 14, 7629 14, 14, 14, 14, 7630 14, 14, 14, 14, 7631 14, 14], 7632 7633 [13, 13, 13, 13, 7634 13, 13, 13, 13, 7635 13, 13, 13, 13, 7636 13], 7637 7638 [12, 12, 12, 12, 7639 12, 12, 12, 12, 7640 12, 12, 12, 12], 7641 7642 [11, 11, 11, 11, 7643 11, 11, 11, 11, 7644 11, 11, 11], 7645 7646 [10, 10, 10, 10, 7647 10, 10, 10, 10, 7648 10, 10], 7649 7650 [9, 9, 9, 9, 7651 9, 9, 9, 9, 7652 9], 7653 7654 [8, 8, 8, 8, 7655 8, 8, 8, 8], 7656 7657 [7, 7, 7, 7, 7658 7, 7, 7], 7659 7660 [6, 6, 6, 6, 7661 6, 6], 7662 7663 [5, 5, 5, 5, 7664 5], 7665 7666 [4, 4, 4, 4], 7667 7668 [3, 3, 3], 7669 7670 [2, 2], 7671 7672 [1] 7673 ]; 7674 7675 },{}],26:[function(require,module,exports){ 7676 /* 7677 * pkcs7 7678 * https://github.com/brightcove/pkcs7 7679 * 7680 * Copyright (c) 2014 Brightcove 7681 * Licensed under the apache2 license. 7682 */ 7683 7684 'use strict'; 7685 7686 exports.pad = require('./pad.js'); 7687 exports.unpad = require('./unpad.js'); 7688 7689 },{"./pad.js":25,"./unpad.js":27}],27:[function(require,module,exports){ 7690 /* 7691 * pkcs7.unpad 7692 * https://github.com/brightcove/pkcs7 7693 * 7694 * Copyright (c) 2014 Brightcove 7695 * Licensed under the apache2 license. 7696 */ 7697 7698 'use strict'; 7699 7700 /** 7701 * Returns the subarray of a Uint8Array without PKCS#7 padding. 7702 * @param padded {Uint8Array} unencrypted bytes that have been padded 7703 * @return {Uint8Array} the unpadded bytes 7704 * @see http://tools.ietf.org/html/rfc5652 7705 */ 7706 module.exports = function unpad(padded) { 7707 return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]); 7708 }; 7709 7710 },{}],28:[function(require,module,exports){ 7711 7712 },{}],29:[function(require,module,exports){ 7713 (function (global){ 7714 var topLevel = typeof global !== 'undefined' ? global : 7715 typeof window !== 'undefined' ? window : {} 7716 var minDoc = require('min-document'); 7717 7718 var doccy; 7719 7720 if (typeof document !== 'undefined') { 7721 doccy = document; 7722 } else { 7723 doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4']; 7724 7725 if (!doccy) { 7726 doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc; 7727 } 7728 } 7729 7730 module.exports = doccy; 7731 7732 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 7733 },{"min-document":28}],30:[function(require,module,exports){ 7734 (function (global){ 7735 var win; 7736 7737 if (typeof window !== "undefined") { 7738 win = window; 7739 } else if (typeof global !== "undefined") { 7740 win = global; 7741 } else if (typeof self !== "undefined"){ 7742 win = self; 7743 } else { 7744 win = {}; 7745 } 7746 7747 module.exports = win; 7748 7749 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 7750 },{}],31:[function(require,module,exports){ 7751 'use strict'; 7752 7753 var _lineStream = require('./line-stream'); 7754 7755 var _lineStream2 = _interopRequireDefault(_lineStream); 7756 7757 var _parseStream = require('./parse-stream'); 7758 7759 var _parseStream2 = _interopRequireDefault(_parseStream); 7760 7761 var _parser = require('./parser'); 7762 7763 var _parser2 = _interopRequireDefault(_parser); 7764 7765 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 7766 7767 module.exports = { 7768 LineStream: _lineStream2['default'],
7769 ParseStream: _parseStream2['default'], 7770 Parser: _parser2['default'] 7771 }; /** 7772 * @file m3u8/index.js 7773 * 7774 * Utilities for parsing M3U8 files. If the entire manifest is available, 7775 * `Parser` will create an object representation with enough detail for managing 7776 * playback. `ParseStream` and `LineStream` are lower-level parsing primitives 7777 * that do not assume the entirety of the manifest is ready and expose a 7778 * ReadableStream-like interface. 7779 */ 7780 },{"./line-stream":32,"./parse-stream":33,"./parser":34}],32:[function(require,module,exports){ 7781 'use strict'; 7782 7783 Object.defineProperty(exports, "__esModule", { 7784 value: true 7785 }); 7786 7787 var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 7788 7789 var _stream = require('./stream'); 7790 7791 var _stream2 = _interopRequireDefault(_stream); 7792 7793 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 7794 7795 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 7796 7797 function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 7798 7799 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** 7800 * @file m3u8/line-stream.js 7801 */ 7802 7803 7804 /** 7805 * A stream that buffers string input and generates a `data` event for each 7806 * line. 7807 * 7808 * @class LineStream 7809 * @extends Stream 7810 */ 7811 var LineStream = function (_Stream) { 7812 _inherits(LineStream, _Stream); 7813 7814 function LineStream() { 7815 _classCallCheck(this, LineStream); 7816 7817 var _this = _possibleConstructorReturn(this, (LineStream.__proto__ || Object.getPrototypeOf(LineStream)).call(this)); 7818 7819 _this.buffer = ''; 7820 return _this; 7821 } 7822 7823 /** 7824 * Add new data to be parsed. 7825 * 7826 * @param {String} data the text to process 7827 */ 7828 7829 7830 _createClass(LineStream, [{ 7831 key: 'push', 7832 value: function push(data) { 7833 var nextNewline = void 0; 7834 7835 this.buffer += data; 7836 nextNewline = this.buffer.indexOf('\n'); 7837 7838 for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) { 7839 this.trigger('data', this.buffer.substring(0, nextNewline)); 7840 this.buffer = this.buffer.substring(nextNewline + 1); 7841 } 7842 } 7843 }]); 7844 7845 return LineStream; 7846 }(_stream2['default']); 7847 7848 exports['default'] = LineStream; 7849 },{"./stream":35}],33:[function(require,module,exports){ 7850 'use strict'; 7851 7852 Object.defineProperty(exports, "__esModule", { 7853 value: true 7854 }); 7855 7856 var _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); }
7856 else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 7857 7858 var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 7859 7860 var _stream = require('./stream'); 7861 7862 var _stream2 = _interopRequireDefault(_stream); 7863 7864 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 7865 7866 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 7867 7868 function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 7869 7870 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** 7871 * @file m3u8/parse-stream.js 7872 */ 7873 7874 7875 /** 7876 * "forgiving" attribute list psuedo-grammar: 7877 * attributes -> keyvalue (',' keyvalue)* 7878 * keyvalue -> key '=' value 7879 * key -> [^=]* 7880 * value -> '"' [^"]* '"' | [^,]* 7881 */ 7882 var attributeSeparator = function attributeSeparator() { 7883 var key = '[^=]*'; 7884 var value = '"[^"]*"|[^,]*'; 7885 var keyvalue = '(?:' + key + ')=(?:' + value + ')'; 7886 7887 return new RegExp('(?:^|,)(' + keyvalue + ')'); 7888 }; 7889 7890 /** 7891 * Parse attributes from a line given the seperator 7892 * 7893 * @param {String} attributes the attibute line to parse 7894 */ 7895 var parseAttributes = function parseAttributes(attributes) { 7896 // split the string using attributes as the separator 7897 var attrs = attributes.split(attributeSeparator()); 7898 var result = {}; 7899 var i = attrs.length; 7900 var attr = void 0; 7901 7902 while (i--) { 7903 // filter out unmatched portions of the string 7904 if (attrs[i] === '') { 7905 continue; 7906 } 7907 7908 // split the key and value 7909 attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); 7910 // trim whitespace and remove optional quotes around the value 7911 attr[0] = attr[0].replace(/^\s+|\s+$/g, ''); 7912 attr[1] = attr[1].replace(/^\s+|\s+$/g, ''); 7913 attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1'); 7914 result[attr[0]] = attr[1]; 7915 } 7916 return result; 7917 }; 7918 7919 /** 7920 * A line-level M3U8 parser event stream. It expects to receive input one 7921 * line at a time and performs a context-free parse of its contents. A stream 7922 * interpretation of a manifest can be useful if the manifest is expected to 7923 * be too large to fit comfortably into memory or the entirety of the input 7924 * is not immediately available. Otherwise, it's probably much easier to work 7925 * with a regular `Parser` object. 7926 * 7927 * Produces `data` events with an object that captures the parser's 7928 * interpretation of the input. That object has a property `tag` that is one 7929 * of `uri`, `comment`, or `tag`. URIs only have a single additional 7930 * property, `line`, which captures the entirety of the input without 7931 * interpretation. Comments similarly have a single additional property 7932 * `text` which is the input without the leading `#`. 7933 * 7934 * Tags always have a property `tagType` which is the lower-cased version of 7935 * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance, 7936 * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized 7937 * tags are given the tag type `unknown` and a single additional property 7938 * `data` with the remainder of the input. 7939 * 7940 * @class ParseStream 7941 * @extends Stream 7942 */ 7943
vendor: 32,768 bytes, lines 7944-8612
7944 var ParseStream = function (_Stream) { 7945 _inherits(ParseStream, _Stream); 7946 7947 function ParseStream() { 7948 _classCallCheck(this, ParseStream); 7949 7950 return _possibleConstructorReturn(this, (ParseStream.__proto__ || Object.getPrototypeOf(ParseStream)).call(this)); 7951 } 7952 7953 /** 7954 * Parses an additional line of input. 7955 * 7956 * @param {String} line a single line of an M3U8 file to parse 7957 */ 7958 7959 7960 _createClass(ParseStream, [{ 7961 key: 'push', 7962 value: function push(line) { 7963 var match = void 0; 7964 var event = void 0; 7965 7966 // strip whitespace 7967 line = line.replace(/^[\u0000\s]+|[\u0000\s]+$/g, ''); 7968 if (line.length === 0) { 7969 // ignore empty lines 7970 return; 7971 } 7972 7973 // URIs 7974 if (line[0] !== '#') { 7975 this.trigger('data', { 7976 type: 'uri', 7977 uri: line 7978 }); 7979 return; 7980 } 7981 7982 // Comments 7983 if (line.indexOf('#EXT') !== 0) { 7984 this.trigger('data', { 7985 type: 'comment', 7986 text: line.slice(1) 7987 }); 7988 return; 7989 } 7990 7991 // strip off any carriage returns here so the regex matching 7992 // doesn't have to account for them. 7993 line = line.replace('\r', ''); 7994 7995 // Tags 7996 match = /^#EXTM3U/.exec(line); 7997 if (match) { 7998 this.trigger('data', { 7999 type: 'tag', 8000 tagType: 'm3u' 8001 }); 8002 return; 8003 } 8004 match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(line); 8005 if (match) { 8006 event = { 8007 type: 'tag', 8008 tagType: 'inf' 8009 }; 8010 if (match[1]) { 8011 event.duration = parseFloat(match[1]); 8012 } 8013 if (match[2]) { 8014 event.title = match[2]; 8015 } 8016 this.trigger('data', event); 8017 return; 8018 } 8019 match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(line); 8020 if (match) { 8021 event = { 8022 type: 'tag', 8023 tagType: 'targetduration' 8024 }; 8025 if (match[1]) { 8026 event.duration = parseInt(match[1], 10); 8027 } 8028 this.trigger('data', event); 8029 return; 8030 } 8031 match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(line); 8032 if (match) { 8033 event = { 8034 type: 'tag', 8035 tagType: 'totalduration' 8036 }; 8037 if (match[1]) { 8038 event.duration = parseInt(match[1], 10); 8039 } 8040 this.trigger('data', event); 8041 return; 8042 } 8043 match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(line); 8044 if (match) { 8045 event = { 8046 type: 'tag', 8047 tagType: 'version' 8048 }; 8049 if (match[1]) { 8050 event.version = parseInt(match[1], 10); 8051 } 8052 this.trigger('data', event); 8053 return; 8054 } 8055 match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(line); 8056 if (match) { 8057 event = { 8058 type: 'tag', 8059 tagType: 'media-sequence' 8060 }; 8061 if (match[1]) { 8062 event.number = parseInt(match[1], 10); 8063 } 8064 this.trigger('data', event); 8065 return; 8066 } 8067 match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(line); 8068 if (match) { 8069 event = { 8070 type: 'tag', 8071 tagType: 'discontinuity-sequence' 8072 }; 8073 if (match[1]) { 8074 event.number = parseInt(match[1], 10); 8075 } 8076 this.trigger('data', event); 8077 return; 8078 } 8079 match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(line); 8080 if (match) { 8081 event = { 8082 type: 'tag', 8083 tagType: 'playlist-type' 8084 }; 8085 if (match[1]) { 8086 event.playlistType = match[1]; 8087 } 8088 this.trigger('data', event); 8089 return; 8090 } 8091 match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(line); 8092 if (match) { 8093 event = { 8094 type: 'tag', 8095 tagType: 'byterange' 8096 }; 8097 if (match[1]) { 8098 event.length = parseInt(match[1], 10); 8099 } 8100 if (match[2]) { 8101 event.offset = parseInt(match[2], 10); 8102 } 8103 this.trigger('data', event); 8104 return; 8105 } 8106 match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(line); 8107 if (match) { 8108 event = { 8109 type: 'tag', 8110 tagType: 'allow-cache' 8111 }; 8112 if (match[1]) { 8113 event.allowed = !/NO/.test(match[1]); 8114 } 8115 this.trigger('data', event); 8116 return; 8117 } 8118 match = /^#EXT-X-MAP:?(.*)$/.exec(line); 8119 if (match) { 8120 event = { 8121 type: 'tag', 8122 tagType: 'map' 8123 }; 8124 8125 if (match[1]) { 8126 var attributes = parseAttributes(match[1]); 8127 8128 if (attributes.URI) { 8129 event.uri = attributes.URI; 8130 } 8131 if (attributes.BYTERANGE) { 8132 var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'), 8133 _attributes$BYTERANGE2 = _slicedToArray(_attributes$BYTERANGE, 2), 8134 length = _attributes$BYTERANGE2[0], 8135 offset = _attributes$BYTERANGE2[1]; 8136 8137 event.byterange = {}; 8138 if (length) { 8139 event.byterange.length = parseInt(length, 10); 8140 } 8141 if (offset) { 8142 event.byterange.offset = parseInt(offset, 10); 8143 } 8144 } 8145 } 8146 8147 this.trigger('data', event); 8148 return; 8149 } 8150 match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(line); 8151 if (match) { 8152 event = { 8153 type: 'tag', 8154 tagType: 'stream-inf' 8155 }; 8156 if (match[1]) { 8157 event.attributes = parseAttributes(match[1]); 8158 8159 if (event.attributes.RESOLUTION) { 8160 var split = event.attributes.RESOLUTION.split('x'); 8161 var resolution = {}; 8162 8163 if (split[0]) { 8164 resolution.width = parseInt(split[0], 10); 8165 } 8166 if (split[1]) { 8167 resolution.height = parseInt(split[1], 10); 8168 } 8169 event.attributes.RESOLUTION = resolution; 8170 } 8171 if (event.attributes.BANDWIDTH) { 8172 event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10); 8173 } 8174 if (event.attributes['PROGRAM-ID']) { 8175 event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10); 8176 } 8177 } 8178 this.trigger('data', event); 8179 return; 8180 } 8181 match = /^#EXT-X-MEDIA:?(.*)$/.exec(line); 8182 if (match) { 8183 event = { 8184 type: 'tag', 8185 tagType: 'media' 8186 }; 8187 if (match[1]) { 8188 event.attributes = parseAttributes(match[1]); 8189 } 8190 this.trigger('data', event); 8191 return; 8192 } 8193 match = /^#EXT-X-ENDLIST/.exec(line); 8194 if (match) { 8195 this.trigger('data', { 8196 type: 'tag', 8197 tagType: 'endlist' 8198 }); 8199 return; 8200 } 8201 match = /^#EXT-X-DISCONTINUITY/.exec(line); 8202 if (match) { 8203 this.trigger('data', { 8204 type: 'tag', 8205 tagType: 'discontinuity' 8206 }); 8207 return; 8208 } 8209 match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(line); 8210 if (match) { 8211 event = { 8212 type: 'tag', 8213 tagType: 'program-date-time' 8214 }; 8215 if (match[1]) { 8216 event.dateTimeString = match[1]; 8217 event.dateTimeObject = new Date(match[1]); 8218 } 8219 this.trigger('data', event); 8220 return; 8221 } 8222 match = /^#EXT-X-KEY:?(.*)$/.exec(line); 8223 if (match) { 8224 event = { 8225 type: 'tag', 8226 tagType: 'key' 8227 }; 8228 if (match[1]) { 8229 event.attributes = parseAttributes(match[1]); 8230 // parse the IV string into a Uint32Array 8231 if (event.attributes.IV) { 8232 if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') { 8233 event.attributes.IV = event.attributes.IV.substring(2); 8234 } 8235 8236 event.attributes.IV = event.attributes.IV.match(/.{8}/g); 8237 event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16); 8238 event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16); 8239 event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16); 8240 event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16); 8241 event.attributes.IV = new Uint32Array(event.attributes.IV); 8242 } 8243 } 8244 this.trigger('data', event); 8245 return; 8246 } 8247 match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(line); 8248 if (match) { 8249 event = { 8250 type: 'tag', 8251 tagType: 'cue-out-cont' 8252 }; 8253 if (match[1]) { 8254 event.data = match[1]; 8255 } else { 8256 event.data = ''; 8257 } 8258 this.trigger('data', event); 8259 return; 8260 } 8261 match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(line); 8262 if (match) { 8263 event = { 8264 type: 'tag', 8265 tagType: 'cue-out' 8266 }; 8267 if (match[1]) { 8268 event.data = match[1]; 8269 } else { 8270 event.data = ''; 8271 } 8272 this.trigger('data', event); 8273 return; 8274 } 8275 match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(line); 8276 if (match) { 8277 event = { 8278 type: 'tag', 8279 tagType: 'cue-in' 8280 }; 8281 if (match[1]) { 8282 event.data = match[1]; 8283 } else { 8284 event.data = ''; 8285 } 8286 this.trigger('data', event); 8287 return; 8288 } 8289 8290 // unknown tag type 8291 this.trigger('data', { 8292 type: 'tag', 8293 data: line.slice(4) 8294 }); 8295 } 8296 }]); 8297 8298 return ParseStream; 8299 }(_stream2['default']); 8300 8301 exports['default'] = ParseStream; 8302 },{"./stream":35}],34:[function(require,module,exports){ 8303 'use strict'; 8304 8305 Object.defineProperty(exports, "__esModule", { 8306 value: true 8307 }); 8308 8309 var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8310 8311 var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8312 8313 var _stream = require('./stream'); 8314 8315 var _stream2 = _interopRequireDefault(_stream); 8316 8317 var _lineStream = require('./line-stream'); 8318 8319 var _lineStream2 = _interopRequireDefault(_lineStream); 8320 8321 var _parseStream = require('./parse-stream'); 8322 8323 var _parseStream2 = _interopRequireDefault(_parseStream); 8324 8325 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 8326 8327 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 8328 8329 function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 8330 8331 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** 8332 * @file m3u8/parser.js 8333 */ 8334 8335 8336 /** 8337 * A parser for M3U8 files. The current interpretation of the input is 8338 * exposed as a property `manifest` on parser objects. It's just two lines to 8339 * create and parse a manifest once you have the contents available as a string: 8340 * 8341 * ```js 8342 * var parser = new m3u8.Parser(); 8343 * parser.push(xhr.responseText); 8344 * ``` 8345 * 8346 * New input can later be applied to update the manifest object by calling 8347 * `push` again. 8348 * 8349 * The parser attempts to create a usable manifest object even if the 8350 * underlying input is somewhat nonsensical. It emits `info` and `warning` 8351 * events during the parse if it encounters input that seems invalid or 8352 * requires some property of the manifest object to be defaulted. 8353 * 8354 * @class Parser 8355 * @extends Stream 8356 */ 8357 var Parser = function (_Stream) { 8358 _inherits(Parser, _Stream); 8359 8360 function Parser() { 8361 _classCallCheck(this, Parser); 8362 8363 var _this = _possibleConstructorReturn(this, (Parser.__proto__ || Object.getPrototypeOf(Parser)).call(this)); 8364 8365 _this.lineStream = new _lineStream2['default'](); 8366 _this.parseStream = new _parseStream2['default'](); 8367 _this.lineStream.pipe(_this.parseStream); 8368 /* eslint-disable consistent-this */ 8369 var self = _this; 8370 /* eslint-enable consistent-this */ 8371 var uris = []; 8372 var currentUri = {}; 8373 // if specified, the active EXT-X-MAP definition 8374 var currentMap = void 0; 8375 // if specified, the active decryption key 8376 var _key = void 0; 8377 var noop = function noop() {}; 8378 var defaultMediaGroups = { 8379 'AUDIO': {}, 8380 'VIDEO': {}, 8381 'CLOSED-CAPTIONS': {}, 8382 'SUBTITLES': {} 8383 }; 8384 // group segments into numbered timelines delineated by discontinuities 8385 var currentTimeline = 0; 8386 8387 // the manifest is empty until the parse stream begins delivering data 8388 _this.manifest = { 8389 allowCache: true, 8390 discontinuityStarts: [], 8391 segments: [] 8392 }; 8393 8394 // update the manifest with the m3u8 entry from the parse stream 8395 _this.parseStream.on('data', function (entry) { 8396 var mediaGroup = void 0; 8397 var rendition = void 0; 8398 8399 ({ 8400 tag: function tag() { 8401 // switch based on the tag type 8402 (({ 8403 'allow-cache': function allowCache() { 8404 this.manifest.allowCache = entry.allowed; 8405 if (!('allowed' in entry)) { 8406 this.trigger('info', { 8407 message: 'defaulting allowCache to YES' 8408 }); 8409 this.manifest.allowCache = true; 8410 } 8411 }, 8412 byterange: function byterange() { 8413 var byterange = {}; 8414 8415 if ('length' in entry) { 8416 currentUri.byterange = byterange; 8417 byterange.length = entry.length; 8418 8419 if (!('offset' in entry)) { 8420 this.trigger('info', { 8421 message: 'defaulting offset to zero' 8422 }); 8423 entry.offset = 0; 8424 } 8425 } 8426 if ('offset' in entry) { 8427 currentUri.byterange = byterange; 8428 byterange.offset = entry.offset; 8429 } 8430 }, 8431 endlist: function endlist() { 8432 this.manifest.endList = true; 8433 }, 8434 inf: function inf() { 8435 if (!('mediaSequence' in this.manifest)) { 8436 this.manifest.mediaSequence = 0; 8437 this.trigger('info', { 8438 message: 'defaulting media sequence to zero' 8439 }); 8440 } 8441 if (!('discontinuitySequence' in this.manifest)) { 8442 this.manifest.discontinuitySequence = 0; 8443 this.trigger('info', { 8444 message: 'defaulting discontinuity sequence to zero' 8445 }); 8446 } 8447 if (entry.duration > 0) { 8448 currentUri.duration = entry.duration; 8449 } 8450 8451 if (entry.duration === 0) { 8452 currentUri.duration = 0.01; 8453 this.trigger('info', { 8454 message: 'updating zero segment duration to a small value' 8455 }); 8456 } 8457 8458 this.manifest.segments = uris; 8459 }, 8460 key: function key() { 8461 if (!entry.attributes) { 8462 this.trigger('warn', { 8463 message: 'ignoring key declaration without attribute list' 8464 }); 8465 return; 8466 } 8467 // clear the active encryption key 8468 if (entry.attributes.METHOD === 'NONE') { 8469 _key = null; 8470 return; 8471 } 8472 if (!entry.attributes.URI) { 8473 this.trigger('warn', { 8474 message: 'ignoring key declaration without URI' 8475 }); 8476 return; 8477 } 8478 if (!entry.attributes.METHOD) { 8479 this.trigger('warn', { 8480 message: 'defaulting key method to AES-128' 8481 }); 8482 } 8483 8484 // setup an encryption key for upcoming segments 8485 _key = { 8486 method: entry.attributes.METHOD || 'AES-128', 8487 uri: entry.attributes.URI 8488 }; 8489 8490 if (typeof entry.attributes.IV !== 'undefined') { 8491 _key.iv = entry.attributes.IV; 8492 } 8493 }, 8494 'media-sequence': function mediaSequence() { 8495 if (!isFinite(entry.number)) { 8496 this.trigger('warn', { 8497 message: 'ignoring invalid media sequence: ' + entry.number 8498 }); 8499 return; 8500 } 8501 this.manifest.mediaSequence = entry.number; 8502 }, 8503 'discontinuity-sequence': function discontinuitySequence() { 8504 if (!isFinite(entry.number)) { 8505 this.trigger('warn', { 8506 message: 'ignoring invalid discontinuity sequence: ' + entry.number 8507 }); 8508 return; 8509 } 8510 this.manifest.discontinuitySequence = entry.number; 8511 currentTimeline = entry.number; 8512 }, 8513 'playlist-type': function playlistType() { 8514 if (!/VOD|EVENT/.test(entry.playlistType)) { 8515 this.trigger('warn', { 8516 message: 'ignoring unknown playlist type: ' + entry.playlist 8517 }); 8518 return; 8519 } 8520 this.manifest.playlistType = entry.playlistType; 8521 }, 8522 map: function map() { 8523 currentMap = {}; 8524 if (entry.uri) { 8525 currentMap.uri = entry.uri; 8526 } 8527 if (entry.byterange) { 8528 currentMap.byterange = entry.byterange; 8529 } 8530 }, 8531 'stream-inf': function streamInf() { 8532 this.manifest.playlists = uris; 8533 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 8534 8535 if (!entry.attributes) { 8536 this.trigger('warn', { 8537 message: 'ignoring empty stream-inf attributes' 8538 }); 8539 return; 8540 } 8541 8542 if (!currentUri.attributes) { 8543 currentUri.attributes = {}; 8544 } 8545 _extends(currentUri.attributes, entry.attributes); 8546 }, 8547 media: function media() { 8548 this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups; 8549 8550 if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) { 8551 this.trigger('warn', { 8552 message: 'ignoring incomplete or missing media group' 8553 }); 8554 return; 8555 } 8556 8557 // find the media group, creating defaults as necessary 8558 var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE]; 8559 8560 mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {}; 8561 mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; 8562 8563 // collect the rendition metadata 8564 rendition = { 8565 'default': /yes/i.test(entry.attributes.DEFAULT) 8566 }; 8567 if (rendition['default']) { 8568 rendition.autoselect = true; 8569 } else { 8570 rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT); 8571 } 8572 if (entry.attributes.LANGUAGE) { 8573 rendition.language = entry.attributes.LANGUAGE; 8574 } 8575 if (entry.attributes.URI) { 8576 rendition.uri = entry.attributes.URI; 8577 } 8578 if (entry.attributes['INSTREAM-ID']) { 8579 rendition.instreamId = entry.attributes['INSTREAM-ID']; 8580 } 8581 if (entry.attributes.CHARACTERISTICS) { 8582 rendition.characteristics = entry.attributes.CHARACTERISTICS; 8583 } 8584 if (entry.attributes.FORCED) { 8585 rendition.forced = /yes/i.test(entry.attributes.FORCED); 8586 } 8587 8588 // insert the new rendition 8589 mediaGroup[entry.attributes.NAME] = rendition; 8590 }, 8591 discontinuity: function discontinuity() { 8592 currentTimeline += 1; 8593 currentUri.discontinuity = true; 8594 this.manifest.discontinuityStarts.push(uris.length); 8595 }, 8596 'program-date-time': function programDateTime() { 8597 this.manifest.dateTimeString = entry.dateTimeString; 8598 this.manifest.dateTimeObject = entry.dateTimeObject; 8599 }, 8600 targetduration: function targetduration() { 8601 if (!isFinite(entry.duration) || entry.duration < 0) { 8602 this.trigger('warn', { 8603 message: 'ignoring invalid target duration: ' + entry.duration 8604 }); 8605 return; 8606 } 8607 this.manifest.targetDuration = entry.duration; 8608 }, 8609 totalduration: function totalduration() { 8610 if (!isFinite(entry.duration) || entry.duration < 0) { 8611 this.trigger('warn', { 8612
8612message: 'ignoring invalid total duration: ' + entry.duration 8613 }); 8614 return; 8615 } 8616 this.manifest.totalDuration = entry.duration; 8617 }, 8618 'cue-out': function cueOut() { 8619 currentUri.cueOut = entry.data; 8620 }, 8621 'cue-out-cont': function cueOutCont() { 8622 currentUri.cueOutCont = entry.data; 8623 }, 8624 'cue-in': function cueIn() { 8625 currentUri.cueIn = entry.data; 8626 } 8627 })[entry.tagType] || noop).call(self); 8628 }, 8629 uri: function uri() { 8630 currentUri.uri = entry.uri; 8631 uris.push(currentUri); 8632 8633 // if no explicit duration was declared, use the target duration 8634 if (this.manifest.targetDuration && !('duration' in currentUri)) { 8635 this.trigger('warn', { 8636 message: 'defaulting segment duration to the target duration' 8637 }); 8638 currentUri.duration = this.manifest.targetDuration; 8639 } 8640 // annotate with encryption information, if necessary 8641 if (_key) { 8642 currentUri.key = _key; 8643 } 8644 currentUri.timeline = currentTimeline; 8645 // annotate with initialization segment information, if necessary 8646 if (currentMap) { 8647 currentUri.map = currentMap; 8648 } 8649 8650 // prepare for the next URI 8651 currentUri = {}; 8652 }, 8653 comment: function comment() { 8654 // comments are not important for playback 8655 } 8656 })[entry.type].call(self); 8657 }); 8658 8659 return _this; 8660 } 8661 8662 /** 8663 * Parse the input string and update the manifest object. 8664 * 8665 * @param {String} chunk a potentially incomplete portion of the manifest 8666 */ 8667 8668 8669 _createClass(Parser, [{ 8670 key: 'push', 8671 value: function push(chunk) { 8672 this.lineStream.push(chunk); 8673 } 8674 8675 /** 8676 * Flush any remaining input. This can be handy if the last line of an M3U8 8677 * manifest did not contain a trailing newline but the file has been 8678 * completely received. 8679 */ 8680 8681 }, { 8682 key: 'end', 8683 value: function end() { 8684 // flush any buffered input 8685 this.lineStream.push('\n'); 8686 } 8687 }]); 8688 8689 return Parser; 8690 }(_stream2['default']); 8691 8692 exports['default'] = Parser; 8693 },{"./line-stream":32,"./parse-stream":33,"./stream":35}],35:[function(require,module,exports){ 8694 'use strict'; 8695 8696 Object.defineProperty(exports, "__esModule", { 8697 value: true 8698 }); 8699 8700 var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8701 8702 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 8703 8704 /** 8705 * @file stream.js 8706 */ 8707 /** 8708 * A lightweight readable stream implemention that handles event dispatching. 8709 * 8710 * @class Stream 8711 */ 8712 var Stream = function () { 8713 function Stream() { 8714 _classCallCheck(this, Stream); 8715 8716 this.listeners = {}; 8717 } 8718 8719 /** 8720 * Add a listener for a specified event type. 8721 * 8722 * @param {String} type the event name 8723 * @param {Function} listener the callback to be invoked when an event of 8724 * the specified type occurs 8725 */ 8726 8727 8728 _createClass(Stream, [{ 8729 key: 'on', 8730 value: function on(type, listener) { 8731 if (!this.listeners[type]) { 8732 this.listeners[type] = []; 8733 } 8734 this.listeners[type].push(listener); 8735 } 8736 8737 /** 8738 * Remove a listener for a specified event type. 8739 * 8740 * @param {String} type the event name 8741 * @param {Function} listener a function previously registered for this 8742 * type of event through `on` 8743 * @return {Boolean} if we could turn it off or not 8744 */ 8745 8746 }, { 8747 key: 'off', 8748 value: function off(type, listener) { 8749 if (!this.listeners[type]) { 8750 return false;
vendor: 40,732 bytes, lines 8751-9764
8751 } 8752 8753 var index = this.listeners[type].indexOf(listener); 8754 8755 this.listeners[type].splice(index, 1); 8756 return index > -1; 8757 } 8758 8759 /** 8760 * Trigger an event of the specified type on this stream. Any additional 8761 * arguments to this function are passed as parameters to event listeners. 8762 * 8763 * @param {String} type the event name 8764 */ 8765 8766 }, { 8767 key: 'trigger', 8768 value: function trigger(type) { 8769 var callbacks = this.listeners[type]; 8770 var i = void 0; 8771 var length = void 0; 8772 var args = void 0; 8773 8774 if (!callbacks) { 8775 return; 8776 } 8777 // Slicing the arguments on every invocation of this method 8778 // can add a significant amount of overhead. Avoid the 8779 // intermediate object creation for the common case of a 8780 // single callback argument 8781 if (arguments.length === 2) { 8782 length = callbacks.length; 8783 for (i = 0; i < length; ++i) { 8784 callbacks[i].call(this, arguments[1]); 8785 } 8786 } else { 8787 args = Array.prototype.slice.call(arguments, 1); 8788 length = callbacks.length; 8789 for (i = 0; i < length; ++i) { 8790 callbacks[i].apply(this, args); 8791 } 8792 } 8793 } 8794 8795 /** 8796 * Destroys the stream and cleans up. 8797 */ 8798 8799 }, { 8800 key: 'dispose', 8801 value: function dispose() { 8802 this.listeners = {}; 8803 } 8804 /** 8805 * Forwards all `data` events on this stream to the destination stream. The 8806 * destination stream should provide a method `push` to receive the data 8807 * events as they arrive. 8808 * 8809 * @param {Stream} destination the stream that will receive all `data` events 8810 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 8811 */ 8812 8813 }, { 8814 key: 'pipe', 8815 value: function pipe(destination) { 8816 this.on('data', function (data) { 8817 destination.push(data); 8818 }); 8819 } 8820 }]); 8821 8822 return Stream; 8823 }(); 8824 8825 exports['default'] = Stream; 8826 },{}],36:[function(require,module,exports){ 8827 /** 8828 * mux.js 8829 * 8830 * Copyright (c) 2016 Brightcove 8831 * All rights reserved. 8832 * 8833 * A stream-based aac to mp4 converter. This utility can be used to 8834 * deliver mp4s to a SourceBuffer on platforms that support native 8835 * Media Source Extensions. 8836 */ 8837 'use strict'; 8838 var Stream = require('../utils/stream.js'); 8839 8840// Constants 8841 var AacStream; 8842 8843 /** 8844 * Splits an incoming stream of binary data into ADTS and ID3 Frames. 8845 */ 8846 8847 AacStream = function() { 8848 var 8849 everything = new Uint8Array(), 8850 timeStamp = 0; 8851 8852 AacStream.prototype.init.call(this); 8853 8854 this.setTimestamp = function(timestamp) { 8855 timeStamp = timestamp; 8856 }; 8857 8858 this.parseId3TagSize = function(header, byteIndex) { 8859 var 8860 returnSize = (header[byteIndex + 6] << 21) | 8861 (header[byteIndex + 7] << 14) | 8862 (header[byteIndex + 8] << 7) | 8863 (header[byteIndex + 9]), 8864 flags = header[byteIndex + 5], 8865 footerPresent = (flags & 16) >> 4; 8866 8867 if (footerPresent) { 8868 return returnSize + 20; 8869 } 8870 return returnSize + 10; 8871 }; 8872 8873 this.parseAdtsSize = function(header, byteIndex) { 8874 var 8875 lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 8876 middle = header[byteIndex + 4] << 3, 8877 highTwo = header[byteIndex + 3] & 0x3 << 11; 8878 8879 return (highTwo | middle) | lowThree; 8880 }; 8881 8882 this.push = function(bytes) { 8883 var 8884 frameSize = 0, 8885 byteIndex = 0, 8886 bytesLeft, 8887 chunk, 8888 packet, 8889 tempLength; 8890 8891 // If there are bytes remaining from the last segment, prepend them to the 8892 // bytes that were pushed in 8893 if (everything.length) { 8894 tempLength = everything.length; 8895 everything = new Uint8Array(bytes.byteLength + tempLength); 8896 everything.set(everything.subarray(0, tempLength)); 8897 everything.set(bytes, tempLength); 8898 } else { 8899 everything = bytes; 8900 } 8901 8902 while (everything.length - byteIndex >= 3) { 8903 if ((everything[byteIndex] === 'I'.charCodeAt(0)) && 8904 (everything[byteIndex + 1] === 'D'.charCodeAt(0)) && 8905 (everything[byteIndex + 2] === '3'.charCodeAt(0))) { 8906 8907 // Exit early because we don't have enough to parse 8908 // the ID3 tag header 8909 if (everything.length - byteIndex < 10) { 8910 break; 8911 } 8912 8913 // check framesize 8914 frameSize = this.parseId3TagSize(everything, byteIndex); 8915 8916 // Exit early if we don't have enough in the buffer 8917 // to emit a full packet 8918 if (frameSize > everything.length) { 8919 break; 8920 } 8921 chunk = { 8922 type: 'timed-metadata', 8923 data: everything.subarray(byteIndex, byteIndex + frameSize) 8924 }; 8925 this.trigger('data', chunk); 8926 byteIndex += frameSize; 8927 continue; 8928 } else if ((everything[byteIndex] & 0xff === 0xff) && 8929 ((everything[byteIndex + 1] & 0xf0) === 0xf0)) { 8930 8931 // Exit early because we don't have enough to parse 8932 // the ADTS frame header 8933 if (everything.length - byteIndex < 7) { 8934 break; 8935 } 8936 8937 frameSize = this.parseAdtsSize(everything, byteIndex); 8938 8939 // Exit early if we don't have enough in the buffer 8940 // to emit a full packet 8941 if (frameSize > everything.length) { 8942 break; 8943 } 8944 8945 packet = { 8946 type: 'audio', 8947 data: everything.subarray(byteIndex, byteIndex + frameSize), 8948 pts: timeStamp, 8949 dts: timeStamp 8950 }; 8951 this.trigger('data', packet); 8952 byteIndex += frameSize; 8953 continue; 8954 } 8955 byteIndex++; 8956 } 8957 bytesLeft = everything.length - byteIndex; 8958 8959 if (bytesLeft > 0) { 8960 everything = everything.subarray(byteIndex); 8961 } else { 8962 everything = new Uint8Array(); 8963 } 8964 }; 8965 }; 8966 8967 AacStream.prototype = new Stream(); 8968 8969 module.exports = AacStream; 8970 8971 },{"../utils/stream.js":60}],37:[function(require,module,exports){ 8972 /** 8973 * mux.js 8974 * 8975 * Copyright (c) 2016 Brightcove 8976 * All rights reserved. 8977 * 8978 * Utilities to detect basic properties and metadata about Aac data. 8979 */ 8980 'use strict'; 8981 8982 var ADTS_SAMPLING_FREQUENCIES = [ 8983 96000, 8984 88200, 8985 64000, 8986 48000, 8987 44100, 8988 32000, 8989 24000, 8990 22050, 8991 16000, 8992 12000, 8993 11025, 8994 8000, 8995 7350 8996 ]; 8997 8998 var parseSyncSafeInteger = function(data) { 8999 return (data[0] << 21) | 9000 (data[1] << 14) | 9001 (data[2] << 7) | 9002 (data[3]); 9003 }; 9004 9005// return a percent-encoded representation of the specified byte range 9006// @see http://en.wikipedia.org/wiki/Percent-encoding 9007 var percentEncode = function(bytes, start, end) { 9008 var i, result = ''; 9009 for (i = start; i < end; i++) { 9010 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 9011 } 9012 return result; 9013 }; 9014 9015// return the string representation of the specified byte range, 9016// interpreted as ISO-8859-1. 9017 var parseIso88591 = function(bytes, start, end) { 9018 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 9019 }; 9020 9021 var parseId3TagSize = function(header, byteIndex) { 9022 var 9023 returnSize = (header[byteIndex + 6] << 21) | 9024 (header[byteIndex + 7] << 14) | 9025 (header[byteIndex + 8] << 7) | 9026 (header[byteIndex + 9]), 9027 flags = header[byteIndex + 5], 9028 footerPresent = (flags & 16) >> 4; 9029 9030 if (footerPresent) { 9031 return returnSize + 20; 9032 } 9033 return returnSize + 10; 9034 }; 9035 9036 var parseAdtsSize = function(header, byteIndex) { 9037 var 9038 lowThree = (header[byteIndex + 5] & 0xE0) >> 5, 9039 middle = header[byteIndex + 4] << 3, 9040 highTwo = header[byteIndex + 3] & 0x3 << 11; 9041 9042 return (highTwo | middle) | lowThree; 9043 }; 9044 9045 var parseType = function(header, byteIndex) { 9046 if ((header[byteIndex] === 'I'.charCodeAt(0)) && 9047 (header[byteIndex + 1] === 'D'.charCodeAt(0)) && 9048 (header[byteIndex + 2] === '3'.charCodeAt(0))) { 9049 return 'timed-metadata'; 9050 } else if ((header[byteIndex] & 0xff === 0xff) && 9051 ((header[byteIndex + 1] & 0xf0) === 0xf0)) { 9052 return 'audio'; 9053 } 9054 return null; 9055 }; 9056 9057 var parseSampleRate = function(packet) { 9058 var i = 0; 9059 9060 while (i + 5 < packet.length) { 9061 if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) { 9062 // If a valid header was not found, jump one forward and attempt to 9063 // find a valid ADTS header starting at the next byte 9064 i++; 9065 continue; 9066 } 9067 return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2]; 9068 } 9069 9070 return null; 9071 }; 9072 9073 var parseAacTimestamp = function(packet) { 9074 var frameStart, frameSize, frame, frameHeader; 9075 9076 // find the start of the first frame and the end of the tag 9077 frameStart = 10; 9078 if (packet[5] & 0x40) { 9079 // advance the frame start past the extended header 9080 frameStart += 4; // header size field 9081 frameStart += parseSyncSafeInteger(packet.subarray(10, 14)); 9082 } 9083 9084 // parse one or more ID3 frames 9085 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 9086 do { 9087 // determine the number of bytes in this frame 9088 frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8)); 9089 if (frameSize < 1) { 9090 return null; 9091 } 9092 frameHeader = String.fromCharCode(packet[frameStart], 9093 packet[frameStart + 1], 9094 packet[frameStart + 2], 9095 packet[frameStart + 3]); 9096 9097 if (frameHeader === 'PRIV') { 9098 frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10); 9099 9100 for (var i = 0; i < frame.byteLength; i++) { 9101 if (frame[i] === 0) { 9102 var owner = parseIso88591(frame, 0, i); 9103 if (owner === 'com.apple.streaming.transportStreamTimestamp') { 9104 var d = frame.subarray(i + 1); 9105 var size = ((d[3] & 0x01) << 30) | 9106 (d[4] << 22) | 9107 (d[5] << 14) | 9108 (d[6] << 6) | 9109 (d[7] >>> 2); 9110 size *= 4; 9111 size += d[7] & 0x03; 9112 9113 return size; 9114 } 9115 break; 9116 } 9117 } 9118 } 9119 9120 frameStart += 10; // advance past the frame header 9121 frameStart += frameSize; // advance past the frame body 9122 } while (frameStart < packet.byteLength); 9123 return null; 9124 }; 9125 9126 module.exports = { 9127 parseId3TagSize: parseId3TagSize, 9128 parseAdtsSize: parseAdtsSize, 9129 parseType: parseType, 9130 parseSampleRate: parseSampleRate, 9131 parseAacTimestamp: parseAacTimestamp 9132 }; 9133 9134 },{}],38:[function(require,module,exports){ 9135 'use strict'; 9136 9137 var Stream = require('../utils/stream.js'); 9138 9139 var AdtsStream; 9140 9141 var 9142 ADTS_SAMPLING_FREQUENCIES = [ 9143 96000, 9144 88200, 9145 64000, 9146 48000, 9147 44100, 9148 32000, 9149 24000, 9150 22050, 9151 16000, 9152 12000, 9153 11025, 9154 8000, 9155 7350 9156 ]; 9157 9158 /* 9159 * Accepts a ElementaryStream and emits data events with parsed 9160 * AAC Audio Frames of the individual packets. Input audio in ADTS 9161 * format is unpacked and re-emitted as AAC frames. 9162 * 9163 * @see http://wiki.multimedia.cx/index.php?title=ADTS 9164 * @see http://wiki.multimedia.cx/?title=Understanding_AAC 9165 */ 9166 AdtsStream = function() { 9167 var buffer; 9168 9169 AdtsStream.prototype.init.call(this); 9170 9171 this.push = function(packet) { 9172 var 9173 i = 0, 9174 frameNum = 0, 9175 frameLength, 9176 protectionSkipBytes, 9177 frameEnd, 9178 oldBuffer, 9179 sampleCount, 9180 adtsFrameDuration; 9181 9182 if (packet.type !== 'audio') { 9183 // ignore non-audio data 9184 return; 9185 } 9186 9187 // Prepend any data in the buffer to the input data so that we can parse 9188 // aac frames the cross a PES packet boundary 9189 if (buffer) { 9190 oldBuffer = buffer; 9191 buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength); 9192 buffer.set(oldBuffer); 9193 buffer.set(packet.data, oldBuffer.byteLength); 9194 } else { 9195 buffer = packet.data; 9196 } 9197 9198 // unpack any ADTS frames which have been fully received 9199 // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS 9200 while (i + 5 < buffer.length) { 9201 9202 // Loook for the start of an ADTS header.. 9203 if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) { 9204 // If a valid header was not found, jump one forward and attempt to 9205 // find a valid ADTS header starting at the next byte 9206 i++; 9207 continue; 9208 } 9209 9210 // The protection skip bit tells us if we have 2 bytes of CRC data at the 9211 // end of the ADTS header 9212 protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2; 9213 9214 // Frame length is a 13 bit integer starting 16 bits from the 9215 // end of the sync sequence 9216 frameLength = ((buffer[i + 3] & 0x03) << 11) | 9217 (buffer[i + 4] << 3) | 9218 ((buffer[i + 5] & 0xe0) >> 5); 9219 9220 sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024; 9221 adtsFrameDuration = (sampleCount * 90000) / 9222 ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2]; 9223 9224 frameEnd = i + frameLength; 9225 9226 // If we don't have enough data to actually finish this ADTS frame, return 9227 // and wait for more data 9228 if (buffer.byteLength < frameEnd) { 9229 return; 9230 } 9231 9232 // Otherwise, deliver the complete AAC frame 9233 this.trigger('data', { 9234 pts: packet.pts + (frameNum * adtsFrameDuration), 9235 dts: packet.dts + (frameNum * adtsFrameDuration), 9236 sampleCount: sampleCount, 9237 audioobjecttype: ((buffer[i + 2] >>> 6) & 0x03) + 1, 9238 channelcount: ((buffer[i + 2] & 1) << 2) | 9239 ((buffer[i + 3] & 0xc0) >>> 6), 9240 samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2], 9241 samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2, 9242 // assume ISO/IEC 14496-12 AudioSampleEntry default of 16 9243 samplesize: 16, 9244 data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd) 9245 }); 9246 9247 // If the buffer is empty, clear it and return 9248 if (buffer.byteLength === frameEnd) { 9249 buffer = undefined; 9250 return; 9251 } 9252 9253 frameNum++; 9254 9255 // Remove the finished frame from the buffer and start the process again 9256 buffer = buffer.subarray(frameEnd); 9257 } 9258 }; 9259 this.flush = function() { 9260 this.trigger('done'); 9261 }; 9262 }; 9263 9264 AdtsStream.prototype = new Stream(); 9265 9266 module.exports = AdtsStream; 9267 9268 },{"../utils/stream.js":60}],39:[function(require,module,exports){ 9269 'use strict'; 9270 9271 var Stream = require('../utils/stream.js'); 9272 var ExpGolomb = require('../utils/exp-golomb.js'); 9273 9274 var H264Stream, NalByteStream; 9275 var PROFILES_WITH_OPTIONAL_SPS_DATA; 9276 9277 /** 9278 * Accepts a NAL unit byte stream and unpacks the embedded NAL units. 9279 */ 9280 NalByteStream = function() { 9281 var 9282 syncPoint = 0, 9283 i, 9284 buffer; 9285 NalByteStream.prototype.init.call(this); 9286 9287 this.push = function(data) { 9288 var swapBuffer; 9289 9290 if (!buffer) { 9291 buffer = data.data; 9292 } else { 9293 swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength); 9294 swapBuffer.set(buffer); 9295 swapBuffer.set(data.data, buffer.byteLength); 9296 buffer = swapBuffer; 9297 } 9298 9299 // Rec. ITU-T H.264, Annex B 9300 // scan for NAL unit boundaries 9301 9302 // a match looks like this: 9303 // 0 0 1 .. NAL .. 0 0 1 9304 // ^ sync point ^ i 9305 // or this: 9306 // 0 0 1 .. NAL .. 0 0 0 9307 // ^ sync point ^ i 9308 9309 // advance the sync point to a NAL start, if necessary 9310 for (; syncPoint < buffer.byteLength - 3; syncPoint++) { 9311 if (buffer[syncPoint + 2] === 1) { 9312 // the sync point is properly aligned 9313 i = syncPoint + 5; 9314 break; 9315 } 9316 } 9317 9318 while (i < buffer.byteLength) { 9319 // look at the current byte to determine if we've hit the end of 9320 // a NAL unit boundary 9321 switch (buffer[i]) { 9322 case 0: 9323 // skip past non-sync sequences 9324 if (buffer[i - 1] !== 0) { 9325 i += 2; 9326 break; 9327 } else if (buffer[i - 2] !== 0) { 9328 i++; 9329 break; 9330 } 9331 9332 // deliver the NAL unit if it isn't empty 9333 if (syncPoint + 3 !== i - 2) { 9334 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 9335 } 9336 9337 // drop trailing zeroes 9338 do { 9339 i++; 9340 } while (buffer[i] !== 1 && i < buffer.length); 9341 syncPoint = i - 2; 9342 i += 3; 9343 break; 9344 case 1: 9345 // skip past non-sync sequences 9346 if (buffer[i - 1] !== 0 || 9347 buffer[i - 2] !== 0) { 9348 i += 3; 9349 break; 9350 } 9351 9352 // deliver the NAL unit 9353 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2)); 9354 syncPoint = i - 2; 9355 i += 3; 9356 break; 9357 default: 9358 // the current byte isn't a one or zero, so it cannot be part 9359 // of a sync sequence 9360 i += 3; 9361 break; 9362 } 9363 } 9364 // filter out the NAL units that were delivered 9365 buffer = buffer.subarray(syncPoint); 9366 i -= syncPoint; 9367 syncPoint = 0; 9368 }; 9369 9370 this.flush = function() { 9371 // deliver the last buffered NAL unit 9372 if (buffer && buffer.byteLength > 3) { 9373 this.trigger('data', buffer.subarray(syncPoint + 3)); 9374 } 9375 // reset the stream state 9376 buffer = null; 9377 syncPoint = 0; 9378 this.trigger('done'); 9379 }; 9380 }; 9381 NalByteStream.prototype = new Stream(); 9382 9383// values of profile_idc that indicate additional fields are included in the SPS 9384// see Recommendation ITU-T H.264 (4/2013), 9385// 7.3.2.1.1 Sequence parameter set data syntax 9386 PROFILES_WITH_OPTIONAL_SPS_DATA = { 9387 100: true, 9388 110: true, 9389 122: true, 9390 244: true, 9391 44: true, 9392 83: true, 9393 86: true, 9394 118: true, 9395 128: true, 9396 138: true, 9397 139: true, 9398 134: true 9399 }; 9400 9401 /** 9402 * Accepts input from a ElementaryStream and produces H.264 NAL unit data 9403 * events. 9404 */ 9405 H264Stream = function() { 9406 var 9407 nalByteStream = new NalByteStream(), 9408 self, 9409 trackId, 9410 currentPts, 9411 currentDts, 9412 9413 discardEmulationPreventionBytes, 9414 readSequenceParameterSet, 9415 skipScalingList; 9416 9417 H264Stream.prototype.init.call(this); 9418 self = this; 9419 9420 this.push = function(packet) { 9421 if (packet.type !== 'video') { 9422 return; 9423 } 9424 trackId = packet.trackId; 9425 currentPts = packet.pts; 9426 currentDts = packet.dts; 9427 9428 nalByteStream.push(packet); 9429 }; 9430 9431 nalByteStream.on('data', function(data) { 9432 var 9433 event = { 9434 trackId: trackId, 9435 pts: currentPts, 9436 dts: currentDts, 9437 data: data 9438 }; 9439 9440 switch (data[0] & 0x1f) { 9441 case 0x05: 9442 event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr'; 9443 break; 9444 case 0x06: 9445 event.nalUnitType = 'sei_rbsp'; 9446 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 9447 break; 9448 case 0x07: 9449 event.nalUnitType = 'seq_parameter_set_rbsp'; 9450 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1)); 9451 event.config = readSequenceParameterSet(event.escapedRBSP); 9452 break; 9453 case 0x08: 9454 event.nalUnitType = 'pic_parameter_set_rbsp'; 9455 break; 9456 case 0x09: 9457 event.nalUnitType = 'access_unit_delimiter_rbsp'; 9458 break; 9459 9460 default: 9461 break; 9462 } 9463 self.trigger('data', event); 9464 }); 9465 nalByteStream.on('done', function() { 9466 self.trigger('done'); 9467 }); 9468 9469 this.flush = function() { 9470 nalByteStream.flush(); 9471 }; 9472 9473 /** 9474 * Advance the ExpGolomb decoder past a scaling list. The scaling 9475 * list is optionally transmitted as part of a sequence parameter 9476 * set and is not relevant to transmuxing. 9477 * @param count {number} the number of entries in this scaling list 9478 * @param expGolombDecoder {object} an ExpGolomb pointed to the 9479 * start of a scaling list 9480 * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 9481 */ 9482 skipScalingList = function(count, expGolombDecoder) { 9483 var 9484 lastScale = 8, 9485 nextScale = 8, 9486 j, 9487 deltaScale; 9488 9489 for (j = 0; j < count; j++) { 9490 if (nextScale !== 0) { 9491 deltaScale = expGolombDecoder.readExpGolomb(); 9492 nextScale = (lastScale + deltaScale + 256) % 256; 9493 } 9494 9495 lastScale = (nextScale === 0) ? lastScale : nextScale; 9496 } 9497 }; 9498 9499 /** 9500 * Expunge any "Emulation Prevention" bytes from a "Raw Byte 9501 * Sequence Payload" 9502 * @param data {Uint8Array} the bytes of a RBSP from a NAL 9503 * unit 9504 * @return {Uint8Array} the RBSP without any Emulation 9505 * Prevention Bytes 9506 */ 9507 discardEmulationPreventionBytes = function(data) { 9508 var 9509 length = data.byteLength, 9510 emulationPreventionBytesPositions = [], 9511 i = 1, 9512 newLength, newData; 9513 9514 // Find all `Emulation Prevention Bytes` 9515 while (i < length - 2) { 9516 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { 9517 emulationPreventionBytesPositions.push(i + 2); 9518 i += 2; 9519 } else { 9520 i++; 9521 } 9522 } 9523 9524 // If no Emulation Prevention Bytes were found just return the original 9525 // array 9526 if (emulationPreventionBytesPositions.length === 0) { 9527 return data; 9528 } 9529 9530 // Create a new array to hold the NAL unit data 9531 newLength = length - emulationPreventionBytesPositions.length; 9532 newData = new Uint8Array(newLength); 9533 var sourceIndex = 0; 9534 9535 for (i = 0; i < newLength; sourceIndex++, i++) { 9536 if (sourceIndex === emulationPreventionBytesPositions[0]) { 9537 // Skip this byte 9538 sourceIndex++; 9539 // Remove this position index 9540 emulationPreventionBytesPositions.shift(); 9541 } 9542 newData[i] = data[sourceIndex]; 9543 } 9544 9545 return newData; 9546 }; 9547 9548 /** 9549 * Read a sequence parameter set and return some interesting video 9550 * properties. A sequence parameter set is the H264 metadata that 9551 * describes the properties of upcoming video frames. 9552 * @param data {Uint8Array} the bytes of a sequence parameter set 9553 * @return {object} an object with configuration parsed from the 9554 * sequence parameter set, including the dimensions of the 9555 * associated video frames. 9556 */ 9557 readSequenceParameterSet = function(data) { 9558 var 9559 frameCropLeftOffset = 0, 9560 frameCropRightOffset = 0, 9561 frameCropTopOffset = 0, 9562 frameCropBottomOffset = 0, 9563 sarScale = 1, 9564 expGolombDecoder, profileIdc, levelIdc, profileCompatibility, 9565 chromaFormatIdc, picOrderCntType, 9566 numRefFramesInPicOrderCntCycle, picWidthInMbsMinus1, 9567 picHeightInMapUnitsMinus1, 9568 frameMbsOnlyFlag, 9569 scalingListCount, 9570 sarRatio, 9571 aspectRatioIdc, 9572 i; 9573 9574 expGolombDecoder = new ExpGolomb(data); 9575 profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc 9576 profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag 9577 levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8) 9578 expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id 9579 9580 // some profiles have more optional data we don't need 9581 if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) { 9582 chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb(); 9583 if (chromaFormatIdc === 3) { 9584 expGolombDecoder.skipBits(1); // separate_colour_plane_flag 9585 } 9586 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8 9587 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8 9588 expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag 9589 if (expGolombDecoder.readBoolean()) { // seq_scaling_matrix_present_flag 9590 scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12; 9591 for (i = 0; i < scalingListCount; i++) { 9592 if (expGolombDecoder.readBoolean()) { // seq_scaling_list_present_flag[ i ] 9593 if (i < 6) { 9594 skipScalingList(16, expGolombDecoder); 9595 } else { 9596 skipScalingList(64, expGolombDecoder); 9597 } 9598 } 9599 } 9600 } 9601 } 9602 9603 expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4 9604 picOrderCntType = expGolombDecoder.readUnsignedExpGolomb(); 9605 9606 if (picOrderCntType === 0) { 9607 expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4 9608 } else if (picOrderCntType === 1) { 9609 expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag 9610 expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic 9611 expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field 9612 numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb(); 9613 for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) { 9614 expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ] 9615 } 9616 } 9617 9618 expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames 9619 expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag 9620 9621 picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 9622 picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); 9623 9624 frameMbsOnlyFlag = expGolombDecoder.readBits(1); 9625 if (frameMbsOnlyFlag === 0) { 9626 expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag 9627 } 9628 9629 expGolombDecoder.skipBits(1); // direct_8x8_inference_flag 9630 if (expGolombDecoder.readBoolean()) { // frame_cropping_flag 9631 frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb(); 9632 frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb(); 9633 frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb(); 9634 frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb(); 9635 } 9636 if (expGolombDecoder.readBoolean()) { 9637 // vui_parameters_present_flag 9638 if (expGolombDecoder.readBoolean()) { 9639 // aspect_ratio_info_present_flag 9640 aspectRatioIdc = expGolombDecoder.readUnsignedByte(); 9641 switch (aspectRatioIdc) { 9642 case 1: sarRatio = [1, 1]; break; 9643 case 2: sarRatio = [12, 11]; break; 9644 case 3: sarRatio = [10, 11]; break; 9645 case 4: sarRatio = [16, 11]; break; 9646 case 5: sarRatio = [40, 33]; break; 9647 case 6: sarRatio = [24, 11]; break; 9648 case 7: sarRatio = [20, 11]; break; 9649 case 8: sarRatio = [32, 11]; break; 9650 case 9: sarRatio = [80, 33]; break; 9651 case 10: sarRatio = [18, 11]; break; 9652 case 11: sarRatio = [15, 11]; break; 9653 case 12: sarRatio = [64, 33]; break; 9654 case 13: sarRatio = [160, 99]; break; 9655 case 14: sarRatio = [4, 3]; break; 9656 case 15: sarRatio = [3, 2]; break; 9657 case 16: sarRatio = [2, 1]; break; 9658 case 255: { 9659 sarRatio = [expGolombDecoder.readUnsignedByte() << 8 | 9660 expGolombDecoder.readUnsignedByte(), 9661 expGolombDecoder.readUnsignedByte() << 8 | 9662 expGolombDecoder.readUnsignedByte() ]; 9663 break; 9664 } 9665 } 9666 if (sarRatio) { 9667 sarScale = sarRatio[0] / sarRatio[1]; 9668 } 9669 } 9670 } 9671 return { 9672 profileIdc: profileIdc, 9673 levelIdc: levelIdc, 9674 profileCompatibility: profileCompatibility, 9675 width: Math.ceil((((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale), 9676 height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - (frameCropTopOffset * 2) - (frameCropBottomOffset * 2) 9677 }; 9678 }; 9679 9680 }; 9681 H264Stream.prototype = new Stream(); 9682 9683 module.exports = { 9684 H264Stream: H264Stream, 9685 NalByteStream: NalByteStream 9686 }; 9687 9688 },{"../utils/exp-golomb.js":59,"../utils/stream.js":60}],40:[function(require,module,exports){ 9689 var highPrefix = [33, 16, 5, 32, 164, 27]; 9690 var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252]; 9691 var zeroFill = function(count) { 9692 var a = []; 9693 while (count--) { 9694 a.push(0); 9695 } 9696 return a; 9697 }; 9698 9699 var makeTable = function(metaTable) { 9700 return Object.keys(metaTable).reduce(function(obj, key) { 9701 obj[key] = new Uint8Array(metaTable[key].reduce(function(arr, part) { 9702 return arr.concat(part); 9703 }, [])); 9704 return obj; 9705 }, {}); 9706 }; 9707 9708// Frames-of-silence to use for filling in missing AAC frames 9709 var coneOfSilence = { 9710 96000: [highPrefix, [227, 64], zeroFill(154), [56]], 9711 88200: [highPrefix, [231], zeroFill(170), [56]], 9712 64000: [highPrefix, [248, 192], zeroFill(240), [56]], 9713 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]], 9714 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]], 9715 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]], 9716 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]], 9717 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]], 9718 12000: [lowPrefix, zeroFill(268), [3, 127, 248], zeroFill(268), [6, 255, 240], zeroFill(268), [13, 255, 224], zeroFill(268), [27, 253, 128], zeroFill(259), [56]], 9719 11025: [lowPrefix, zeroFill(268), [3, 127, 248], zeroFill(268), [6, 255, 240], zeroFill(268), [13, 255, 224], zeroFill(268), [27, 255, 192], zeroFill(268), [55, 175, 128], zeroFill(108), [112]], 9720 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]] 9721 }; 9722 9723 module.exports = makeTable(coneOfSilence); 9724 9725 },{}],41:[function(require,module,exports){ 9726 'use strict'; 9727 9728 var Stream = require('../utils/stream.js'); 9729 9730 /** 9731 * The final stage of the transmuxer that emits the flv tags 9732 * for audio, video, and metadata. Also tranlates in time and 9733 * outputs caption data and id3 cues. 9734 */ 9735 var CoalesceStream = function(options) { 9736 // Number of Tracks per output segment 9737 // If greater than 1, we combine multiple 9738 // tracks into a single segment 9739 this.numberOfTracks = 0; 9740 this.metadataStream = options.metadataStream; 9741 9742 this.videoTags = []; 9743 this.audioTags = []; 9744 this.videoTrack = null; 9745 this.audioTrack = null; 9746 this.pendingCaptions = []; 9747 this.pendingMetadata = []; 9748 this.pendingTracks = 0; 9749 this.processedTracks = 0; 9750 9751 CoalesceStream.prototype.init.call(this); 9752 9753 // Take output from multiple 9754 this.push = function(output) { 9755 // buffer incoming captions until the associated video segment 9756 // finishes 9757 if (output.text) { 9758 return this.pendingCaptions.push(output); 9759 } 9760 // buffer incoming id3 tags until the final flush 9761 if (output.frames) { 9762 return this.pendingMetadata.push(output); 9763 } 9764
9765 if (output.track.type === 'video') { 9766 this.videoTrack = output.track; 9767 this.videoTags = output.tags; 9768 this.pendingTracks++; 9769 } 9770 if (output.track.type === 'audio') { 9771 this.audioTrack = output.track; 9772 this.audioTags = output.tags; 9773 this.pendingTracks++; 9774 } 9775 }; 9776 }; 9777 9778 CoalesceStream.prototype = new Stream(); 9779 CoalesceStream.prototype.flush = function(flushSource) { 9780 var 9781 id3, 9782 caption, 9783 i, 9784 timelineStartPts, 9785 event = { 9786 tags: {}, 9787 captions: [], 9788 metadata: [] 9789 }; 9790 9791 if (this.pendingTracks < this.numberOfTracks) { 9792 if (flushSource !== 'VideoSegmentStream' && 9793 flushSource !== 'AudioSegmentStream') { 9794 // Return because we haven't received a flush from a data-generating 9795 // portion of the segment (meaning that we have only recieved meta-data 9796 // or captions.) 9797 return; 9798 } else if (this.pendingTracks === 0) { 9799 // In the case where we receive a flush without any data having been 9800 // received we consider it an emitted track for the purposes of coalescing 9801 // `done` events. 9802 // We do this for the case where there is an audio and video track in the 9803 // segment but no audio data. (seen in several playlists with alternate 9804 // audio tracks and no audio present in the main TS segments.) 9805 this.processedTracks++; 9806 9807 if (this.processedTracks < this.numberOfTracks) { 9808 return; 9809 } 9810 } 9811 } 9812 9813 this.processedTracks += this.pendingTracks; 9814 this.pendingTracks = 0; 9815 9816 if (this.processedTracks < this.numberOfTracks) { 9817 return; 9818 } 9819 9820 if (this.videoTrack) { 9821 timelineStartPts = this.videoTrack.timelineStartInfo.pts; 9822 } else if (this.audioTrack) { 9823 timelineStartPts = this.audioTrack.timelineStartInfo.pts; 9824 } 9825 9826 event.tags.videoTags = this.videoTags; 9827 event.tags.audioTags = this.audioTags; 9828 9829 // Translate caption PTS times into second offsets into the 9830 // video timeline for the segment 9831 for (i = 0; i < this.pendingCaptions.length; i++) { 9832 caption = this.pendingCaptions[i]; 9833 caption.startTime = caption.startPts - timelineStartPts; 9834 caption.startTime /= 90e3; 9835 caption.endTime = caption.endPts - timelineStartPts; 9836 caption.endTime /= 90e3; 9837 event.captions.push(caption); 9838 } 9839 9840 // Translate ID3 frame PTS times into second offsets into the 9841 // video timeline for the segment 9842 for (i = 0; i < this.pendingMetadata.length; i++) { 9843 id3 = this.pendingMetadata[i]; 9844 id3.cueTime = id3.pts - timelineStartPts; 9845 id3.cueTime /= 90e3; 9846 event.metadata.push(id3); 9847 } 9848 // We add this to every single emitted segment even though we only need 9849 // it for the first 9850 event.metadata.dispatchType = this.metadataStream.dispatchType; 9851 9852 // Reset stream state 9853 this.videoTrack = null; 9854 this.audioTrack = null; 9855 this.videoTags = []; 9856 this.audioTags = []; 9857 this.pendingCaptions.length = 0; 9858 this.pendingMetadata.length = 0; 9859 this.pendingTracks = 0; 9860 this.processedTracks = 0; 9861 9862 // Emit the final segment 9863 this.trigger('data', event); 9864 9865 this.trigger('done'); 9866 }; 9867 9868 module.exports = CoalesceStream; 9869 9870 },{"../utils/stream.js":60}],42:[function(require,module,exports){ 9871 'use strict'; 9872 9873 var FlvTag = require('./flv-tag.js'); 9874 9875// For information on the FLV format, see 9876// http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf. 9877// Technically, this function returns the header and a metadata FLV tag 9878// if duration is greater than zero 9879// duration in seconds 9880// @return {object} the bytes of the FLV header as a Uint8Array 9881 var getFlvHeader = function(duration, audio, video) { // :ByteArray { 9882 var 9883 headBytes = new Uint8Array(3 + 1 + 1 + 4),
vendor: 16,081 bytes, lines 9884-10292
9884 head = new DataView(headBytes.buffer), 9885 metadata, 9886 result, 9887 metadataLength; 9888 9889 // default arguments 9890 duration = duration || 0; 9891 audio = audio === undefined ? true : audio; 9892 video = video === undefined ? true : video; 9893 9894 // signature 9895 head.setUint8(0, 0x46); // 'F' 9896 head.setUint8(1, 0x4c); // 'L' 9897 head.setUint8(2, 0x56); // 'V' 9898 9899 // version 9900 head.setUint8(3, 0x01); 9901 9902 // flags 9903 head.setUint8(4, (audio ? 0x04 : 0x00) | (video ? 0x01 : 0x00)); 9904 9905 // data offset, should be 9 for FLV v1 9906 head.setUint32(5, headBytes.byteLength); 9907 9908 // init the first FLV tag 9909 if (duration <= 0) { 9910 // no duration available so just write the first field of the first 9911 // FLV tag 9912 result = new Uint8Array(headBytes.byteLength + 4); 9913 result.set(headBytes); 9914 result.set([0, 0, 0, 0], headBytes.byteLength); 9915 return result; 9916 } 9917 9918 // write out the duration metadata tag 9919 metadata = new FlvTag(FlvTag.METADATA_TAG); 9920 metadata.pts = metadata.dts = 0; 9921 metadata.writeMetaDataDouble('duration', duration); 9922 metadataLength = metadata.finalize().length; 9923 result = new Uint8Array(headBytes.byteLength + metadataLength); 9924 result.set(headBytes); 9925 result.set(head.byteLength, metadataLength); 9926 9927 return result; 9928 }; 9929 9930 module.exports = getFlvHeader; 9931 9932 },{"./flv-tag.js":43}],43:[function(require,module,exports){ 9933 /** 9934 * An object that stores the bytes of an FLV tag and methods for 9935 * querying and manipulating that data. 9936 * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf 9937 */ 9938 'use strict'; 9939 9940 var FlvTag; 9941 9942// (type:uint, extraData:Boolean = false) extends ByteArray 9943 FlvTag = function(type, extraData) { 9944 var 9945 // Counter if this is a metadata tag, nal start marker if this is a video 9946 // tag. unused if this is an audio tag 9947 adHoc = 0, // :uint 9948 9949 // The default size is 16kb but this is not enough to hold iframe 9950 // data and the resizing algorithm costs a bit so we create a larger 9951 // starting buffer for video tags 9952 bufferStartSize = 16384, 9953 9954 // checks whether the FLV tag has enough capacity to accept the proposed 9955 // write and re-allocates the internal buffers if necessary 9956 prepareWrite = function(flv, count) { 9957 var 9958 bytes, 9959 minLength = flv.position + count; 9960 if (minLength < flv.bytes.byteLength) { 9961 // there's enough capacity so do nothing 9962 return; 9963 } 9964 9965 // allocate a new buffer and copy over the data that will not be modified 9966 bytes = new Uint8Array(minLength * 2); 9967 bytes.set(flv.bytes.subarray(0, flv.position), 0); 9968 flv.bytes = bytes; 9969 flv.view = new DataView(flv.bytes.buffer); 9970 }, 9971 9972 // commonly used metadata properties 9973 widthBytes = FlvTag.widthBytes || new Uint8Array('width'.length), 9974 heightBytes = FlvTag.heightBytes || new Uint8Array('height'.length), 9975 videocodecidBytes = FlvTag.videocodecidBytes || new Uint8Array('videocodecid'.length), 9976 i; 9977 9978 if (!FlvTag.widthBytes) { 9979 // calculating the bytes of common metadata names ahead of time makes the 9980 // corresponding writes faster because we don't have to loop over the 9981 // characters 9982 // re-test with test/perf.html if you're planning on changing this 9983 for (i = 0; i < 'width'.length; i++) { 9984 widthBytes[i] = 'width'.charCodeAt(i); 9985 } 9986 for (i = 0; i < 'height'.length; i++) { 9987 heightBytes[i] = 'height'.charCodeAt(i); 9988 } 9989 for (i = 0; i < 'videocodecid'.length; i++) { 9990 videocodecidBytes[i] = 'videocodecid'.charCodeAt(i); 9991 } 9992 9993 FlvTag.widthBytes = widthBytes; 9994 FlvTag.heightBytes = heightBytes; 9995 FlvTag.videocodecidBytes = videocodecidBytes; 9996 } 9997 9998 this.keyFrame = false; // :Boolean 9999 10000 switch (type) { 10001 case FlvTag.VIDEO_TAG: 10002 this.length = 16; 10003 // Start the buffer at 256k 10004 bufferStartSize *= 6; 10005 break; 10006 case FlvTag.AUDIO_TAG: 10007 this.length = 13; 10008 this.keyFrame = true; 10009 break; 10010 case FlvTag.METADATA_TAG: 10011 this.length = 29; 10012 this.keyFrame = true; 10013 break; 10014 default: 10015 throw new Error('Unknown FLV tag type'); 10016 } 10017 10018 this.bytes = new Uint8Array(bufferStartSize); 10019 this.view = new DataView(this.bytes.buffer); 10020 this.bytes[0] = type; 10021 this.position = this.length; 10022 this.keyFrame = extraData; // Defaults to false 10023 10024 // presentation timestamp 10025 this.pts = 0; 10026 // decoder timestamp 10027 this.dts = 0; 10028 10029 // ByteArray#writeBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0) 10030 this.writeBytes = function(bytes, offset, length) { 10031 var 10032 start = offset || 0, 10033 end; 10034 length = length || bytes.byteLength; 10035 end = start + length; 10036 10037 prepareWrite(this, length); 10038 this.bytes.set(bytes.subarray(start, end), this.position); 10039 10040 this.position += length; 10041 this.length = Math.max(this.length, this.position); 10042 }; 10043 10044 // ByteArray#writeByte(value:int):void 10045 this.writeByte = function(byte) { 10046 prepareWrite(this, 1); 10047 this.bytes[this.position] = byte; 10048 this.position++; 10049 this.length = Math.max(this.length, this.position); 10050 }; 10051 10052 // ByteArray#writeShort(value:int):void 10053 this.writeShort = function(short) { 10054 prepareWrite(this, 2); 10055 this.view.setUint16(this.position, short); 10056 this.position += 2; 10057 this.length = Math.max(this.length, this.position); 10058 }; 10059 10060 // Negative index into array 10061 // (pos:uint):int 10062 this.negIndex = function(pos) { 10063 return this.bytes[this.length - pos]; 10064 }; 10065 10066 // The functions below ONLY work when this[0] == VIDEO_TAG. 10067 // We are not going to check for that because we dont want the overhead 10068 // (nal:ByteArray = null):int 10069 this.nalUnitSize = function() { 10070 if (adHoc === 0) { 10071 return 0; 10072 } 10073 10074 return this.length - (adHoc + 4); 10075 }; 10076 10077 this.startNalUnit = function() { 10078 // remember position and add 4 bytes 10079 if (adHoc > 0) { 10080 throw new Error('Attempted to create new NAL wihout closing the old one'); 10081 } 10082 10083 // reserve 4 bytes for nal unit size 10084 adHoc = this.length; 10085 this.length += 4; 10086 this.position = this.length; 10087 }; 10088 10089 // (nal:ByteArray = null):void 10090 this.endNalUnit = function(nalContainer) { 10091 var 10092 nalStart, // :uint 10093 nalLength; // :uint 10094 10095 // Rewind to the marker and write the size 10096 if (this.length === adHoc + 4) { 10097 // we started a nal unit, but didnt write one, so roll back the 4 byte size value 10098 this.length -= 4; 10099 } else if (adHoc > 0) { 10100 nalStart = adHoc + 4; 10101 nalLength = this.length - nalStart; 10102 10103 this.position = adHoc; 10104 this.view.setUint32(this.position, nalLength); 10105 this.position = this.length; 10106 10107 if (nalContainer) { 10108 // Add the tag to the NAL unit 10109 nalContainer.push(this.bytes.subarray(nalStart, nalStart + nalLength)); 10110 } 10111 } 10112 10113 adHoc = 0; 10114 }; 10115 10116 /** 10117 * Write out a 64-bit floating point valued metadata property. This method is 10118 * called frequently during a typical parse and needs to be fast. 10119 */ 10120 // (key:String, val:Number):void 10121 this.writeMetaDataDouble = function(key, val) { 10122 var i; 10123 prepareWrite(this, 2 + key.length + 9); 10124 10125 // write size of property name 10126 this.view.setUint16(this.position, key.length); 10127 this.position += 2; 10128 10129 // this next part looks terrible but it improves parser throughput by 10130 // 10kB/s in my testing 10131 10132 // write property name 10133 if (key === 'width') { 10134 this.bytes.set(widthBytes, this.position); 10135 this.position += 5; 10136 } else if (key === 'height') { 10137 this.bytes.set(heightBytes, this.position); 10138 this.position += 6; 10139 } else if (key === 'videocodecid') { 10140 this.bytes.set(videocodecidBytes, this.position); 10141 this.position += 12; 10142 } else { 10143 for (i = 0; i < key.length; i++) { 10144 this.bytes[this.position] = key.charCodeAt(i); 10145 this.position++; 10146 } 10147 } 10148 10149 // skip null byte 10150 this.position++; 10151 10152 // write property value 10153 this.view.setFloat64(this.position, val); 10154 this.position += 8; 10155 10156 // update flv tag length 10157 this.length = Math.max(this.length, this.position); 10158 ++adHoc; 10159 }; 10160 10161 // (key:String, val:Boolean):void 10162 this.writeMetaDataBoolean = function(key, val) { 10163 var i; 10164 prepareWrite(this, 2); 10165 this.view.setUint16(this.position, key.length); 10166 this.position += 2; 10167 for (i = 0; i < key.length; i++) { 10168 // if key.charCodeAt(i) >= 255, handle error 10169 prepareWrite(this, 1); 10170 this.bytes[this.position] = key.charCodeAt(i); 10171 this.position++; 10172 } 10173 prepareWrite(this, 2); 10174 this.view.setUint8(this.position, 0x01); 10175 this.position++; 10176 this.view.setUint8(this.position, val ? 0x01 : 0x00); 10177 this.position++; 10178 this.length = Math.max(this.length, this.position); 10179 ++adHoc; 10180 }; 10181 10182 // ():ByteArray 10183 this.finalize = function() { 10184 var 10185 dtsDelta, // :int 10186 len; // :int 10187 10188 switch (this.bytes[0]) { 10189 // Video Data 10190 case FlvTag.VIDEO_TAG: 10191 // We only support AVC, 1 = key frame (for AVC, a seekable 10192 // frame), 2 = inter frame (for AVC, a non-seekable frame) 10193 this.bytes[11] = ((this.keyFrame || extraData) ? 0x10 : 0x20) | 0x07; 10194 this.bytes[12] = extraData ? 0x00 : 0x01; 10195 10196 dtsDelta = this.pts - this.dts; 10197 this.bytes[13] = (dtsDelta & 0x00FF0000) >>> 16; 10198 this.bytes[14] = (dtsDelta & 0x0000FF00) >>> 8; 10199 this.bytes[15] = (dtsDelta & 0x000000FF) >>> 0; 10200 break; 10201 10202 case FlvTag.AUDIO_TAG: 10203 this.bytes[11] = 0xAF; // 44 kHz, 16-bit stereo 10204 this.bytes[12] = extraData ? 0x00 : 0x01; 10205 break; 10206 10207 case FlvTag.METADATA_TAG: 10208 this.position = 11; 10209 this.view.setUint8(this.position, 0x02); // String type 10210 this.position++; 10211 this.view.setUint16(this.position, 0x0A); // 10 Bytes 10212 this.position += 2; 10213 // set "onMetaData" 10214 this.bytes.set([0x6f, 0x6e, 0x4d, 0x65, 10215 0x74, 0x61, 0x44, 0x61, 10216 0x74, 0x61], this.position); 10217 this.position += 10; 10218 this.bytes[this.position] = 0x08; // Array type 10219 this.position++; 10220 this.view.setUint32(this.position, adHoc); 10221 this.position = this.length; 10222 this.bytes.set([0, 0, 9], this.position); 10223 this.position += 3; // End Data Tag 10224 this.length = this.position; 10225 break; 10226 } 10227 10228 len = this.length - 11; 10229 10230 // write the DataSize field 10231 this.bytes[ 1] = (len & 0x00FF0000) >>> 16; 10232 this.bytes[ 2] = (len & 0x0000FF00) >>> 8; 10233 this.bytes[ 3] = (len & 0x000000FF) >>> 0; 10234 // write the Timestamp 10235 this.bytes[ 4] = (this.dts & 0x00FF0000) >>> 16; 10236 this.bytes[ 5] = (this.dts & 0x0000FF00) >>> 8; 10237 this.bytes[ 6] = (this.dts & 0x000000FF) >>> 0; 10238 this.bytes[ 7] = (this.dts & 0xFF000000) >>> 24; 10239 // write the StreamID 10240 this.bytes[ 8] = 0; 10241 this.bytes[ 9] = 0; 10242 this.bytes[10] = 0; 10243 10244 // Sometimes we're at the end of the view and have one slot to write a 10245 // uint32, so, prepareWrite of count 4, since, view is uint8 10246 prepareWrite(this, 4); 10247 this.view.setUint32(this.length, this.length); 10248 this.length += 4; 10249 this.position += 4; 10250 10251 // trim down the byte buffer to what is actually being used 10252 this.bytes = this.bytes.subarray(0, this.length); 10253 this.frameTime = FlvTag.frameTime(this.bytes); 10254 // if bytes.bytelength isn't equal to this.length, handle error 10255 return this; 10256 }; 10257 }; 10258 10259 FlvTag.AUDIO_TAG = 0x08; // == 8, :uint 10260 FlvTag.VIDEO_TAG = 0x09; // == 9, :uint 10261 FlvTag.METADATA_TAG = 0x12; // == 18, :uint 10262 10263// (tag:ByteArray):Boolean { 10264 FlvTag.isAudioFrame = function(tag) { 10265 return FlvTag.AUDIO_TAG === tag[0]; 10266 }; 10267 10268// (tag:ByteArray):Boolean { 10269 FlvTag.isVideoFrame = function(tag) { 10270 return FlvTag.VIDEO_TAG === tag[0]; 10271 }; 10272 10273// (tag:ByteArray):Boolean { 10274 FlvTag.isMetaData = function(tag) { 10275 return FlvTag.METADATA_TAG === tag[0]; 10276 }; 10277 10278// (tag:ByteArray):Boolean { 10279 FlvTag.isKeyFrame = function(tag) { 10280 if (FlvTag.isVideoFrame(tag)) { 10281 return tag[11] === 0x17; 10282 } 10283 10284 if (FlvTag.isAudioFrame(tag)) { 10285 return true; 10286 } 10287 10288 if (FlvTag.isMetaData(tag)) { 10289 return true; 10290 } 10291 10292 return false;
10293 }; 10294 10295// (tag:ByteArray):uint { 10296 FlvTag.frameTime = function(tag) { 10297 var pts = tag[ 4] << 16; // :uint 10298 pts |= tag[ 5] << 8; 10299 pts |= tag[ 6] << 0; 10300 pts |= tag[ 7] << 24; 10301 return pts; 10302 }; 10303 10304 module.exports = FlvTag; 10305 10306 },{}],44:[function(require,module,exports){ 10307 module.exports = { 10308 tag: require('./flv-tag'), 10309 Transmuxer: require('./transmuxer'), 10310 getFlvHeader: require('./flv-header') 10311 }; 10312 10313 },{"./flv-header":42,"./flv-tag":43,"./transmuxer":46}],45:[function(require,module,exports){ 10314 'use strict'; 10315 10316 var TagList = function() { 10317 var self = this; 10318 10319 this.list = []; 10320 10321 this.push = function(tag) { 10322 this.list.push({ 10323 bytes: tag.bytes, 10324 dts: tag.dts, 10325 pts: tag.pts, 10326 keyFrame: tag.keyFrame, 10327 metaDataTag: tag.metaDataTag 10328 }); 10329 }; 10330 10331 Object.defineProperty(this, 'length', { 10332 get: function() { 10333 return self.list.length; 10334 } 10335 }); 10336 }; 10337 10338 module.exports = TagList; 10339 10340 },{}],46:[function(require,module,exports){ 10341 'use strict'; 10342 10343 var Stream = require('../utils/stream.js'); 10344 var FlvTag = require('./flv-tag.js'); 10345 var m2ts = require('../m2ts/m2ts.js'); 10346 var AdtsStream = require('../codecs/adts.js'); 10347 var H264Stream = require('../codecs/h264').H264Stream; 10348 var CoalesceStream = require('./coalesce-stream.js'); 10349 var TagList = require('./tag-list.js'); 10350 10351 var 10352 Transmuxer, 10353 VideoSegmentStream, 10354 AudioSegmentStream, 10355 collectTimelineInfo, 10356 metaDataTag, 10357 extraDataTag; 10358 10359 /** 10360 * Store information about the start and end of the tracka and the 10361 * duration for each frame/sample we process in order to calculate 10362 * the baseMediaDecodeTime 10363 */ 10364 collectTimelineInfo = function(track, data) { 10365 if (typeof data.pts === 'number') { 10366 if (track.timelineStartInfo.pts === undefined) { 10367 track.timelineStartInfo.pts = data.pts; 10368 } else { 10369 track.timelineStartInfo.pts = 10370 Math.min(track.timelineStartInfo.pts, data.pts); 10371 } 10372 } 10373 10374 if (typeof data.dts === 'number') { 10375 if (track.timelineStartInfo.dts === undefined) { 10376 track.timelineStartInfo.dts = data.dts; 10377 } else { 10378 track.timelineStartInfo.dts = 10379 Math.min(track.timelineStartInfo.dts, data.dts); 10380 } 10381 } 10382 }; 10383 10384 metaDataTag = function(track, pts) { 10385 var 10386 tag = new FlvTag(FlvTag.METADATA_TAG); // :FlvTag 10387 10388 tag.dts = pts; 10389 tag.pts = pts; 10390 10391 tag.writeMetaDataDouble('videocodecid', 7); 10392 tag.writeMetaDataDouble('width', track.width); 10393 tag.writeMetaDataDouble('height', track.height); 10394 10395 return tag; 10396 }; 10397 10398 extraDataTag = function(track, pts) { 10399 var 10400 i, 10401 tag = new FlvTag(FlvTag.VIDEO_TAG, true); 10402 10403 tag.dts = pts; 10404 tag.pts = pts; 10405 10406 tag.writeByte(0x01);// version 10407 tag.writeByte(track.profileIdc);// profile 10408 tag.writeByte(track.profileCompatibility);// compatibility 10409 tag.writeByte(track.levelIdc);// level 10410 tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits) 10411 tag.writeByte(0xE0 | 0x01); // reserved (3 bits), num of SPS (5 bits) 10412 tag.writeShort(track.sps[0].length); // data of SPS 10413 tag.writeBytes(track.sps[0]); // SPS 10414 10415 tag.writeByte(track.pps.length); // num of PPS (will there ever be more that 1 PPS?) 10416 for (i = 0; i < track.pps.length; ++i) { 10417 tag.writeShort(track.pps[i].length); // 2 bytes for length of PPS 10418 tag.writeBytes(track.pps[i]); // data of PPS 10419 } 10420 10421 return tag; 10422 }; 10423 10424 /** 10425 * Constructs a single-track, media segment from AAC data 10426 * events. The output of this stream can be fed to flash. 10427 */ 10428 AudioSegmentStream = function(track) { 10429 var 10430 adtsFrames = [], 10431 oldExtraData; 10432 10433 AudioSegmentStream.prototype.init.call(this); 10434 10435 this.push = function(data) { 10436 collectTimelineInfo(track, data); 10437 10438 if (track && track.channelcount === undefined) { 10439 track.audioobjecttype = data.audioobjecttype; 10440 track.channelcount = data.channelcount; 10441 track.samplerate = data.samplerate; 10442 track.samplingfrequencyindex = data.samplingfrequencyindex; 10443 track.samplesize = data.samplesize; 10444 track.extraData = (track.audioobjecttype << 11) | 10445 (track.samplingfrequencyindex << 7) | 10446 (track.channelcount << 3); 10447 } 10448 10449 data.pts = Math.round(data.pts / 90); 10450 data.dts = Math.round(data.dts / 90); 10451 10452 // buffer audio data until end() is called 10453 adtsFrames.push(data); 10454 }; 10455 10456 this.flush = function() { 10457 var currentFrame, adtsFrame, lastMetaPts, tags = new TagList(); 10458 // return early if no audio data has been observed 10459 if (adtsFrames.length === 0) { 10460 this.trigger('done', 'AudioSegmentStream'); 10461 return; 10462 } 10463 10464 lastMetaPts = -Infinity; 10465 10466 while (adtsFrames.length) { 10467 currentFrame = adtsFrames.shift(); 10468 10469 // write out metadata tags every 1 second so that the decoder 10470 // is re-initialized quickly after seeking into a different 10471 // audio configuration 10472 if (track.extraData !== oldExtraData || currentFrame.pts - lastMetaPts >= 1000) { 10473 adtsFrame = new FlvTag(FlvTag.METADATA_TAG); 10474 adtsFrame.pts = currentFrame.pts; 10475 adtsFrame.dts = currentFrame.dts; 10476 10477 // AAC is always 10 10478 adtsFrame.writeMetaDataDouble('audiocodecid', 10); 10479 adtsFrame.writeMetaDataBoolean('stereo', track.channelcount === 2); 10480 adtsFrame.writeMetaDataDouble('audiosamplerate', track.samplerate); 10481 // Is AAC always 16 bit? 10482 adtsFrame.writeMetaDataDouble('audiosamplesize', 16); 10483 10484 tags.push(adtsFrame.finalize()); 10485 10486 oldExtraData = track.extraData; 10487 10488 adtsFrame = new FlvTag(FlvTag.AUDIO_TAG, true); 10489 // For audio, DTS is always the same as PTS. We want to set the DTS 10490 // however so we can compare with video DTS to determine approximate 10491 // packet order 10492 adtsFrame.pts = currentFrame.pts; 10493 adtsFrame.dts = currentFrame.dts; 10494 10495 adtsFrame.view.setUint16(adtsFrame.position, track.extraData); 10496 adtsFrame.position += 2; 10497 adtsFrame.length = Math.max(adtsFrame.length, adtsFrame.position); 10498 10499 tags.push(adtsFrame.finalize()); 10500 10501 lastMetaPts = currentFrame.pts; 10502 } 10503 adtsFrame = new FlvTag(FlvTag.AUDIO_TAG); 10504 adtsFrame.pts = currentFrame.pts; 10505 adtsFrame.dts = currentFrame.dts; 10506 10507 adtsFrame.writeBytes(currentFrame.data); 10508 10509 tags.push(adtsFrame.finalize()); 10510 } 10511 10512 oldExtraData = null; 10513 this.trigger('data', {track: track, tags: tags.list}); 10514 10515 this.trigger('done', 'AudioSegmentStream'); 10516 }; 10517 }; 10518 AudioSegmentStream.prototype = new Stream(); 10519 10520 /** 10521 * Store FlvTags for the h264 stream 10522 * @param track {object} track metadata configuration 10523 */ 10524 VideoSegmentStream = function(track) { 10525 var 10526 nalUnits = [], 10527 config, 10528 h264Frame; 10529 VideoSegmentStream.prototype.init.call(this); 10530 10531 this.finishFrame = function(tags, frame) { 10532 if (!frame) { 10533 return; 10534 } 10535 // Check if keyframe and the length of tags. 10536 // This makes sure we write metadata on the first frame of a segment. 10537 if (config && track && track.newMetadata && 10538 (frame.keyFrame || tags.length === 0)) { 10539 // Push extra data on every IDR frame in case we did a stream change + seek 10540 var metaTag = metaDataTag(config, frame.dts).finalize(); 10541 var extraTag = extraDataTag(track, frame.dts).finalize(); 10542 10543 metaTag.metaDataTag = extraTag.metaDataTag = true; 10544 10545 tags.push(metaTag); 10546 tags.push(extraTag); 10547 track.newMetadata = false;
10548 } 10549 10550 frame.endNalUnit(); 10551 tags.push(frame.finalize()); 10552 h264Frame = null; 10553 }; 10554 10555 this.push = function(data) { 10556 collectTimelineInfo(track, data); 10557 10558 data.pts = Math.round(data.pts / 90); 10559 data.dts = Math.round(data.dts / 90); 10560 10561 // buffer video until flush() is called 10562 nalUnits.push(data); 10563 }; 10564 10565 this.flush = function() { 10566 var 10567 currentNal, 10568 tags = new TagList(); 10569 10570 // Throw away nalUnits at the start of the byte stream until we find 10571 // the first AUD 10572 while (nalUnits.length) { 10573 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { 10574 break; 10575 } 10576 nalUnits.shift(); 10577 } 10578 10579 // return early if no video data has been observed 10580 if (nalUnits.length === 0) { 10581 this.trigger('done', 'VideoSegmentStream'); 10582 return; 10583 } 10584 10585 while (nalUnits.length) { 10586 currentNal = nalUnits.shift(); 10587 10588 // record the track config 10589 if (currentNal.nalUnitType === 'seq_parameter_set_rbsp') { 10590 track.newMetadata = true; 10591 config = currentNal.config; 10592 track.width = config.width; 10593 track.height = config.height; 10594 track.sps = [currentNal.data]; 10595 track.profileIdc = config.profileIdc; 10596 track.levelIdc = config.levelIdc; 10597 track.profileCompatibility = config.profileCompatibility; 10598 h264Frame.endNalUnit(); 10599 } else if (currentNal.nalUnitType === 'pic_parameter_set_rbsp') { 10600 track.newMetadata = true; 10601 track.pps = [currentNal.data]; 10602 h264Frame.endNalUnit(); 10603 } else if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { 10604 if (h264Frame) { 10605 this.finishFrame(tags, h264Frame); 10606 } 10607 h264Frame = new FlvTag(FlvTag.VIDEO_TAG); 10608 h264Frame.pts = currentNal.pts; 10609 h264Frame.dts = currentNal.dts; 10610 } else { 10611 if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { 10612 // the current sample is a key frame 10613 h264Frame.keyFrame = true; 10614 } 10615 h264Frame.endNalUnit(); 10616 } 10617 h264Frame.startNalUnit(); 10618 h264Frame.writeBytes(currentNal.data); 10619 } 10620 if (h264Frame) { 10621 this.finishFrame(tags, h264Frame); 10622 } 10623 10624 this.trigger('data', {track: track, tags: tags.list}); 10625 10626 // Continue with the flush process now 10627 this.trigger('done', 'VideoSegmentStream'); 10628 }; 10629 }; 10630 10631 VideoSegmentStream.prototype = new Stream(); 10632 10633 /** 10634 * An object that incrementally transmuxes MPEG2 Trasport Stream 10635 * chunks into an FLV. 10636 */ 10637 Transmuxer = function(options) { 10638 var 10639 self = this, 10640 10641 packetStream, parseStream, elementaryStream, 10642 videoTimestampRolloverStream, audioTimestampRolloverStream, 10643 timedMetadataTimestampRolloverStream, 10644 adtsStream, h264Stream, 10645 videoSegmentStream, audioSegmentStream, captionStream, 10646 coalesceStream; 10647 10648 Transmuxer.prototype.init.call(this); 10649 10650 options = options || {}; 10651 10652 // expose the metadata stream 10653 this.metadataStream = new m2ts.MetadataStream(); 10654 10655 options.metadataStream = this.metadataStream; 10656 10657 // set up the parsing pipeline 10658 packetStream = new m2ts.TransportPacketStream(); 10659 parseStream = new m2ts.TransportParseStream(); 10660 elementaryStream = new m2ts.ElementaryStream(); 10661 videoTimestampRolloverStream = new m2ts.TimestampRolloverStream('video'); 10662 audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio'); 10663 timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata'); 10664 10665 adtsStream = new AdtsStream(); 10666 h264Stream = new H264Stream(); 10667 coalesceStream = new CoalesceStream(options); 10668 10669 // disassemble MPEG2-TS packets into elementary streams 10670 packetStream 10671 .pipe(parseStream) 10672 .pipe(elementaryStream); 10673 10674 // !!THIS ORDER IS IMPORTANT!! 10675 // demux the streams 10676 elementaryStream 10677 .pipe(videoTimestampRolloverStream) 10678 .pipe(h264Stream); 10679 elementaryStream 10680 .pipe(audioTimestampRolloverStream) 10681 .pipe(adtsStream); 10682 10683 elementaryStream 10684 .pipe(timedMetadataTimestampRolloverStream) 10685 .pipe(this.metadataStream) 10686 .pipe(coalesceStream); 10687 // if CEA-708 parsing is available, hook up a caption stream 10688 captionStream = new m2ts.CaptionStream(); 10689 h264Stream.pipe(captionStream) 10690 .pipe(coalesceStream); 10691 10692 // hook up the segment streams once track metadata is delivered 10693 elementaryStream.on('data', function(data) { 10694 var i, videoTrack, audioTrack; 10695 10696 if (data.type === 'metadata') { 10697 i = data.tracks.length; 10698 10699 // scan the tracks listed in the metadata 10700 while (i--) { 10701 if (data.tracks[i].type === 'video') { 10702 videoTrack = data.tracks[i]; 10703 } else if (data.tracks[i].type === 'audio') { 10704 audioTrack = data.tracks[i]; 10705 } 10706 } 10707 10708 // hook up the video segment stream to the first track with h264 data 10709 if (videoTrack && !videoSegmentStream) { 10710 coalesceStream.numberOfTracks++; 10711 videoSegmentStream = new VideoSegmentStream(videoTrack); 10712 10713 // Set up the final part of the video pipeline 10714 h264Stream 10715 .pipe(videoSegmentStream) 10716 .pipe(coalesceStream); 10717 } 10718 10719 if (audioTrack && !audioSegmentStream) { 10720 // hook up the audio segment stream to the first track with aac data 10721 coalesceStream.numberOfTracks++; 10722 audioSegmentStream = new AudioSegmentStream(audioTrack); 10723 10724 // Set up the final part of the audio pipeline 10725 adtsStream 10726 .pipe(audioSegmentStream) 10727 .pipe(coalesceStream); 10728 } 10729 } 10730 }); 10731 10732 // feed incoming data to the front of the parsing pipeline 10733 this.push = function(data) { 10734 packetStream.push(data); 10735 }; 10736 10737 // flush any buffered data 10738 this.flush = function() { 10739 // Start at the top of the pipeline and flush all pending work 10740 packetStream.flush(); 10741 }; 10742 10743 // Re-emit any data coming from the coalesce stream to the outside world 10744 coalesceStream.on('data', function(event) { 10745 self.trigger('data', event); 10746 }); 10747 10748 // Let the consumer know we have finished flushing the entire pipeline 10749 coalesceStream.on('done', function() { 10750 self.trigger('done'); 10751 }); 10752 }; 10753 Transmuxer.prototype = new Stream(); 10754 10755// forward compatibility 10756 module.exports = Transmuxer; 10757 10758 },{"../codecs/adts.js":38,"../codecs/h264":39,"../m2ts/m2ts.js":48,"../utils/stream.js":60,"./coalesce-stream.js":41,"./flv-tag.js":43,"./tag-list.js":45}],47:[function(require,module,exports){ 10759 /** 10760 * mux.js 10761 * 10762 * Copyright (c) 2015 Brightcove 10763 * All rights reserved. 10764 * 10765 * Reads in-band caption information from a video elementary 10766 * stream. Captions must follow the CEA-708 standard for injection 10767 * into an MPEG-2 transport streams. 10768 * @see https://en.wikipedia.org/wiki/CEA-708 10769 */ 10770 10771 'use strict'; 10772 10773// ----------------- 10774// Link To Transport 10775// ----------------- 10776 10777// Supplemental enhancement information (SEI) NAL units have a 10778// payload type field to indicate how they are to be 10779// interpreted. CEAS-708 caption content is always transmitted with 10780// payload type 0x04. 10781 var USER_DATA_REGISTERED_ITU_T_T35 = 4, 10782 RBSP_TRAILING_BITS = 128, 10783 Stream = require('../utils/stream'); 10784 10785 /** 10786 * Parse a supplemental enhancement information (SEI) NAL unit. 10787 * Stops parsing once a message of type ITU T T35 has been found. 10788 * 10789 * @param bytes {Uint8Array} the bytes of a SEI NAL unit 10790 * @return {object} the parsed SEI payload 10791 * @see Rec. ITU-T H.264, 7.3.2.3.1 10792 */ 10793 var parseSei = function(bytes) { 10794 var 10795 i = 0, 10796 result = { 10797 payloadType: -1, 10798 payloadSize: 0 10799 }, 10800 payloadType = 0, 10801 payloadSize = 0; 10802 10803 // go through the sei_rbsp parsing each each individual sei_message 10804 while (i < bytes.byteLength) { 10805 // stop once we have hit the end of the sei_rbsp 10806 if (bytes[i] === RBSP_TRAILING_BITS) { 10807 break; 10808 } 10809 10810 // Parse payload type 10811 while (bytes[i] === 0xFF) { 10812 payloadType += 255; 10813 i++; 10814 } 10815 payloadType += bytes[i++]; 10816 10817 // Parse payload size 10818 while (bytes[i] === 0xFF) { 10819 payloadSize += 255; 10820 i++; 10821 } 10822 payloadSize += bytes[i++]; 10823 10824 // this sei_message is a 608/708 caption so save it and break 10825 // there can only ever be one caption message in a frame's sei 10826 if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) { 10827 result.payloadType = payloadType; 10828 result.payloadSize = payloadSize; 10829 result.payload = bytes.subarray(i, i + payloadSize); 10830 break; 10831 } 10832 10833 // skip the payload and parse the next message 10834 i += payloadSize; 10835 payloadType = 0; 10836 payloadSize = 0; 10837 } 10838 10839 return result; 10840 }; 10841 10842// see ANSI/SCTE 128-1 (2013), section 8.1 10843 var parseUserData = function(sei) { 10844 // itu_t_t35_contry_code must be 181 (United States) for 10845 // captions 10846 if (sei.payload[0] !== 181) { 10847 return null; 10848 } 10849 10850 // itu_t_t35_provider_code should be 49 (ATSC) for captions 10851 if (((sei.payload[1] << 8) | sei.payload[2]) !== 49) { 10852 return null; 10853 } 10854 10855 // the user_identifier should be "GA94" to indicate ATSC1 data 10856 if (String.fromCharCode(sei.payload[3], 10857 sei.payload[4], 10858 sei.payload[5], 10859 sei.payload[6]) !== 'GA94') { 10860 return null; 10861 } 10862 10863 // finally, user_data_type_code should be 0x03 for caption data 10864 if (sei.payload[7] !== 0x03) { 10865 return null; 10866 } 10867 10868 // return the user_data_type_structure and strip the trailing 10869 // marker bits 10870 return sei.payload.subarray(8, sei.payload.length - 1); 10871 }; 10872 10873// see CEA-708-D, section 4.4 10874 var parseCaptionPackets = function(pts, userData) { 10875 var results = [], i, count, offset, data; 10876 10877 // if this is just filler, return immediately 10878 if (!(userData[0] & 0x40)) { 10879 return results; 10880 } 10881 10882 // parse out the cc_data_1 and cc_data_2 fields 10883 count = userData[0] & 0x1f; 10884 for (i = 0; i < count; i++) { 10885 offset = i * 3; 10886 data = { 10887 type: userData[offset + 2] & 0x03, 10888 pts: pts 10889 }; 10890 10891 // capture cc data when cc_valid is 1 10892 if (userData[offset + 2] & 0x04) { 10893 data.ccData = (userData[offset + 3] << 8) | userData[offset + 4]; 10894 results.push(data); 10895 } 10896 } 10897 return results; 10898 }; 10899 10900 var CaptionStream = function() { 10901 CaptionStream.prototype.init.call(this); 10902 10903 this.captionPackets_ = []; 10904 10905 this.field1_ = new Cea608Stream(); // eslint-disable-line no-use-before-define 10906 10907 // forward data and done events from field1_ to this CaptionStream 10908 this.field1_.on('data', this.trigger.bind(this, 'data')); 10909 this.field1_.on('done', this.trigger.bind(this, 'done')); 10910 }; 10911 CaptionStream.prototype = new Stream(); 10912 CaptionStream.prototype.push = function(event) { 10913 var sei, userData; 10914 10915 // only examine SEI NALs 10916 if (event.nalUnitType !== 'sei_rbsp') { 10917 return; 10918 } 10919 10920 // parse the sei 10921 sei = parseSei(event.escapedRBSP); 10922 10923 // ignore everything but user_data_registered_itu_t_t35 10924 if (sei.payloadType !== USER_DATA_REGISTERED_ITU_T_T35) { 10925 return; 10926 } 10927 10928 // parse out the user data payload 10929 userData = parseUserData(sei); 10930 10931 // ignore unrecognized userData 10932 if (!userData) { 10933 return; 10934 } 10935 10936 // parse out CC data packets and save them for later 10937 this.captionPackets_ = this.captionPackets_.concat(parseCaptionPackets(event.pts, userData)); 10938 }; 10939 10940 CaptionStream.prototype.flush = function() { 10941 // make sure we actually parsed captions before proceeding 10942 if (!this.captionPackets_.length) { 10943 this.field1_.flush(); 10944 return; 10945 } 10946 10947 // In Chrome, the Array#sort function is not stable so add a 10948 // presortIndex that we can use to ensure we get a stable-sort 10949 this.captionPackets_.forEach(function(elem, idx) { 10950 elem.presortIndex = idx; 10951 }); 10952 10953 // sort caption byte-pairs based on their PTS values 10954 this.captionPackets_.sort(function(a, b) { 10955 if (a.pts === b.pts) { 10956 return a.presortIndex - b.presortIndex; 10957 } 10958 return a.pts - b.pts; 10959 }); 10960 10961 // Push each caption into Cea608Stream 10962 this.captionPackets_.forEach(this.field1_.push, this.field1_); 10963 10964 this.captionPackets_.length = 0; 10965 this.field1_.flush(); 10966 return; 10967 }; 10968// ---------------------- 10969// Session to Application 10970// ---------------------- 10971 10972 var BASIC_CHARACTER_TRANSLATION = { 10973 0x2a: 0xe1, 10974 0x5c: 0xe9, 10975 0x5e: 0xed, 10976 0x5f: 0xf3, 10977 0x60: 0xfa, 10978 0x7b: 0xe7, 10979 0x7c: 0xf7, 10980 0x7d: 0xd1, 10981 0x7e: 0xf1, 10982 0x7f: 0x2588 10983 }; 10984 10985 var getCharFromCode = function(code) { 10986 if (code === null) { 10987 return ''; 10988 } 10989 code = BASIC_CHARACTER_TRANSLATION[code] || code; 10990 return String.fromCharCode(code); 10991 }; 10992 10993// Constants for the byte codes recognized b
10993y Cea608Stream. This 10994// list is not exhaustive. For a more comprehensive listing and 10995// semantics see 10996// http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf 10997 var PADDING = 0x0000, 10998 10999 // Pop-on Mode 11000 RESUME_CAPTION_LOADING = 0x1420, 11001 END_OF_CAPTION = 0x142f, 11002 11003 // Roll-up Mode 11004 ROLL_UP_2_ROWS = 0x1425, 11005 ROLL_UP_3_ROWS = 0x1426, 11006 ROLL_UP_4_ROWS = 0x1427, 11007 CARRIAGE_RETURN = 0x142d, 11008 // Erasure 11009 BACKSPACE = 0x1421, 11010 ERASE_DISPLAYED_MEMORY = 0x142c, 11011 ERASE_NON_DISPLAYED_MEMORY = 0x142e; 11012 11013// the index of the last row in a CEA-608 display buffer 11014 var BOTTOM_ROW = 14; 11015// CEA-608 captions are rendered onto a 34x15 matrix of character 11016// cells. The "bottom" row is the last element in the outer array. 11017 var createDisplayBuffer = function() { 11018 var result = [], i = BOTTOM_ROW + 1; 11019 while (i--) { 11020 result.push(''); 11021 } 11022 return result; 11023 }; 11024 11025 var Cea608Stream = function() { 11026 Cea608Stream.prototype.init.call(this); 11027 11028 this.mode_ = 'popOn'; 11029 // When in roll-up mode, the index of the last row that will 11030 // actually display captions. If a caption is shifted to a row 11031 // with a lower index than this, it is cleared from the display 11032 // buffer 11033 this.topRow_ = 0; 11034 this.startPts_ = 0; 11035 this.displayed_ = createDisplayBuffer(); 11036 this.nonDisplayed_ = createDisplayBuffer(); 11037 this.lastControlCode_ = null; 11038 11039 this.push = function(packet) { 11040 // Ignore other channels 11041 if (packet.type !== 0) { 11042 return; 11043 } 11044 var data, swap, char0, char1; 11045 // remove the parity bits 11046 data = packet.ccData & 0x7f7f; 11047 11048 // ignore duplicate control codes 11049 if (data === this.lastControlCode_) { 11050 this.lastControlCode_ = null; 11051 return; 11052 } 11053 11054 // Store control codes 11055 if ((data & 0xf000) === 0x1000) { 11056 this.lastControlCode_ = data; 11057 } else { 11058 this.lastControlCode_ = null; 11059 } 11060 11061 switch (data) { 11062 case PADDING: 11063 break; 11064 case RESUME_CAPTION_LOADING: 11065 this.mode_ = 'popOn'; 11066 break; 11067 case END_OF_CAPTION: 11068 // if a caption was being displayed, it's gone now 11069 this.flushDisplayed(packet.pts); 11070 11071 // flip memory 11072 swap = this.displayed_; 11073 this.displayed_ = this.nonDisplayed_; 11074 this.nonDisplayed_ = swap; 11075 11076 // start measuring the time to display the caption 11077 this.startPts_ = packet.pts; 11078 break; 11079 11080 case ROLL_UP_2_ROWS: 11081 this.topRow_ = BOTTOM_ROW - 1; 11082 this.mode_ = 'rollUp'; 11083 break; 11084 case ROLL_UP_3_ROWS: 11085 this.topRow_ = BOTTOM_ROW - 2; 11086 this.mode_ = 'rollUp'; 11087 break; 11088 case ROLL_UP_4_ROWS: 11089 this.topRow_ = BOTTOM_ROW - 3; 11090 this.mode_ = 'rollUp'; 11091 break; 11092 case CARRIAGE_RETURN: 11093 this.flushDisplayed(packet.pts); 11094 this.shiftRowsUp_(); 11095 this.startPts_ = packet.pts; 11096 break; 11097 11098 case BACKSPACE: 11099 if (this.mode_ === 'popOn') { 11100 this.nonDisplayed_[BOTTOM_ROW] = this.nonDisplayed_[BOTTOM_ROW].slice(0, -1); 11101 } else { 11102 this.displayed_[BOTTOM_ROW] = this.displayed_[BOTTOM_ROW].slice(0, -1); 11103 } 11104 break; 11105 case ERASE_DISPLAYED_MEMORY: 11106 this.flushDisplayed(packet.pts); 11107 this.displayed_ = createDisplayBuffer(); 11108 break; 11109 case ERASE_NON_DISPLAYED_MEMORY: 11110 this.nonDisplayed_ = createDisplayBuffer(); 11111 break; 11112 default: 11113 char0 = data >>> 8; 11114 char1 = data & 0xff;
11115 11116 // Look for a Channel 1 Preamble Address Code 11117 if (char0 >= 0x10 && char0 <= 0x17 && 11118 char1 >= 0x40 && char1 <= 0x7F && 11119 (char0 !== 0x10 || char1 < 0x60)) { 11120 // Follow Safari's lead and replace the PAC with a space 11121 char0 = 0x20; 11122 // we only want one space so make the second character null 11123 // which will get become '' in getCharFromCode 11124 char1 = null; 11125 } 11126 11127 // Look for special character sets 11128 if ((char0 === 0x11 || char0 === 0x19) && 11129 (char1 >= 0x30 && char1 <= 0x3F)) { 11130 // Put in eigth note and space 11131 char0 = 0x266A; 11132 char1 = ''; 11133 } 11134 11135 // ignore unsupported control codes 11136 if ((char0 & 0xf0) === 0x10) { 11137 return; 11138 } 11139 11140 // remove null chars 11141 if (char0 === 0x00) { 11142 char0 = null; 11143 } 11144 if (char1 === 0x00) { 11145 char1 = null; 11146 } 11147 11148 // character handling is dependent on the current mode 11149 this[this.mode_](packet.pts, char0, char1); 11150 break; 11151 } 11152 }; 11153 }; 11154 Cea608Stream.prototype = new Stream(); 11155// Trigger a cue point that captures the current state of the 11156// display buffer 11157 Cea608Stream.prototype.flushDisplayed = function(pts) { 11158 var content = this.displayed_ 11159 // remove spaces from the start and end of the string 11160 .map(function(row) { 11161 return row.trim(); 11162 }) 11163 // remove empty rows 11164 .filter(function(row) { 11165 return row.length; 11166 }) 11167 // combine all text rows to display in one cue 11168 .join('\n'); 11169 11170 if (content.length) { 11171 this.trigger('data', { 11172 startPts: this.startPts_, 11173 endPts: pts, 11174 text: content 11175 }); 11176 } 11177 }; 11178 11179// Mode Implementations 11180 Cea608Stream.prototype.popOn = function(pts, char0, char1) { 11181 var baseRow = this.nonDisplayed_[BOTTOM_ROW]; 11182 11183 // buffer characters 11184 baseRow += getCharFromCode(char0); 11185 baseRow += getCharFromCode(char1); 11186 this.nonDisplayed_[BOTTOM_ROW] = baseRow; 11187 }; 11188 11189 Cea608Stream.prototype.rollUp = function(pts, char0, char1) { 11190 var baseRow = this.displayed_[BOTTOM_ROW]; 11191 if (baseRow === '') { 11192 // we're starting to buffer new display input, so flush out the 11193 // current display 11194 this.flushDisplayed(pts); 11195 11196 this.startPts_ = pts; 11197 } 11198 11199 baseRow += getCharFromCode(char0); 11200 baseRow += getCharFromCode(char1); 11201 11202 this.displayed_[BOTTOM_ROW] = baseRow; 11203 }; 11204 Cea608Stream.prototype.shiftRowsUp_ = function() { 11205 var i; 11206 // clear out inactive rows 11207 for (i = 0; i < this.topRow_; i++) { 11208 this.displayed_[i] = ''; 11209 } 11210 // shift displayed rows up 11211 for (i = this.topRow_; i < BOTTOM_ROW; i++) { 11212 this.displayed_[i] = this.displayed_[i + 1]; 11213 } 11214 // clear out the bottom row 11215 this.displayed_[BOTTOM_ROW] = ''; 11216 }; 11217 11218// exports 11219 module.exports = { 11220 CaptionStream: CaptionStream, 11221 Cea608Stream: Cea608Stream 11222 }; 11223 11224 },{"../utils/stream":60}],48:[function(require,module,exports){ 11225 /** 11226 * mux.js 11227 * 11228 * Copyright (c) 2015 Brightcove 11229 * All rights reserved. 11230 * 11231 * A stream-based mp2t to mp4 converter. This utility can be used to 11232 * deliver mp4s to a SourceBuffer on platforms that support native 11233 * Media Source Extensions. 11234 */ 11235 'use strict'; 11236 var Stream = require('../utils/stream.js'), 11237 CaptionStream = require('./caption-stream'), 11238 StreamTypes = require('./stream-types'), 11239 TimestampRolloverStream = require('./timestamp-rollover-stream').TimestampRolloverStream; 11240 11241 var m2tsStreamTypes = require('./stream-types.js'); 11242 11243// object types
vendor: 9,193 bytes, lines 11244-11448
11244 var TransportPacketStream, TransportParseStream, ElementaryStream; 11245 11246// constants 11247 var 11248 MP2T_PACKET_LENGTH = 188, // bytes 11249 SYNC_BYTE = 0x47; 11250 11251 /** 11252 * Splits an incoming stream of binary data into MPEG-2 Transport 11253 * Stream packets. 11254 */ 11255 TransportPacketStream = function() { 11256 var 11257 buffer = new Uint8Array(MP2T_PACKET_LENGTH), 11258 bytesInBuffer = 0; 11259 11260 TransportPacketStream.prototype.init.call(this); 11261 11262 // Deliver new bytes to the stream. 11263 11264 this.push = function(bytes) { 11265 var 11266 startIndex = 0, 11267 endIndex = MP2T_PACKET_LENGTH, 11268 everything; 11269 11270 // If there are bytes remaining from the last segment, prepend them to the 11271 // bytes that were pushed in 11272 if (bytesInBuffer) { 11273 everything = new Uint8Array(bytes.byteLength + bytesInBuffer); 11274 everything.set(buffer.subarray(0, bytesInBuffer)); 11275 everything.set(bytes, bytesInBuffer); 11276 bytesInBuffer = 0; 11277 } else { 11278 everything = bytes; 11279 } 11280 11281 // While we have enough data for a packet 11282 while (endIndex < everything.byteLength) { 11283 // Look for a pair of start and end sync bytes in the data.. 11284 if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) { 11285 // We found a packet so emit it and jump one whole packet forward in 11286 // the stream 11287 this.trigger('data', everything.subarray(startIndex, endIndex)); 11288 startIndex += MP2T_PACKET_LENGTH; 11289 endIndex += MP2T_PACKET_LENGTH; 11290 continue; 11291 } 11292 // If we get here, we have somehow become de-synchronized and we need to step 11293 // forward one byte at a time until we find a pair of sync bytes that denote 11294 // a packet 11295 startIndex++; 11296 endIndex++; 11297 } 11298 11299 // If there was some data left over at the end of the segment that couldn't 11300 // possibly be a whole packet, keep it because it might be the start of a packet 11301 // that continues in the next segment 11302 if (startIndex < everything.byteLength) { 11303 buffer.set(everything.subarray(startIndex), 0); 11304 bytesInBuffer = everything.byteLength - startIndex; 11305 } 11306 }; 11307 11308 this.flush = function() { 11309 // If the buffer contains a whole packet when we are being flushed, emit it 11310 // and empty the buffer. Otherwise hold onto the data because it may be 11311 // important for decoding the next segment 11312 if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) { 11313 this.trigger('data', buffer); 11314 bytesInBuffer = 0; 11315 } 11316 this.trigger('done'); 11317 }; 11318 }; 11319 TransportPacketStream.prototype = new Stream(); 11320 11321 /** 11322 * Accepts an MP2T TransportPacketStream and emits data events with parsed 11323 * forms of the individual transport stream packets. 11324 */ 11325 TransportParseStream = function() { 11326 var parsePsi, parsePat, parsePmt, self; 11327 TransportParseStream.prototype.init.call(this); 11328 self = this; 11329 11330 this.packetsWaitingForPmt = []; 11331 this.programMapTable = undefined; 11332 11333 parsePsi = function(payload, psi) { 11334 var offset = 0; 11335 11336 // PSI packets may be split into multiple sections and those 11337 // sections may be split into multiple packets. If a PSI 11338 // section starts in this packet, the payload_unit_start_indicator 11339 // will be true and the first byte of the payload will indicate 11340 // the offset from the current position to the start of the 11341 // section. 11342 if (psi.payloadUnitStartIndicator) { 11343 offset += payload[offset] + 1; 11344 } 11345 11346 if (psi.type === 'pat') { 11347 parsePat(payload.subarray(offset), psi); 11348 } else { 11349 parsePmt(payload.subarray(offset), psi); 11350 } 11351 }; 11352 11353 parsePat = function(payload, pat) { 11354 pat.section_number = payload[7]; // eslint-disable-line camelcase 11355 pat.last_section_number = payload[8]; // eslint-disable-line camelcase 11356 11357 // skip the PSI header and parse the first PMT entry 11358 self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11]; 11359 pat.pmtPid = self.pmtPid; 11360 }; 11361 11362 /** 11363 * Parse out the relevant fields of a Program Map Table (PMT). 11364 * @param payload {Uint8Array} the PMT-specific portion of an MP2T 11365 * packet. The first byte in this array should be the table_id 11366 * field. 11367 * @param pmt {object} the object that should be decorated with 11368 * fields parsed from the PMT. 11369 */ 11370 parsePmt = function(payload, pmt) { 11371 var sectionLength, tableEnd, programInfoLength, offset; 11372 11373 // PMTs can be sent ahead of the time when they should actually 11374 // take effect. We don't believe this should ever be the case 11375 // for HLS but we'll ignore "forward" PMT declarations if we see 11376 // them. Future PMT declarations have the current_next_indicator 11377 // set to zero. 11378 if (!(payload[5] & 0x01)) { 11379 return; 11380 } 11381 11382 // overwrite any existing program map table 11383 self.programMapTable = {}; 11384 11385 // the mapping table ends at the end of the current section 11386 sectionLength = (payload[1] & 0x0f) << 8 | payload[2]; 11387 tableEnd = 3 + sectionLength - 4; 11388 11389 // to determine where the table is, we have to figure out how 11390 // long the program info descriptors are 11391 programInfoLength = (payload[10] & 0x0f) << 8 | payload[11]; 11392 11393 // advance the offset to the first entry in the mapping table 11394 offset = 12 + programInfoLength; 11395 while (offset < tableEnd) { 11396 // add an entry that maps the elementary_pid to the stream_type 11397 self.programMapTable[(payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]] = payload[offset]; 11398 11399 // move to the next table entry 11400 // skip past the elementary stream descriptors, if present 11401 offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5; 11402 } 11403 11404 // record the map on the packet as well 11405 pmt.programMapTable = self.programMapTable; 11406 11407 // if there are any packets waiting for a PMT to be found, process them now 11408 while (self.packetsWaitingForPmt.length) { 11409 self.processPes_.apply(self, self.packetsWaitingForPmt.shift()); 11410 } 11411 }; 11412 11413 /** 11414 * Deliver a new MP2T packet to the stream. 11415 */ 11416 this.push = function(packet) { 11417 var 11418 result = {}, 11419 offset = 4; 11420 11421 result.payloadUnitStartIndicator = !!(packet[1] & 0x40); 11422 11423 // pid is a 13-bit field starting at the last bit of packet[1] 11424 result.pid = packet[1] & 0x1f; 11425 result.pid <<= 8; 11426 result.pid |= packet[2]; 11427 11428 // if an adaption field is present, its length is specified by the 11429 // fifth byte of the TS packet header. The adaptation field is 11430 // used to add stuffing to PES packets that don't fill a complete 11431 // TS packet, and to specify some forms of timing and control data 11432 // that we do not currently use. 11433 if (((packet[3] & 0x30) >>> 4) > 0x01) { 11434 offset += packet[offset] + 1; 11435 } 11436 11437 // parse the rest of the packet based on the type 11438 if (result.pid === 0) { 11439 result.type = 'pat'; 11440 parsePsi(packet.subarray(offset), result); 11441 this.trigger('data', result); 11442 } else if (result.pid === this.pmtPid) { 11443 result.type = 'pmt'; 11444 parsePsi(packet.subarray(offset), result); 11445 this.trigger('data', result); 11446 } else if (this.programMapTable === undefined) { 11447 // When we have not seen a PMT yet, defer further processing of 11448 // PES packets until one has been parsed
11449 this.packetsWaitingForPmt.push([packet, offset, result]); 11450 } else { 11451 this.processPes_(packet, offset, result); 11452 } 11453 }; 11454 11455 this.processPes_ = function(packet, offset, result) { 11456 result.streamType = this.programMapTable[result.pid]; 11457 result.type = 'pes'; 11458 result.data = packet.subarray(offset); 11459 11460 this.trigger('data', result); 11461 }; 11462 11463 }; 11464 TransportParseStream.prototype = new Stream(); 11465 TransportParseStream.STREAM_TYPES = { 11466 h264: 0x1b, 11467 adts: 0x0f 11468 }; 11469 11470 /** 11471 * Reconsistutes program elementary stream (PES) packets from parsed 11472 * transport stream packets. That is, if you pipe an 11473 * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output 11474 * events will be events which capture the bytes for individual PES 11475 * packets plus relevant metadata that has been extracted from the 11476 * container. 11477 */ 11478 ElementaryStream = function() { 11479 var 11480 self = this, 11481 // PES packet fragments 11482 video = { 11483 data: [], 11484 size: 0 11485 }, 11486 audio = { 11487 data: [], 11488 size: 0 11489 }, 11490 timedMetadata = { 11491 data: [], 11492 size: 0 11493 }, 11494 parsePes = function(payload, pes) { 11495 var ptsDtsFlags; 11496 11497 // find out if this packets starts a new keyframe 11498 pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0; 11499 // PES packets may be annotated with a PTS value, or a PTS value 11500 // and a DTS value. Determine what combination of values is 11501 // available to work with. 11502 ptsDtsFlags = payload[7]; 11503 11504 // PTS and DTS are normally stored as a 33-bit number. Javascript 11505 // performs all bitwise operations on 32-bit integers but javascript 11506 // supports a much greater range (52-bits) of integer using standard 11507 // mathematical operations. 11508 // We construct a 31-bit value using bitwise operators over the 31 11509 // most significant bits and then multiply by 4 (equal to a left-shift 11510 // of 2) before we add the final 2 least significant bits of the 11511 // timestamp (equal to an OR.) 11512 if (ptsDtsFlags & 0xC0) { 11513 // the PTS and DTS are not written out directly. For information 11514 // on how they are encoded, see 11515 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 11516 pes.pts = (payload[9] & 0x0E) << 27 | 11517 (payload[10] & 0xFF) << 20 | 11518 (payload[11] & 0xFE) << 12 | 11519 (payload[12] & 0xFF) << 5 | 11520 (payload[13] & 0xFE) >>> 3; 11521 pes.pts *= 4; // Left shift by 2 11522 pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs 11523 pes.dts = pes.pts; 11524 if (ptsDtsFlags & 0x40) { 11525 pes.dts = (payload[14] & 0x0E) << 27 | 11526 (payload[15] & 0xFF) << 20 | 11527 (payload[16] & 0xFE) << 12 | 11528 (payload[17] & 0xFF) << 5 | 11529 (payload[18] & 0xFE) >>> 3; 11530 pes.dts *= 4; // Left shift by 2 11531 pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs 11532 } 11533 } 11534 // the data section starts immediately after the PES header. 11535 // pes_header_data_length specifies the number of header bytes
11536 // that follow the last byte of the field. 11537 pes.data = payload.subarray(9 + payload[8]); 11538 }, 11539 flushStream = function(stream, type) { 11540 var 11541 packetData = new Uint8Array(stream.size), 11542 event = { 11543 type: type 11544 }, 11545 i = 0, 11546 fragment; 11547 11548 // do nothing if there is no buffered data 11549 if (!stream.data.length) { 11550 return; 11551 } 11552 event.trackId = stream.data[0].pid; 11553 11554 // reassemble the packet 11555 while (stream.data.length) { 11556 fragment = stream.data.shift(); 11557 11558 packetData.set(fragment.data, i); 11559 i += fragment.data.byteLength; 11560 } 11561 11562 // parse assembled packet's PES header 11563 parsePes(packetData, event); 11564 11565 stream.size = 0; 11566 11567 self.trigger('data', event); 11568 }; 11569 11570 ElementaryStream.prototype.init.call(this); 11571 11572 this.push = function(data) { 11573 ({ 11574 pat: function() { 11575 // we have to wait for the PMT to arrive as well before we 11576 // have any meaningful metadata 11577 }, 11578 pes: function() { 11579 var stream, streamType; 11580 11581 switch (data.streamType) { 11582 case StreamTypes.H264_STREAM_TYPE: 11583 case m2tsStreamTypes.H264_STREAM_TYPE: 11584 stream = video; 11585 streamType = 'video'; 11586 break; 11587 case StreamTypes.ADTS_STREAM_TYPE: 11588 stream = audio; 11589 streamType = 'audio'; 11590 break; 11591 case StreamTypes.METADATA_STREAM_TYPE: 11592 stream = timedMetadata; 11593 streamType = 'timed-metadata'; 11594 break; 11595 default: 11596 // ignore unknown stream types 11597 return; 11598 } 11599 11600 // if a new packet is starting, we can flush the completed 11601 // packet 11602 if (data.payloadUnitStartIndicator) { 11603 flushStream(stream, streamType); 11604 } 11605 11606 // buffer this fragment until we are sure we've received the 11607 // complete payload 11608 stream.data.push(data); 11609 stream.size += data.data.byteLength; 11610 }, 11611 pmt: function() { 11612 var 11613 event = { 11614 type: 'metadata', 11615 tracks: [] 11616 }, 11617 programMapTable = data.programMapTable, 11618 k, 11619 track; 11620 11621 // translate streams to tracks 11622 for (k in programMapTable) { 11623 if (programMapTable.hasOwnProperty(k)) { 11624 track = { 11625 timelineStartInfo: { 11626 baseMediaDecodeTime: 0 11627 } 11628 }; 11629 track.id = +k; 11630 if (programMapTable[k] === m2tsStreamTypes.H264_STREAM_TYPE) { 11631 track.codec = 'avc'; 11632 track.type = 'video'; 11633 } else if (programMapTable[k] === m2tsStreamTypes.ADTS_STREAM_TYPE) { 11634 track.codec = 'adts'; 11635 track.type = 'audio'; 11636 } 11637 event.tracks.push(track); 11638 } 11639 } 11640 self.trigger('data', event); 11641 } 11642 })[data.type](); 11643 }; 11644 11645 /** 11646 * Flush any remaining input. Video PES packets may be of variable 11647 * length. Normally, the start of a new video packet can trigger the 11648 * finalization of the previous packet. That is not possible if no 11649 * more video is forthcoming, however. In that case, some other 11650 * mechanism (like the end of the file) has to be employed. When it is 11651 * clear that no additional data is forthcoming, calling this method 11652 * will flush the buffered packets. 11653 */ 11654 this.flush = function() { 11655 // !!THIS ORDER IS IMPORTANT!! 11656 // video first then audio 11657 flushStream(video, 'video'); 11658 flushStream(audio, 'audio'); 11659 flushStream(timedMetadata, 'timed-metadata'); 11660 this.trigger('done'); 11661 }; 11662 }; 11663 ElementaryStream.prototype = new Stream(); 11664 11665 var m2ts = { 11666 PAT_PID: 0x0000, 11667 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH, 11668 TransportPacketStream: TransportPacketStream,
vendor: 54,702 bytes, lines 11669-13030
11669 TransportParseStream: TransportParseStream, 11670 ElementaryStream: ElementaryStream, 11671 TimestampRolloverStream: TimestampRolloverStream, 11672 CaptionStream: CaptionStream.CaptionStream, 11673 Cea608Stream: CaptionStream.Cea608Stream, 11674 MetadataStream: require('./metadata-stream') 11675 }; 11676 11677 for (var type in StreamTypes) { 11678 if (StreamTypes.hasOwnProperty(type)) { 11679 m2ts[type] = StreamTypes[type]; 11680 } 11681 } 11682 11683 module.exports = m2ts; 11684 11685 },{"../utils/stream.js":60,"./caption-stream":47,"./metadata-stream":49,"./stream-types":51,"./stream-types.js":51,"./timestamp-rollover-stream":52}],49:[function(require,module,exports){ 11686 /** 11687 * Accepts program elementary stream (PES) data events and parses out 11688 * ID3 metadata from them, if present. 11689 * @see http://id3.org/id3v2.3.0 11690 */ 11691 'use strict'; 11692 var 11693 Stream = require('../utils/stream'), 11694 StreamTypes = require('./stream-types'), 11695 // return a percent-encoded representation of the specified byte range 11696 // @see http://en.wikipedia.org/wiki/Percent-encoding 11697 percentEncode = function(bytes, start, end) { 11698 var i, result = ''; 11699 for (i = start; i < end; i++) { 11700 result += '%' + ('00' + bytes[i].toString(16)).slice(-2); 11701 } 11702 return result; 11703 }, 11704 // return the string representation of the specified byte range, 11705 // interpreted as UTf-8. 11706 parseUtf8 = function(bytes, start, end) { 11707 return decodeURIComponent(percentEncode(bytes, start, end)); 11708 }, 11709 // return the string representation of the specified byte range, 11710 // interpreted as ISO-8859-1. 11711 parseIso88591 = function(bytes, start, end) { 11712 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line 11713 }, 11714 parseSyncSafeInteger = function(data) { 11715 return (data[0] << 21) | 11716 (data[1] << 14) | 11717 (data[2] << 7) | 11718 (data[3]); 11719 }, 11720 tagParsers = { 11721 TXXX: function(tag) { 11722 var i; 11723 if (tag.data[0] !== 3) { 11724 // ignore frames with unrecognized character encodings 11725 return; 11726 } 11727 11728 for (i = 1; i < tag.data.length; i++) { 11729 if (tag.data[i] === 0) { 11730 // parse the text fields 11731 tag.description = parseUtf8(tag.data, 1, i); 11732 // do not include the null terminator in the tag value 11733 tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, ''); 11734 break; 11735 } 11736 } 11737 tag.data = tag.value; 11738 }, 11739 WXXX: function(tag) { 11740 var i; 11741 if (tag.data[0] !== 3) { 11742 // ignore frames with unrecognized character encodings 11743 return; 11744 } 11745 11746 for (i = 1; i < tag.data.length; i++) { 11747 if (tag.data[i] === 0) { 11748 // parse the description and URL fields 11749 tag.description = parseUtf8(tag.data, 1, i); 11750 tag.url = parseUtf8(tag.data, i + 1, tag.data.length); 11751 break; 11752 } 11753 } 11754 }, 11755 PRIV: function(tag) { 11756 var i; 11757 11758 for (i = 0; i < tag.data.length; i++) { 11759 if (tag.data[i] === 0) { 11760 // parse the description and URL fields 11761 tag.owner = parseIso88591(tag.data, 0, i); 11762 break; 11763 } 11764 } 11765 tag.privateData = tag.data.subarray(i + 1); 11766 tag.data = tag.privateData; 11767 } 11768 }, 11769 MetadataStream; 11770 11771 MetadataStream = function(options) { 11772 var 11773 settings = { 11774 debug: !!(options && options.debug), 11775 11776 // the bytes of the program-level descriptor field in MP2T 11777 // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and 11778 // program element descriptors" 11779 descriptor: options && options.descriptor 11780 }, 11781 // the total size in bytes of the ID3 tag being parsed 11782 tagSize = 0, 11783 // tag data that is not complete enough to be parsed 11784 buffer = [], 11785 // the total number of bytes currently in the buffer 11786 bufferSize = 0, 11787 i; 11788 11789 MetadataStream.prototype.init.call(this); 11790 11791 // calculate the text track in-band metadata track dispatch type 11792 // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track 11793 this.dispatchType = StreamTypes.METADATA_STREAM_TYPE.toString(16); 11794 if (settings.descriptor) { 11795 for (i = 0; i < settings.descriptor.length; i++) { 11796 this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2); 11797 } 11798 } 11799 11800 this.push = function(chunk) { 11801 var tag, frameStart, frameSize, frame, i, frameHeader; 11802 if (chunk.type !== 'timed-metadata') { 11803 return; 11804 } 11805 11806 // if data_alignment_indicator is set in the PES header, 11807 // we must have the start of a new ID3 tag. Assume anything 11808 // remaining in the buffer was malformed and throw it out 11809 if (chunk.dataAlignmentIndicator) { 11810 bufferSize = 0; 11811 buffer.length = 0; 11812 } 11813 11814 // ignore events that don't look like ID3 data 11815 if (buffer.length === 0 && 11816 (chunk.data.length < 10 || 11817 chunk.data[0] !== 'I'.charCodeAt(0) || 11818 chunk.data[1] !== 'D'.charCodeAt(0) || 11819 chunk.data[2] !== '3'.charCodeAt(0))) { 11820 if (settings.debug) { 11821 // eslint-disable-next-line no-console 11822 console.log('Skipping unrecognized metadata packet'); 11823 } 11824 return; 11825 } 11826 11827 // add this chunk to the data we've collected so far 11828 11829 buffer.push(chunk); 11830 bufferSize += chunk.data.byteLength; 11831 11832 // grab the size of the entire frame from the ID3 header 11833 if (buffer.length === 1) { 11834 // the frame size is transmitted as a 28-bit integer in the 11835 // last four bytes of the ID3 header. 11836 // The most significant bit of each byte is dropped and the 11837 // results concatenated to recover the actual value. 11838 tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10)); 11839 11840 // ID3 reports the tag size excluding the header but it's more 11841 // convenient for our comparisons to include it 11842 tagSize += 10; 11843 } 11844 11845 // if the entire frame has not arrived, wait for more data 11846 if (bufferSize < tagSize) { 11847 return; 11848 } 11849 11850 // collect the entire frame so it can be parsed 11851 tag = { 11852 data: new Uint8Array(tagSize), 11853 frames: [], 11854 pts: buffer[0].pts, 11855 dts: buffer[0].dts 11856 }; 11857 for (i = 0; i < tagSize;) { 11858 tag.data.set(buffer[0].data.subarray(0, tagSize - i), i); 11859 i += buffer[0].data.byteLength; 11860 bufferSize -= buffer[0].data.byteLength; 11861 buffer.shift(); 11862 } 11863 11864 // find the start of the first frame and the end of the tag 11865 frameStart = 10; 11866 if (tag.data[5] & 0x40) { 11867 // advance the frame start past the extended header 11868 frameStart += 4; // header size field 11869 frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14)); 11870 11871 // clip any padding off the end 11872 tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20)); 11873 } 11874 11875 // parse one or more ID3 frames 11876 // http://id3.org/id3v2.3.0#ID3v2_frame_overview 11877 do { 11878 // determine the number of bytes in this frame 11879 frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8)); 11880 if (frameSize < 1) { 11881 // eslint-disable-next-line no-console 11882 return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.'); 11883 } 11884 frameHeader = String.fromCharCode(tag.data[frameStart], 11885 tag.data[frameStart + 1], 11886 tag.data[frameStart + 2], 11887 tag.data[frameStart + 3]); 11888 11889 11890 frame = { 11891 id: frameHeader, 11892 data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10) 11893 }; 11894 frame.key = frame.id; 11895 if (tagParsers[frame.id]) { 11896 tagParsers[frame.id](frame); 11897 11898 // handle the special PRIV frame used to indicate the start 11899 // time for raw AAC data 11900 if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') { 11901 var 11902 d = frame.data, 11903 size = ((d[3] & 0x01) << 30) | 11904 (d[4] << 22) | 11905 (d[5] << 14) | 11906 (d[6] << 6) | 11907 (d[7] >>> 2); 11908 11909 size *= 4; 11910 size += d[7] & 0x03; 11911 frame.timeStamp = size; 11912 // in raw AAC, all subsequent data will be timestamped based 11913 // on the value of this frame 11914 // we couldn't have known the appropriate pts and dts before 11915 // parsing this ID3 tag so set those values now 11916 if (tag.pts === undefined && tag.dts === undefined) { 11917 tag.pts = frame.timeStamp; 11918 tag.dts = frame.timeStamp; 11919 } 11920 this.trigger('timestamp', frame); 11921 } 11922 } 11923 tag.frames.push(frame); 11924 11925 frameStart += 10; // advance past the frame header 11926 frameStart += frameSize; // advance past the frame body 11927 } while (frameStart < tagSize); 11928 this.trigger('data', tag); 11929 }; 11930 }; 11931 MetadataStream.prototype = new Stream(); 11932 11933 module.exports = MetadataStream; 11934 11935 },{"../utils/stream":60,"./stream-types":51}],50:[function(require,module,exports){ 11936 /** 11937 * mux.js 11938 * 11939 * Copyright (c) 2016 Brightcove 11940 * All rights reserved. 11941 * 11942 * Utilities to detect basic properties and metadata about TS Segments. 11943 */ 11944 'use strict'; 11945 11946 var StreamTypes = require('./stream-types.js'); 11947 11948 var parsePid = function(packet) { 11949 var pid = packet[1] & 0x1f; 11950 pid <<= 8; 11951 pid |= packet[2]; 11952 return pid; 11953 }; 11954 11955 var parsePayloadUnitStartIndicator = function(packet) { 11956 return !!(packet[1] & 0x40); 11957 }; 11958 11959 var parseAdaptionField = function(packet) { 11960 var offset = 0; 11961 // if an adaption field is present, its length is specified by the 11962 // fifth byte of the TS packet header. The adaptation field is 11963 // used to add stuffing to PES packets that don't fill a complete 11964 // TS packet, and to specify some forms of timing and control data 11965 // that we do not currently use. 11966 if (((packet[3] & 0x30) >>> 4) > 0x01) { 11967 offset += packet[4] + 1; 11968 } 11969 return offset; 11970 }; 11971 11972 var parseType = function(packet, pmtPid) { 11973 var pid = parsePid(packet); 11974 if (pid === 0) { 11975 return 'pat'; 11976 } else if (pid === pmtPid) { 11977 return 'pmt'; 11978 } else if (pmtPid) { 11979 return 'pes'; 11980 } 11981 return null; 11982 }; 11983 11984 var parsePat = function(packet) { 11985 var pusi = parsePayloadUnitStartIndicator(packet); 11986 var offset = 4 + parseAdaptionField(packet); 11987 11988 if (pusi) { 11989 offset += packet[offset] + 1; 11990 } 11991 11992 return (packet[offset + 10] & 0x1f) << 8 | packet[offset + 11]; 11993 }; 11994 11995 var parsePmt = function(packet) { 11996 var programMapTable = {}; 11997 var pusi = parsePayloadUnitStartIndicator(packet); 11998 var payloadOffset = 4 + parseAdaptionField(packet); 11999 12000 if (pusi) { 12001 payloadOffset += packet[payloadOffset] + 1; 12002 } 12003 12004 // PMTs can be sent ahead of the time when they should actually 12005 // take effect. We don't believe this should ever be the case 12006 // for HLS but we'll ignore "forward" PMT declarations if we see 12007 // them. Future PMT declarations have the current_next_indicator 12008 // set to zero. 12009 if (!(packet[payloadOffset + 5] & 0x01)) { 12010 return; 12011 } 12012 12013 var sectionLength, tableEnd, programInfoLength; 12014 // the mapping table ends at the end of the current section 12015 sectionLength = (packet[payloadOffset + 1] & 0x0f) << 8 | packet[payloadOffset + 2]; 12016 tableEnd = 3 + sectionLength - 4; 12017 12018 // to determine where the table is, we have to figure out how 12019 // long the program info descriptors are 12020 programInfoLength = (packet[payloadOffset + 10] & 0x0f) << 8 | packet[payloadOffset + 11]; 12021 12022 // advance the offset to the first entry in the mapping table 12023 var offset = 12 + programInfoLength; 12024 while (offset < tableEnd) { 12025 var i = payloadOffset + offset; 12026 // add an entry that maps the elementary_pid to the stream_type 12027 programMapTable[(packet[i + 1] & 0x1F) << 8 | packet[i + 2]] = packet[i]; 12028 12029 // move to the next table entry 12030 // skip past the elementary stream descriptors, if present 12031 offset += ((packet[i + 3] & 0x0F) << 8 | packet[i + 4]) + 5; 12032 } 12033 return programMapTable; 12034 }; 12035 12036 var parsePesType = function(packet, programMapTable) { 12037 var pid = parsePid(packet); 12038 var type = programMapTable[pid]; 12039 switch (type) { 12040 case StreamTypes.H264_STREAM_TYPE: 12041 return 'video'; 12042 case StreamTypes.ADTS_STREAM_TYPE: 12043 return 'audio'; 12044 case StreamTypes.METADATA_STREAM_TYPE: 12045 return 'timed-metadata'; 12046 default: 12047 return null; 12048 } 12049 }; 12050 12051 var parsePesTime = function(packet) { 12052 var pusi = parsePayloadUnitStartIndicator(packet); 12053 if (!pusi) { 12054 return null; 12055 } 12056 12057 var offset = 4 + parseAdaptionField(packet); 12058 12059 if (offset >= packet.byteLength) { 12060 // From the H 222.0 MPEG-TS spec 12061 // "For transport stream packets carrying PES packets, stuffing is needed when there 12062 // is insufficient PES packet data to completely fill the transport stream packet 12063 // payload bytes. Stuffing is accomplished by defining an adaptation field longer than 12064 // the sum of the lengths of the data elements in it, so that the payload bytes 12065 // remaining after the adaptation field exactly accommodates the available PES packet 12066 // data." 12067 // 12068 // If the offset is >= the length of the packet, then the packet contains no data 12069 // and instead is just adaption field stuffing bytes 12070 return null; 12071 } 12072 12073 var pes = null; 12074 var ptsDtsFlags; 12075 12076 // PES packets may be annotated with a PTS value, or a PTS value 12077 // and a DTS value. Determine what combination of values is 12078 // available to work with. 12079 ptsDtsFlags = packet[offset + 7]; 12080 12081 // PTS and DTS are normally stored as a 33-bit number. Javascript 12082 // performs all bitwise operations on 32-bit integers but javascript 12083 // supports a much greater range (52-bits) of integer using standard 12084 // mathematical operations. 12085 // We construct a 31-bit value using bitwise operators over the 31 12086 // most significant bits and then multiply by 4 (equal to a left-shift 12087 // of 2) before we add the final 2 least significant bits of the 12088 // timestamp (equal to an OR.) 12089 if (ptsDtsFlags & 0xC0) { 12090 pes = {}; 12091 // the PTS and DTS are not written out directly. For information 12092 // on how they are encoded, see 12093 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 12094 pes.pts = (packet[offset + 9] & 0x0E) << 27 | 12095 (packet[offset + 10] & 0xFF) << 20 | 12096 (packet[offset + 11] & 0xFE) << 12 | 12097 (packet[offset + 12] & 0xFF) << 5 | 12098 (packet[offset + 13] & 0xFE) >>> 3; 12099 pes.pts *= 4; // Left shift by 2 12100 pes.pts += (packet[offset + 13] & 0x06) >>> 1; // OR by the two LSBs 12101 pes.dts = pes.pts; 12102 if (ptsDtsFlags & 0x40) { 12103 pes.dts = (packet[offset + 14] & 0x0E) << 27 | 12104 (packet[offset + 15] & 0xFF) << 20 | 12105 (packet[offset + 16] & 0xFE) << 12 | 12106 (packet[offset + 17] & 0xFF) << 5 | 12107 (packet[offset + 18] & 0xFE) >>> 3; 12108 pes.dts *= 4; // Left shift by 2 12109 pes.dts += (packet[offset + 18] & 0x06) >>> 1; // OR by the two LSBs 12110 } 12111 } 12112 return pes; 12113 }; 12114 12115 var parseNalUnitType = function(type) { 12116 switch (type) { 12117 case 0x05: 12118 return 'slice_layer_without_partitioning_rbsp_idr'; 12119 case 0x06: 12120 return 'sei_rbsp'; 12121 case 0x07: 12122 return 'seq_parameter_set_rbsp'; 12123 case 0x08: 12124 return 'pic_parameter_set_rbsp'; 12125 case 0x09: 12126 return 'access_unit_delimiter_rbsp'; 12127 default: 12128 return null; 12129 } 12130 }; 12131 12132 var videoPacketContainsKeyFrame = function(packet) { 12133 var offset = 4 + parseAdaptionField(packet); 12134 var frameBuffer = packet.subarray(offset); 12135 var frameI = 0; 12136 var frameSyncPoint = 0; 12137 var foundKeyFrame = false; 12138 var nalType; 12139 12140 // advance the sync point to a NAL start, if necessary 12141 for (; frameSyncPoint < frameBuffer.byteLength - 3; frameSyncPoint++) { 12142 if (frameBuffer[frameSyncPoint + 2] === 1) { 12143 // the sync point is properly aligned 12144 frameI = frameSyncPoint + 5; 12145 break; 12146 } 12147 } 12148 12149 while (frameI < frameBuffer.byteLength) { 12150 // look at the current byte to determine if we've hit the end of 12151 // a NAL unit boundary 12152 switch (frameBuffer[frameI]) { 12153 case 0: 12154 // skip past non-sync sequences 12155 if (frameBuffer[frameI - 1] !== 0) { 12156 frameI += 2; 12157 break; 12158 } else if (frameBuffer[frameI - 2] !== 0) { 12159 frameI++; 12160 break; 12161 } 12162 12163 if (frameSyncPoint + 3 !== frameI - 2) { 12164 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 12165 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 12166 foundKeyFrame = true; 12167 } 12168 } 12169 12170 // drop trailing zeroes 12171 do { 12172 frameI++; 12173 } while (frameBuffer[frameI] !== 1 && frameI < frameBuffer.length); 12174 frameSyncPoint = frameI - 2; 12175 frameI += 3; 12176 break; 12177 case 1: 12178 // skip past non-sync sequences 12179 if (frameBuffer[frameI - 1] !== 0 || 12180 frameBuffer[frameI - 2] !== 0) { 12181 frameI += 3; 12182 break; 12183 } 12184 12185 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 12186 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 12187 foundKeyFrame = true; 12188 } 12189 frameSyncPoint = frameI - 2; 12190 frameI += 3; 12191 break; 12192 default: 12193 // the current byte isn't a one or zero, so it cannot be part 12194 // of a sync sequence 12195 frameI += 3; 12196 break; 12197 } 12198 } 12199 frameBuffer = frameBuffer.subarray(frameSyncPoint); 12200 frameI -= frameSyncPoint; 12201 frameSyncPoint = 0; 12202 // parse the final nal 12203 if (frameBuffer && frameBuffer.byteLength > 3) { 12204 nalType = parseNalUnitType(frameBuffer[frameSyncPoint + 3] & 0x1f); 12205 if (nalType === 'slice_layer_without_partitioning_rbsp_idr') { 12206 foundKeyFrame = true; 12207 } 12208 } 12209 12210 return foundKeyFrame; 12211 }; 12212 12213 12214 module.exports = { 12215 parseType: parseType, 12216 parsePat: parsePat, 12217 parsePmt: parsePmt, 12218 parsePayloadUnitStartIndicator: parsePayloadUnitStartIndicator, 12219 parsePesType: parsePesType, 12220 parsePesTime: parsePesTime, 12221 videoPacketContainsKeyFrame: videoPacketContainsKeyFrame 12222 }; 12223 12224 },{"./stream-types.js":51}],51:[function(require,module,exports){ 12225 'use strict'; 12226 12227 module.exports = { 12228 H264_STREAM_TYPE: 0x1B, 12229 ADTS_STREAM_TYPE: 0x0F, 12230 METADATA_STREAM_TYPE: 0x15 12231 }; 12232 12233 },{}],52:[function(require,module,exports){ 12234 /** 12235 * mux.js 12236 * 12237 * Copyright (c) 2016 Brightcove 12238 * All rights reserved. 12239 * 12240 * Accepts program elementary stream (PES) data events and corrects 12241 * decode and presentation time stamps to account for a rollover 12242 * of the 33 bit value. 12243 */ 12244 12245 'use strict'; 12246 12247 var Stream = require('../utils/stream'); 12248 12249 var MAX_TS = 8589934592; 12250 12251 var RO_THRESH = 4294967296; 12252 12253 var handleRollover = function(value, reference) { 12254 var direction = 1; 12255 12256 if (value > reference) { 12257 // If the current timestamp value is greater than our reference timestamp and we detect a 12258 // timestamp rollover, this means the roll over is happening in the opposite direction. 12259 // Example scenario: Enter a long stream/video just after a rollover occurred. The reference 12260 // point will be set to a small number, e.g. 1. The user then seeks backwards over the 12261 // rollover point. In loading this segment, the timestamp values will be very large, 12262 // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust 12263 // the time stamp to be `value - 2^33`. 12264 direction = -1; 12265 } 12266 12267 // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will 12268 // cause an incorrect adjustment. 12269 while (Math.abs(reference - value) > RO_THRESH) { 12270 value += (direction * MAX_TS); 12271 } 12272 12273 return value; 12274 }; 12275 12276 var TimestampRolloverStream = function(type) { 12277 var lastDTS, referenceDTS; 12278 12279 TimestampRolloverStream.prototype.init.call(this); 12280 12281 this.type_ = type; 12282 12283 this.push = function(data) { 12284 if (data.type !== this.type_) { 12285 return; 12286 } 12287 12288 if (referenceDTS === undefined) { 12289 referenceDTS = data.dts; 12290 } 12291 12292 data.dts = handleRollover(data.dts, referenceDTS); 12293 data.pts = handleRollover(data.pts, referenceDTS); 12294 12295 lastDTS = data.dts; 12296 12297 this.trigger('data', data); 12298 }; 12299 12300 this.flush = function() { 12301 referenceDTS = lastDTS; 12302 this.trigger('done'); 12303 }; 12304 12305 this.discontinuity = function() { 12306 referenceDTS = void 0; 12307 lastDTS = void 0; 12308 }; 12309 12310 }; 12311 12312 TimestampRolloverStream.prototype = new Stream(); 12313 12314 module.exports = { 12315 TimestampRolloverStream: TimestampRolloverStream, 12316 handleRollover: handleRollover 12317 }; 12318 12319 },{"../utils/stream":60}],53:[function(require,module,exports){ 12320 module.exports = { 12321 generator: require('./mp4-generator'), 12322 Transmuxer: require('./transmuxer').Transmuxer, 12323 AudioSegmentStream: require('./transmuxer').AudioSegmentStream, 12324 VideoSegmentStream: require('./transmuxer').VideoSegmentStream 12325 }; 12326 12327 },{"./mp4-generator":54,"./transmuxer":56}],54:[function(require,module,exports){ 12328 /** 12329 * mux.js 12330 * 12331 * Copyright (c) 2015 Brightcove 12332 * All rights reserved. 12333 * 12334 * Functions that generate fragmented MP4s suitable for use with Media 12335 * Source Extensions. 12336 */ 12337 'use strict'; 12338 12339 var UINT32_MAX = Math.pow(2, 32) - 1; 12340 12341 var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, 12342 trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, 12343 trun, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, 12344 AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; 12345 12346// pre-calculate constants 12347 (function() { 12348 var i; 12349 types = { 12350 avc1: [], // codingname 12351 avcC: [], 12352 btrt: [], 12353 dinf: [], 12354 dref: [], 12355 esds: [], 12356 ftyp: [], 12357 hdlr: [], 12358 mdat: [], 12359 mdhd: [], 12360 mdia: [], 12361 mfhd: [], 12362 minf: [], 12363 moof: [], 12364 moov: [], 12365 mp4a: [], // codingname 12366 mvex: [], 12367 mvhd: [], 12368 sdtp: [], 12369 smhd: [], 12370 stbl: [], 12371 stco: [], 12372 stsc: [], 12373 stsd: [], 12374 stsz: [], 12375 stts: [], 12376 styp: [], 12377 tfdt: [], 12378 tfhd: [], 12379 traf: [], 12380 trak: [], 12381 trun: [], 12382 trex: [], 12383 tkhd: [], 12384 vmhd: [] 12385 }; 12386 12387 // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we 12388 // don't throw an error 12389 if (typeof Uint8Array === 'undefined') { 12390 return; 12391 } 12392 12393 for (i in types) { 12394 if (types.hasOwnProperty(i)) { 12395 types[i] = [ 12396 i.charCodeAt(0), 12397 i.charCodeAt(1), 12398 i.charCodeAt(2), 12399 i.charCodeAt(3) 12400 ]; 12401 } 12402 } 12403 12404 MAJOR_BRAND = new Uint8Array([ 12405 'i'.charCodeAt(0), 12406 's'.charCodeAt(0), 12407 'o'.charCodeAt(0), 12408 'm'.charCodeAt(0) 12409 ]); 12410 AVC1_BRAND = new Uint8Array([ 12411 'a'.charCodeAt(0), 12412 'v'.charCodeAt(0), 12413 'c'.charCodeAt(0), 12414 '1'.charCodeAt(0) 12415 ]); 12416 MINOR_VERSION = new Uint8Array([0, 0, 0, 1]); 12417 VIDEO_HDLR = new Uint8Array([ 12418 0x00, // version 0 12419 0x00, 0x00, 0x00, // flags 12420 0x00, 0x00, 0x00, 0x00, // pre_defined 12421 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' 12422 0x00, 0x00, 0x00, 0x00, // reserved 12423 0x00, 0x00, 0x00, 0x00, // reserved 12424 0x00, 0x00, 0x00, 0x00, // reserved 12425 0x56, 0x69, 0x64, 0x65, 12426 0x6f, 0x48, 0x61, 0x6e, 12427 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' 12428 ]); 12429 AUDIO_HDLR = new Uint8Array([ 12430 0x00, // version 0 12431 0x00, 0x00, 0x00, // flags 12432 0x00, 0x00, 0x00, 0x00, // pre_defined 12433 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun' 12434 0x00, 0x00, 0x00, 0x00, // reserved 12435 0x00, 0x00, 0x00, 0x00, // reserved 12436 0x00, 0x00, 0x00, 0x00, // reserved 12437 0x53, 0x6f, 0x75, 0x6e, 12438 0x64, 0x48, 0x61, 0x6e, 12439 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' 12440 ]); 12441 HDLR_TYPES = { 12442 video: VIDEO_HDLR, 12443 audio: AUDIO_HDLR 12444 }; 12445 DREF = new Uint8Array([ 12446 0x00, // version 0 12447 0x00, 0x00, 0x00, // flags 12448 0x00, 0x00, 0x00, 0x01, // entry_count 12449 0x00, 0x00, 0x00, 0x0c, // entry_size 12450 0x75, 0x72, 0x6c, 0x20, // 'url' type 12451 0x00, // version 0 12452 0x00, 0x00, 0x01 // entry_flags 12453 ]); 12454 SMHD = new Uint8Array([ 12455 0x00, // version 12456 0x00, 0x00, 0x00, // flags 12457 0x00, 0x00, // balance, 0 means centered 12458 0x00, 0x00 // reserved 12459 ]); 12460 STCO = new Uint8Array([ 12461 0x00, // version 12462 0x00, 0x00, 0x00, // flags 12463 0x00, 0x00, 0x00, 0x00 // entry_count 12464 ]); 12465 STSC = STCO; 12466 STSZ = new Uint8Array([ 12467 0x00, // version 12468 0x00, 0x00, 0x00, // flags 12469 0x00, 0x00, 0x00, 0x00, // sample_size 12470 0x00, 0x00, 0x00, 0x00 // sample_count 12471 ]); 12472 STTS = STCO; 12473 VMHD = new Uint8Array([ 12474 0x00, // version 12475 0x00, 0x00, 0x01, // flags 12476 0x00, 0x00, // graphicsmode 12477 0x00, 0x00, 12478 0x00, 0x00, 12479 0x00, 0x00 // opcolor 12480 ]); 12481 }()); 12482 12483 box = function(type) { 12484 var 12485 payload = [], 12486 size = 0, 12487 i, 12488 result, 12489 view; 12490 12491 for (i = 1; i < arguments.length; i++) { 12492 payload.push(arguments[i]); 12493 } 12494 12495 i = payload.length; 12496 12497 // calculate the total size we need to allocate 12498 while (i--) { 12499 size += payload[i].byteLength; 12500 } 12501 result = new Uint8Array(size + 8); 12502 view = new DataView(result.buffer, result.byteOffset, result.byteLength); 12503 view.setUint32(0, result.byteLength); 12504 result.set(type, 4); 12505 12506 // copy the payload into the result 12507 for (i = 0, size = 8; i < payload.length; i++) { 12508 result.set(payload[i], size); 12509 size += payload[i].byteLength; 12510 } 12511 return result; 12512 }; 12513 12514 dinf = function() { 12515 return box(types.dinf, box(types.dref, DREF)); 12516 }; 12517 12518 esds = function(track) { 12519 return box(types.esds, new Uint8Array([ 12520 0x00, // version 12521 0x00, 0x00, 0x00, // flags 12522 12523 // ES_Descriptor 12524 0x03, // tag, ES_DescrTag 12525 0x19, // length 12526 0x00, 0x00, // ES_ID 12527 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority 12528 12529 // DecoderConfigDescriptor 12530 0x04, // tag, DecoderConfigDescrTag 12531 0x11, // length 12532 0x40, // object type 12533 0x15, // streamType 12534 0x00, 0x06, 0x00, // bufferSizeDB 12535 0x00, 0x00, 0xda, 0xc0, // maxBitrate 12536 0x00, 0x00, 0xda, 0xc0, // avgBitrate 12537 12538 // DecoderSpecificInfo 12539 0x05, // tag, DecoderSpecificInfoTag 12540 0x02, // length 12541 // ISO/IEC 14496-3, AudioSpecificConfig 12542 // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35 12543 (track.audioobjecttype << 3) | (track.samplingfrequencyindex >>> 1), 12544 (track.samplingfrequencyindex << 7) | (track.channelcount << 3), 12545 0x06, 0x01, 0x02 // GASpecificConfig 12546 ])); 12547 }; 12548 12549 ftyp = function() { 12550 return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND); 12551 }; 12552 12553 hdlr = function(type) { 12554 return box(types.hdlr, HDLR_TYPES[type]); 12555 }; 12556 mdat = function(data) { 12557 return box(types.mdat, data); 12558 }; 12559 mdhd = function(track) { 12560 var result = new Uint8Array([ 12561 0x00, // version 0 12562 0x00, 0x00, 0x00, // flags 12563 0x00, 0x00, 0x00, 0x02, // creation_time 12564 0x00, 0x00, 0x00, 0x03, // modification_time 12565 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 12566 12567 (track.duration >>> 24) & 0xFF, 12568 (track.duration >>> 16) & 0xFF, 12569 (track.duration >>> 8) & 0xFF, 12570 track.duration & 0xFF, // duration 12571 0x55, 0xc4, // 'und' language (undetermined) 12572 0x00, 0x00 12573 ]); 12574 12575 // Use the sample rate from the track metadata, when it is 12576 // defined. The sample rate can be parsed out of an ADTS header, for 12577 // instance. 12578 if (track.samplerate) { 12579 result[12] = (track.samplerate >>> 24) & 0xFF; 12580 result[13] = (track.samplerate >>> 16) & 0xFF; 12581 result[14] = (track.samplerate >>> 8) & 0xFF; 12582 result[15] = (track.samplerate) & 0xFF; 12583 } 12584 12585 return box(types.mdhd, result); 12586 }; 12587 mdia = function(track) { 12588 return box(types.mdia, mdhd(track), hdlr(track.type), minf(track)); 12589 }; 12590 mfhd = function(sequenceNumber) { 12591 return box(types.mfhd, new Uint8Array([ 12592 0x00, 12593 0x00, 0x00, 0x00, // flags 12594 (sequenceNumber & 0xFF000000) >> 24, 12595 (sequenceNumber & 0xFF0000) >> 16, 12596 (sequenceNumber & 0xFF00) >> 8, 12597 sequenceNumber & 0xFF // sequence_number 12598 ])); 12599 }; 12600 minf = function(track) { 12601 return box(types.minf, 12602 track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), 12603 dinf(), 12604 stbl(track)); 12605 }; 12606 moof = function(sequenceNumber, tracks) { 12607 var 12608 trackFragments = [], 12609 i = tracks.length; 12610 // build traf boxes for each track fragment 12611 while (i--) { 12612 trackFragments[i] = traf(tracks[i]); 12613 } 12614 return box.apply(null, [ 12615 types.moof, 12616 mfhd(sequenceNumber) 12617 ].concat(trackFragments)); 12618 }; 12619 /** 12620 * Returns a movie box. 12621 * @param tracks {array} the tracks associated with this movie 12622 * @see ISO/IEC 14496-12:2012(E), section 8.2.1 12623 */ 12624 moov = function(tracks) { 12625 var 12626 i = tracks.length, 12627 boxes = []; 12628 12629 while (i--) { 12630 boxes[i] = trak(tracks[i]); 12631 } 12632 12633 return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks))); 12634 }; 12635 mvex = function(tracks) { 12636 var 12637 i = tracks.length, 12638 boxes = []; 12639 12640 while (i--) { 12641 boxes[i] = trex(tracks[i]); 12642 } 12643 return box.apply(null, [types.mvex].concat(boxes)); 12644 }; 12645 mvhd = function(duration) { 12646 var 12647 bytes = new Uint8Array([ 12648 0x00, // version 0 12649 0x00, 0x00, 0x00, // flags 12650 0x00, 0x00, 0x00, 0x01, // creation_time 12651 0x00, 0x00, 0x00, 0x02, // modification_time 12652 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second 12653 (duration & 0xFF000000) >> 24, 12654 (duration & 0xFF0000) >> 16, 12655 (duration & 0xFF00) >> 8, 12656 duration & 0xFF, // duration 12657 0x00, 0x01, 0x00, 0x00, // 1.0 rate 12658 0x01, 0x00, // 1.0 volume 12659 0x00, 0x00, // reserved 12660 0x00, 0x00, 0x00, 0x00, // reserved 12661 0x00, 0x00, 0x00, 0x00, // reserved 12662 0x00, 0x01, 0x00, 0x00, 12663 0x00, 0x00, 0x00, 0x00, 12664 0x00, 0x00, 0x00, 0x00, 12665 0x00, 0x00, 0x00, 0x00, 12666 0x00, 0x01, 0x00, 0x00, 12667 0x00, 0x00, 0x00, 0x00, 12668 0x00, 0x00, 0x00, 0x00, 12669 0x00, 0x00, 0x00, 0x00, 12670 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix 12671 0x00, 0x00, 0x00, 0x00, 12672 0x00, 0x00, 0x00, 0x00, 12673 0x00, 0x00, 0x00, 0x00, 12674 0x00, 0x00, 0x00, 0x00, 12675 0x00, 0x00, 0x00, 0x00, 12676 0x00, 0x00, 0x00, 0x00, // pre_defined 12677 0xff, 0xff, 0xff, 0xff // next_track_ID 12678 ]); 12679 return box(types.mvhd, bytes); 12680 }; 12681 12682 sdtp = function(track) { 12683 var 12684 samples = track.samples || [], 12685 bytes = new Uint8Array(4 + samples.length), 12686 flags, 12687 i; 12688 12689 // leave the full box header (4 bytes) all zero 12690 12691 // write the sample table 12692 for (i = 0; i < samples.length; i++) { 12693 flags = samples[i].flags; 12694 12695 bytes[i + 4] = (flags.dependsOn << 4) | 12696 (flags.isDependedOn << 2) | 12697 (flags.hasRedundancy); 12698 } 12699 12700 return box(types.sdtp, 12701 bytes); 12702 }; 12703 12704 stbl = function(track) { 12705 return box(types.stbl, 12706 stsd(track), 12707 box(types.stts, STTS), 12708 box(types.stsc, STSC), 12709 box(types.stsz, STSZ), 12710 box(types.stco, STCO)); 12711 }; 12712 12713 (function() { 12714 var videoSample, audioSample; 12715 12716 stsd = function(track) { 12717 12718 return box(types.stsd, new Uint8Array([ 12719 0x00, // version 0 12720 0x00, 0x00, 0x00, // flags 12721 0x00, 0x00, 0x00, 0x01 12722 ]), track.type === 'video' ? videoSample(track) : audioSample(track)); 12723 }; 12724 12725 videoSample = function(track) { 12726 var 12727 sps = track.sps || [], 12728 pps = track.pps || [], 12729 sequenceParameterSets = [], 12730 pictureParameterSets = [], 12731 i; 12732 12733 // assemble the SPSs 12734 for (i = 0; i < sps.length; i++) { 12735 sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8); 12736 sequenceParameterSets.push((sps[i].byteLength & 0xFF)); // sequenceParameterSetLength 12737 sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS 12738 } 12739 12740 // assemble the PPSs 12741 for (i = 0; i < pps.length; i++) { 12742 pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8); 12743 pictureParameterSets.push((pps[i].byteLength & 0xFF)); 12744 pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i])); 12745 } 12746 12747 return box(types.avc1, new Uint8Array([ 12748 0x00, 0x00, 0x00, 12749 0x00, 0x00, 0x00, // reserved 12750 0x00, 0x01, // data_reference_index 12751 0x00, 0x00, // pre_defined 12752 0x00, 0x00, // reserved 12753 0x00, 0x00, 0x00, 0x00, 12754 0x00, 0x00, 0x00, 0x00, 12755 0x00, 0x00, 0x00, 0x00, // pre_defined 12756 (track.width & 0xff00) >> 8, 12757 track.width & 0xff, // width 12758 (track.height & 0xff00) >> 8, 12759 track.height & 0xff, // height 12760 0x00, 0x48, 0x00, 0x00, // horizresolution 12761 0x00, 0x48, 0x00, 0x00, // vertresolution 12762 0x00, 0x00, 0x00, 0x00, // reserved 12763 0x00, 0x01, // frame_count 12764 0x13, 12765 0x76, 0x69, 0x64, 0x65, 12766 0x6f, 0x6a, 0x73, 0x2d, 12767 0x63, 0x6f, 0x6e, 0x74, 12768 0x72, 0x69, 0x62, 0x2d, 12769 0x68, 0x6c, 0x73, 0x00, 12770 0x00, 0x00, 0x00, 0x00, 12771 0x00, 0x00, 0x00, 0x00, 12772 0x00, 0x00, 0x00, // compressorname 12773 0x00, 0x18, // depth = 24 12774 0x11, 0x11 // pre_defined = -1 12775 ]), box(types.avcC, new Uint8Array([ 12776 0x01, // configurationVersion 12777 track.profileIdc, // AVCProfileIndication 12778 track.profileCompatibility, // profile_compatibility 12779 track.levelIdc, // AVCLevelIndication 12780 0xff // lengthSizeMinusOne, hard-coded to 4 bytes 12781 ].concat([ 12782 sps.length // numOfSequenceParameterSets 12783 ]).concat(sequenceParameterSets).concat([ 12784 pps.length // numOfPictureParameterSets 12785 ]).concat(pictureParameterSets))), // "PPS" 12786 box(types.btrt, new Uint8Array([ 12787 0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB 12788 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate 12789 0x00, 0x2d, 0xc6, 0xc0 12790 ])) // avgBitrate 12791 ); 12792 }; 12793 12794 audioSample = function(track) { 12795 return box(types.mp4a, new Uint8Array([ 12796 12797 // SampleEntry, ISO/IEC 14496-12 12798 0x00, 0x00, 0x00, 12799 0x00, 0x00, 0x00, // reserved 12800 0x00, 0x01, // data_reference_index 12801 12802 // AudioSampleEntry, ISO/IEC 14496-12 12803 0x00, 0x00, 0x00, 0x00, // reserved 12804 0x00, 0x00, 0x00, 0x00, // reserved 12805 (track.channelcount & 0xff00) >> 8, 12806 (track.channelcount & 0xff), // channelcount 12807 12808 (track.samplesize & 0xff00) >> 8, 12809 (track.samplesize & 0xff), // samplesize 12810 0x00, 0x00, // pre_defined 12811 0x00, 0x00, // reserved 12812 12813 (track.samplerate & 0xff00) >> 8, 12814 (track.samplerate & 0xff), 12815 0x00, 0x00 // samplerate, 16.16 12816 12817 // MP4AudioSampleEntry, ISO/IEC 14496-14 12818 ]), esds(track)); 12819 }; 12820 }()); 12821 12822 tkhd = function(track) { 12823 var result = new Uint8Array([ 12824 0x00, // version 0 12825 0x00, 0x00, 0x07, // flags 12826 0x00, 0x00, 0x00, 0x00, // creation_time 12827 0x00, 0x00, 0x00, 0x00, // modification_time 12828 (track.id & 0xFF000000) >> 24, 12829 (track.id & 0xFF0000) >> 16, 12830 (track.id & 0xFF00) >> 8, 12831 track.id & 0xFF, // track_ID 12832 0x00, 0x00, 0x00, 0x00, // reserved 12833 (track.duration & 0xFF000000) >> 24, 12834 (track.duration & 0xFF0000) >> 16, 12835 (track.duration & 0xFF00) >> 8, 12836 track.duration & 0xFF, // duration 12837 0x00, 0x00, 0x00, 0x00, 12838 0x00, 0x00, 0x00, 0x00, // reserved 12839 0x00, 0x00, // layer 12840 0x00, 0x00, // alternate_group 12841 0x01, 0x00, // non-audio track volume 12842 0x00, 0x00, // reserved 12843 0x00, 0x01, 0x00, 0x00, 12844 0x00, 0x00, 0x00, 0x00, 12845 0x00, 0x00, 0x00, 0x00, 12846 0x00, 0x00, 0x00, 0x00, 12847 0x00, 0x01, 0x00, 0x00, 12848 0x00, 0x00, 0x00, 0x00, 12849 0x00, 0x00, 0x00, 0x00, 12850 0x00, 0x00, 0x00, 0x00, 12851 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix 12852 (track.width & 0xFF00) >> 8, 12853 track.width & 0xFF, 12854 0x00, 0x00, // width 12855 (track.height & 0xFF00) >> 8, 12856 track.height & 0xFF, 12857 0x00, 0x00 // height 12858 ]); 12859 12860 return box(types.tkhd, result); 12861 }; 12862 12863 /** 12864 * Generate a track fragment (traf) box. A traf box collects metadata 12865 * about tracks in a movie fragment (moof) box. 12866 */ 12867 traf = function(track) { 12868 var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, 12869 sampleDependencyTable, dataOffset, 12870 upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime; 12871 12872 trackFragmentHeader = box(types.tfhd, new Uint8Array([ 12873 0x00, // version 0 12874 0x00, 0x00, 0x3a, // flags 12875 (track.id & 0xFF000000) >> 24, 12876 (track.id & 0xFF0000) >> 16, 12877 (track.id & 0xFF00) >> 8, 12878 (track.id & 0xFF), // track_ID 12879 0x00, 0x00, 0x00, 0x01, // sample_description_index 12880 0x00, 0x00, 0x00, 0x00, // default_sample_duration 12881 0x00, 0x00, 0x00, 0x00, // default_sample_size 12882 0x00, 0x00, 0x00, 0x00 // default_sample_flags 12883 ])); 12884 12885 upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1)); 12886 lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1)); 12887 12888 trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([ 12889 0x01, // version 1 12890 0x00, 0x00, 0x00, // flags 12891 // baseMediaDecodeTime 12892 (upperWordBaseMediaDecodeTime >>> 24) & 0xFF, 12893 (upperWordBaseMediaDecodeTime >>> 16) & 0xFF, 12894 (upperWordBaseMediaDecodeTime >>> 8) & 0xFF, 12895 upperWordBaseMediaDecodeTime & 0xFF, 12896 (lowerWordBaseMediaDecodeTime >>> 24) & 0xFF, 12897 (lowerWordBaseMediaDecodeTime >>> 16) & 0xFF, 12898 (lowerWordBaseMediaDecodeTime >>> 8) & 0xFF, 12899 lowerWordBaseMediaDecodeTime & 0xFF 12900 ])); 12901 12902 // the data offset specifies the number of bytes from the start of 12903 // the containing moof to the first payload byte of the associated 12904 // mdat 12905 dataOffset = (32 + // tfhd 12906 20 + // tfdt 12907 8 + // traf header 12908 16 + // mfhd 12909 8 + // moof header 12910 8); // mdat header 12911 12912 // audio tracks require less metadata 12913 if (track.type === 'audio') { 12914 trackFragmentRun = trun(track, dataOffset); 12915 return box(types.traf, 12916 trackFragmentHeader, 12917 trackFragmentDecodeTime, 12918 trackFragmentRun); 12919 } 12920 12921 // video tracks should contain an independent and disposable samples 12922 // box (sdtp) 12923 // generate one and adjust offsets to match 12924 sampleDependencyTable = sdtp(track); 12925 trackFragmentRun = trun(track, 12926 sampleDependencyTable.length + dataOffset); 12927 return box(types.traf, 12928 trackFragmentHeader, 12929 trackFragmentDecodeTime, 12930 trackFragmentRun, 12931 sampleDependencyTable); 12932 }; 12933 12934 /** 12935 * Generate a track box. 12936 * @param track {object} a track definition 12937 * @return {Uint8Array} the track box 12938 */ 12939 trak = function(track) { 12940 track.duration = track.duration || 0xffffffff; 12941 return box(types.trak, 12942 tkhd(track), 12943 mdia(track)); 12944 }; 12945 12946 trex = function(track) { 12947 var result = new Uint8Array([ 12948 0x00, // version 0 12949 0x00, 0x00, 0x00, // flags 12950 (track.id & 0xFF000000) >> 24, 12951 (track.id & 0xFF0000) >> 16, 12952 (track.id & 0xFF00) >> 8, 12953 (track.id & 0xFF), // track_ID 12954 0x00, 0x00, 0x00, 0x01, // default_sample_description_index 12955 0x00, 0x00, 0x00, 0x00, // default_sample_duration 12956 0x00, 0x00, 0x00, 0x00, // default_sample_size 12957 0x00, 0x01, 0x00, 0x01 // default_sample_flags 12958 ]); 12959 // the last two bytes of default_sample_flags is the sample 12960 // degradation priority, a hint about the importance of this sample 12961 // relative to others. Lower the degradation priority for all sample 12962 // types other than video. 12963 if (track.type !== 'video') { 12964 result[result.length - 1] = 0x00; 12965 } 12966 12967 return box(types.trex, result); 12968 }; 12969 12970 (function() { 12971 var audioTrun, videoTrun, trunHeader; 12972 12973 // This method assumes all samples are uniform. That is, if a 12974 // duration is present for the first sample, it will be present for 12975 // all subsequent samples. 12976 // see ISO/IEC 14496-12:2012, Section 8.8.8.1 12977 trunHeader = function(samples, offset) { 12978 var durationPresent = 0, sizePresent = 0, 12979 flagsPresent = 0, compositionTimeOffset = 0; 12980 12981 // trun flag constants 12982 if (samples.length) { 12983 if (samples[0].duration !== undefined) { 12984 durationPresent = 0x1; 12985 } 12986 if (samples[0].size !== undefined) { 12987 sizePresent = 0x2; 12988 } 12989 if (samples[0].flags !== undefined) { 12990 flagsPresent = 0x4; 12991 } 12992 if (samples[0].compositionTimeOffset !== undefined) { 12993 compositionTimeOffset = 0x8; 12994 } 12995 } 12996 12997 return [ 12998 0x00, // version 0 12999 0x00, 13000 durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 13001 0x01, // flags 13002 (samples.length & 0xFF000000) >>> 24, 13003 (samples.length & 0xFF0000) >>> 16, 13004 (samples.length & 0xFF00) >>> 8, 13005 samples.length & 0xFF, // sample_count 13006 (offset & 0xFF000000) >>> 24, 13007 (offset & 0xFF0000) >>> 16, 13008 (offset & 0xFF00) >>> 8, 13009 offset & 0xFF // data_offset 13010 ]; 13011 }; 13012 13013 videoTrun = function(track, offset) { 13014 var bytes, samples, sample, i; 13015 13016 samples = track.samples || []; 13017 offset += 8 + 12 + (16 * samples.length); 13018 13019 bytes = trunHeader(samples, offset); 13020 13021 for (i = 0; i < samples.length; i++) { 13022 sample = samples[i]; 13023 bytes = bytes.concat([ 13024 (sample.duration & 0xFF000000) >>> 24, 13025 (sample.duration & 0xFF0000) >>> 16, 13026 (sample.duration & 0xFF00) >>> 8, 13027 sample.duration & 0xFF, // sample_duration 13028 (sample.size & 0xFF000000) >>> 24, 13029 (sample.size & 0xFF0000) >>> 16, 13030 (sample.size & 0xFF00) >>> 8,
13031 sample.size & 0xFF, // sample_size 13032 (sample.flags.isLeading << 2) | sample.flags.dependsOn, 13033 (sample.flags.isDependedOn << 6) | 13034 (sample.flags.hasRedundancy << 4) | 13035 (sample.flags.paddingValue << 1) | 13036 sample.flags.isNonSyncSample, 13037 sample.flags.degradationPriority & 0xF0 << 8, 13038 sample.flags.degradationPriority & 0x0F, // sample_flags 13039 (sample.compositionTimeOffset & 0xFF000000) >>> 24, 13040 (sample.compositionTimeOffset & 0xFF0000) >>> 16, 13041 (sample.compositionTimeOffset & 0xFF00) >>> 8, 13042 sample.compositionTimeOffset & 0xFF // sample_composition_time_offset 13043 ]); 13044 } 13045 return box(types.trun, new Uint8Array(bytes)); 13046 }; 13047 13048 audioTrun = function(track, offset) { 13049 var bytes, samples, sample, i; 13050 13051 samples = track.samples || []; 13052 offset += 8 + 12 + (8 * samples.length); 13053 13054 bytes = trunHeader(samples, offset); 13055 13056 for (i = 0; i < samples.length; i++) { 13057 sample = samples[i]; 13058 bytes = bytes.concat([ 13059 (sample.duration & 0xFF000000) >>> 24, 13060 (sample.duration & 0xFF0000) >>> 16, 13061 (sample.duration & 0xFF00) >>> 8, 13062 sample.duration & 0xFF, // sample_duration 13063 (sample.size & 0xFF000000) >>> 24, 13064 (sample.size & 0xFF0000) >>> 16, 13065 (sample.size & 0xFF00) >>> 8, 13066 sample.size & 0xFF]); // sample_size 13067 } 13068 13069 return box(types.trun, new Uint8Array(bytes)); 13070 }; 13071 13072 trun = function(track, offset) { 13073 if (track.type === 'audio') { 13074 return audioTrun(track, offset); 13075 } 13076 13077 return videoTrun(track, offset); 13078 }; 13079 }()); 13080 13081 module.exports = { 13082 ftyp: ftyp, 13083 mdat: mdat, 13084 moof: moof, 13085 moov: moov, 13086 initSegment: function(tracks) { 13087 var 13088 fileType = ftyp(), 13089 movie = moov(tracks), 13090 result; 13091 13092 result = new Uint8Array(fileType.byteLength + movie.byteLength); 13093 result.set(fileType); 13094 result.set(movie, fileType.byteLength); 13095 return result; 13096 } 13097 }; 13098 13099 },{}],55:[function(require,module,exports){ 13100 /** 13101 * mux.js 13102 * 13103 * Copyright (c) 2015 Brightcove 13104 * All rights reserved. 13105 * 13106 * Utilities to detect basic properties and metadata about MP4s. 13107 */ 13108 'use strict'; 13109 13110 var findBox, parseType, timescale, startTime; 13111 13112// Find the data for a box specified by its path 13113 findBox = function(data, path) { 13114 var results = [], 13115 i, size, type, end, subresults; 13116 13117 if (!path.length) { 13118 // short-circuit the search for empty paths 13119 return null; 13120 } 13121 13122 for (i = 0; i < data.byteLength;) { 13123 size = data[i] << 24; 13124 size |= data[i + 1] << 16; 13125 size |= data[i + 2] << 8; 13126 size |= data[i + 3]; 13127 13128 type = parseType(data.subarray(i + 4, i + 8)); 13129 13130 end = size > 1 ? i + size : data.byteLength; 13131 13132 if (type === path[0]) { 13133 if (path.length === 1) { 13134 // this is the end of the path and we've found the box we were 13135 // looking for 13136 results.push(data.subarray(i + 8, end)); 13137 } else { 13138 // recursively search for the next box along the path 13139 subresults = findBox(data.subarray(i + 8, end), path.slice(1)); 13140 if (subresults.length) { 13141 results = results.concat(subresults); 13142 } 13143 } 13144 } 13145 i = end; 13146 } 13147 13148 // we've finished searching all of data 13149 return results; 13150 }; 13151 13152 /** 13153 * Returns the string representation of an ASCII encoded four byte buffer. 13154 * @param buffer {Uint8Array} a four-byte buffer to translate 13155 * @return {string} the corresponding string 13156 */ 13157 parseType = function(buffer) { 13158 var result = ''; 13159 result += String.fromCharCode(buffer[0]); 13160 result += String.fromCharCode(buffer[1]); 13161 result += String.fromCharCode(buffer[2]); 13162 result += String.fromCharCode(buffer[3]); 13163 return result; 13164 }; 13165 13166 /** 13167 * Parses an MP4 initialization segment and extracts the timescale 13168 * values for any declared tracks. Timescale values indicate the
13169 * number of clock ticks per second to assume for time-based values 13170 * elsewhere in the MP4. 13171 * 13172 * To determine the start time of an MP4, you need two pieces of 13173 * information: the timescale unit and the earliest base media decode 13174 * time. Multiple timescales can be specified within an MP4 but the 13175 * base media decode time is always expressed in the timescale from 13176 * the media header box for the track: 13177 * ``` 13178 * moov > trak > mdia > mdhd.timescale 13179 * ``` 13180 * @param init {Uint8Array} the bytes of the init segment 13181 * @return {object} a hash of track ids to timescale values or null if 13182 * the init segment is malformed. 13183 */ 13184 timescale = function(init) { 13185 var 13186 result = {}, 13187 traks = findBox(init, ['moov', 'trak']); 13188 13189 // mdhd timescale 13190 return traks.reduce(function(result, trak) { 13191 var tkhd, version, index, id, mdhd; 13192 13193 tkhd = findBox(trak, ['tkhd'])[0]; 13194 if (!tkhd) { 13195 return null; 13196 } 13197 version = tkhd[0]; 13198 index = version === 0 ? 12 : 20; 13199 id = tkhd[index] << 24 | 13200 tkhd[index + 1] << 16 | 13201 tkhd[index + 2] << 8 | 13202 tkhd[index + 3]; 13203 13204 mdhd = findBox(trak, ['mdia', 'mdhd'])[0]; 13205 if (!mdhd) { 13206 return null; 13207 } 13208 version = mdhd[0]; 13209 index = version === 0 ? 12 : 20; 13210 result[id] = mdhd[index] << 24 | 13211 mdhd[index + 1] << 16 | 13212 mdhd[index + 2] << 8 | 13213 mdhd[index + 3]; 13214 return result; 13215 }, result); 13216 }; 13217 13218 /** 13219 * Determine the base media decode start time, in seconds, for an MP4 13220 * fragment. If multiple fragments are specified, the earliest time is 13221 * returned. 13222 * 13223 * The base media decode time can be parsed from track fragment 13224 * metadata: 13225 * ``` 13226 * moof > traf > tfdt.baseMediaDecodeTime 13227 * ``` 13228 * It requires the timescale value from the mdhd to interpret. 13229 * 13230 * @param timescale {object} a hash of track ids to timescale values. 13231 * @return {number} the earliest base media decode start time for the 13232 * fragment, in seconds 13233 */ 13234 startTime = function(timescale, fragment) { 13235 var trafs, baseTimes, result; 13236 13237 // we need info from two childrend of each track fragment box 13238 trafs = findBox(fragment, ['moof', 'traf']); 13239 13240 // determine the start times for each track 13241 baseTimes = [].concat.apply([], trafs.map(function(traf) { 13242 return findBox(traf, ['tfhd']).map(function(tfhd) { 13243 var id, scale, baseTime; 13244 13245 // get the track id from the tfhd 13246 id = tfhd[4] << 24 | 13247 tfhd[5] << 16 | 13248 tfhd[6] << 8 | 13249 tfhd[7]; 13250 // assume a 90kHz clock if no timescale was specified 13251 scale = timescale[id] || 90e3; 13252 13253 // get the base media decode time from the tfdt 13254 baseTime = findBox(traf, ['tfdt']).map(function(tfdt) { 13255 var version, result; 13256 13257 version = tfdt[0]; 13258 result = tfdt[4] << 24 | 13259 tfdt[5] << 16 | 13260 tfdt[6] << 8 | 13261 tfdt[7]; 13262 if (version === 1) { 13263 result *= Math.pow(2, 32); 13264 result += tfdt[8] << 24 | 13265 tfdt[9] << 16 | 13266 tfdt[10] << 8 | 13267 tfdt[11]; 13268 } 13269 return result; 13270 })[0]; 13271 baseTime = baseTime || Infinity; 13272 13273 // convert base time to seconds 13274 return baseTime / scale; 13275 }); 13276 })); 13277 13278 // return the minimum 13279 result = Math.min.apply(null, baseTimes); 13280 return isFinite(result) ? result : 0; 13281 }; 13282 13283 module.exports = { 13284 parseType: parseType, 13285 timescale: timescale, 13286 startTime: startTime 13287 }; 13288 13289 },{}],56:[function(require,module,exports){ 13290 /** 13291 * mux.js 13292 * 13293 * Copyright (c) 2015 Brightcove 13294 * All rights reserved. 13295 * 13296 * A stream-based mp2t to mp4 converter. This utility can be used to 13297 * deliver mp4s to a SourceBuffer on platforms that support native 13298 * Media Source Extensions. 13299 */ 13300 'use strict'; 13301 13302 var Stream = require('../utils/stream.js'); 13303 var mp4 = require('./mp4-generator.js'); 13304 var m2ts = require('../m2ts/m2ts.js'); 13305 var AdtsStream = require('../codecs/adts.js'); 13306 var H264Stream = require('../codecs/h264').H264Stream; 13307 var AacStream = require('../aac'); 13308 var coneOfSilence = require('../data/silence'); 13309 var clock = require('../utils/clock'); 13310 13311// constants 13312 var AUDIO_PROPERTIES = [ 13313 'audioobjecttype', 13314 'channelcount',
vendor: 8,746 bytes, lines 13315-13563
13315 'samplerate', 13316 'samplingfrequencyindex', 13317 'samplesize' 13318 ]; 13319 13320 var VIDEO_PROPERTIES = [ 13321 'width', 13322 'height', 13323 'profileIdc', 13324 'levelIdc', 13325 'profileCompatibility' 13326 ]; 13327 13328 var ONE_SECOND_IN_TS = 90000; // 90kHz clock 13329 13330// object types 13331 var VideoSegmentStream, AudioSegmentStream, Transmuxer, CoalesceStream; 13332 13333// Helper functions 13334 var 13335 createDefaultSample, 13336 isLikelyAacData, 13337 collectDtsInfo, 13338 clearDtsInfo, 13339 calculateTrackBaseMediaDecodeTime, 13340 arrayEquals, 13341 sumFrameByteLengths; 13342 13343 /** 13344 * Default sample object 13345 * see ISO/IEC 14496-12:2012, section 8.6.4.3 13346 */ 13347 createDefaultSample = function() { 13348 return { 13349 size: 0, 13350 flags: { 13351 isLeading: 0, 13352 dependsOn: 1, 13353 isDependedOn: 0, 13354 hasRedundancy: 0, 13355 degradationPriority: 0 13356 } 13357 }; 13358 }; 13359 13360 isLikelyAacData = function(data) { 13361 if ((data[0] === 'I'.charCodeAt(0)) && 13362 (data[1] === 'D'.charCodeAt(0)) && 13363 (data[2] === '3'.charCodeAt(0))) { 13364 return true; 13365 } 13366 return false; 13367 }; 13368 13369 /** 13370 * Compare two arrays (even typed) for same-ness 13371 */ 13372 arrayEquals = function(a, b) { 13373 var 13374 i; 13375 13376 if (a.length !== b.length) { 13377 return false; 13378 } 13379 13380 // compare the value of each element in the array 13381 for (i = 0; i < a.length; i++) { 13382 if (a[i] !== b[i]) { 13383 return false; 13384 } 13385 } 13386 13387 return true; 13388 }; 13389 13390 /** 13391 * Sum the `byteLength` properties of the data in each AAC frame 13392 */ 13393 sumFrameByteLengths = function(array) { 13394 var 13395 i, 13396 currentObj, 13397 sum = 0; 13398 13399 // sum the byteLength's all each nal unit in the frame 13400 for (i = 0; i < array.length; i++) { 13401 currentObj = array[i]; 13402 sum += currentObj.data.byteLength; 13403 } 13404 13405 return sum; 13406 }; 13407 13408 /** 13409 * Constructs a single-track, ISO BMFF media segment from AAC data 13410 * events. The output of this stream can be fed to a SourceBuffer 13411 * configured with a suitable initialization segment. 13412 */ 13413 AudioSegmentStream = function(track) { 13414 var 13415 adtsFrames = [], 13416 sequenceNumber = 0, 13417 earliestAllowedDts = 0, 13418 audioAppendStartTs = 0, 13419 videoBaseMediaDecodeTime = Infinity; 13420 13421 AudioSegmentStream.prototype.init.call(this); 13422 13423 this.push = function(data) { 13424 collectDtsInfo(track, data); 13425 13426 if (track) { 13427 AUDIO_PROPERTIES.forEach(function(prop) { 13428 track[prop] = data[prop]; 13429 }); 13430 } 13431 13432 // buffer audio data until end() is called 13433 adtsFrames.push(data); 13434 }; 13435 13436 this.setEarliestDts = function(earliestDts) { 13437 earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime; 13438 }; 13439 13440 this.setVideoBaseMediaDecodeTime = function(baseMediaDecodeTime) { 13441 videoBaseMediaDecodeTime = baseMediaDecodeTime; 13442 }; 13443 13444 this.setAudioAppendStart = function(timestamp) { 13445 audioAppendStartTs = timestamp; 13446 }; 13447 13448 this.flush = function() { 13449 var 13450 frames, 13451 moof, 13452 mdat, 13453 boxes; 13454 13455 // return early if no audio data has been observed 13456 if (adtsFrames.length === 0) { 13457 this.trigger('done', 'AudioSegmentStream'); 13458 return; 13459 } 13460 13461 frames = this.trimAdtsFramesByEarliestDts_(adtsFrames); 13462 track.baseMediaDecodeTime = calculateTrackBaseMediaDecodeTime(track); 13463 13464 this.prefixWithSilence_(track, frames); 13465 13466 // we have to build the index from byte locations to 13467 // samples (that is, adts frames) in the audio data 13468 track.samples = this.generateSampleTable_(frames); 13469 13470 // concatenate the audio data to constuct the mdat 13471 mdat = mp4.mdat(this.concatenateFrameData_(frames)); 13472 13473 adtsFrames = []; 13474 13475 moof = mp4.moof(sequenceNumber, [track]); 13476 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); 13477 13478 // bump the sequence number for next time 13479 sequenceNumber++; 13480 13481 boxes.set(moof); 13482 boxes.set(mdat, moof.byteLength); 13483 13484 clearDtsInfo(track); 13485 13486 this.trigger('data', {track: track, boxes: boxes}); 13487 this.trigger('done', 'AudioSegmentStream'); 13488 }; 13489 13490 // Possibly pad (prefix) the audio track with silence if appending this track 13491 // would lead to the introduction of a gap in the audio buffer 13492 this.prefixWithSilence_ = function(track, frames) { 13493 var 13494 baseMediaDecodeTimeTs, 13495 frameDuration = 0, 13496 audioGapDuration = 0, 13497 audioFillFrameCount = 0, 13498 audioFillDuration = 0, 13499 silentFrame, 13500 i; 13501 13502 if (!frames.length) { 13503 return; 13504 } 13505 13506 baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); 13507 // determine frame clock duration based on sample rate, round up to avoid overfills 13508 frameDuration = Math.ceil(ONE_SECOND_IN_TS / (track.samplerate / 1024)); 13509 13510 if (audioAppendStartTs && videoBaseMediaDecodeTime) { 13511 // insert the shortest possible amount (audio gap or audio to video gap) 13512 audioGapDuration = 13513 baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); 13514 // number of full frames in the audio gap 13515 audioFillFrameCount = Math.floor(audioGapDuration / frameDuration); 13516 audioFillDuration = audioFillFrameCount * frameDuration; 13517 } 13518 13519 // don't attempt to fill gaps smaller than a single frame or larger 13520 // than a half second 13521 if (audioFillFrameCount < 1 || audioFillDuration > ONE_SECOND_IN_TS / 2) { 13522 return; 13523 } 13524 13525 silentFrame = coneOfSilence[track.samplerate]; 13526 13527 if (!silentFrame) { 13528 // we don't have a silent frame pregenerated for the sample rate, so use a frame 13529 // from the content instead 13530 silentFrame = frames[0].data; 13531 } 13532 13533 for (i = 0; i < audioFillFrameCount; i++) { 13534 frames.splice(i, 0, { 13535 data: silentFrame 13536 }); 13537 } 13538 13539 track.baseMediaDecodeTime -= 13540 Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate)); 13541 }; 13542 13543 // If the audio segment extends before the earliest allowed dts 13544 // value, remove AAC frames until starts at or after the earliest 13545 // allowed DTS so that we don't end up with a negative baseMedia- 13546 // DecodeTime for the audio track 13547 this.trimAdtsFramesByEarliestDts_ = function(adtsFrames) { 13548 if (track.minSegmentDts >= earliestAllowedDts) { 13549 return adtsFrames; 13550 } 13551 13552 // We will need to recalculate the earliest segment Dts 13553 track.minSegmentDts = Infinity; 13554 13555 return adtsFrames.filter(function(currentFrame) { 13556 // If this is an allowed frame, keep it and record it's Dts 13557 if (currentFrame.dts >= earliestAllowedDts) { 13558 track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts); 13559 track.minSegmentPts = track.minSegmentDts; 13560 return true; 13561 } 13562 // Otherwise, discard it 13563 return false;
13564 }); 13565 }; 13566 13567 // generate the track's raw mdat data from an array of frames 13568 this.generateSampleTable_ = function(frames) { 13569 var 13570 i, 13571 currentFrame, 13572 samples = []; 13573 13574 for (i = 0; i < frames.length; i++) { 13575 currentFrame = frames[i]; 13576 samples.push({ 13577 size: currentFrame.data.byteLength, 13578 duration: 1024 // For AAC audio, all samples contain 1024 samples 13579 }); 13580 } 13581 return samples; 13582 }; 13583 13584 // generate the track's sample table from an array of frames 13585 this.concatenateFrameData_ = function(frames) { 13586 var 13587 i, 13588 currentFrame, 13589 dataOffset = 0, 13590 data = new Uint8Array(sumFrameByteLengths(frames)); 13591 13592 for (i = 0; i < frames.length; i++) { 13593 currentFrame = frames[i]; 13594 13595 data.set(currentFrame.data, dataOffset); 13596 dataOffset += currentFrame.data.byteLength; 13597 } 13598 return data; 13599 }; 13600 }; 13601 13602 AudioSegmentStream.prototype = new Stream(); 13603 13604 /** 13605 * Constructs a single-track, ISO BMFF media segment from H264 data 13606 * events. The output of this stream can be fed to a SourceBuffer 13607 * configured with a suitable initialization segment. 13608 * @param track {object} track metadata configuration 13609 */ 13610 VideoSegmentStream = function(track) { 13611 var 13612 sequenceNumber = 0, 13613 nalUnits = [], 13614 config, 13615 pps; 13616 13617 VideoSegmentStream.prototype.init.call(this); 13618 13619 delete track.minPTS; 13620 13621 this.gopCache_ = []; 13622 13623 this.push = function(nalUnit) { 13624 collectDtsInfo(track, nalUnit); 13625 13626 // record the track config 13627 if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) { 13628 config = nalUnit.config; 13629 track.sps = [nalUnit.data]; 13630 13631 VIDEO_PROPERTIES.forEach(function(prop) { 13632 track[prop] = config[prop]; 13633 }, this); 13634 } 13635 13636 if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' && 13637 !pps) { 13638 pps = nalUnit.data; 13639 track.pps = [nalUnit.data]; 13640 } 13641 13642 // buffer video until flush() is called 13643 nalUnits.push(nalUnit); 13644 }; 13645 13646 this.flush = function() { 13647 var 13648 frames, 13649 gopForFusion, 13650 gops, 13651 moof, 13652 mdat, 13653 boxes; 13654 13655 // Throw away nalUnits at the start of the byte stream until 13656 // we find the first AUD 13657 while (nalUnits.length) { 13658 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') { 13659 break; 13660 } 13661 nalUnits.shift(); 13662 } 13663 13664 // Return early if no video data has been observed 13665 if (nalUnits.length === 0) { 13666 this.resetStream_(); 13667 this.trigger('done', 'VideoSegmentStream'); 13668 return; 13669 } 13670 13671 // Organize the raw nal-units into arrays that represent 13672 // higher-level constructs such as frames and gops 13673 // (group-of-pictures) 13674 frames = this.groupNalsIntoFrames_(nalUnits); 13675 gops = this.groupFramesIntoGops_(frames); 13676 13677 // If the first frame of this fragment is not a keyframe we have 13678 // a problem since MSE (on Chrome) requires a leading keyframe. 13679 // 13680 // We have two approaches to repairing this situation: 13681 // 1) GOP-FUSION: 13682 // This is where we keep track of the GOPS (group-of-pictures) 13683 // from previous fragments and attempt to find one that we can 13684 // prepend to the current fragment in order to create a valid 13685 // fragment. 13686 // 2) KEYFRAME-PULLING: 13687 // Here we search for the first keyframe in the fragment and 13688 // throw away all the frames between the start of the fragment 13689 // and that keyframe. We then extend the duration and pull the 13690 // PTS of the keyframe forward so that it covers the time range 13691 // of the frames that were disposed of. 13692 // 13693 // #1 is far prefereable over #2 which can cause "stuttering" but 13694 // requires more things to be just right. 13695 if (!gops[0][0].keyFrame) { 13696 // Search for a gop for fusion from our gopCache 13697 gopForFusion = this.getGopForFusion_(nalUnits[0], track); 13698 13699 if (gopForFusion) { 13700 gops.unshift(gopForFusion); 13701 // Adjust Gops' metadata to account for the inclusion of the 13702 // new gop at the beginning 13703 gops.byteLength += gopForFusion.byteLength; 13704 gops.nalCount += gopForFusion.nalCount; 13705 gops.pts = gopForFusion.pts; 13706 gops.dts = gopForFusion.dts; 13707 gops.duration += gopForFusion.duration; 13708 } else { 13709 // If we didn't find a candidate gop fall back to keyrame-pulling 13710 gops = this.extendFirstKeyFrame_(gops); 13711 } 13712 } 13713 collectDtsInfo(track, gops); 13714 13715 // First, we have to build the index from byte locations to 13716 // samples (that is, frames) in the video data 13717 track.samples = this.generateSampleTable_(gops); 13718 13719 // Concatenate the video data and construct the mdat 13720 mdat = mp4.mdat(this.concatenateNalData_(gops)); 13721 13722 // save all the nals in the last GOP into the gop cache 13723 this.gopCache_.unshift({ 13724 gop: gops.pop(), 13725 pps: track.pps, 13726 sps: track.sps 13727 }); 13728 13729 // Keep a maximum of 6 GOPs in the cache 13730 this.gopCache_.length = Math.min(6, this.gopCache_.length); 13731 13732 // Clear nalUnits 13733 nalUnits = []; 13734 13735 track.baseMediaDecodeTime = calculateTrackBaseMediaDecodeTime(track); 13736 13737 this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime); 13738 this.trigger('timelineStartInfo', track.timelineStartInfo); 13739 13740 moof = mp4.moof(sequenceNumber, [track]); 13741 13742 // it would be great to allocate this array up front instead of 13743 // throwing away hundreds of media segment fragments 13744 boxes = new Uint8Array(moof.byteLength + mdat.byteLength); 13745 13746 // Bump the sequence number for next time 13747 sequenceNumber++; 13748 13749 boxes.set(moof); 13750 boxes.set(mdat, moof.byteLength); 13751 13752 this.trigger('data', {track: track, boxes: boxes}); 13753
vendor: 34,235 bytes, lines 13754-14527
13754 this.resetStream_(); 13755 13756 // Continue with the flush process now 13757 this.trigger('done', 'VideoSegmentStream'); 13758 }; 13759 13760 this.resetStream_ = function() { 13761 clearDtsInfo(track); 13762 13763 // reset config and pps because they may differ across segments 13764 // for instance, when we are rendition switching 13765 config = undefined; 13766 pps = undefined; 13767 }; 13768 13769 // Search for a candidate Gop for gop-fusion from the gop cache and 13770 // return it or return null if no good candidate was found 13771 this.getGopForFusion_ = function(nalUnit) { 13772 var 13773 halfSecond = 45000, // Half-a-second in a 90khz clock 13774 allowableOverlap = 10000, // About 3 frames @ 30fps 13775 nearestDistance = Infinity, 13776 dtsDistance, 13777 nearestGopObj, 13778 currentGop, 13779 currentGopObj, 13780 i; 13781 13782 // Search for the GOP nearest to the beginning of this nal unit 13783 for (i = 0; i < this.gopCache_.length; i++) { 13784 currentGopObj = this.gopCache_[i]; 13785 currentGop = currentGopObj.gop; 13786 13787 // Reject Gops with different SPS or PPS 13788 if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) || 13789 !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) { 13790 continue; 13791 } 13792 13793 // Reject Gops that would require a negative baseMediaDecodeTime 13794 if (currentGop.dts < track.timelineStartInfo.dts) { 13795 continue; 13796 } 13797 13798 // The distance between the end of the gop and the start of the nalUnit 13799 dtsDistance = (nalUnit.dts - currentGop.dts) - currentGop.duration; 13800 13801 // Only consider GOPS that start before the nal unit and end within 13802 // a half-second of the nal unit 13803 if (dtsDistance >= -allowableOverlap && 13804 dtsDistance <= halfSecond) { 13805 13806 // Always use the closest GOP we found if there is more than 13807 // one candidate 13808 if (!nearestGopObj || 13809 nearestDistance > dtsDistance) { 13810 nearestGopObj = currentGopObj; 13811 nearestDistance = dtsDistance; 13812 } 13813 } 13814 } 13815 13816 if (nearestGopObj) { 13817 return nearestGopObj.gop; 13818 } 13819 return null; 13820 }; 13821 13822 this.extendFirstKeyFrame_ = function(gops) { 13823 var currentGop; 13824 13825 if (!gops[0][0].keyFrame && gops.length > 1) { 13826 // Remove the first GOP 13827 currentGop = gops.shift(); 13828 13829 gops.byteLength -= currentGop.byteLength; 13830 gops.nalCount -= currentGop.nalCount; 13831 13832 // Extend the first frame of what is now the 13833 // first gop to cover the time period of the 13834 // frames we just removed 13835 gops[0][0].dts = currentGop.dts; 13836 gops[0][0].pts = currentGop.pts; 13837 gops[0][0].duration += currentGop.duration; 13838 } 13839 13840 return gops; 13841 }; 13842 13843 // Convert an array of nal units into an array of frames with each frame being 13844 // composed of the nal units that make up that frame 13845 // Also keep track of cummulative data about the frame from the nal units such 13846 // as the frame duration, starting pts, etc. 13847 this.groupNalsIntoFrames_ = function(nalUnits) { 13848 var 13849 i, 13850 currentNal, 13851 currentFrame = [], 13852 frames = []; 13853 13854 currentFrame.byteLength = 0; 13855 13856 for (i = 0; i < nalUnits.length; i++) { 13857 currentNal = nalUnits[i]; 13858 13859 // Split on 'aud'-type nal units 13860 if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') { 13861 // Since the very first nal unit is expected to be an AUD 13862 // only push to the frames array when currentFrame is not empty 13863 if (currentFrame.length) { 13864 currentFrame.duration = currentNal.dts - currentFrame.dts; 13865 frames.push(currentFrame); 13866 } 13867 currentFrame = [currentNal]; 13868 currentFrame.byteLength = currentNal.data.byteLength; 13869 currentFrame.pts = currentNal.pts; 13870 currentFrame.dts = currentNal.dts; 13871 } else { 13872 // Specifically flag key frames for ease of use later 13873 if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') { 13874 currentFrame.keyFrame = true; 13875 } 13876 currentFrame.duration = currentNal.dts - currentFrame.dts; 13877 currentFrame.byteLength += currentNal.data.byteLength; 13878 currentFrame.push(currentNal); 13879 } 13880 } 13881 13882 // For the last frame, use the duration of the previous frame if we 13883 // have nothing better to go on 13884 if (frames.length && 13885 (!currentFrame.duration || 13886 currentFrame.duration <= 0)) { 13887 currentFrame.duration = frames[frames.length - 1].duration; 13888 } 13889 13890 // Push the final frame 13891 frames.push(currentFrame); 13892 return frames; 13893 }; 13894 13895 // Convert an array of frames into an array of Gop with each Gop being composed 13896 // of the frames that make up that Gop 13897 // Also keep track of cummulative data about the Gop from the frames such as the 13898 // Gop duration, starting pts, etc. 13899 this.groupFramesIntoGops_ = function(frames) { 13900 var 13901 i, 13902 currentFrame, 13903 currentGop = [], 13904 gops = []; 13905 13906 // We must pre-set some of the values on the Gop since we 13907 // keep running totals of these values 13908 currentGop.byteLength = 0; 13909 currentGop.nalCount = 0; 13910 currentGop.duration = 0; 13911 currentGop.pts = frames[0].pts; 13912 currentGop.dts = frames[0].dts; 13913 13914 // store some metadata about all the Gops 13915 gops.byteLength = 0; 13916 gops.nalCount = 0; 13917 gops.duration = 0; 13918 gops.pts = frames[0].pts; 13919 gops.dts = frames[0].dts; 13920 13921 for (i = 0; i < frames.length; i++) { 13922 currentFrame = frames[i]; 13923 13924 if (currentFrame.keyFrame) { 13925 // Since the very first frame is expected to be an keyframe 13926 // only push to the gops array when currentGop is not empty 13927 if (currentGop.length) { 13928 gops.push(currentGop); 13929 gops.byteLength += currentGop.byteLength; 13930 gops.nalCount += currentGop.nalCount; 13931 gops.duration += currentGop.duration; 13932 } 13933 13934 currentGop = [currentFrame]; 13935 currentGop.nalCount = currentFrame.length; 13936 currentGop.byteLength = currentFrame.byteLength; 13937 currentGop.pts = currentFrame.pts; 13938 currentGop.dts = currentFrame.dts; 13939 currentGop.duration = currentFrame.duration; 13940 } else { 13941 currentGop.duration += currentFrame.duration; 13942 currentGop.nalCount += currentFrame.length; 13943 currentGop.byteLength += currentFrame.byteLength; 13944 currentGop.push(currentFrame); 13945 } 13946 } 13947 13948 if (gops.length && currentGop.duration <= 0) { 13949 currentGop.duration = gops[gops.length - 1].duration; 13950 } 13951 gops.byteLength += currentGop.byteLength; 13952 gops.nalCount += currentGop.nalCount; 13953 gops.duration += currentGop.duration; 13954 13955 // push the final Gop 13956 gops.push(currentGop); 13957 return gops; 13958 }; 13959 13960 // generate the track's sample table from an array of gops 13961 this.generateSampleTable_ = function(gops, baseDataOffset) { 13962 var 13963 h, i, 13964 sample, 13965 currentGop, 13966 currentFrame, 13967 dataOffset = baseDataOffset || 0, 13968 samples = []; 13969 13970 for (h = 0; h < gops.length; h++) { 13971 currentGop = gops[h]; 13972 13973 for (i = 0; i < currentGop.length; i++) { 13974 currentFrame = currentGop[i]; 13975 13976 sample = createDefaultSample(); 13977 13978 sample.dataOffset = dataOffset; 13979 sample.compositionTimeOffset = currentFrame.pts - currentFrame.dts; 13980 sample.duration = currentFrame.duration; 13981 sample.size = 4 * currentFrame.length; // Space for nal unit size 13982 sample.size += currentFrame.byteLength; 13983 13984 if (currentFrame.keyFrame) { 13985 sample.flags.dependsOn = 2; 13986 } 13987 13988 dataOffset += sample.size; 13989 13990 samples.push(sample); 13991 } 13992 } 13993 return samples; 13994 }; 13995 13996 // generate the track's raw mdat data from an array of gops 13997 this.concatenateNalData_ = function(gops) { 13998 var 13999 h, i, j, 14000 currentGop, 14001 currentFrame, 14002 currentNal, 14003 dataOffset = 0, 14004 nalsByteLength = gops.byteLength, 14005 numberOfNals = gops.nalCount, 14006 totalByteLength = nalsByteLength + 4 * numberOfNals, 14007 data = new Uint8Array(totalByteLength), 14008 view = new DataView(data.buffer); 14009 14010 // For each Gop.. 14011 for (h = 0; h < gops.length; h++) { 14012 currentGop = gops[h]; 14013 14014 // For each Frame.. 14015 for (i = 0; i < currentGop.length; i++) { 14016 currentFrame = currentGop[i]; 14017 14018 // For each NAL.. 14019 for (j = 0; j < currentFrame.length; j++) { 14020 currentNal = currentFrame[j]; 14021 14022 view.setUint32(dataOffset, currentNal.data.byteLength); 14023 dataOffset += 4; 14024 data.set(currentNal.data, dataOffset); 14025 dataOffset += currentNal.data.byteLength; 14026 } 14027 } 14028 } 14029 return data; 14030 }; 14031 }; 14032 14033 VideoSegmentStream.prototype = new Stream(); 14034 14035 /** 14036 * Store information about the start and end of the track and the 14037 * duration for each frame/sample we process in order to calculate 14038 * the baseMediaDecodeTime 14039 */ 14040 collectDtsInfo = function(track, data) { 14041 if (typeof data.pts === 'number') { 14042 if (track.timelineStartInfo.pts === undefined) { 14043 track.timelineStartInfo.pts = data.pts; 14044 } 14045 14046 if (track.minSegmentPts === undefined) { 14047 track.minSegmentPts = data.pts; 14048 } else { 14049 track.minSegmentPts = Math.min(track.minSegmentPts, data.pts); 14050 } 14051 14052 if (track.maxSegmentPts === undefined) { 14053 track.maxSegmentPts = data.pts; 14054 } else { 14055 track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts); 14056 } 14057 } 14058 14059 if (typeof data.dts === 'number') { 14060 if (track.timelineStartInfo.dts === undefined) { 14061 track.timelineStartInfo.dts = data.dts; 14062 } 14063 14064 if (track.minSegmentDts === undefined) { 14065 track.minSegmentDts = data.dts; 14066 } else { 14067 track.minSegmentDts = Math.min(track.minSegmentDts, data.dts); 14068 } 14069 14070 if (track.maxSegmentDts === undefined) { 14071 track.maxSegmentDts = data.dts; 14072 } else { 14073 track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts); 14074 } 14075 } 14076 }; 14077 14078 /** 14079 * Clear values used to calculate the baseMediaDecodeTime between 14080 * tracks 14081 */ 14082 clearDtsInfo = function(track) { 14083 delete track.minSegmentDts; 14084 delete track.maxSegmentDts; 14085 delete track.minSegmentPts; 14086 delete track.maxSegmentPts; 14087 }; 14088 14089 /** 14090 * Calculate the track's baseMediaDecodeTime based on the earliest 14091 * DTS the transmuxer has ever seen and the minimum DTS for the 14092 * current track 14093 */ 14094 calculateTrackBaseMediaDecodeTime = function(track) { 14095 var 14096 baseMediaDecodeTime, 14097 scale, 14098 // Calculate the distance, in time, that this segment starts from the start 14099 // of the timeline (earliest time seen since the transmuxer initialized) 14100 timeSinceStartOfTimeline = track.minSegmentDts - track.timelineStartInfo.dts; 14101 14102 // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where 14103 // we want the start of the first segment to be placed 14104 baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; 14105 14106 // Add to that the distance this segment is from the very first 14107 baseMediaDecodeTime += timeSinceStartOfTimeline; 14108 14109 // baseMediaDecodeTime must not become negative 14110 baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime); 14111 14112 if (track.type === 'audio') { 14113 // Audio has a different clock equal to the sampling_rate so we need to 14114 // scale the PTS values into the clock rate of the track 14115 scale = track.samplerate / ONE_SECOND_IN_TS; 14116 baseMediaDecodeTime *= scale; 14117 baseMediaDecodeTime = Math.floor(baseMediaDecodeTime); 14118 } 14119 14120 return baseMediaDecodeTime; 14121 }; 14122 14123 /** 14124 * A Stream that can combine multiple streams (ie. audio & video) 14125 * into a single output segment for MSE. Also supports audio-only 14126 * and video-only streams. 14127 */ 14128 CoalesceStream = function(options, metadataStream) { 14129 // Number of Tracks per output segment 14130 // If greater than 1, we combine multiple 14131 // tracks into a single segment 14132 this.numberOfTracks = 0; 14133 this.metadataStream = metadataStream; 14134 14135 if (typeof options.remux !== 'undefined') { 14136 this.remuxTracks = !!options.remux; 14137 } else { 14138 this.remuxTracks = true; 14139 } 14140 14141 this.pendingTracks = []; 14142 this.videoTrack = null; 14143 this.pendingBoxes = []; 14144 this.pendingCaptions = []; 14145 this.pendingMetadata = []; 14146 this.pendingBytes = 0; 14147 this.emittedTracks = 0; 14148 14149 CoalesceStream.prototype.init.call(this); 14150 14151 // Take output from multiple 14152 this.push = function(output) { 14153 // buffer incoming captions until the associated video segment 14154 // finishes 14155 if (output.text) { 14156 return this.pendingCaptions.push(output); 14157 } 14158 // buffer incoming id3 tags until the final flush 14159 if (output.frames) { 14160 return this.pendingMetadata.push(output); 14161 } 14162 14163 // Add this track to the list of pending tracks and store 14164 // important information required for the construction of 14165 // the final segment 14166 this.pendingTracks.push(output.track); 14167 this.pendingBoxes.push(output.boxes); 14168 this.pendingBytes += output.boxes.byteLength; 14169 14170 if (output.track.type === 'video') { 14171 this.videoTrack = output.track; 14172 } 14173 if (output.track.type === 'audio') { 14174 this.audioTrack = output.track; 14175 } 14176 }; 14177 }; 14178 14179 CoalesceStream.prototype = new Stream(); 14180 CoalesceStream.prototype.flush = function(flushSource) { 14181 var 14182 offset = 0, 14183 event = { 14184 captions: [], 14185 metadata: [], 14186 info: {} 14187 }, 14188 caption, 14189 id3, 14190 initSegment, 14191 timelineStartPts = 0, 14192 i; 14193 14194 if (this.pendingTracks.length < this.numberOfTracks) { 14195 if (flushSource !== 'VideoSegmentStream' && 14196 flushSource !== 'AudioSegmentStream') { 14197 // Return because we haven't received a flush from a data-generating 14198 // portion of the segment (meaning that we have only recieved meta-data 14199 // or captions.) 14200 return; 14201 } else if (this.remuxTracks) { 14202 // Return until we have enough tracks from the pipeline to remux (if we 14203 // are remuxing audio and video into a single MP4) 14204 return; 14205 } else if (this.pendingTracks.length === 0) { 14206 // In the case where we receive a flush without any data having been 14207 // received we consider it an emitted track for the purposes of coalescing 14208 // `done` events. 14209 // We do this for the case where there is an audio and video track in the 14210 // segment but no audio data. (seen in several playlists with alternate 14211 // audio tracks and no audio present in the main TS segments.) 14212 this.emittedTracks++; 14213 14214 if (this.emittedTracks >= this.numberOfTracks) { 14215 this.trigger('done'); 14216 this.emittedTracks = 0; 14217 } 14218 return; 14219 } 14220 } 14221 14222 if (this.videoTrack) { 14223 timelineStartPts = this.videoTrack.timelineStartInfo.pts; 14224 VIDEO_PROPERTIES.forEach(function(prop) { 14225 event.info[prop] = this.videoTrack[prop]; 14226 }, this); 14227 } else if (this.audioTrack) { 14228 timelineStartPts = this.audioTrack.timelineStartInfo.pts; 14229 AUDIO_PROPERTIES.forEach(function(prop) { 14230 event.info[prop] = this.audioTrack[prop]; 14231 }, this); 14232 } 14233 14234 if (this.pendingTracks.length === 1) { 14235 event.type = this.pendingTracks[0].type; 14236 } else { 14237 event.type = 'combined'; 14238 } 14239 14240 this.emittedTracks += this.pendingTracks.length; 14241 14242 initSegment = mp4.initSegment(this.pendingTracks); 14243 14244 // Create a new typed array to hold the init segment 14245 event.initSegment = new Uint8Array(initSegment.byteLength); 14246 14247 // Create an init segment containing a moov 14248 // and track definitions 14249 event.initSegment.set(initSegment); 14250 14251 // Create a new typed array to hold the moof+mdats 14252 event.data = new Uint8Array(this.pendingBytes); 14253 14254 // Append each moof+mdat (one per track) together 14255 for (i = 0; i < this.pendingBoxes.length; i++) { 14256 event.data.set(this.pendingBoxes[i], offset); 14257 offset += this.pendingBoxes[i].byteLength; 14258 } 14259 14260 // Translate caption PTS times into second offsets into the 14261 // video timeline for the segment 14262 for (i = 0; i < this.pendingCaptions.length; i++) { 14263 caption = this.pendingCaptions[i]; 14264 caption.startTime = (caption.startPts - timelineStartPts); 14265 caption.startTime /= 90e3; 14266 caption.endTime = (caption.endPts - timelineStartPts); 14267 caption.endTime /= 90e3; 14268 event.captions.push(caption); 14269 } 14270 14271 // Translate ID3 frame PTS times into second offsets into the 14272 // video timeline for the segment 14273 for (i = 0; i < this.pendingMetadata.length; i++) { 14274 id3 = this.pendingMetadata[i]; 14275 id3.cueTime = (id3.pts - timelineStartPts); 14276 id3.cueTime /= 90e3; 14277 event.metadata.push(id3); 14278 } 14279 // We add this to every single emitted segment even though we only need 14280 // it for the first 14281 event.metadata.dispatchType = this.metadataStream.dispatchType; 14282 14283 // Reset stream state 14284 this.pendingTracks.length = 0; 14285 this.videoTrack = null; 14286 this.pendingBoxes.length = 0; 14287 this.pendingCaptions.length = 0; 14288 this.pendingBytes = 0; 14289 this.pendingMetadata.length = 0; 14290 14291 // Emit the built segment 14292 this.trigger('data', event); 14293 14294 // Only emit `done` if all tracks have been flushed and emitted 14295 if (this.emittedTracks >= this.numberOfTracks) { 14296 this.trigger('done'); 14297 this.emittedTracks = 0; 14298 } 14299 }; 14300 /** 14301 * A Stream that expects MP2T binary data as input and produces 14302 * corresponding media segments, suitable for use with Media Source 14303 * Extension (MSE) implementations that support the ISO BMFF byte 14304 * stream format, like Chrome. 14305 */ 14306 Transmuxer = function(options) { 14307 var 14308 self = this, 14309 hasFlushed = true, 14310 videoTrack, 14311 audioTrack; 14312 14313 Transmuxer.prototype.init.call(this); 14314 14315 options = options || {}; 14316 this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0; 14317 this.transmuxPipeline_ = {}; 14318 14319 this.setupAacPipeline = function() { 14320 var pipeline = {}; 14321 this.transmuxPipeline_ = pipeline; 14322 14323 pipeline.type = 'aac'; 14324 pipeline.metadataStream = new m2ts.MetadataStream(); 14325 14326 // set up the parsing pipeline 14327 pipeline.aacStream = new AacStream(); 14328 pipeline.audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio'); 14329 pipeline.timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata'); 14330 pipeline.adtsStream = new AdtsStream(); 14331 pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream); 14332 pipeline.headOfPipeline = pipeline.aacStream; 14333 14334 pipeline.aacStream 14335 .pipe(pipeline.audioTimestampRolloverStream) 14336 .pipe(pipeline.adtsStream); 14337 pipeline.aacStream 14338 .pipe(pipeline.timedMetadataTimestampRolloverStream) 14339 .pipe(pipeline.metadataStream) 14340 .pipe(pipeline.coalesceStream); 14341 14342 pipeline.metadataStream.on('timestamp', function(frame) { 14343 pipeline.aacStream.setTimestamp(frame.timeStamp); 14344 }); 14345 14346 pipeline.aacStream.on('data', function(data) { 14347 if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) { 14348 audioTrack = audioTrack || { 14349 timelineStartInfo: { 14350 baseMediaDecodeTime: self.baseMediaDecodeTime 14351 }, 14352 codec: 'adts', 14353 type: 'audio' 14354 }; 14355 // hook up the audio segment stream to the first track with aac data 14356 pipeline.coalesceStream.numberOfTracks++; 14357 pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack); 14358 // Set up the final part of the audio pipeline 14359 pipeline.adtsStream 14360 .pipe(pipeline.audioSegmentStream) 14361 .pipe(pipeline.coalesceStream); 14362 } 14363 }); 14364 14365 // Re-emit any data coming from the coalesce stream to the outside world 14366 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); 14367 // Let the consumer know we have finished flushing the entire pipeline 14368 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 14369 }; 14370 14371 this.setupTsPipeline = function() { 14372 var pipeline = {}; 14373 this.transmuxPipeline_ = pipeline; 14374 14375 pipeline.type = 'ts'; 14376 pipeline.metadataStream = new m2ts.MetadataStream(); 14377 14378 // set up the parsing pipeline 14379 pipeline.packetStream = new m2ts.TransportPacketStream(); 14380 pipeline.parseStream = new m2ts.TransportParseStream(); 14381 pipeline.elementaryStream = new m2ts.ElementaryStream(); 14382 pipeline.videoTimestampRolloverStream = new m2ts.TimestampRolloverStream('video'); 14383 pipeline.audioTimestampRolloverStream = new m2ts.TimestampRolloverStream('audio'); 14384 pipeline.timedMetadataTimestampRolloverStream = new m2ts.TimestampRolloverStream('timed-metadata'); 14385 pipeline.adtsStream = new AdtsStream(); 14386 pipeline.h264Stream = new H264Stream(); 14387 pipeline.captionStream = new m2ts.CaptionStream(); 14388 pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream); 14389 pipeline.headOfPipeline = pipeline.packetStream; 14390 14391 // disassemble MPEG2-TS packets into elementary streams 14392 pipeline.packetStream 14393 .pipe(pipeline.parseStream) 14394 .pipe(pipeline.elementaryStream); 14395 14396 // !!THIS ORDER IS IMPORTANT!! 14397 // demux the streams 14398 pipeline.elementaryStream 14399 .pipe(pipeline.videoTimestampRolloverStream) 14400 .pipe(pipeline.h264Stream); 14401 pipeline.elementaryStream 14402 .pipe(pipeline.audioTimestampRolloverStream) 14403 .pipe(pipeline.adtsStream); 14404 14405 pipeline.elementaryStream 14406 .pipe(pipeline.timedMetadataTimestampRolloverStream) 14407 .pipe(pipeline.metadataStream) 14408 .pipe(pipeline.coalesceStream); 14409 14410 // Hook up CEA-608/708 caption stream 14411 pipeline.h264Stream.pipe(pipeline.captionStream) 14412 .pipe(pipeline.coalesceStream); 14413 14414 pipeline.elementaryStream.on('data', function(data) { 14415 var i; 14416 14417 if (data.type === 'metadata') { 14418 i = data.tracks.length; 14419 14420 // scan the tracks listed in the metadata 14421 while (i--) { 14422 if (!videoTrack && data.tracks[i].type === 'video') { 14423 videoTrack = data.tracks[i]; 14424 videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 14425 } else if (!audioTrack && data.tracks[i].type === 'audio') { 14426 audioTrack = data.tracks[i]; 14427 audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime; 14428 } 14429 } 14430 14431 // hook up the video segment stream to the first track with h264 data 14432 if (videoTrack && !pipeline.videoSegmentStream) { 14433 pipeline.coalesceStream.numberOfTracks++; 14434 pipeline.videoSegmentStream = new VideoSegmentStream(videoTrack); 14435 14436 pipeline.videoSegmentStream.on('timelineStartInfo', function(timelineStartInfo) { 14437 // When video emits timelineStartInfo data after a flush, we forward that 14438 // info to the AudioSegmentStream, if it exists, because video timeline 14439 // data takes precedence. 14440 if (audioTrack) { 14441 audioTrack.timelineStartInfo = timelineStartInfo; 14442 // On the first segment we trim AAC frames that exist before the 14443 // very earliest DTS we have seen in video because Chrome will 14444 // interpret any video track with a baseMediaDecodeTime that is 14445 // non-zero as a gap. 14446 pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts); 14447 } 14448 }); 14449 14450 pipeline.videoSegmentStream.on('baseMediaDecodeTime', function(baseMediaDecodeTime) { 14451 if (audioTrack) { 14452 pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime); 14453 } 14454 }); 14455 14456 // Set up the final part of the video pipeline 14457 pipeline.h264Stream 14458 .pipe(pipeline.videoSegmentStream) 14459 .pipe(pipeline.coalesceStream); 14460 } 14461 14462 if (audioTrack && !pipeline.audioSegmentStream) { 14463 // hook up the audio segment stream to the first track with aac data 14464 pipeline.coalesceStream.numberOfTracks++; 14465 pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack); 14466 14467 // Set up the final part of the audio pipeline 14468 pipeline.adtsStream 14469 .pipe(pipeline.audioSegmentStream) 14470 .pipe(pipeline.coalesceStream); 14471 } 14472 } 14473 }); 14474 14475 // Re-emit any data coming from the coalesce stream to the outside world 14476 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data')); 14477 // Let the consumer know we have finished flushing the entire pipeline 14478 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done')); 14479 }; 14480 14481 // hook up the segment streams once track metadata is delivered 14482 this.setBaseMediaDecodeTime = function(baseMediaDecodeTime) { 14483 var pipeline = this.transmuxPipeline_; 14484 14485 this.baseMediaDecodeTime = baseMediaDecodeTime; 14486 if (audioTrack) { 14487 audioTrack.timelineStartInfo.dts = undefined; 14488 audioTrack.timelineStartInfo.pts = undefined; 14489 clearDtsInfo(audioTrack); 14490 audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 14491 if (pipeline.audioTimestampRolloverStream) { 14492 pipeline.audioTimestampRolloverStream.discontinuity(); 14493 } 14494 } 14495 if (videoTrack) { 14496 if (pipeline.videoSegmentStream) { 14497 pipeline.videoSegmentStream.gopCache_ = []; 14498 pipeline.videoTimestampRolloverStream.discontinuity(); 14499 } 14500 videoTrack.timelineStartInfo.dts = undefined; 14501 videoTrack.timelineStartInfo.pts = undefined; 14502 clearDtsInfo(videoTrack); 14503 videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime; 14504 } 14505 14506 if (pipeline.timedMetadataTimestampRolloverStream) { 14507 pipeline.timedMetadataTimestampRolloverStream.discontinuity(); 14508 } 14509 }; 14510 14511 this.setAudioAppendStart = function(timestamp) { 14512 if (audioTrack) { 14513 this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp); 14514 } 14515 }; 14516 14517 // feed incoming data to the front of the parsing pipeline 14518 this.push = function(data) { 14519 if (hasFlushed) { 14520 var isAac = isLikelyAacData(data); 14521 14522 if (isAac && this.transmuxPipeline_.type !== 'aac') { 14523 this.setupAacPipeline(); 14524 } else if (!isAac && this.transmuxPipeline_.type !== 'ts') { 14525 this.setupTsPipeline(); 14526 } 14527 hasFlushed = false;
14528 } 14529 this.transmuxPipeline_.headOfPipeline.push(data); 14530 }; 14531 14532 // flush any buffered data 14533 this.flush = function() { 14534 hasFlushed = true; 14535 // Start at the top of the pipeline and flush all pending work 14536 this.transmuxPipeline_.headOfPipeline.flush(); 14537 }; 14538 }; 14539 Transmuxer.prototype = new Stream(); 14540 14541 module.exports = { 14542 Transmuxer: Transmuxer, 14543 VideoSegmentStream: VideoSegmentStream, 14544 AudioSegmentStream: AudioSegmentStream, 14545 AUDIO_PROPERTIES: AUDIO_PROPERTIES, 14546 VIDEO_PROPERTIES: VIDEO_PROPERTIES 14547 }; 14548 14549 },{"../aac":36,"../codecs/adts.js":38,"../codecs/h264":39,"../data/silence":40,"../m2ts/m2ts.js":48,"../utils/clock":58,"../utils/stream.js":60,"./mp4-generator.js":54}],57:[function(require,module,exports){ 14550 /** 14551 * mux.js 14552 * 14553 * Copyright (c) 2016 Brightcove 14554 * All rights reserved. 14555 * 14556 * Parse mpeg2 transport stream packets to extract basic timing information 14557 */ 14558 'use strict'; 14559 14560 var StreamTypes = require('../m2ts/stream-types.js'); 14561 var handleRollover = require('../m2ts/timestamp-rollover-stream.js').handleRollover; 14562 var probe = {}; 14563 probe.ts = require('../m2ts/probe.js'); 14564 probe.aac = require('../aac/probe.js'); 14565 14566 14567 var 14568 PES_TIMESCALE = 90000, 14569 MP2T_PACKET_LENGTH = 188, // bytes 14570 SYNC_BYTE = 0x47; 14571 14572 var isLikelyAacData = function(data) { 14573 if ((data[0] === 'I'.charCodeAt(0)) && 14574 (data[1] === 'D'.charCodeAt(0)) && 14575 (data[2] === '3'.charCodeAt(0))) { 14576 return true; 14577 } 14578 return false; 14579 }; 14580 14581 /** 14582 * walks through segment data looking for pat and pmt packets to parse out 14583 * program map table information 14584 */ 14585 var parsePsi_ = function(bytes, pmt) { 14586 var 14587 startIndex = 0, 14588 endIndex = MP2T_PACKET_LENGTH, 14589 packet, type; 14590 14591 while (endIndex < bytes.byteLength) { 14592 // Look for a pair of start and end sync bytes in the data.. 14593 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 14594 // We found a packet 14595 packet = bytes.subarray(startIndex, endIndex); 14596 type = probe.ts.parseType(packet, pmt.pid); 14597 14598 switch (type) { 14599 case 'pat': 14600 if (!pmt.pid) { 14601 pmt.pid = probe.ts.parsePat(packet); 14602 } 14603 break; 14604 case 'pmt': 14605 if (!pmt.table) { 14606 pmt.table = probe.ts.parsePmt(packet); 14607 } 14608 break; 14609 default: 14610 break; 14611 } 14612 14613 // Found the pat and pmt, we can stop walking the segment 14614 if (pmt.pid && pmt.table) { 14615 return; 14616 } 14617 14618 startIndex += MP2T_PACKET_LENGTH; 14619 endIndex += MP2T_PACKET_LENGTH; 14620 continue; 14621 } 14622 14623 // If we get here, we have somehow become de-synchronized and we need to step 14624 // forward one byte at a time until we find a pair of sync bytes that denote 14625 // a packet 14626 startIndex++; 14627 endIndex++; 14628 } 14629 }; 14630 14631 /** 14632 * walks through the segment data from the start and end to get timing information 14633 * for the first and last audio pes packets 14634 */ 14635 var parseAudioPes_ = function(bytes, pmt, result) { 14636 var 14637 startIndex = 0, 14638 endIndex = MP2T_PACKET_LENGTH, 14639 packet, type, pesType, pusi, parsed; 14640 14641 var endLoop = false;
vendor: 25,655 bytes, lines 14642-15291
14642 14643 // Start walking from start of segment to get first audio packet 14644 while (endIndex < bytes.byteLength) { 14645 // Look for a pair of start and end sync bytes in the data.. 14646 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 14647 // We found a packet 14648 packet = bytes.subarray(startIndex, endIndex); 14649 type = probe.ts.parseType(packet, pmt.pid); 14650 14651 switch (type) { 14652 case 'pes': 14653 pesType = probe.ts.parsePesType(packet, pmt.table); 14654 pusi = probe.ts.parsePayloadUnitStartIndicator(packet); 14655 if (pesType === 'audio' && pusi) { 14656 parsed = probe.ts.parsePesTime(packet); 14657 if (parsed) { 14658 parsed.type = 'audio'; 14659 result.audio.push(parsed); 14660 endLoop = true; 14661 } 14662 } 14663 break; 14664 default: 14665 break; 14666 } 14667 14668 if (endLoop) { 14669 break; 14670 } 14671 14672 startIndex += MP2T_PACKET_LENGTH; 14673 endIndex += MP2T_PACKET_LENGTH; 14674 continue; 14675 } 14676 14677 // If we get here, we have somehow become de-synchronized and we need to step 14678 // forward one byte at a time until we find a pair of sync bytes that denote 14679 // a packet 14680 startIndex++; 14681 endIndex++; 14682 } 14683 14684 // Start walking from end of segment to get last audio packet 14685 endIndex = bytes.byteLength; 14686 startIndex = endIndex - MP2T_PACKET_LENGTH; 14687 endLoop = false; 14688 while (startIndex >= 0) { 14689 // Look for a pair of start and end sync bytes in the data.. 14690 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 14691 // We found a packet 14692 packet = bytes.subarray(startIndex, endIndex); 14693 type = probe.ts.parseType(packet, pmt.pid); 14694 14695 switch (type) { 14696 case 'pes': 14697 pesType = probe.ts.parsePesType(packet, pmt.table); 14698 pusi = probe.ts.parsePayloadUnitStartIndicator(packet); 14699 if (pesType === 'audio' && pusi) { 14700 parsed = probe.ts.parsePesTime(packet); 14701 if (parsed) { 14702 parsed.type = 'audio'; 14703 result.audio.push(parsed); 14704 endLoop = true; 14705 } 14706 } 14707 break; 14708 default: 14709 break; 14710 } 14711 14712 if (endLoop) { 14713 break; 14714 } 14715 14716 startIndex -= MP2T_PACKET_LENGTH; 14717 endIndex -= MP2T_PACKET_LENGTH; 14718 continue; 14719 } 14720 14721 // If we get here, we have somehow become de-synchronized and we need to step 14722 // forward one byte at a time until we find a pair of sync bytes that denote 14723 // a packet 14724 startIndex--; 14725 endIndex--; 14726 } 14727 }; 14728 14729 /** 14730 * walks through the segment data from the start and end to get timing information 14731 * for the first and last video pes packets as well as timing information for the first 14732 * key frame. 14733 */ 14734 var parseVideoPes_ = function(bytes, pmt, result) { 14735 var 14736 startIndex = 0, 14737 endIndex = MP2T_PACKET_LENGTH, 14738 packet, type, pesType, pusi, parsed, frame, i, pes; 14739 14740 var endLoop = false; 14741 14742 var currentFrame = { 14743 data: [], 14744 size: 0 14745 }; 14746 14747 // Start walking from start of segment to get first video packet 14748 while (endIndex < bytes.byteLength) { 14749 // Look for a pair of start and end sync bytes in the data.. 14750 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 14751 // We found a packet 14752 packet = bytes.subarray(startIndex, endIndex); 14753 type = probe.ts.parseType(packet, pmt.pid); 14754 14755 switch (type) { 14756 case 'pes': 14757 pesType = probe.ts.parsePesType(packet, pmt.table); 14758 pusi = probe.ts.parsePayloadUnitStartIndicator(packet); 14759 if (pesType === 'video') { 14760 if (pusi && !endLoop) { 14761 parsed = probe.ts.parsePesTime(packet); 14762 if (parsed) { 14763 parsed.type = 'video'; 14764 result.video.push(parsed); 14765 endLoop = true; 14766 } 14767 } 14768 if (!result.firstKeyFrame) { 14769 if (pusi) { 14770 if (currentFrame.size !== 0) { 14771 frame = new Uint8Array(currentFrame.size); 14772 i = 0; 14773 while (currentFrame.data.length) { 14774 pes = currentFrame.data.shift(); 14775 frame.set(pes, i); 14776 i += pes.byteLength; 14777 } 14778 if (probe.ts.videoPacketContainsKeyFrame(frame)) { 14779 result.firstKeyFrame = probe.ts.parsePesTime(frame); 14780 result.firstKeyFrame.type = 'video'; 14781 } 14782 currentFrame.size = 0; 14783 } 14784 } 14785 currentFrame.data.push(packet); 14786 currentFrame.size += packet.byteLength; 14787 } 14788 } 14789 break; 14790 default: 14791 break; 14792 } 14793 14794 if (endLoop && result.firstKeyFrame) { 14795 break; 14796 } 14797 14798 startIndex += MP2T_PACKET_LENGTH; 14799 endIndex += MP2T_PACKET_LENGTH; 14800 continue; 14801 } 14802 14803 // If we get here, we have somehow become de-synchronized and we need to step 14804 // forward one byte at a time until we find a pair of sync bytes that denote 14805 // a packet 14806 startIndex++; 14807 endIndex++; 14808 } 14809 14810 // Start walking from end of segment to get last video packet 14811 endIndex = bytes.byteLength; 14812 startIndex = endIndex - MP2T_PACKET_LENGTH; 14813 endLoop = false; 14814 while (startIndex >= 0) { 14815 // Look for a pair of start and end sync bytes in the data.. 14816 if (bytes[startIndex] === SYNC_BYTE && bytes[endIndex] === SYNC_BYTE) { 14817 // We found a packet 14818 packet = bytes.subarray(startIndex, endIndex); 14819 type = probe.ts.parseType(packet, pmt.pid); 14820 14821 switch (type) { 14822 case 'pes': 14823 pesType = probe.ts.parsePesType(packet, pmt.table); 14824 pusi = probe.ts.parsePayloadUnitStartIndicator(packet); 14825 if (pesType === 'video' && pusi) { 14826 parsed = probe.ts.parsePesTime(packet); 14827 if (parsed) { 14828 parsed.type = 'video'; 14829 result.video.push(parsed); 14830 endLoop = true; 14831 } 14832 } 14833 break; 14834 default: 14835 break; 14836 } 14837 14838 if (endLoop) { 14839 break; 14840 } 14841 14842 startIndex -= MP2T_PACKET_LENGTH; 14843 endIndex -= MP2T_PACKET_LENGTH; 14844 continue; 14845 } 14846 14847 // If we get here, we have somehow become de-synchronized and we need to step 14848 // forward one byte at a time until we find a pair of sync bytes that denote 14849 // a packet 14850 startIndex--; 14851 endIndex--; 14852 } 14853 }; 14854 14855 /** 14856 * Adjusts the timestamp information for the segment to account for 14857 * rollover and convert to seconds based on pes packet timescale (90khz clock) 14858 */ 14859 var adjustTimestamp_ = function(segmentInfo, baseTimestamp) { 14860 if (segmentInfo.audio && segmentInfo.audio.length) { 14861 var audioBaseTimestamp = baseTimestamp; 14862 if (typeof audioBaseTimestamp === 'undefined') { 14863 audioBaseTimestamp = segmentInfo.audio[0].dts; 14864 } 14865 segmentInfo.audio.forEach(function(info) { 14866 info.dts = handleRollover(info.dts, audioBaseTimestamp); 14867 info.pts = handleRollover(info.pts, audioBaseTimestamp); 14868 // time in seconds 14869 info.dtsTime = info.dts / PES_TIMESCALE; 14870 info.ptsTime = info.pts / PES_TIMESCALE; 14871 }); 14872 } 14873 14874 if (segmentInfo.video && segmentInfo.video.length) { 14875 var videoBaseTimestamp = baseTimestamp; 14876 if (typeof videoBaseTimestamp === 'undefined') { 14877 videoBaseTimestamp = segmentInfo.video[0].dts; 14878 } 14879 segmentInfo.video.forEach(function(info) { 14880 info.dts = handleRollover(info.dts, videoBaseTimestamp); 14881 info.pts = handleRollover(info.pts, videoBaseTimestamp); 14882 // time in seconds 14883 info.dtsTime = info.dts / PES_TIMESCALE; 14884 info.ptsTime = info.pts / PES_TIMESCALE; 14885 }); 14886 if (segmentInfo.firstKeyFrame) { 14887 var frame = segmentInfo.firstKeyFrame; 14888 frame.dts = handleRollover(frame.dts, videoBaseTimestamp); 14889 frame.pts = handleRollover(frame.pts, videoBaseTimestamp); 14890 // time in seconds 14891 frame.dtsTime = frame.dts / PES_TIMESCALE; 14892 frame.ptsTime = frame.dts / PES_TIMESCALE; 14893 } 14894 } 14895 }; 14896 14897 /** 14898 * inspects the aac data stream for start and end time information 14899 */ 14900 var inspectAac_ = function(bytes) { 14901 var 14902 endLoop = false, 14903 audioCount = 0, 14904 sampleRate = null, 14905 timestamp = null, 14906 frameSize = 0, 14907 byteIndex = 0, 14908 packet; 14909 14910 while (bytes.length - byteIndex >= 3) { 14911 var type = probe.aac.parseType(bytes, byteIndex); 14912 switch (type) { 14913 case 'timed-metadata': 14914 // Exit early because we don't have enough to parse 14915 // the ID3 tag header 14916 if (bytes.length - byteIndex < 10) { 14917 endLoop = true; 14918 break; 14919 } 14920 14921 frameSize = probe.aac.parseId3TagSize(bytes, byteIndex); 14922 14923 // Exit early if we don't have enough in the buffer 14924 // to emit a full packet 14925 if (frameSize > bytes.length) { 14926 endLoop = true; 14927 break; 14928 } 14929 if (timestamp === null) { 14930 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 14931 timestamp = probe.aac.parseAacTimestamp(packet); 14932 } 14933 byteIndex += frameSize; 14934 break; 14935 case 'audio': 14936 // Exit early because we don't have enough to parse 14937 // the ADTS frame header 14938 if (bytes.length - byteIndex < 7) { 14939 endLoop = true; 14940 break; 14941 } 14942 14943 frameSize = probe.aac.parseAdtsSize(bytes, byteIndex); 14944 14945 // Exit early if we don't have enough in the buffer 14946 // to emit a full packet 14947 if (frameSize > bytes.length) { 14948 endLoop = true; 14949 break; 14950 } 14951 if (sampleRate === null) { 14952 packet = bytes.subarray(byteIndex, byteIndex + frameSize); 14953 sampleRate = probe.aac.parseSampleRate(packet); 14954 } 14955 audioCount++; 14956 byteIndex += frameSize; 14957 break; 14958 default: 14959 byteIndex++; 14960 break; 14961 } 14962 if (endLoop) { 14963 return null; 14964 } 14965 } 14966 if (sampleRate === null || timestamp === null) { 14967 return null; 14968 } 14969 14970 var audioTimescale = PES_TIMESCALE / sampleRate; 14971 14972 var result = { 14973 audio: [ 14974 { 14975 type: 'audio', 14976 dts: timestamp, 14977 pts: timestamp 14978 }, 14979 { 14980 type: 'audio', 14981 dts: timestamp + (audioCount * 1024 * audioTimescale), 14982 pts: timestamp + (audioCount * 1024 * audioTimescale) 14983 } 14984 ] 14985 }; 14986 14987 return result; 14988 }; 14989 14990 /** 14991 * inspects the transport stream segment data for start and end time information 14992 * of the audio and video tracks (when present) as well as the first key frame's 14993 * start time. 14994 */ 14995 var inspectTs_ = function(bytes) { 14996 var pmt = { 14997 pid: null, 14998 table: null 14999 }; 15000 15001 var result = {}; 15002 15003 parsePsi_(bytes, pmt); 15004 15005 for (var pid in pmt.table) { 15006 if (pmt.table.hasOwnProperty(pid)) { 15007 var type = pmt.table[pid]; 15008 switch (type) { 15009 case StreamTypes.H264_STREAM_TYPE: 15010 result.video = []; 15011 parseVideoPes_(bytes, pmt, result); 15012 if (result.video.length === 0) { 15013 delete result.video; 15014 } 15015 break; 15016 case StreamTypes.ADTS_STREAM_TYPE: 15017 result.audio = []; 15018 parseAudioPes_(bytes, pmt, result); 15019 if (result.audio.length === 0) { 15020 delete result.audio; 15021 } 15022 break; 15023 default: 15024 break; 15025 } 15026 } 15027 } 15028 return result; 15029 }; 15030 15031 /** 15032 * Inspects segment byte data and returns an object with start and end timing information 15033 * 15034 * @param {Uint8Array} bytes The segment byte data 15035 * @param {Number} baseTimestamp Relative reference timestamp used when adjusting frame 15036 * timestamps for rollover. This value must be in 90khz clock. 15037 * @return {Object} Object containing start and end frame timing info of segment. 15038 */ 15039 var inspect = function(bytes, baseTimestamp) { 15040 var isAacData = isLikelyAacData(bytes); 15041 15042 var result; 15043 15044 if (isAacData) { 15045 result = inspectAac_(bytes); 15046 } else { 15047 result = inspectTs_(bytes); 15048 } 15049 15050 if (!result || (!result.audio && !result.video)) { 15051 return null; 15052 } 15053 15054 adjustTimestamp_(result, baseTimestamp); 15055 15056 return result; 15057 }; 15058 15059 module.exports = { 15060 inspect: inspect 15061 }; 15062 15063 },{"../aac/probe.js":37,"../m2ts/probe.js":50,"../m2ts/stream-types.js":51,"../m2ts/timestamp-rollover-stream.js":52}],58:[function(require,module,exports){ 15064 var 15065 ONE_SECOND_IN_TS = 90000, // 90kHz clock 15066 secondsToVideoTs, 15067 secondsToAudioTs, 15068 videoTsToSeconds, 15069 audioTsToSeconds, 15070 audioTsToVideoTs, 15071 videoTsToAudioTs; 15072 15073 secondsToVideoTs = function(seconds) { 15074 return seconds * ONE_SECOND_IN_TS; 15075 }; 15076 15077 secondsToAudioTs = function(seconds, sampleRate) { 15078 return seconds * sampleRate; 15079 }; 15080 15081 videoTsToSeconds = function(timestamp) { 15082 return timestamp / ONE_SECOND_IN_TS; 15083 }; 15084 15085 audioTsToSeconds = function(timestamp, sampleRate) { 15086 return timestamp / sampleRate; 15087 }; 15088 15089 audioTsToVideoTs = function(timestamp, sampleRate) { 15090 return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate)); 15091 }; 15092 15093 videoTsToAudioTs = function(timestamp, sampleRate) { 15094 return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate); 15095 }; 15096 15097 module.exports = { 15098 secondsToVideoTs: secondsToVideoTs, 15099 secondsToAudioTs: secondsToAudioTs, 15100 videoTsToSeconds: videoTsToSeconds, 15101 audioTsToSeconds: audioTsToSeconds, 15102 audioTsToVideoTs: audioTsToVideoTs, 15103 videoTsToAudioTs: videoTsToAudioTs 15104 }; 15105 15106 },{}],59:[function(require,module,exports){ 15107 'use strict'; 15108 15109 var ExpGolomb; 15110 15111 /** 15112 * Parser for exponential Golomb codes, a variable-bitwidth number encoding 15113 * scheme used by h264. 15114 */ 15115 ExpGolomb = function(workingData) { 15116 var 15117 // the number of bytes left to examine in workingData 15118 workingBytesAvailable = workingData.byteLength, 15119 15120 // the current word being examined 15121 workingWord = 0, // :uint 15122 15123 // the number of bits left to examine in the current word 15124 workingBitsAvailable = 0; // :uint; 15125 15126 // ():uint 15127 this.length = function() { 15128 return (8 * workingBytesAvailable); 15129 }; 15130 15131 // ():uint 15132 this.bitsAvailable = function() { 15133 return (8 * workingBytesAvailable) + workingBitsAvailable; 15134 }; 15135 15136 // ():void 15137 this.loadWord = function() { 15138 var 15139 position = workingData.byteLength - workingBytesAvailable, 15140 workingBytes = new Uint8Array(4), 15141 availableBytes = Math.min(4, workingBytesAvailable); 15142 15143 if (availableBytes === 0) { 15144 throw new Error('no bytes available'); 15145 } 15146 15147 workingBytes.set(workingData.subarray(position, 15148 position + availableBytes)); 15149 workingWord = new DataView(workingBytes.buffer).getUint32(0); 15150 15151 // track the amount of workingData that has been processed 15152 workingBitsAvailable = availableBytes * 8; 15153 workingBytesAvailable -= availableBytes; 15154 }; 15155 15156 // (count:int):void 15157 this.skipBits = function(count) { 15158 var skipBytes; // :int 15159 if (workingBitsAvailable > count) { 15160 workingWord <<= count; 15161 workingBitsAvailable -= count; 15162 } else { 15163 count -= workingBitsAvailable; 15164 skipBytes = Math.floor(count / 8); 15165 15166 count -= (skipBytes * 8); 15167 workingBytesAvailable -= skipBytes; 15168 15169 this.loadWord(); 15170 15171 workingWord <<= count; 15172 workingBitsAvailable -= count; 15173 } 15174 }; 15175 15176 // (size:int):uint 15177 this.readBits = function(size) { 15178 var 15179 bits = Math.min(workingBitsAvailable, size), // :uint 15180 valu = workingWord >>> (32 - bits); // :uint 15181 // if size > 31, handle error 15182 workingBitsAvailable -= bits; 15183 if (workingBitsAvailable > 0) { 15184 workingWord <<= bits; 15185 } else if (workingBytesAvailable > 0) { 15186 this.loadWord(); 15187 } 15188 15189 bits = size - bits; 15190 if (bits > 0) { 15191 return valu << bits | this.readBits(bits); 15192 } 15193 return valu; 15194 }; 15195 15196 // ():uint 15197 this.skipLeadingZeros = function() { 15198 var leadingZeroCount; // :uint 15199 for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) { 15200 if ((workingWord & (0x80000000 >>> leadingZeroCount)) !== 0) { 15201 // the first bit of working word is 1 15202 workingWord <<= leadingZeroCount; 15203 workingBitsAvailable -= leadingZeroCount; 15204 return leadingZeroCount; 15205 } 15206 } 15207 15208 // we exhausted workingWord and still have not found a 1 15209 this.loadWord(); 15210 return leadingZeroCount + this.skipLeadingZeros(); 15211 }; 15212 15213 // ():void 15214 this.skipUnsignedExpGolomb = function() { 15215 this.skipBits(1 + this.skipLeadingZeros()); 15216 }; 15217 15218 // ():void 15219 this.skipExpGolomb = function() { 15220 this.skipBits(1 + this.skipLeadingZeros()); 15221 }; 15222 15223 // ():uint 15224 this.readUnsignedExpGolomb = function() { 15225 var clz = this.skipLeadingZeros(); // :uint 15226 return this.readBits(clz + 1) - 1; 15227 }; 15228 15229 // ():int 15230 this.readExpGolomb = function() { 15231 var valu = this.readUnsignedExpGolomb(); // :int 15232 if (0x01 & valu) { 15233 // the number is odd if the low order bit is set 15234 return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2 15235 } 15236 return -1 * (valu >>> 1); // divide by two then make it negative 15237 }; 15238 15239 // Some convenience functions 15240 // :Boolean 15241 this.readBoolean = function() { 15242 return this.readBits(1) === 1; 15243 }; 15244 15245 // ():int 15246 this.readUnsignedByte = function() { 15247 return this.readBits(8); 15248 }; 15249 15250 this.loadWord(); 15251 }; 15252 15253 module.exports = ExpGolomb; 15254 15255 },{}],60:[function(require,module,exports){ 15256 /** 15257 * mux.js 15258 * 15259 * Copyright (c) 2014 Brightcove 15260 * All rights reserved. 15261 * 15262 * A lightweight readable stream implemention that handles event dispatching. 15263 * Objects that inherit from streams should call init in their constructors. 15264 */ 15265 'use strict'; 15266 15267 var Stream = function() { 15268 this.init = function() { 15269 var listeners = {}; 15270 /** 15271 * Add a listener for a specified event type. 15272 * @param type {string} the event name 15273 * @param listener {function} the callback to be invoked when an event of 15274 * the specified type occurs 15275 */ 15276 this.on = function(type, listener) { 15277 if (!listeners[type]) { 15278 listeners[type] = []; 15279 } 15280 listeners[type] = listeners[type].concat(listener); 15281 }; 15282 /** 15283 * Remove a listener for a specified event type. 15284 * @param type {string} the event name 15285 * @param listener {function} a function previously registered for this 15286 * type of event through `on` 15287 */ 15288 this.off = function(type, listener) { 15289 var index; 15290 if (!listeners[type]) { 15291 return false;
15292 } 15293 index = listeners[type].indexOf(listener); 15294 listeners[type] = listeners[type].slice(); 15295 listeners[type].splice(index, 1); 15296 return index > -1; 15297 }; 15298 /** 15299 * Trigger an event of the specified type on this stream. Any additional 15300 * arguments to this function are passed as parameters to event listeners. 15301 * @param type {string} the event name 15302 */ 15303 this.trigger = function(type) { 15304 var callbacks, i, length, args; 15305 callbacks = listeners[type]; 15306 if (!callbacks) { 15307 return; 15308 } 15309 // Slicing the arguments on every invocation of this method 15310 // can add a significant amount of overhead. Avoid the 15311 // intermediate object creation for the common case of a 15312 // single callback argument 15313 if (arguments.length === 2) { 15314 length = callbacks.length; 15315 for (i = 0; i < length; ++i) { 15316 callbacks[i].call(this, arguments[1]); 15317 } 15318 } else { 15319 args = []; 15320 i = arguments.length; 15321 for (i = 1; i < arguments.length; ++i) { 15322 args.push(arguments[i]); 15323 } 15324 length = callbacks.length; 15325 for (i = 0; i < length; ++i) { 15326 callbacks[i].apply(this, args); 15327 } 15328 } 15329 }; 15330 /** 15331 * Destroys the stream and cleans up. 15332 */ 15333 this.dispose = function() { 15334 listeners = {}; 15335 }; 15336 }; 15337 }; 15338 15339 /** 15340 * Forwards all `data` events on this stream to the destination stream. The 15341 * destination stream should provide a method `push` to receive the data 15342 * events as they arrive. 15343 * @param destination {stream} the stream that will receive all `data` events 15344 * @param autoFlush {boolean} if false, we will not call `flush` on the destination 15345 * when the current stream emits a 'done' event 15346 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options 15347 */ 15348 Stream.prototype.pipe = function(destination) { 15349 this.on('data', function(data) { 15350 destination.push(data); 15351 }); 15352 15353 this.on('done', function(flushSource) { 15354 destination.flush(flushSource); 15355 }); 15356 15357 return destination; 15358 }; 15359 15360// Default stream functions that are expected to be overridden to perform 15361// actual work. These are provided by the prototype as a sort of no-op 15362// implementation so that we don't have to check for their existence in the 15363// `pipe` function above. 15364 Stream.prototype.push = function(data) { 15365 this.trigger('data', data); 15366 }; 15367 15368 Stream.prototype.flush = function(flushSource) { 15369 this.trigger('done', flushSource); 15370 }; 15371 15372 module.exports = Stream; 15373 15374 },{}],61:[function(require,module,exports){ 15375 /* jshint ignore:start */ 15376 (function(root) { 15377 /* jshint ignore:end */ 15378 var URLToolkit = { 15379 // build an absolute URL from a relative one using the provided baseURL 15380 // if relativeURL is an absolute URL it will be returned as is. 15381 buildAbsoluteURL: function(baseURL, relativeURL) { 15382 // remove any remaining space and CRLF 15383 relativeURL = relativeURL.trim(); 15384 if (/^[a-z]+:/i.test(relativeURL)) { 15385 // complete url, not relative 15386 return relativeURL; 15387 } 15388 15389 var relativeURLQuery = null; 15390 var relativeURLHash = null; 15391 15392 var relativeURLHashSplit = /^([^#]*)(.*)$/.exec(relativeURL); 15393 if (relativeURLHashSplit) { 15394 relativeURLHash = relativeURLHashSplit[2]; 15395 relativeURL = relativeURLHashSplit[1]; 15396 } 15397 var relativeURLQuerySplit = /^([^\?]*)(.*)$/.exec(relativeURL); 15398 if (relativeURLQuerySplit) { 15399 relativeURLQuery = relativeURLQuerySplit[2]; 15400 relativeURL = relativeURLQuerySplit[1]; 15401 } 15402 15403 var baseURLHashSplit = /^([^#]*)(.*)$/.exec(baseURL); 15404 if (baseURLHashSplit) { 15405 baseURL = baseURLHashSplit[1]; 15406 } 15407 var baseURLQuerySplit = /^([^\?]*)(.*)$/.exec(baseURL); 15408 if (baseURLQuerySplit) { 15409 baseURL = baseURLQuerySplit[1]; 15410 } 15411 15412 var baseURLDomainSplit = /^(([a-z]+:)?\/\/[^:\/]+(:[0-9]+)?)?(\/?.*)$/i.exec(baseURL); 15413 if (!baseURLDomainSplit) { 15414 throw new Error('Error trying to parse base URL.'); 15415 } 15416 15417 // e.g. 'http:', 'https:', '' 15418 var baseURLProtocol = baseURLDomainSplit[2] || ''; 15419 // e.g. 'http://example.com', '//example.com', '' 15420 var baseURLProtocolDomain = baseURLDomainSplit[1] || ''; 15421 // e.g. '/a/b/c/playlist.m3u8', 'a/b/c/playlist.m3u8' 15422 var baseURLPath = baseURLDomainSplit[4]; 15423 if (baseURLPath.indexOf('/') !== 0 && baseURLProtocolDomain !== '') { 15424 // this handles a base url of http://example.com (missing last slash) 15425 baseURLPath = '/'+baseURLPath; 15426 } 15427 15428 var builtURL = null; 15429 if (/^\/\//.test(relativeURL)) { 15430 // relative url starts wth '//' so copy protocol (which may be '' if baseUrl didn't provide one) 15431 builtURL = baseURLProtocol+'//'+URLToolkit.buildAbsolutePath('', relativeURL.substring(2)); 15432 } 15433 else if (/^\//.test(relativeURL)) { 15434 // relative url starts with '/' so start from root of domain 15435 builtURL = baseURLProtocolDomain+'/'+URLToolkit.buildAbsolutePath('', relativeURL.substring(1)); 15436 } 15437 else { 15438 builtURL = URLToolkit.buildAbsolutePath(baseURLProtocolDomain+baseURLPath, relativeURL); 15439 } 15440 15441 // put the query and hash parts back 15442 if (relativeURLQuery) { 15443 builtURL += relativeURLQuery; 15444 } 15445 if (relativeURLHash) { 15446 builtURL += relativeURLHash; 15447 } 15448 return builtURL; 15449 }, 15450 15451 // build an absolute path using the provided basePath 15452 // adapted from https://developer.mozilla.org/en-US/docs/Web/API/document/cookie#Using_relative_URLs_in_the_path_parameter 15453 // this does not handle the case where relativePath is "/" or "//". These
15453cases should be handled outside this. 15454 buildAbsolutePath: function(basePath, relativePath) { 15455 var sRelPath = relativePath; 15456 var nUpLn, sDir = '', sPath = basePath.replace(/[^\/]*$/, sRelPath.replace(/(\/|^)(?:\.?\/+)+/g, '$1')); 15457 for (var nEnd, nStart = 0; nEnd = sPath.indexOf('/../', nStart), nEnd > -1; nStart = nEnd + nUpLn) { 15458 nUpLn = /^\/(?:\.\.\/)*/.exec(sPath.slice(nEnd))[0].length; 15459 sDir = (sDir + sPath.substring(nStart, nEnd)).replace(new RegExp('(?:\\\/+[^\\\/]*){0,' + ((nUpLn - 1) / 3) + '}$'), '/'); 15460 } 15461 return sDir + sPath.substr(nStart); 15462 } 15463 }; 15464 15465 /* jshint ignore:start */ 15466 if(typeof exports === 'object' && typeof module === 'object') 15467 module.exports = URLToolkit; 15468 else if(typeof define === 'function' && define.amd) 15469 define([], function() { return URLToolkit; }); 15470 else if(typeof exports === 'object') 15471 exports["URLToolkit"] = URLToolkit; 15472 else 15473 root["URLToolkit"] = URLToolkit; 15474 })(this); 15475 /* jshint ignore:end */ 15476 15477 },{}],62:[function(require,module,exports){ 15478 (function (global){ 15479 /** 15480 * @file add-text-track-data.js 15481 */ 15482 'use strict'; 15483 15484 Object.defineProperty(exports, '__esModule', { 15485 value: true 15486 }); 15487 15488 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 15489 15490 var _globalWindow = require('global/window'); 15491 15492 var _globalWindow2 = _interopRequireDefault(_globalWindow); 15493 15494 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 15495 15496 var _videoJs2 = _interopRequireDefault(_videoJs); 15497 15498 /** 15499 * Define properties on a cue for backwards compatability, 15500 * but warn the user that the way that they are using it 15501 * is depricated and will be removed at a later date. 15502 * 15503 * @param {Cue} cue the cue to add the properties on 15504 * @private 15505 */ 15506 var deprecateOldCue = function deprecateOldCue(cue) { 15507 Object.defineProperties(cue.frame, { 15508 id: { 15509 get: function get() { 15510 _videoJs2['default'].log.warn('cue.frame.id is deprecated. Use cue.value.key instead.'); 15511 return cue.value.key; 15512 } 15513 }, 15514 value: { 15515 get: function get() { 15516 _videoJs2['default'].log.warn('cue.frame.value is deprecated. Use cue.value.data instead.'); 15517 return cue.value.data; 15518 } 15519 }, 15520 privateData: { 15521 get: function get() { 15522 _videoJs2['default'].log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.'); 15523 return cue.value.data; 15524 } 15525 } 15526 }); 15527 }; 15528 15529 var durationOfVideo = function durationOfVideo(duration) { 15530 var dur = undefined; 15531 15532 if (isNaN(duration) || Math.abs(duration) === Infinity) { 15533 dur = Number.MAX_VALUE; 15534 } else { 15535 dur = duration; 15536 } 15537 return dur; 15538 }; 15539 /** 15540 * Add text track data to a source handler given the captions and 15541 * metadata from the buffer. 15542 * 15543 * @param {Object} sourceHandler the flash or virtual source buffer 15544 * @param {Array} captionArray an array of caption data 15545 * @param {Array} metadataArray an array of meta data 15546 * @private 15547 */ 15548 var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) { 15549 var Cue = _globalWindow2['default'].WebKitDataCue || _globalWindow2['default'].VTTCue; 15550 15551 if (captionArray) { 15552 captionArray.forEach(function (caption) { 15553 this.inbandTextTrack_.addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text)); 15554 }, sourceHandler); 15555 } 15556 15557 if (metadataArray) { 15558 (function () { 15559 var videoDuration = durationOfVideo(sourceHandler.mediaSource_.duration); 15560 15561 metadataArray.forEach(function (metadata) { 15562 var time = metadata.cueTime + this.timestampOffset; 15563
15564 metadata.frames.forEach(function (frame) { 15565 var cue = new Cue(time, time, frame.value || frame.url || frame.data || ''); 15566 15567 cue.frame = frame; 15568 cue.value = frame; 15569 deprecateOldCue(cue); 15570 15571 this.metadataTrack_.addCue(cue); 15572 }, this); 15573 }, sourceHandler); 15574 15575 // Updating the metadeta cues so that 15576 // the endTime of each cue is the startTime of the next cue 15577 // the endTime of last cue is the duration of the video 15578 if (sourceHandler.metadataTrack_ && sourceHandler.metadataTrack_.cues && sourceHandler.metadataTrack_.cues.length) { 15579 (function () { 15580 var cues = sourceHandler.metadataTrack_.cues; 15581 var cuesArray = []; 15582 15583 // Create a copy of the TextTrackCueList... 15584 // ...disregarding cues with a falsey value 15585 for (var i = 0; i < cues.length; i++) { 15586 if (cues[i]) { 15587 cuesArray.push(cues[i]); 15588 } 15589 } 15590 15591 // Group cues by their startTime value 15592 var cuesGroupedByStartTime = cuesArray.reduce(function (obj, cue) { 15593 var timeSlot = obj[cue.startTime] || []; 15594 15595 timeSlot.push(cue); 15596 obj[cue.startTime] = timeSlot; 15597 15598 return obj; 15599 }, {}); 15600 15601 // Sort startTimes by ascending order 15602 var sortedStartTimes = Object.keys(cuesGroupedByStartTime).sort(function (a, b) { 15603 return Number(a) - Number(b); 15604 }); 15605 15606 // Map each cue group's endTime to the next group's startTime 15607 sortedStartTimes.forEach(function (startTime, idx) { 15608 var cueGroup = cuesGroupedByStartTime[startTime]; 15609 var nextTime = Number(sortedStartTimes[idx + 1]) || videoDuration; 15610 15611 // Map each cue's endTime the next group's startTime 15612 cueGroup.forEach(function (cue) { 15613 cue.endTime = nextTime; 15614 }); 15615 }); 15616 })(); 15617 } 15618 })(); 15619 } 15620 }; 15621 15622 exports['default'] = { 15623 addTextTrackData: addTextTrackData, 15624 durationOfVideo: durationOfVideo 15625 }; 15626 module.exports = exports['default']; 15627 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 15628 },{"global/window":30}],63:[function(require,module,exports){ 15629 /** 15630 * Remove the text track from the player if one with matching kind and 15631 * label properties already exists on the player 15632 * 15633 * @param {Object} player the video.js player object 15634 * @param {String} kind to be considered the text track's `kind` must match 15635 * @param {String} label to be considered the text track's `label` must match 15636 * @private 15637 */ 15638 'use strict'; 15639 15640 Object.defineProperty(exports, '__esModule', { 15641 value: true 15642 }); 15643 var removeExistingTrack = function removeExistingTrack(player, kind, label) { 15644 var tracks = player.remoteTextTracks() || []; 15645 15646 for (var i = 0; i < tracks.length; i++) { 15647 var track = tracks[i]; 15648 15649 if (track.kind === kind && track.label === label) { 15650 player.removeRemoteTextTrack(track); 15651 } 15652 } 15653 }; 15654 15655 exports.removeExistingTrack = removeExistingTrack; 15656 /** 15657 * Cleaup text tracks on video.js if they exist 15658 * 15659 * @param {Object} player the video.js player object 15660 * @private 15661 */ 15662 var cleanupTextTracks = function cleanupTextTracks(player) { 15663 removeExistingTrack(player, 'captions', 'cc1'); 15664 removeExistingTrack(player, 'metadata', 'Timed Metadata'); 15665 }; 15666 exports.cleanupTextTracks = cleanupTextTracks; 15667 },{}],64:[function(require,module,exports){ 15668 /** 15669 * @file codec-utils.js 15670 */ 15671 15672 /** 15673 * Check if a codec string refers to an audio codec. 15674 * 15675 * @param {String} codec codec string to check 15676 * @return {Boolean} if this is an audio codec 15677 * @private 15678 */ 15679 'use strict'; 15680 15681 Object.defineProperty(exports, '__esModule', { 15682 value: true 15683 }); 15684 var isAudioCodec = function isAudioCodec(codec) { 15685 return (/mp4a\.\d+.\d+/i.test(codec) 15686 ); 15687 }; 15688 15689 /** 15690 * Check if a codec string refers to a video codec. 15691 * 15692 * @param {String} codec codec string to check 15693 * @return {Boolean} if this is a video codec 15694 * @private 15695 */ 15696 var isVideoCodec = function isVideoCodec(codec) { 15697 return (/avc1\.[\da-f]+/i.test(codec) 15698 ); 15699 }; 15700 15701 /** 15702 * Parse a content type header into a type and parameters 15703 * object 15704 * 15705 * @param {String} type the content type header 15706 * @return {Object} the parsed content-type 15707 * @private 15708 */ 15709 var parseContentType = function parseContentType(type) { 15710 var object = { type: '', parameters: {} }; 15711 var parameters = type.trim().split(';'); 15712 15713 // first parameter should always be content-type 15714 object.type = parameters.shift().trim();
15715 parameters.forEach(function (parameter) { 15716 var pair = parameter.trim().split('='); 15717 15718 if (pair.length > 1) { 15719 var _name = pair[0].replace(/"/g, '').trim(); 15720 var value = pair[1].replace(/"/g, '').trim(); 15721 15722 object.parameters[_name] = value; 15723 } 15724 }); 15725 15726 return object; 15727 }; 15728 15729 /** 15730 * Replace the old apple-style `avc1.<dd>.<dd>` codec string with the standard 15731 * `avc1.<hhhhhh>` 15732 * 15733 * @param {Array} codecs an array of codec strings to fix 15734 * @return {Array} the translated codec array 15735 * @private 15736 */ 15737 var translateLegacyCodecs = function translateLegacyCodecs(codecs) { 15738 return codecs.map(function (codec) { 15739 return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) { 15740 var profileHex = ('00' + Number(profile).toString(16)).slice(-2); 15741 var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2); 15742 15743 return 'avc1.' + profileHex + '00' + avcLevelHex; 15744 }); 15745 }); 15746 }; 15747 15748 exports['default'] = { 15749 isAudioCodec: isAudioCodec, 15750 parseContentType: parseContentType, 15751 isVideoCodec: isVideoCodec, 15752 translateLegacyCodecs: translateLegacyCodecs 15753 }; 15754 module.exports = exports['default']; 15755 },{}],65:[function(require,module,exports){ 15756 /** 15757 * @file create-text-tracks-if-necessary.js 15758 */ 15759 'use strict'; 15760 15761 Object.defineProperty(exports, '__esModule', { 15762 value: true 15763 }); 15764 15765 var _cleanupTextTracks = require('./cleanup-text-tracks'); 15766 15767 /** 15768 * Create text tracks on video.js if they exist on a segment. 15769 * 15770 * @param {Object} sourceBuffer the VSB or FSB 15771 * @param {Object} mediaSource the HTML or Flash media source 15772 * @param {Object} segment the segment that may contain the text track 15773 * @private 15774 */ 15775 var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) { 15776 var player = mediaSource.player_; 15777 15778 // create an in-band caption track if one is present in the segment 15779 if (segment.captions && segment.captions.length && !sourceBuffer.inbandTextTrack_) { 15780 (0, _cleanupTextTracks.removeExistingTrack)(player, 'captions', 'cc1'); 15781 sourceBuffer.inbandTextTrack_ = player.addRemoteTextTrack({ 15782 kind: 'captions', 15783 label: 'cc1' 15784 }, false).track; 15785 } 15786 15787 if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) { 15788 (0, _cleanupTextTracks.removeExistingTrack)(player, 'metadata', 'Timed Metadata', true); 15789 sourceBuffer.metadataTrack_ = player.addRemoteTextTrack({ 15790 kind: 'metadata', 15791 label: 'Timed Metadata' 15792 }, false).track; 15793 sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType; 15794 } 15795 }; 15796 15797 exports['default'] = createTextTracksIfNecessary; 15798 module.exports = exports['default']; 15799 },{"./cleanup-text-tracks":63}],66:[function(require,module,exports){ 15800 /** 15801 * @file flash-constants.js 15802 */ 15803 /** 15804 * The maximum size in bytes for append operations to the video.js 15805 * SWF. Calling through to Flash blocks and can be expensive so 15806 * we chunk data and pass through 4KB at a time, yielding to the 15807 * browser between chunks. This gives a theoretical maximum rate of 15808 * 1MB/s into Flash. Any higher and we begin to drop frames and UI 15809 * responsiveness suffers. 15810 * 15811 * @private 15812 */ 15813 "use strict"; 15814 15815 Object.defineProperty(exports, "__esModule", { 15816 value: true 15817 }); 15818 var flashConstants = { 15819 // times in milliseconds 15820 TIME_BETWEEN_CHUNKS: 1, 15821 BYTES_PER_CHUNK: 1024 * 32 15822 }; 15823 15824 exports["default"] = flashConstants; 15825 module.exports = exports["default"]; 15826 },{}],67:[function(require,module,exports){ 15827 (function (global){ 15828 /** 15829 * @file flash-media-source.js 15830 */ 15831 'use strict'; 15832 15833 Object.defineProperty(exports, '__esModule', { 15834 value: true 15835 }); 15836 15837 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 15838 15839 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 15840 15841 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 15842 15843 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 15844 15845 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
15845onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 15846 15847 var _globalDocument = require('global/document'); 15848 15849 var _globalDocument2 = _interopRequireDefault(_globalDocument); 15850 15851 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 15852 15853 var _videoJs2 = _interopRequireDefault(_videoJs); 15854 15855 var _flashSourceBuffer = require('./flash-source-buffer'); 15856 15857 var _flashSourceBuffer2 = _interopRequireDefault(_flashSourceBuffer); 15858 15859 var _flashConstants = require('./flash-constants'); 15860 15861 var _flashConstants2 = _interopRequireDefault(_flashConstants); 15862 15863 var _codecUtils = require('./codec-utils'); 15864 15865 var _cleanupTextTracks = require('./cleanup-text-tracks'); 15866 15867 /** 15868 * A flash implmentation of HTML MediaSources and a polyfill 15869 * for browsers that don't support native or HTML MediaSources.. 15870 * 15871 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource 15872 * @class FlashMediaSource 15873 * @extends videojs.EventTarget 15874 */ 15875 15876 var FlashMediaSource = (function (_videojs$EventTarget) { 15877 _inherits(FlashMediaSource, _videojs$EventTarget); 15878 15879 function FlashMediaSource() { 15880 var _this = this; 15881 15882 _classCallCheck(this, FlashMediaSource); 15883 15884 _get(Object.getPrototypeOf(FlashMediaSource.prototype), 'constructor', this).call(this); 15885 this.sourceBuffers = []; 15886 this.readyState = 'closed'; 15887 15888 this.on(['sourceopen', 'webkitsourceopen'], function (event) { 15889 // find the swf where we will push media data 15890 _this.swfObj = _globalDocument2['default'].getElementById(event.swfId); 15891 _this.player_ = (0, _videoJs2['default'])(_this.swfObj.parentNode); 15892 _this.tech_ = _this.swfObj.tech; 15893 _this.readyState = 'open'; 15894 15895 _this.tech_.on('seeking', function () { 15896 var i = _this.sourceBuffers.length; 15897 15898 while (i--) { 15899 _this.sourceBuffers[i].abort(); 15900 } 15901 }); 15902 15903 if (_this.tech_.hls) { 15904 _this.tech_.hls.on('dispose', function () { 15905 (0, _cleanupTextTracks.cleanupTextTracks)(_this.player_); 15906 }); 15907 } 15908 15909 // trigger load events 15910 if (_this.swfObj) { 15911 _this.swfObj.vjs_load(); 15912 } 15913 }); 15914 } 15915 15916 /** 15917 * Set or return the presentation duration. 15918 * 15919 * @param {Double} value the duration of the media in seconds 15920 * @param {Double} the current presentation duration 15921 * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration 15922 */ 15923 15924 /** 15925 * We have this function so that the html and flash interfaces 15926 * are the same. 15927 * 15928 * @private 15929 */ 15930 15931 _createClass(FlashMediaSource, [{ 15932 key: 'addSeekableRange_', 15933 value: function addSeekableRange_() {} 15934 // intentional no-op 15935 15936 /** 15937 * Create a new flash source buffer and add it to our flash media source. 15938 * 15939 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer 15940 * @param {String} type the content-type of the source 15941 * @return {Object} the flash source buffer 15942 */ 15943 15944 }, { 15945 key: 'addSourceBuffer', 15946 value: function addSourceBuffer(type) { 15947 var parsedType = (0, _codecUtils.parseContentType)(type); 15948 var sourceBuffer = undefined; 15949 15950 // if this is an FLV type, we'll push data to flash 15951 if (parsedType.type === 'video/mp2t') { 15952 // Flash source buffers 15953 sourceBuffer = new _flashSourceBuffer2['default'](this); 15954 } else { 15955 throw new Error('NotSupportedError (Video.js)'); 15956 } 15957 15958 this.sourceBuffers.push(sourceBuffer); 15959 return sourceBuffer; 15960 } 15961 15962 /** 15963 * Signals the end of the stream. 15964 * 15965 * @link https://w3c.github.io/media-source/#widl-MediaSource-endOfStream-void-EndOfStreamError-error 15966 * @param {String=} error Signals that a playback error 15967 * has occurred. If specified, it must be either "network" or 15968 * "decode". 15969 */ 15970 }, { 15971 key: 'endOfStream', 15972 value: function endOfStream(error) { 15973 if (error === 'network') { 15974 // MEDIA_ERR_NETWORK 15975 this.tech_.error(2); 15976 } else if (error === 'decode') { 15977 // MEDIA_ERR_DECODE 15978 this.tech_.error(3); 15979 } 15980 if (this.readyState !== 'ended') { 15981 this.readyState = 'ended'; 15982 this.swfObj.vjs_endOfStream(); 15983 } 15984 } 15985 }]); 15986 15987 return FlashMediaSource; 15988 })(_videoJs2['default'].EventTarget); 15989 15990 exports['default'] = FlashMediaSource; 15991 try { 15992 Object.defineProperty(FlashMediaSource.prototype, 'duration', { 15993 /** 15994 * Return the presentation duration. 15995 * 15996 * @return {Double} the duration of the media in seconds 15997 * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration 15998 */ 15999 get: function get() { 16000 if (!this.swfObj) { 16001 return NaN; 16002 } 16003 // get the current duration from the SWF 16004 return this.swfObj.vjs_getProperty('duration'); 16005 }, 16006 /** 16007 * Set the presentation duration. 16008 * 16009 * @param {Double} value the duration of the media in seconds 16010 * @return {Double} the duration of the media in seconds 16011 * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration 16012 */ 16013 set: function set(value) { 16014 var i = undefined; 16015 var oldDuration = this.swfObj.vjs_getProperty('duration'); 16016 16017 this.swfObj.vjs_setProperty('duration', value); 16018 16019 if (value < oldDuration) { 16020 // In MSE, this triggers the range removal algorithm which causes 16021 // an update to occur 16022 for (i = 0; i < this.sourceBuffers.length; i++) { 16023 this.sourceBuffers[i].remove(value, oldDuration); 16024 } 16025 } 16026 16027 return value; 16028 } 16029 }); 16030 } catch (e) { 16031 // IE8 throws if defineProperty is called on a non-DOM node. We 16032 // don't support IE8 but we shouldn't throw an error if loaded 16033 // there. 16034 FlashMediaSource.prototype.duration = NaN; 16035 } 16036 16037 for (var property in _flashConstants2['default']) { 16038 FlashMediaSource[property] = _flashConstants2['default'][property]; 16039 } 16040 module.exports = exports['default']; 16041 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 16042 },{"./cleanup-text-tracks":63,"./codec-utils":64,"./flash-constants":66,"./flash-source-buffer":68,"global/document":29}],68:[function(require,module,exports){ 16043 (function (global){ 16044 /** 16045 * @file flash-source-buffer.js 16046 */ 16047 'use strict'; 16048 16049 Object.defineProperty(exports, '__esModule', { 16050 value: true 16051 }); 16052 16053 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 16054 16055 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 16056 16057 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 16058 16059 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 16060 16061 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
16061onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 16062 16063 var _globalWindow = require('global/window'); 16064 16065 var _globalWindow2 = _interopRequireDefault(_globalWindow); 16066 16067 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 16068 16069 var _videoJs2 = _interopRequireDefault(_videoJs); 16070 16071 var _muxJsLibFlv = require('mux.js/lib/flv'); 16072 16073 var _muxJsLibFlv2 = _interopRequireDefault(_muxJsLibFlv); 16074 16075 var _removeCuesFromTrack = require('./remove-cues-from-track'); 16076 16077 var _removeCuesFromTrack2 = _interopRequireDefault(_removeCuesFromTrack); 16078 16079 var _createTextTracksIfNecessary = require('./create-text-tracks-if-necessary'); 16080 16081 var _createTextTracksIfNecessary2 = _interopRequireDefault(_createTextTracksIfNecessary); 16082 16083 var _addTextTrackData = require('./add-text-track-data'); 16084 16085 var _flashTransmuxerWorker = require('./flash-transmuxer-worker'); 16086 16087 var _flashTransmuxerWorker2 = _interopRequireDefault(_flashTransmuxerWorker); 16088 16089 var _webworkify = require('webworkify'); 16090 16091 var _webworkify2 = _interopRequireDefault(_webworkify); 16092 16093 var _flashConstants = require('./flash-constants'); 16094 16095 var _flashConstants2 = _interopRequireDefault(_flashConstants); 16096 16097 /** 16098 * A wrapper around the setTimeout function that uses 16099 * the flash constant time between ticks value. 16100 * 16101 * @param {Function} func the function callback to run 16102 * @private 16103 */ 16104 var scheduleTick = function scheduleTick(func) { 16105 // Chrome doesn't invoke requestAnimationFrame callbacks 16106 // in background tabs, so use setTimeout. 16107 _globalWindow2['default'].setTimeout(func, _flashConstants2['default'].TIME_BETWEEN_CHUNKS); 16108 }; 16109 16110 /** 16111 * Generates a random string of max length 6 16112 * 16113 * @return {String} the randomly generated string 16114 * @function generateRandomString 16115 * @private 16116 */ 16117 var generateRandomString = function generateRandomString() { 16118 return Math.random().toString(36).slice(2, 8); 16119 }; 16120 16121 /** 16122 * Round a number to a specified number of places much like 16123 * toFixed but return a number instead of a string representation. 16124 * 16125 * @param {Number} num A number 16126 * @param {Number} places The number of decimal places which to 16127 * round 16128 * @private 16129 */ 16130 var toDecimalPlaces = function toDecimalPlaces(num, places) { 16131 if (typeof places !== 'number' || places < 0) { 16132 places = 0; 16133 } 16134 16135 var scale = Math.pow(10, places); 16136 16137 return Math.round(num * scale) / scale; 16138 }; 16139 16140 /** 16141 * A SourceBuffer implementation for Flash rather than HTML. 16142 * 16143 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource 16144 * @param {Object} mediaSource the flash media source 16145 * @class FlashSourceBuffer 16146 * @extends videojs.EventTarget 16147 */ 16148 16149 var FlashSourceBuffer = (function (_videojs$EventTarget) { 16150 _inherits(FlashSourceBuffer, _videojs$EventTarget); 16151 16152 function FlashSourceBuffer(mediaSource) { 16153 var _this = this; 16154 16155 _classCallCheck(this, FlashSourceBuffer); 16156 16157 _get(Object.getPrototypeOf(FlashSourceBuffer.prototype), 'constructor', this).call(this); 16158 var encodedHeader = undefined; 16159 16160 // Start off using the globally defined value but refine 16161 // as we append data into flash 16162 this.chunkSize_ = _flashConstants2['default'].BYTES_PER_CHUNK; 16163 16164 // byte arrays queued to be appended 16165 this.buffer_ = []; 16166 16167 // the total number of queued bytes 16168 this.bufferSize_ = 0; 16169 16170 // to be able to determine the correct position to seek to, we 16171 // need to retain information about the mapping between the 16172 // media timeline and PTS values 16173 this.basePtsOffset_ = NaN; 16174 16175 this.mediaSource_ = mediaSource; 16176 16177 this.audioBufferEnd_ = NaN; 16178 this.videoBufferEnd_ = NaN; 16179 16180 // indicates whether the asynchronous continuation of an operation 16181 // is still being processed 16182 // see https://w3c.github.io/media-source/#widl-SourceBuffer-updating 16183 this.updating = false;
16184 this.timestampOffset_ = 0; 16185 16186 encodedHeader = _globalWindow2['default'].btoa(String.fromCharCode.apply(null, Array.prototype.slice.call(_muxJsLibFlv2['default'].getFlvHeader()))); 16187 16188 // create function names with added randomness for the global callbacks flash will use 16189 // to get data from javascript into the swf. Random strings are added as a safety 16190 // measure for pages with multiple players since these functions will be global 16191 // instead of per instance. When making a call to the swf, the browser generates a 16192 // try catch code snippet, but just takes the function name and writes out an unquoted 16193 // call to that function. If the player id has any special characters, this will result 16194 // in an error, so safePlayerId replaces all special characters to '_' 16195 var safePlayerId = this.mediaSource_.player_.id().replace(/[^a-zA-Z0-9]/g, '_'); 16196 16197 this.flashEncodedHeaderName_ = 'vjs_flashEncodedHeader_' + safePlayerId + generateRandomString(); 16198 this.flashEncodedDataName_ = 'vjs_flashEncodedData_' + safePlayerId + generateRandomString(); 16199 16200 _globalWindow2['default'][this.flashEncodedHeaderName_] = function () { 16201 delete _globalWindow2['default'][_this.flashEncodedHeaderName_]; 16202 return encodedHeader; 16203 }; 16204 16205 this.mediaSource_.swfObj.vjs_appendChunkReady(this.flashEncodedHeaderName_); 16206 16207 this.transmuxer_ = (0, _webworkify2['default'])(_flashTransmuxerWorker2['default']); 16208 this.transmuxer_.postMessage({ action: 'init', options: {} }); 16209 this.transmuxer_.onmessage = function (event) { 16210 if (event.data.action === 'data') { 16211 _this.receiveBuffer_(event.data.segment); 16212 } 16213 }; 16214 16215 this.one('updateend', function () { 16216 _this.mediaSource_.tech_.trigger('loadedmetadata'); 16217 }); 16218 16219 Object.defineProperty(this, 'timestampOffset', { 16220 get: function get() { 16221 return this.timestampOffset_; 16222 }, 16223 set: function set(val) { 16224 if (typeof val === 'number' && val >= 0) { 16225 this.timestampOffset_ = val; 16226 // We have to tell flash to expect a discontinuity 16227 this.mediaSource_.swfObj.vjs_discontinuity(); 16228 // the media <-> PTS mapping must be re-established after 16229 // the discontinuity 16230 this.basePtsOffset_ = NaN; 16231 this.audioBufferEnd_ = NaN; 16232 this.videoBufferEnd_ = NaN; 16233 16234 this.transmuxer_.postMessage({ action: 'reset' }); 16235 } 16236 } 16237 }); 16238 16239 Object.defineProperty(this, 'buffered', { 16240 get: function get() { 16241 if (!this.mediaSource_ || !this.mediaSource_.swfObj || !('vjs_getProperty' in this.mediaSource_.swfObj)) { 16242 return _videoJs2['default'].createTimeRange(); 16243 } 16244 16245 var buffered = this.mediaSource_.swfObj.vjs_getProperty('buffered'); 16246 16247 if (buffered && buffered.length) { 16248 buffered[0][0] = toDecimalPlaces(buffered[0][0], 3); 16249 buffered[0][1] = toDecimalPlaces(buffered[0][1], 3); 16250 } 16251 return _videoJs2['default'].createTimeRanges(buffered); 16252 } 16253 }); 16254 16255 // On a seek we remove all text track data since flash has no concept 16256 // of a buffered-range and everything else is reset on seek 16257 this.mediaSource_.player_.on('seeked', function () { 16258 (0, _removeCuesFromTrack2['default'])(0, Infinity, _this.metadataTrack_); 16259 (0, _removeCuesFromTrack2['default'])(0, Infinity, _this.inbandTextTrack_); 16260 }); 16261 16262 this.mediaSource_.player_.tech_.hls.on('dispose', function () { 16263 _this.transmuxer_.terminate(); 16264 }); 16265 } 16266 16267 /** 16268 * Append bytes to the sourcebuffers buffer, in this case we 16269 * have to append it to swf object. 16270 * 16271 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer 16272 * @param {Array} bytes 16273 */ 16274 16275 _createClass(FlashSourceBuffer, [{ 16276 key: 'appendBuffer', 16277 value: function appendBuffer(bytes) { 16278 var error = undefined; 16279 16280 if (this.updating) { 16281 error = new Error('SourceBuffer.append() cannot be called ' + 'while an update is in progress');
16282 error.name = 'InvalidStateError'; 16283 error.code = 11; 16284 throw error; 16285 } 16286 this.updating = true; 16287 this.mediaSource_.readyState = 'open'; 16288 this.trigger({ type: 'update' }); 16289 16290 this.transmuxer_.postMessage({ 16291 action: 'push', 16292 data: bytes.buffer, 16293 byteOffset: bytes.byteOffset, 16294 byteLength: bytes.byteLength 16295 }, [bytes.buffer]); 16296 this.transmuxer_.postMessage({ action: 'flush' }); 16297 } 16298 16299 /** 16300 * Reset the parser and remove any data queued to be sent to the SWF. 16301 * 16302 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort 16303 */ 16304 }, { 16305 key: 'abort', 16306 value: function abort() { 16307 this.buffer_ = []; 16308 this.bufferSize_ = 0; 16309 this.mediaSource_.swfObj.vjs_abort(); 16310 16311 // report any outstanding updates have ended 16312 if (this.updating) { 16313 this.updating = false; 16314 this.trigger({ type: 'updateend' }); 16315 } 16316 } 16317 16318 /** 16319 * Flash cannot remove ranges already buffered in the NetStream 16320 * but seeking clears the buffer entirely. For most purposes, 16321 * having this operation act as a no-op is acceptable. 16322 * 16323 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove 16324 * @param {Double} start start of the section to remove 16325 * @param {Double} end end of the section to remove 16326 */ 16327 }, { 16328 key: 'remove', 16329 value: function remove(start, end) { 16330 (0, _removeCuesFromTrack2['default'])(start, end, this.metadataTrack_); 16331 (0, _removeCuesFromTrack2['default'])(start, end, this.inbandTextTrack_); 16332 this.trigger({ type: 'update' }); 16333 this.trigger({ type: 'updateend' }); 16334 } 16335 16336 /** 16337 * Receive a buffer from the flv. 16338 * 16339 * @param {Object} segment 16340 * @private 16341 */ 16342 }, { 16343 key: 'receiveBuffer_', 16344 value: function receiveBuffer_(segment) { 16345 var _this2 = this; 16346 16347 // create an in-band caption track if one is present in the segment 16348 (0, _createTextTracksIfNecessary2['default'])(this, this.mediaSource_, segment); 16349 (0, _addTextTrackData.addTextTrackData)(this, segment.captions, segment.metadata); 16350 16351 // Do this asynchronously since convertTagsToData_ can be time consuming 16352 scheduleTick(function () { 16353 var flvBytes = _this2.convertTagsToData_(segment); 16354 16355 if (_this2.buffer_.length === 0) { 16356 scheduleTick(_this2.processBuffer_.bind(_this2)); 16357 } 16358 16359 if (flvBytes) { 16360 _this2.buffer_.push(flvBytes); 16361 _this2.bufferSize_ += flvBytes.byteLength; 16362 } 16363 }); 16364 } 16365 16366 /** 16367 * Append a portion of the current buffer to the SWF. 16368 * 16369 * @private 16370 */ 16371 }, { 16372 key: 'processBuffer_', 16373 value: function processBuffer_() { 16374 var _this3 = this; 16375 16376 var chunkSize = _flashConstants2['default'].BYTES_PER_CHUNK; 16377 16378 if (!this.buffer_.length) { 16379 if (this.updating !== false) { 16380 this.updating = false;
16381 this.trigger({ type: 'updateend' }); 16382 } 16383 // do nothing if the buffer is empty 16384 return; 16385 } 16386 16387 // concatenate appends up to the max append size 16388 var chunk = this.buffer_[0].subarray(0, chunkSize); 16389 16390 // requeue any bytes that won't make it this round 16391 if (chunk.byteLength < chunkSize || this.buffer_[0].byteLength === chunkSize) { 16392 this.buffer_.shift(); 16393 } else { 16394 this.buffer_[0] = this.buffer_[0].subarray(chunkSize); 16395 } 16396 16397 this.bufferSize_ -= chunk.byteLength; 16398 16399 // base64 encode the bytes 16400 var binary = []; 16401 var length = chunk.byteLength; 16402 16403 for (var i = 0; i < length; i++) { 16404 binary.push(String.fromCharCode(chunk[i])); 16405 } 16406 var b64str = _globalWindow2['default'].btoa(binary.join('')); 16407 16408 _globalWindow2['default'][this.flashEncodedDataName_] = function () { 16409 // schedule another processBuffer to process any left over data or to 16410 // trigger updateend 16411 scheduleTick(_this3.processBuffer_.bind(_this3)); 16412 delete _globalWindow2['default'][_this3.flashEncodedDataName_]; 16413 return b64str; 16414 }; 16415 16416 // Notify the swf that segment data is ready to be appended 16417 this.mediaSource_.swfObj.vjs_appendChunkReady(this.flashEncodedDataName_); 16418 } 16419 16420 /** 16421 * Turns an array of flv tags into a Uint8Array representing the 16422 * flv data. Also removes any tags that are before the current 16423 * time so that playback begins at or slightly after the right 16424 * place on a seek 16425 * 16426 * @private 16427 * @param {Object} segmentData object of segment data 16428 */ 16429 }, { 16430 key: 'convertTagsToData_', 16431 value: function convertTagsToData_(segmentData) { 16432 var segmentByteLength = 0; 16433 var tech = this.mediaSource_.tech_; 16434 var videoTargetPts = 0; 16435 var segment = undefined; 16436 var videoTags = segmentData.tags.videoTags; 16437 var audioTags = segmentData.tags.audioTags; 16438 16439 // Establish the media timeline to PTS translation if we don't 16440 // have one already 16441 if (isNaN(this.basePtsOffset_) && (videoTags.length || audioTags.length)) { 16442 // We know there is at least one video or audio tag, but since we may not have both, 16443 // we use pts: Infinity for the missing tag. The will force the following Math.min 16444 // call will to use the proper pts value since it will always be less than Infinity 16445 var firstVideoTag = videoTags[0] || { pts: Infinity }; 16446 var firstAudioTag = audioTags[0] || { pts: Infinity }; 16447 16448 this.basePtsOffset_ = Math.min(firstAudioTag.pts, firstVideoTag.pts); 16449 } 16450 16451 if (tech.seeking()) { 16452 // Do not use previously saved buffer end values while seeking since buffer 16453 // is cleared on all seeks 16454 this.videoBufferEnd_ = NaN; 16455 this.audioBufferEnd_ = NaN; 16456 } 16457 16458 if (isNaN(this.videoBufferEnd_)) { 16459 if (tech.buffered().length) { 16460 videoTargetPts = tech.buffered().end(0) - this.timestampOffset; 16461 } 16462 16463 // Trim to currentTime if seeking 16464 if (tech.seeking()) { 16465 videoTargetPts = Math.max(videoTargetPts, tech.currentTime() - this.timestampOffset); 16466 } 16467 16468 // PTS values are represented in milliseconds 16469 videoTargetPts *= 1e3; 16470 videoTargetPts += this.basePtsOffset_; 16471 } else { 16472 // Add a fudge factor of 0.1 to the last video pts appended since a rendition change 16473 // could append an overlapping segment, in which case there is a high likelyhood 16474 // a tag could have a matching pts to videoBufferEnd_, which would cause 16475 // that tag to get appended by the tag.pts >= targetPts check below even though it 16476 // is a duplicate of what was previously appended 16477 videoTargetPts = this.videoBufferEnd_ + 0.1; 16478 } 16479 16480 // filter complete GOPs with a presentation time less than the seek target/end of buffer 16481 var currentIndex = videoTags.length; 16482 16483 // if the last tag is beyond videoTargetPts, then do not search the list for a GOP 16484 // since our videoTargetPts lies in a future segment 16485 if (currentIndex && videoTags[currentIndex - 1].pts >= videoTargetPts) { 16486 // Start by walking backwards from the end of the list until we reach a tag that 16487 // is equal to or less than videoTargetPts 16488 while (--currentIndex) { 16489 var currentTag = videoTags[currentIndex]; 16490 16491 if (currentTag.pts > videoTargetPts) { 16492 continue; 16493 } 16494 16495 // if we see a keyFrame or metadata tag once we've gone below videoTargetPts, 16496 // exit the loop as this is the start of the GOP that we want to append 16497 if (currentTag.keyFrame || currentTag.metaDataTag) { 16498 break; 16499 } 16500 } 16501 16502 // We need to check if there are any metadata tags that come before currentIndex 16503 // as those will be metadata tags associated with the GOP we are appending 16504 // There could be 0 to 2 metadata tags that come before the currentIndex depending 16505 // on what videoTargetPts is and whether the transmuxer prepended metadata tags to this 16506 // key frame 16507 while (currentIndex) { 16508 var nextTag = videoTags[currentIndex - 1]; 16509 16510 if (!nextTag.metaDataTag) { 16511 break; 16512 } 16513 16514 currentIndex--; 16515 } 16516 } 16517 16518 var filteredVideoTags = videoTags.slice(currentIndex); 16519 16520 var audioTargetPts = undefined; 16521 16522 if (isNaN(this.audioBufferEnd_)) { 16523 audioTargetPts = videoTargetPts; 16524 } else { 16525 // Add a fudge factor of 0.1 to the last video pts appended since a rendition change 16526 // could append an overlapping segment, in which case there is a high likelyhood 16527 // a tag could have a matching pts to videoBufferEnd_, which would cause 16528 // that tag to get appended by the tag.pts >= targetPts check below even though it 16529 // is a duplicate of what was previously appended 16530 audioTargetPts = this.audioBufferEnd_ + 0.1; 16531 } 16532 16533 if (filteredVideoTags.length) { 16534 // If targetPts intersects a GOP and we appended the tags for the GOP that came 16535 // before targetPts, we want to make sure to trim audio tags at the pts 16536 // of the first video tag to avoid brief moments of silence 16537 audioTargetPts = Math.min(audioTargetPts, filteredVideoTags[0].pts); 16538 } 16539 16540 // skip tags with a presentation time less than the seek target/end of buffer 16541 currentIndex = 0; 16542 16543 while (currentIndex < audioTags.length) { 16544 if (audioTags[currentIndex].pts >= audioTargetPts) { 16545 break; 16546 } 16547 16548 currentIndex++; 16549 } 16550 16551 var filteredAudioTags = audioTags.slice(currentIndex); 16552 16553 // update the audio and video buffer ends 16554 if (filteredAudioTags.length) { 16555 this.audioBufferEnd_ = filteredAudioTags[filteredAudioTags.length - 1].pts; 16556 } 16557 if (filteredVideoTags.length) { 16558 this.videoBufferEnd_ = filteredVideoTags[filteredVideoTags.length - 1].pts; 16559 } 16560 16561 var tags = this.getOrderedTags_(filteredVideoTags, filteredAudioTags); 16562 16563 if (tags.length === 0) { 16564 return; 16565 } 16566 16567 // If we are appending data that comes before our target pts, we want to tell 16568 // the swf to adjust its notion of current time to account for the extra tags 16569 // we are appending to complete the GOP that intersects with targetPts 16570 if (tags[0].pts < videoTargetPts && tech.seeking()) { 16571 var fudgeFactor = 1 / 30; 16572 var currentTime = tech.currentTime(); 16573 var diff = (videoTargetPts - tags[0].pts) / 1e3; 16574 var adjustedTime = currentTime - diff; 16575 16576 if (adjustedTime < fudgeFactor) { 16577 adjustedTime = 0; 16578 } 16579 16580 try { 16581 this.mediaSource_.swfObj.vjs_adjustCurrentTime(adjustedTime); 16582 } catch (e) { 16583 // no-op for backwards compatability of swf. If adjustCurrentTime fails, 16584 // the swf may incorrectly report currentTime and buffered ranges 16585 // but should not affect playback over than the time displayed on the 16586 // progress bar is inaccurate 16587 } 16588 } 16589 16590 // concatenate the bytes into a single segment 16591 for (var i = 0; i < tags.length; i++) { 16592 segmentByteLength += tags[i].bytes.byteLength; 16593 } 16594 segment = new Uint8Array(segmentByteLength); 16595 for (var i = 0, j = 0; i < tags.length; i++) { 16596 segment.set(tags[i].bytes, j); 16597 j += tags[i].bytes.byteLength; 16598 } 16599 16600 return segment; 16601 } 16602 16603 /** 16604 * Assemble the FLV tags in decoder order. 16605 * 16606 * @private 16607 * @param {Array} videoTags list of video tags 16608 * @param {Array} audioTags list of audio tags 16609 */ 16610 }, { 16611 key: 'getOrderedTags_', 16612 value: function getOrderedTags_(videoTags, audioTags) { 16613 var tag = undefined; 16614 var tags = []; 16615 16616 while (videoTags.length || audioTags.length) { 16617 if (!videoTags.length) { 16618 // only audio tags remain 16619 tag = audioTags.shift(); 16620 } else if (!audioTags.length) { 16621 // only video tags remain 16622 tag = videoTags.shift(); 16623 } else if (audioTags[0].dts < videoTags[0].dts) { 16624 // audio should be decoded next 16625 tag = audioTags.shift(); 16626 } else { 16627 // video should be decoded next 16628 tag = videoTags.shift(); 16629 } 16630 16631 tags.push(tag); 16632 } 16633 16634 return tags; 16635 } 16636 }]); 16637 16638 return FlashSourceBuffer; 16639 })(_videoJs2['default'].EventTarget); 16640 16641 exports['default'] = FlashSourceBuffer; 16642 module.exports = exports['default']; 16643 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 16644 },{"./add-text-track-data":62,"./create-text-tracks-if-necessary":65,"./flash-constants":66,"./flash-transmuxer-worker":69,"./remove-cues-from-track":71,"global/window":30,"mux.js/lib/flv":44,"webworkify":75}],69:[function(require,module,exports){ 16645 /** 16646 * @file flash-transmuxer-worker.js 16647 */ 16648 'use strict'; 16649 16650 Object.defineProperty(exports, '__esModule', { 16651 value: true 16652 }); 16653 16654 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 16655 16656 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 16657 16658 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 16659 16660 var _globalWindow = require('global/window'); 16661 16662 var _globalWindow2 = _interopRequireDefault(_globalWindow); 16663 16664 var _muxJsLibFlv = require('mux.js/lib/flv'); 16665 16666 var _muxJsLibFlv2 = _interopRequireDefault(_muxJsLibFlv); 16667 16668 /**
16669 * Re-emits transmuxer events by converting them into messages to the 16670 * world outside the worker. 16671 * 16672 * @param {Object} transmuxer the transmuxer to wire events on 16673 * @private 16674 */ 16675 var wireTransmuxerEvents = function wireTransmuxerEvents(transmuxer) { 16676 transmuxer.on('data', function (segment) { 16677 _globalWindow2['default'].postMessage({ 16678 action: 'data', 16679 segment: segment 16680 }); 16681 }); 16682 16683 transmuxer.on('done', function (data) { 16684 _globalWindow2['default'].postMessage({ action: 'done' }); 16685 }); 16686 }; 16687 16688 /** 16689 * All incoming messages route through this hash. If no function exists 16690 * to handle an incoming message, then we ignore the message. 16691 * 16692 * @class MessageHandlers 16693 * @param {Object} options the options to initialize with 16694 */ 16695 16696 var MessageHandlers = (function () { 16697 function MessageHandlers(options) { 16698 _classCallCheck(this, MessageHandlers); 16699 16700 this.options = options || {}; 16701 this.init(); 16702 } 16703 16704 /** 16705 * Our web wroker interface so that things can talk to mux.js 16706 * that will be running in a web worker. The scope is passed to this by 16707 * webworkify. 16708 * 16709 * @param {Object} self the scope for the web worker 16710 */ 16711 16712 /** 16713 * initialize our web worker and wire all the events. 16714 */ 16715 16716 _createClass(MessageHandlers, [{ 16717 key: 'init', 16718 value: function init() { 16719 if (this.transmuxer) { 16720 this.transmuxer.dispose(); 16721 } 16722 this.transmuxer = new _muxJsLibFlv2['default'].Transmuxer(this.options); 16723 wireTransmuxerEvents(this.transmuxer); 16724 } 16725 16726 /** 16727 * Adds data (a ts segment) to the start of the transmuxer pipeline for 16728 * processing. 16729 * 16730 * @param {ArrayBuffer} data data to push into the muxer 16731 */ 16732 }, { 16733 key: 'push', 16734 value: function push(data) { 16735 // Cast array buffer to correct type for transmuxer 16736 var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); 16737 16738 this.transmuxer.push(segment); 16739 } 16740 16741 /** 16742 * Recreate the transmuxer so that the next segment added via `push` 16743 * start with a fresh transmuxer. 16744 */ 16745 }, { 16746 key: 'reset', 16747 value: function reset() { 16748 this.init(); 16749 } 16750 16751 /** 16752 * Forces the pipeline to finish processing the last segment and emit its 16753 * results. 16754 */ 16755 }, { 16756 key: 'flush', 16757 value: function flush() { 16758 this.transmuxer.flush(); 16759 } 16760 }]); 16761 16762 return MessageHandlers; 16763 })(); 16764 16765 var FlashTransmuxerWorker = function FlashTransmuxerWorker(self) { 16766 self.onmessage = function (event) { 16767 if (event.data.action === 'init' && event.data.options) { 16768 this.messageHandlers = new MessageHandlers(event.data.options); 16769 return; 16770 } 16771 16772 if (!this.messageHandlers) { 16773 this.messageHandlers = new MessageHandlers(); 16774 } 16775 16776 if (event.data && event.data.action && event.data.action !== 'init') { 16777 if (this.messageHandlers[event.data.action]) { 16778 this.messageHandlers[event.data.action](event.data); 16779 } 16780 } 16781 }; 16782 }; 16783 16784 exports['default'] = function (self) { 16785 return new FlashTransmuxerWorker(self); 16786 }; 16787 16788 module.exports = exports['default']; 16789 },{"global/window":30,"mux.js/lib/flv":44}],70:[function(require,module,exports){ 16790 (function (global){ 16791 /** 16792 * @file html-media-source.js 16793 */ 16794 'use strict'; 16795 16796 Object.defineProperty(exports, '__esModule', { 16797 value: true 16798 }); 16799 16800 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 16801 16802 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 16803 16804 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 16805 16806 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 16807 16808 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
16808onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 16809 16810 var _globalWindow = require('global/window'); 16811 16812 var _globalWindow2 = _interopRequireDefault(_globalWindow); 16813 16814 var _globalDocument = require('global/document'); 16815 16816 var _globalDocument2 = _interopRequireDefault(_globalDocument); 16817 16818 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 16819 16820 var _videoJs2 = _interopRequireDefault(_videoJs); 16821 16822 var _virtualSourceBuffer = require('./virtual-source-buffer'); 16823 16824 var _virtualSourceBuffer2 = _interopRequireDefault(_virtualSourceBuffer); 16825 16826 var _addTextTrackData = require('./add-text-track-data'); 16827 16828 var _codecUtils = require('./codec-utils'); 16829 16830 var _cleanupTextTracks = require('./cleanup-text-tracks'); 16831 16832 /** 16833 * Our MediaSource implementation in HTML, mimics native 16834 * MediaSource where/if possible. 16835 * 16836 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource 16837 * @class HtmlMediaSource 16838 * @extends videojs.EventTarget 16839 */ 16840 16841 var HtmlMediaSource = (function (_videojs$EventTarget) { 16842 _inherits(HtmlMediaSource, _videojs$EventTarget); 16843 16844 function HtmlMediaSource() { 16845 var _this = this; 16846 16847 _classCallCheck(this, HtmlMediaSource); 16848 16849 _get(Object.getPrototypeOf(HtmlMediaSource.prototype), 'constructor', this).call(this); 16850 var property = undefined; 16851 16852 this.nativeMediaSource_ = new _globalWindow2['default'].MediaSource(); 16853 // delegate to the native MediaSource's methods by default 16854 for (property in this.nativeMediaSource_) { 16855 if (!(property in HtmlMediaSource.prototype) && typeof this.nativeMediaSource_[property] === 'function') { 16856 this[property] = this.nativeMediaSource_[property].bind(this.nativeMediaSource_); 16857 } 16858 } 16859 16860 // emulate `duration` and `seekable` until seeking can be 16861 // handled uniformly for live streams 16862 // see https://github.com/w3c/media-source/issues/5 16863 this.duration_ = NaN; 16864 Object.defineProperty(this, 'duration', { 16865 get: function get() { 16866 if (this.duration_ === Infinity) { 16867 return this.duration_; 16868 } 16869 return this.nativeMediaSource_.duration; 16870 }, 16871 set: function set(duration) { 16872 this.duration_ = duration; 16873 if (duration !== Infinity) { 16874 this.nativeMediaSource_.duration = duration; 16875 return; 16876 } 16877 } 16878 }); 16879 Object.defineProperty(this, 'seekable', { 16880 get: function get() { 16881 if (this.duration_ === Infinity) { 16882 return _videoJs2['default'].createTimeRanges([[0, this.nativeMediaSource_.duration]]); 16883 } 16884 return this.nativeMediaSource_.seekable; 16885 } 16886 }); 16887 16888 Object.defineProperty(this, 'readyState', { 16889 get: function get() { 16890 return this.nativeMediaSource_.readyState; 16891 } 16892 }); 16893 16894 Object.defineProperty(this, 'activeSourceBuffers', { 16895 get: function get() { 16896 return this.activeSourceBuffers_; 16897 } 16898 }); 16899 16900 // the list of virtual and native SourceBuffers created by this 16901 // MediaSource 16902 this.sourceBuffers = []; 16903 16904 this.activeSourceBuffers_ = []; 16905 16906 /** 16907 * update the list of active source buffers based upon various 16908 * imformation from HLS and video.js 16909 * 16910 * @private 16911 */ 16912 this.updateActiveSourceBuffers_ = function () { 16913 // Retain the reference but empty the array 16914 _this.activeSourceBuffers_.length = 0; 16915 16916 // By default, the audio in the combined virtual source buffer is enabled 16917 // and the audio-only source buffer (if it exists) is disabled. 16918 var combined = false;
16919 var audioOnly = true; 16920 16921 // TODO: maybe we can store the sourcebuffers on the track objects? 16922 // safari may do something like this 16923 for (var i = 0; i < _this.player_.audioTracks().length; i++) { 16924 var track = _this.player_.audioTracks()[i]; 16925 16926 if (track.enabled && track.kind !== 'main') { 16927 // The enabled track is an alternate audio track so disable the audio in 16928 // the combined source buffer and enable the audio-only source buffer. 16929 combined = true; 16930 audioOnly = false; 16931 break; 16932 } 16933 } 16934 16935 // Since we currently support a max of two source buffers, add all of the source 16936 // buffers (in order). 16937 _this.sourceBuffers.forEach(function (sourceBuffer) { 16938 /* eslinst-disable */ 16939 // TODO once codecs are required, we can switch to using the codecs to determine 16940 // what stream is the video stream, rather than relying on videoTracks 16941 /* eslinst-enable */ 16942 16943 sourceBuffer.appendAudioInitSegment_ = true; 16944 16945 if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { 16946 // combined 16947 sourceBuffer.audioDisabled_ = combined; 16948 } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) { 16949 // If the "combined" source buffer is video only, then we do not want 16950 // disable the audio-only source buffer (this is mostly for demuxed 16951 // audio and video hls) 16952 sourceBuffer.audioDisabled_ = true; 16953 audioOnly = false; 16954 } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) { 16955 // audio only 16956 sourceBuffer.audioDisabled_ = audioOnly; 16957 if (audioOnly) { 16958 return; 16959 } 16960 } 16961 16962 _this.activeSourceBuffers_.push(sourceBuffer); 16963 }); 16964 }; 16965 16966 this.onPlayerMediachange_ = function () { 16967 _this.sourceBuffers.forEach(function (sourceBuffer) { 16968 sourceBuffer.appendAudioInitSegment_ = true; 16969 }); 16970 }; 16971 16972 // Re-emit MediaSource events on the polyfill 16973 ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) { 16974 this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this)); 16975 }, this); 16976 16977 // capture the associated player when the MediaSource is 16978 // successfully attached 16979 this.on('sourceopen', function (event) { 16980 // Get the player this MediaSource is attached to 16981 var video = _globalDocument2['default'].querySelector('[src="' + _this.url_ + '"]'); 16982 16983 if (!video) { 16984 return; 16985 } 16986 16987 _this.player_ = (0, _videoJs2['default'])(video.parentNode); 16988 16989 if (_this.player_.audioTracks && _this.player_.audioTracks()) { 16990 _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_); 16991 _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_); 16992 _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_); 16993 } 16994 16995 _this.player_.on('mediachange', _this.onPlayerMediachange_); 16996 }); 16997 16998 this.on('sourceended', function (event) { 16999 var duration = (0, _addTextTrackData.durationOfVideo)(_this.duration); 17000 17001 for (var i = 0; i < _this.sourceBuffers.length; i++) { 17002 var sourcebuffer = _this.sourceBuffers[i]; 17003 var cues = sourcebuffer.metadataTrack_ && sourcebuffer.metadataTrack_.cues; 17004 17005 if (cues && cues.length) { 17006 cues[cues.length - 1].endTime = duration; 17007 } 17008 } 17009 }); 17010 17011 // explicitly terminate any WebWorkers that were created 17012 // by SourceHandlers 17013 this.on('sourceclose', function (event) {
17014 this.sourceBuffers.forEach(function (sourceBuffer) { 17015 if (sourceBuffer.transmuxer_) { 17016 sourceBuffer.transmuxer_.terminate(); 17017 } 17018 }); 17019 17020 this.sourceBuffers.length = 0; 17021 if (!this.player_) { 17022 return; 17023 } 17024 17025 (0, _cleanupTextTracks.cleanupTextTracks)(this.player_); 17026 17027 if (this.player_.audioTracks && this.player_.audioTracks()) { 17028 this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_); 17029 this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_); 17030 this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_); 17031 } 17032 17033 // We can only change this if the player hasn't been disposed of yet 17034 // because `off` eventually tries to use the el_ property. If it has 17035 // been disposed of, then don't worry about it because there are no 17036 // event handlers left to unbind anyway 17037 if (this.player_.el_) { 17038 this.player_.off('mediachange', this.onPlayerMediachange_); 17039 } 17040 }); 17041 } 17042 17043 /** 17044 * Add a range that that can now be seeked to. 17045 * 17046 * @param {Double} start where to start the addition 17047 * @param {Double} end where to end the addition 17048 * @private 17049 */ 17050 17051 _createClass(HtmlMediaSource, [{ 17052 key: 'addSeekableRange_', 17053 value: function addSeekableRange_(start, end) { 17054 var error = undefined; 17055 17056 if (this.duration !== Infinity) { 17057 error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity'); 17058 error.name = 'InvalidStateError'; 17059 error.code = 11; 17060 throw error; 17061 } 17062 17063 if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) { 17064 this.nativeMediaSource_.duration = end; 17065 } 17066 } 17067 17068 /** 17069 * Add a source buffer to the media source. 17070 * 17071 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer 17072 * @param {String} type the content-type of the content 17073 * @return {Object} the created source buffer 17074 */ 17075 }, { 17076 key: 'addSourceBuffer', 17077 value: function addSourceBuffer(type) { 17078 var buffer = undefined; 17079 var parsedType = (0, _codecUtils.parseContentType)(type); 17080 17081 // Create a VirtualSourceBuffer to transmux MPEG-2 transport 17082 // stream segments into fragmented MP4s 17083 if (/^(video|audio)\/mp2t$/i.test(parsedType.type)) { 17084 var codecs = []; 17085 17086 if (parsedType.parameters && parsedType.parameters.codecs) { 17087 codecs = parsedType.parameters.codecs.split(','); 17088 codecs = (0, _codecUtils.translateLegacyCodecs)(codecs); 17089 codecs = codecs.filter(function (codec) { 17090 return (0, _codecUtils.isAudioCodec)(codec) || (0, _codecUtils.isVideoCodec)(codec); 17091 }); 17092 } 17093 17094 if (codecs.length === 0) { 17095 codecs = ['avc1.4d400d', 'mp4a.40.2']; 17096 } 17097 17098 buffer = new _virtualSourceBuffer2['default'](this, codecs); 17099 17100 if (this.sourceBuffers.length !== 0) { 17101 // If another VirtualSourceBuffer already exists, then we are creating a 17102 // SourceBuffer for an alternate audio track and therefore we know that 17103 // the source has both an audio and video track. 17104 // That means we should trigger the manual creation of the real 17105 // SourceBuffers instead of waiting for the transmuxer to return data 17106 this.sourceBuffers[0].createRealSourceBuffers_(); 17107 buffer.createRealSourceBuffers_(); 17108 17109 // Automatically disable the audio on the first source buffer if 17110 // a second source buffer is ever created 17111 this.sourceBuffers[0].audioDisabled_ = true; 17112 } 17113 } else { 17114 // delegate to the native implementation 17115 buffer = this.nativeMediaSource_.addSourceBuffer(type); 17116 } 17117 17118 this.sourceBuffers.push(buffer); 17119 return buffer; 17120 } 17121 }]); 17122 17123 return HtmlMediaSource; 17124 })(_videoJs2['default'].EventTarget); 17125 17126 exports['default'] = HtmlMediaSource; 17127 module.exports = exports['default']; 17128 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 17129 },{"./add-text-track-data":62,"./cleanup-text-tracks":63,"./codec-utils":64,"./virtual-source-buffer":74,"global/document":29,"global/window":30}],71:[function(require,module,exports){ 17130 /** 17131 * @file remove-cues-from-track.js 17132 */ 17133 17134 /** 17135 * Remove cues from a track on video.js. 17136 * 17137 * @param {Double} start start of where we should remove the cue 17138 * @param {Double} end end of where the we should remove the cue 17139 * @param {Object} track the text track to remove the cues from 17140 * @private 17141 */ 17142 "use strict"; 17143 17144 Object.defineProperty(exports, "__esModule", { 17145 value: true 17146 }); 17147 var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) { 17148 var i = undefined; 17149 var cue = undefined; 17150 17151 if (!track) { 17152 return; 17153 } 17154 17155 if (!track.cues) { 17156 return; 17157 } 17158 17159 i = track.cues.length; 17160 17161 while (i--) { 17162 cue = track.cues[i]; 17163 17164 // Remove any overlapping cue 17165 if (cue.startTime <= end && cue.endTime >= start) { 17166 track.removeCue(cue); 17167 } 17168 } 17169 }; 17170 17171 exports["default"] = removeCuesFromTrack; 17172 module.exports = exports["default"]; 17173 },{}],72:[function(require,module,exports){ 17174 /** 17175 * @file transmuxer-worker.js 17176 */ 17177 17178 /** 17179 * videojs-contrib-media-sources 17180 * 17181 * Copyright (c) 2015 Brightcove 17182 * All rights reserved. 17183 * 17184 * Handles communication between the browser-world and the mux.js 17185 * transmuxer running inside of a WebWorker by exposing a simple 17186 * message-based interface to a Transmuxer object. 17187 */ 17188 'use strict'; 17189 17190 Object.defineProperty(exports, '__esModule', { 17191 value: true 17192 }); 17193 17194 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 17195 17196 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 17197 17198 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 17199 17200 var _globalWindow = require('global/window'); 17201 17202 var _globalWindow2 = _interopRequireDefault(_globalWindow); 17203 17204 var _muxJsLibMp4 = require('mux.js/lib/mp4'); 17205 17206 var _muxJsLibMp42 = _interopRequireDefault(_muxJsLibMp4); 17207 17208 /**
17209 * Re-emits transmuxer events by converting them into messages to the 17210 * world outside the worker. 17211 * 17212 * @param {Object} transmuxer the transmuxer to wire events on 17213 * @private 17214 */ 17215 var wireTransmuxerEvents = function wireTransmuxerEvents(transmuxer) { 17216 transmuxer.on('data', function (segment) { 17217 // transfer ownership of the underlying ArrayBuffer 17218 // instead of doing a copy to save memory 17219 // ArrayBuffers are transferable but generic TypedArrays are not 17220 // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects) 17221 var initArray = segment.initSegment; 17222 17223 segment.initSegment = { 17224 data: initArray.buffer, 17225 byteOffset: initArray.byteOffset, 17226 byteLength: initArray.byteLength 17227 }; 17228 17229 var typedArray = segment.data; 17230 17231 segment.data = typedArray.buffer; 17232 _globalWindow2['default'].postMessage({ 17233 action: 'data', 17234 segment: segment, 17235 byteOffset: typedArray.byteOffset, 17236 byteLength: typedArray.byteLength 17237 }, [segment.data]); 17238 }); 17239 17240 if (transmuxer.captionStream) { 17241 transmuxer.captionStream.on('data', function (caption) { 17242 _globalWindow2['default'].postMessage({ 17243 action: 'caption', 17244 data: caption 17245 }); 17246 }); 17247 } 17248 17249 transmuxer.on('done', function (data) { 17250 _globalWindow2['default'].postMessage({ action: 'done' }); 17251 }); 17252 }; 17253 17254 /** 17255 * All incoming messages route through this hash. If no function exists 17256 * to handle an incoming message, then we ignore the message. 17257 * 17258 * @class MessageHandlers 17259 * @param {Object} options the options to initialize with 17260 */ 17261 17262 var MessageHandlers = (function () { 17263 function MessageHandlers(options) { 17264 _classCallCheck(this, MessageHandlers); 17265 17266 this.options = options || {}; 17267 this.init(); 17268 } 17269 17270 /** 17271 * Our web wroker interface so that things can talk to mux.js 17272 * that will be running in a web worker. the scope is passed to this by 17273 * webworkify. 17274 * 17275 * @param {Object} self the scope for the web worker 17276 */ 17277 17278 /** 17279 * initialize our web worker and wire all the events. 17280 */ 17281 17282 _createClass(MessageHandlers, [{ 17283 key: 'init', 17284 value: function init() { 17285 if (this.transmuxer) { 17286 this.transmuxer.dispose(); 17287 } 17288 this.transmuxer = new _muxJsLibMp42['default'].Transmuxer(this.options); 17289 wireTransmuxerEvents(this.transmuxer); 17290 } 17291 17292 /** 17293 * Adds data (a ts segment) to the start of the transmuxer pipeline for 17294 * processing. 17295 * 17296 * @param {ArrayBuffer} data data to push into the muxer 17297 */ 17298 }, { 17299 key: 'push', 17300 value: function push(data) { 17301 // Cast array buffer to correct type for transmuxer 17302 var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength); 17303 17304 this.transmuxer.push(segment); 17305 } 17306 17307 /** 17308 * Recreate the transmuxer so that the next segment added via `push` 17309 * start with a fresh transmuxer. 17310 */ 17311 }, { 17312 key: 'reset', 17313 value: function reset() { 17314 this.init(); 17315 } 17316 17317 /** 17318 * Set the value that will be used as the `baseMediaDecodeTime` time for the 17319 * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime` 17320 * set relative to the first based on the PTS values. 17321 * 17322 * @param {Object} data used to set the timestamp offset in the muxer 17323 */ 17324 }, { 17325 key: 'setTimestampOffset', 17326 value: function setTimestampOffset(data) { 17327 var timestampOffset = data.timestampOffset || 0; 17328 17329 this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000)); 17330 } 17331 }, { 17332 key: 'setAudioAppendStart', 17333 value: function setAudioAppendStart(data) { 17334 this.transmuxer.setAudioAppendStart(Math.ceil(data.appendStart * 90000)); 17335 } 17336 17337 /** 17338 * Forces the pipeline to finish processing the last segment and emit it's 17339 * results. 17340 * 17341 * @param {Object} data event data, not really used 17342 */ 17343 }, { 17344 key: 'flush', 17345 value: function flush(data) { 17346 this.transmuxer.flush(); 17347 } 17348 }]); 17349 17350 return MessageHandlers; 17351 })(); 17352 17353 var TransmuxerWorker = function TransmuxerWorker(self) { 17354 self.onmessage = function (event) {
17355 if (event.data.action === 'init' && event.data.options) { 17356 this.messageHandlers = new MessageHandlers(event.data.options); 17357 return; 17358 } 17359 17360 if (!this.messageHandlers) { 17361 this.messageHandlers = new MessageHandlers(); 17362 } 17363 17364 if (event.data && event.data.action && event.data.action !== 'init') { 17365 if (this.messageHandlers[event.data.action]) { 17366 this.messageHandlers[event.data.action](event.data); 17367 } 17368 } 17369 }; 17370 }; 17371 17372 exports['default'] = function (self) { 17373 return new TransmuxerWorker(self); 17374 }; 17375 17376 module.exports = exports['default']; 17377 },{"global/window":30,"mux.js/lib/mp4":53}],73:[function(require,module,exports){ 17378 (function (global){ 17379 /** 17380 * @file videojs-contrib-media-sources.js 17381 */ 17382 'use strict'; 17383 17384 Object.defineProperty(exports, '__esModule', { 17385 value: true 17386 }); 17387 17388 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 17389 17390 var _globalWindow = require('global/window'); 17391 17392 var _globalWindow2 = _interopRequireDefault(_globalWindow); 17393 17394 var _flashMediaSource = require('./flash-media-source'); 17395 17396 var _flashMediaSource2 = _interopRequireDefault(_flashMediaSource); 17397 17398 var _htmlMediaSource = require('./html-media-source'); 17399 17400 var _htmlMediaSource2 = _interopRequireDefault(_htmlMediaSource); 17401 17402 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 17403 17404 var _videoJs2 = _interopRequireDefault(_videoJs); 17405 17406 var urlCount = 0; 17407 17408// ------------ 17409// Media Source 17410// ------------ 17411 17412 var defaults = { 17413 // how to determine the MediaSource implementation to use. There 17414 // are three available modes: 17415 // - auto: use native MediaSources where available and Flash 17416 // everywhere else 17417 // - html5: always use native MediaSources 17418 // - flash: always use the Flash MediaSource polyfill 17419 mode: 'auto' 17420 }; 17421 17422// store references to the media sources so they can be connected 17423// to a video element (a swf object) 17424// TODO: can we store this somewhere local to this module? 17425 _videoJs2['default'].mediaSources = {}; 17426 17427 /** 17428 * Provide a method for a swf object to notify JS that a 17429 * media source is now open. 17430 * 17431 * @param {String} msObjectURL string referencing the MSE Object URL 17432 * @param {String} swfId the swf id 17433 */ 17434 var open = function open(msObjectURL, swfId) { 17435 var mediaSource = _videoJs2['default'].mediaSources[msObjectURL]; 17436 17437 if (mediaSource) { 17438 mediaSource.trigger({ type: 'sourceopen', swfId: swfId }); 17439 } else { 17440 throw new Error('Media Source not found (Video.js)'); 17441 } 17442 }; 17443 17444 /** 17445 * Check to see if the native MediaSource object exists and supports 17446 * an MP4 container with both H.264 video and AAC-LC audio. 17447 * 17448 * @return {Boolean} if native media sources are supported 17449 */ 17450 var supportsNativeMediaSources = function supportsNativeMediaSources() { 17451 return !!_globalWindow2['default'].MediaSource && !!_globalWindow2['default'].MediaSource.isTypeSupported && _globalWindow2['default'].MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"'); 17452 }; 17453 17454 /** 17455 * An emulation of the MediaSource API so that we can support 17456 * native and non-native functionality such as flash and 17457 * video/mp2t videos. returns an instance of HtmlMediaSource or 17458 * FlashMediaSource depending on what is supported and what options 17459 * are passed in. 17460 * 17461 * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource 17462 * @param {Object} options options to use during setup. 17463 */ 17464 var MediaSource = function MediaSource(options) { 17465 var settings = _videoJs2['default'].mergeOptions(defaults, options); 17466 17467 this.MediaSource = { 17468 open: open, 17469 supportsNativeMediaSources: supportsNativeMediaSources 17470 }; 17471 17472 // determine whether HTML MediaSources should be used 17473 if (settings.mode === 'html5' || settings.mode === 'auto' && supportsNativeMediaSources()) { 17474 return new _htmlMediaSource2['default'](); 17475 } else if (_videoJs2['default'].getTech('Flash')) { 17476 return new _flashMediaSource2['default'](); 17477 } 17478 17479 throw new Error('Cannot use Flash or Html5 to create a MediaSource for this video'); 17480 }; 17481 17482 exports.MediaSource = MediaSource; 17483 MediaSource.open = open; 17484 MediaSource.supportsNativeMediaSources = supportsNativeMediaSources; 17485 17486 /** 17487 * A wrapper around the native URL for our MSE object 17488 * implementation, this object is exposed under videojs.URL 17489 * 17490 * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL 17491 */ 17492 var URL = { 17493 /** 17494 * A wrapper around the native createObjectURL for our objects. 17495 * This function maps a native or emulated mediaSource to a blob 17496 * url so that it can be loaded into video.js 17497 * 17498 * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL 17499 * @param {MediaSource} object the object to create a blob url to 17500 */ 17501 createObjectURL: function createObjectURL(object) { 17502 var objectUrlPrefix = 'blob:vjs-media-source/'; 17503 var url = undefined; 17504 17505 // use the native MediaSource to generate an object URL 17506 if (object instanceof _htmlMediaSource2['default']) { 17507 url = _globalWindow2['default'].URL.createObjectURL(object.nativeMediaSource_); 17508 object.url_ = url; 17509 return url; 17510 } 17511 // if the object isn't an emulated MediaSource, delegate to the 17512 // native implementation 17513 if (!(object instanceof _flashMediaSource2['default'])) { 17514 url = _globalWindow2['default'].URL.createObjectURL(object); 17515 object.url_ = url; 17516 return url; 17517 } 17518 17519 // build a URL that can be used to map back to the emulated 17520 // MediaSource 17521 url = objectUrlPrefix + urlCount; 17522 17523 urlCount++; 17524 17525 // setup the mapping back to object 17526 _videoJs2['default'].mediaSources[url] = object; 17527 17528 return url; 17529 } 17530 }; 17531 17532 exports.URL = URL; 17533 _videoJs2['default'].MediaSource = MediaSource; 17534 _videoJs2['default'].URL = URL; 17535 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 17536 }
17536,{"./flash-media-source":67,"./html-media-source":70,"global/window":30}],74:[function(require,module,exports){ 17537 (function (global){ 17538 /** 17539 * @file virtual-source-buffer.js 17540 */ 17541 'use strict'; 17542 17543 Object.defineProperty(exports, '__esModule', { 17544 value: true 17545 }); 17546 17547 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 17548 17549 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 17550 17551 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 17552 17553 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 17554 17555 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 17556 17557 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 17558 17559 var _videoJs2 = _interopRequireDefault(_videoJs); 17560 17561 var _createTextTracksIfNecessary = require('./create-text-tracks-if-necessary'); 17562 17563 var _createTextTracksIfNecessary2 = _interopRequireDefault(_createTextTracksIfNecessary); 17564 17565 var _removeCuesFromTrack = require('./remove-cues-from-track'); 17566 17567 var _removeCuesFromTrack2 = _interopRequireDefault(_removeCuesFromTrack); 17568 17569 var _addTextTrackData = require('./add-text-track-data'); 17570 17571 var _webworkify = require('webworkify'); 17572 17573 var _webworkify2 = _interopRequireDefault(_webworkify); 17574 17575 var _transmuxerWorker = require('./transmuxer-worker'); 17576 17577 var _transmuxerWorker2 = _interopRequireDefault(_transmuxerWorker); 17578 17579 var _codecUtils = require('./codec-utils'); 17580 17581// We create a wrapper around the SourceBuffer so that we can manage the 17582// state of the `updating` property manually. We have to do this because 17583// Firefox changes `updating` to false long before triggering `updateend` 17584// events and that was causing strange problems in videojs-contrib-hls 17585 var makeWrappedSourceBuffer = function makeWrappedSourceBuffer(mediaSource, mimeType) { 17586 var sourceBuffer = mediaSource.addSourceBuffer(mimeType); 17587 var wrapper = Object.create(null); 17588 17589 wrapper.updating = false; 17590 wrapper.realBuffer_ = sourceBuffer; 17591 17592 var _loop = function (key) { 17593 if (typeof sourceBuffer[key] === 'function') { 17594 wrapper[key] = function () { 17595 return sourceBuffer[key].apply(sourceBuffer, arguments); 17596 }; 17597 } else if (typeof wrapper[key] === 'undefined') { 17598 Object.defineProperty(wrapper, key, { 17599 get: function get() { 17600 return sourceBuffer[key]; 17601 }, 17602 set: function set(v) { 17603 return sourceBuffer[key] = v; 17604 } 17605 }); 17606 } 17607 }; 17608 17609 for (var key in sourceBuffer) { 17610 _loop(key); 17611 } 17612 17613 return wrapper; 17614 }; 17615 17616 /** 17617 * VirtualSourceBuffers exist so that we can transmux non native formats 17618 * into a native format, but keep the same api as a native source buffer. 17619 * It creates a transmuxer, that works in its own thread (a web worker) and 17620 * that transmuxer muxes the data into a native format. VirtualSourceBuffer will 17621 * then send all of that data to the naive sourcebuffer so that it is 17622 * indestinguishable from a natively supported format. 17623 * 17624 * @param {HtmlMediaSource} mediaSource the parent mediaSource 17625 * @param {Array} codecs array of codecs that we will be dealing with 17626 * @class VirtualSourceBuffer 17627 * @extends video.js.EventTarget 17628 */ 17629 17630 var VirtualSourceBuffer = (function (_videojs$EventTarget) { 17631 _inherits(VirtualSourceBuffer, _videojs$EventTarget); 17632 17633 function VirtualSourceBuffer(mediaSource, codecs) { 17634 var _this = this; 17635 17636 _classCallCheck(this, VirtualSourceBuffer); 17637 17638 _get(Object.getPrototypeOf(VirtualSourceBuffer.prototype), 'constructor', this).call(this, _videoJs2['default'].EventTarget); 17639 this.timestampOffset_ = 0; 17640 this.pendingBuffers_ = []; 17641 this.bufferUpdating_ = false;
17642 17643 this.mediaSource_ = mediaSource; 17644 this.codecs_ = codecs; 17645 this.audioCodec_ = null; 17646 this.videoCodec_ = null; 17647 this.audioDisabled_ = false; 17648 this.appendAudioInitSegment_ = true; 17649 17650 var options = { 17651 remux: false 17652 }; 17653 17654 this.codecs_.forEach(function (codec) { 17655 if ((0, _codecUtils.isAudioCodec)(codec)) { 17656 _this.audioCodec_ = codec; 17657 } else if ((0, _codecUtils.isVideoCodec)(codec)) { 17658 _this.videoCodec_ = codec; 17659 } 17660 }); 17661 17662 // append muxed segments to their respective native buffers as 17663 // soon as they are available 17664 this.transmuxer_ = (0, _webworkify2['default'])(_transmuxerWorker2['default']); 17665 this.transmuxer_.postMessage({ action: 'init', options: options }); 17666 17667 this.transmuxer_.onmessage = function (event) { 17668 if (event.data.action === 'data') { 17669 return _this.data_(event); 17670 } 17671 17672 if (event.data.action === 'done') { 17673 return _this.done_(event); 17674 } 17675 }; 17676 17677 // this timestampOffset is a property with the side-effect of resetting 17678 // baseMediaDecodeTime in the transmuxer on the setter 17679 Object.defineProperty(this, 'timestampOffset', { 17680 get: function get() { 17681 return this.timestampOffset_; 17682 }, 17683 set: function set(val) { 17684 if (typeof val === 'number' && val >= 0) { 17685 this.timestampOffset_ = val; 17686 this.appendAudioInitSegment_ = true; 17687 17688 // We have to tell the transmuxer to set the baseMediaDecodeTime to 17689 // the desired timestampOffset for the next segment 17690 this.transmuxer_.postMessage({ 17691 action: 'setTimestampOffset', 17692 timestampOffset: val 17693 }); 17694 } 17695 } 17696 }); 17697 17698 // setting the append window affects both source buffers 17699 Object.defineProperty(this, 'appendWindowStart', { 17700 get: function get() { 17701 return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart; 17702 }, 17703 set: function set(start) { 17704 if (this.videoBuffer_) { 17705 this.videoBuffer_.appendWindowStart = start; 17706 } 17707 if (this.audioBuffer_) { 17708 this.audioBuffer_.appendWindowStart = start; 17709 } 17710 } 17711 }); 17712 17713 // this buffer is "updating" if either of its native buffers are 17714 Object.defineProperty(this, 'updating', { 17715 get: function get() { 17716 return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating); 17717 } 17718 }); 17719 17720 // the buffered property is the intersection of the buffered 17721 // ranges of the native source buffers 17722 Object.defineProperty(this, 'buffered', { 17723 get: function get() { 17724 var start = null; 17725 var end = null; 17726 var arity = 0; 17727 var extents = []; 17728 var ranges = []; 17729 17730 // neither buffer has been created yet 17731 if (!this.videoBuffer_ && !this.audioBuffer_) { 17732 return _videoJs2['default'].createTimeRange(); 17733 } 17734 17735 // only one buffer is configured 17736 if (!this.videoBuffer_) { 17737 return this.audioBuffer_.buffered; 17738 } 17739 if (!this.audioBuffer_) { 17740 return this.videoBuffer_.buffered; 17741 } 17742 17743 // both buffers are configured 17744 if (this.audioDisabled_) { 17745 return this.videoBuffer_.buffered; 17746 } 17747 17748 // both buffers are empty 17749 if (this.videoBuffer_.buffered.length === 0 && this.audioBuffer_.buffered.length === 0) { 17750 return _videoJs2['default'].createTimeRange(); 17751 } 17752 17753 // Handle the case where we have both buffers and create an 17754 // intersection of the two 17755 var videoBuffered = this.videoBuffer_.buffered; 17756 var audioBuffered = this.audioBuffer_.buffered; 17757 var count = videoBuffered.length; 17758 17759 // A) Gather up all start and end times 17760 while (count--) { 17761 extents.push({ time: videoBuffered.start(count), type: 'start' }); 17762 extents.push({ time: videoBuffered.end(count), type: 'end' }); 17763 } 17764 count = audioBuffered.length; 17765 while (count--) { 17766 extents.push({ time: audioBuffered.start(count), type: 'start' }); 17767 extents.push({ time: audioBuffered.end(count), type: 'end' }); 17768 } 17769 // B) Sort them by time 17770 extents.sort(function (a, b) { 17771 return a.time - b.time; 17772 }); 17773 17774 // C) Go along one by one incrementing arity for start and decrementing 17775 // arity for ends 17776 for (count = 0; count < extents.length; count++) { 17777 if (extents[count].type === 'start') { 17778 arity++; 17779 17780 // D) If arity is ever incremented to 2 we are entering an 17781 // overlapping range 17782 if (arity === 2) { 17783 start = extents[count].time; 17784 } 17785 } else if (extents[count].type === 'end') { 17786 arity--; 17787 17788 // E) If arity is ever decremented to 1 we leav
17788ing an 17789 // overlapping range 17790 if (arity === 1) { 17791 end = extents[count].time; 17792 } 17793 } 17794 17795 // F) Record overlapping ranges 17796 if (start !== null && end !== null) { 17797 ranges.push([start, end]); 17798 start = null; 17799 end = null; 17800 } 17801 } 17802 17803 return _videoJs2['default'].createTimeRanges(ranges); 17804 } 17805 }); 17806 } 17807 17808 /** 17809 * When we get a data event from the transmuxer 17810 * we call this function and handle the data that 17811 * was sent to us 17812 * 17813 * @private 17814 * @param {Event} event the data event from the transmuxer 17815 */ 17816 17817 _createClass(VirtualSourceBuffer, [{ 17818 key: 'data_', 17819 value: function data_(event) { 17820 var segment = event.data.segment; 17821 17822 // Cast ArrayBuffer to TypedArray 17823 segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength); 17824 17825 segment.initSegment = new Uint8Array(segment.initSegment.data, segment.initSegment.byteOffset, segment.initSegment.byteLength); 17826 17827 (0, _createTextTracksIfNecessary2['default'])(this, this.mediaSource_, segment); 17828 17829 // Add the segments to the pendingBuffers array 17830 this.pendingBuffers_.push(segment); 17831 return; 17832 } 17833 17834 /** 17835 * When we get a done event from the transmuxer 17836 * we call this function and we process all 17837 * of the pending data that we have been saving in the 17838 * data_ function 17839 * 17840 * @private 17841 * @param {Event} event the done event from the transmuxer 17842 */ 17843 }, { 17844 key: 'done_', 17845 value: function done_(event) { 17846 // All buffers should have been flushed from the muxer 17847 // start processing anything we have received 17848 this.processPendingSegments_(); 17849 return; 17850 } 17851 17852 /** 17853 * Create our internal native audio/video source buffers and add 17854 * event handlers to them with the following conditions: 17855 * 1. they do not already exist on the mediaSource 17856 * 2. this VSB has a codec for them 17857 * 17858 * @private 17859 */ 17860 }, { 17861 key: 'createRealSourceBuffers_', 17862 value: function createRealSourceBuffers_() { 17863 var _this2 = this; 17864 17865 var types = ['audio', 'video']; 17866 17867 types.forEach(function (type) { 17868 // Don't create a SourceBuffer of this type if we don't have a 17869 // codec for it 17870 if (!_this2[type + 'Codec_']) { 17871 return; 17872 } 17873 17874 // Do nothing if a SourceBuffer of this type already exists 17875 if (_this2[type + 'Buffer_']) { 17876 return; 17877 } 17878 17879 var buffer = null; 17880 17881 // If the mediasource already has a SourceBuffer for the codec 17882 // use that 17883 if (_this2.mediaSource_[type + 'Buffer_']) { 17884 buffer = _this2.mediaSource_[type + 'Buffer_']; 17885 // In multiple audio track cases, the audio source buffer is disabled 17886 // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier 17887 // than createRealSourceBuffers_ is called to create the second 17888 // VirtualSourceBuffer because that happens as a side-effect of 17889 // videojs-contrib-hls starting the audioSegmentLoader. As a result, 17890 // the audioBuffer is essentially "ownerless" and no one will toggle 17891 // the `updating` state back to fal
vendor: 9,901 bytes, lines 17891-18085
17891se once the `updateend` event is received 17892 // 17893 // Setting `updating` to false manually will work around this 17894 // situation and allow work to continue 17895 buffer.updating = false; 17896 } else { 17897 var codecProperty = type + 'Codec_'; 17898 var mimeType = type + '/mp4;codecs="' + _this2[codecProperty] + '"'; 17899 17900 buffer = makeWrappedSourceBuffer(_this2.mediaSource_.nativeMediaSource_, mimeType); 17901 17902 _this2.mediaSource_[type + 'Buffer_'] = buffer; 17903 } 17904 17905 _this2[type + 'Buffer_'] = buffer; 17906 17907 // Wire up the events to the SourceBuffer 17908 ['update', 'updatestart', 'updateend'].forEach(function (event) { 17909 buffer.addEventListener(event, function () { 17910 // if audio is disabled 17911 if (type === 'audio' && _this2.audioDisabled_) { 17912 return; 17913 } 17914 17915 if (event === 'updateend') { 17916 _this2[type + 'Buffer_'].updating = false; 17917 } 17918 17919 var shouldTrigger = types.every(function (t) { 17920 // skip checking audio's updating status if audio 17921 // is not enabled 17922 if (t === 'audio' && _this2.audioDisabled_) { 17923 return true; 17924 } 17925 // if the other type if updating we don't trigger 17926 if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) { 17927 return false; 17928 } 17929 return true; 17930 }); 17931 17932 if (shouldTrigger) { 17933 return _this2.trigger(event); 17934 } 17935 }); 17936 }); 17937 }); 17938 } 17939 17940 /** 17941 * Emulate the native mediasource function, but our function will 17942 * send all of the proposed segments to the transmuxer so that we 17943 * can transmux them before we append them to our internal 17944 * native source buffers in the correct format. 17945 * 17946 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer 17947 * @param {Uint8Array} segment the segment to append to the buffer 17948 */ 17949 }, { 17950 key: 'appendBuffer', 17951 value: function appendBuffer(segment) { 17952 // Start the internal "updating" state 17953 this.bufferUpdating_ = true; 17954 17955 if (this.audioBuffer_ && this.audioBuffer_.buffered.length) { 17956 var audioBuffered = this.audioBuffer_.buffered; 17957 17958 this.transmuxer_.postMessage({ 17959 action: 'setAudioAppendStart', 17960 appendStart: audioBuffered.end(audioBuffered.length - 1) 17961 }); 17962 } 17963 17964 this.transmuxer_.postMessage({ 17965 action: 'push', 17966 // Send the typed-array of data as an ArrayBuffer so that 17967 // it can be sent as a "Transferable" and avoid the costly 17968 // memory copy 17969 data: segment.buffer, 17970 17971 // To recreate the original typed-array, we need information 17972 // about what portion of the ArrayBuffer it was a view into 17973 byteOffset: segment.byteOffset, 17974 byteLength: segment.byteLength 17975 }, [segment.buffer]); 17976 this.transmuxer_.postMessage({ action: 'flush' }); 17977 } 17978 17979 /** 17980 * Emulate the native mediasource function and remove parts 17981 * of the buffer from any of our internal buffers that exist 17982 * 17983 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove 17984 * @param {Double} start position to start the remove at 17985 * @param {Double} end position to end the remove at 17986 */ 17987 }, { 17988 key: 'remove', 17989 value: function remove(start, end) { 17990 if (this.videoBuffer_) { 17991 this.videoBuffer_.updating = true; 17992 this.videoBuffer_.remove(start, end); 17993 } 17994 if (!this.audioDisabled_ && this.audioBuffer_) { 17995 this.audioBuffer_.updating = true; 17996 this.audioBuffer_.remove(start, end); 17997 } 17998 17999 // Remove Metadata Cues (id3) 18000 (0, _removeCuesFromTrack2['default'])(start, end, this.metadataTrack_); 18001 18002 // Remove Any Captions 18003 (0, _removeCuesFromTrack2['default'])(start, end, this.inbandTextTrack_); 18004 } 18005 18006 /** 18007 * Process any segments that the muxer has output 18008 * Concatenate segments together based on type and append them into 18009 * their respective sourceBuffers 18010 * 18011 * @private 18012 */ 18013 }, { 18014 key: 'processPendingSegments_', 18015 value: function processPendingSegments_() { 18016 var sortedSegments = { 18017 video: { 18018 segments: [], 18019 bytes: 0 18020 }, 18021 audio: { 18022 segments: [], 18023 bytes: 0 18024 }, 18025 captions: [], 18026 metadata: [] 18027 }; 18028 18029 // Sort segments into separate video/audio arrays and 18030 // keep track of their total byte lengths 18031 sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) { 18032 var type = segment.type; 18033 var data = segment.data; 18034 var initSegment = segment.initSegment; 18035 18036 segmentObj[type].segments.push(data); 18037 segmentObj[type].bytes += data.byteLength; 18038 18039 segmentObj[type].initSegment = initSegment; 18040 18041 // Gather any captions into a single array 18042 if (segment.captions) { 18043 segmentObj.captions = segmentObj.captions.concat(segment.captions); 18044 } 18045 18046 if (segment.info) { 18047 segmentObj[type].info = segment.info; 18048 } 18049 18050 // Gather any metadata into a single array 18051 if (segment.metadata) { 18052 segmentObj.metadata = segmentObj.metadata.concat(segment.metadata); 18053 } 18054 18055 return segmentObj; 18056 }, sortedSegments); 18057 18058 // Create the real source buffers if they don't exist by now since we 18059 // finally are sure what tracks are contained in the source 18060 if (!this.videoBuffer_ && !this.audioBuffer_) { 18061 // Remove any codecs that may have been specified by default but 18062 // are no longer applicable now 18063 if (sortedSegments.video.bytes === 0) { 18064 this.videoCodec_ = null; 18065 } 18066 if (sortedSegments.audio.bytes === 0) { 18067 this.audioCodec_ = null; 18068 } 18069 18070 this.createRealSourceBuffers_(); 18071 } 18072 18073 if (sortedSegments.audio.info) { 18074 this.mediaSource_.trigger({ type: 'audioinfo', info: sortedSegments.audio.info }); 18075 } 18076 if (sortedSegments.video.info) { 18077 this.mediaSource_.trigger({ type: 'videoinfo', info: sortedSegments.video.info }); 18078 } 18079 18080 if (this.appendAudioInitSegment_) { 18081 if (!this.audioDisabled_ && this.audioBuffer_) { 18082 sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment); 18083 sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength; 18084 } 18085 this.appendAudioInitSegment_ = false;
18086 } 18087 18088 // Merge multiple video and audio segments into one and append 18089 if (this.videoBuffer_) { 18090 sortedSegments.video.segments.unshift(sortedSegments.video.initSegment); 18091 sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength; 18092 this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_); 18093 // TODO: are video tracks the only ones with text tracks? 18094 (0, _addTextTrackData.addTextTrackData)(this, sortedSegments.captions, sortedSegments.metadata); 18095 } 18096 18097 if (!this.audioDisabled_ && this.audioBuffer_) { 18098 this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_); 18099 } 18100 18101 this.pendingBuffers_.length = 0; 18102 18103 // We are no longer in the internal "updating" state 18104 this.bufferUpdating_ = false; 18105 } 18106 18107 /** 18108 * Combine all segments into a single Uint8Array and then append them 18109 * to the destination buffer 18110 * 18111 * @param {Object} segmentObj 18112 * @param {SourceBuffer} destinationBuffer native source buffer to append data to 18113 * @private 18114 */ 18115 }, { 18116 key: 'concatAndAppendSegments_', 18117 value: function concatAndAppendSegments_(segmentObj, destinationBuffer) { 18118 var offset = 0; 18119 var tempBuffer = undefined; 18120 18121 if (segmentObj.bytes) { 18122 tempBuffer = new Uint8Array(segmentObj.bytes); 18123 18124 // Combine the individual segments into one large typed-array 18125 segmentObj.segments.forEach(function (segment) { 18126 tempBuffer.set(segment, offset); 18127 offset += segment.byteLength; 18128 }); 18129 18130 try { 18131 destinationBuffer.updating = true; 18132 destinationBuffer.appendBuffer(tempBuffer); 18133 } catch (error) { 18134 if (this.mediaSource_.player_) { 18135 this.mediaSource_.player_.error({ 18136 code: -3, 18137 type: 'APPEND_BUFFER_ERR', 18138 message: error.message, 18139 originalError: error 18140 }); 18141 } 18142 } 18143 } 18144 } 18145 18146 /** 18147 * Emulate the native mediasource function. abort any soureBuffer 18148 * actions and throw out any un-appended data. 18149 * 18150 * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort 18151 */ 18152 }, { 18153 key: 'abort', 18154 value: function abort() { 18155 if (this.videoBuffer_) { 18156 this.videoBuffer_.abort(); 18157 } 18158 if (!this.audioDisabled_ && this.audioBuffer_) { 18159 this.audioBuffer_.abort(); 18160 } 18161 if (this.transmuxer_) { 18162 this.transmuxer_.postMessage({ action: 'reset' }); 18163 } 18164 this.pendingBuffers_.length = 0; 18165 this.bufferUpdating_ = false; 18166 } 18167 }]); 18168 18169 return VirtualSourceBuffer; 18170 })(_videoJs2['default'].EventTarget); 18171 18172 exports['default'] = VirtualSourceBuffer; 18173 module.exports = exports['default']; 18174 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 18175 },{"./add-text-track-data":62,"./codec-utils":64,"./create-text-tracks-if-necessary":65,"./remove-cues-from-track":71,"./transmuxer-worker":72,"webworkify":75}],75:[function(require,module,exports){ 18176 var bundleFn = arguments[3]; 18177 var sources = arguments[4]; 18178 var cache = arguments[5]; 18179 18180 var stringify = JSON.stringify; 18181 18182 module.exports = function (fn) { 18183 var keys = []; 18184 var wkey; 18185 var cacheKeys = Object.keys(cache); 18186 18187 for (var i = 0, l = cacheKeys.length; i < l; i++) { 18188 var key = cacheKeys[i]; 18189 if (cache[key].exports === fn) { 18190 wkey = key; 18191 break; 18192 } 18193 } 18194 18195 if (!wkey) { 18196 wkey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16); 18197 var wcache = {}; 18198 for (var i = 0, l = cacheKeys.length; i < l; i++) { 18199 var key = cacheKeys[i]; 18200 wcache[key] = key; 18201 } 18202 sources[wkey] = [ 18203 Function(['require','module','exports'], '(' + fn + ')(self)'), 18204 wcache 18205 ]; 18206 } 18207 var skey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16); 18208 18209 var scache = {}; scache[wkey] = wkey; 18210 sources[skey] = [ 18211 Function(['require'],'require(' + stringify(wkey) + ')(self)'), 18212 scache 18213 ]; 18214 18215 var src = '(' + bundleFn + ')({' 18216 + Object.keys(sources).map(function (key) { 18217 return stringify(key) + ':[' 18218 + sources[key][0] 18219 + ',' + stringify(sources[key][1]) + ']' 18220 ; 18221 }).join(',') 18222 + '},{},[' + stringify(skey) + '])' 18223 ; 18224 18225 var URL = window.URL || window.webkitURL || window.mozURL || window.msURL; 18226 18227 return new Worker(URL.createObjectURL( 18228 new Blob([src], { type: 'text/javascript' }) 18229 )); 18230 }; 18231 18232 },{}],76:[function(require,module,exports){ 18233 (function (global){ 18234 /** 18235 * @file videojs-contrib-hls.js 18236 * 18237 * The main file for the HLS project. 18238 * License: https://github.com/videojs/videojs-contrib-hls/blob/master/LICENSE 18239 */ 18240 'use strict'; 18241 18242 var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 18243 18244 var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 18245 18246 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 18247 18248 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 18249 18250 function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { c
18250onstructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 18251 18252 var _globalDocument = require('global/document'); 18253 18254 var _globalDocument2 = _interopRequireDefault(_globalDocument); 18255 18256 var _playlistLoader = require('./playlist-loader'); 18257 18258 var _playlistLoader2 = _interopRequireDefault(_playlistLoader); 18259 18260 var _playlist = require('./playlist'); 18261 18262 var _playlist2 = _interopRequireDefault(_playlist); 18263 18264 var _xhr = require('./xhr'); 18265 18266 var _xhr2 = _interopRequireDefault(_xhr); 18267 18268 var _aesDecrypter = require('aes-decrypter'); 18269 18270 var _binUtils = require('./bin-utils'); 18271 18272 var _binUtils2 = _interopRequireDefault(_binUtils); 18273 18274 var _videojsContribMediaSources = require('videojs-contrib-media-sources'); 18275 18276 var _m3u8Parser = require('m3u8-parser'); 18277 18278 var _m3u8Parser2 = _interopRequireDefault(_m3u8Parser); 18279 18280 var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null); 18281 18282 var _videoJs2 = _interopRequireDefault(_videoJs); 18283 18284 var _masterPlaylistController = require('./master-playlist-controller'); 18285 18286 var _config = require('./config'); 18287 18288 var _config2 = _interopRequireDefault(_config); 18289 18290 var _renditionMixin = require('./rendition-mixin'); 18291 18292 var _renditionMixin2 = _interopRequireDefault(_renditionMixin); 18293 18294 var _globalWindow = require('global/window'); 18295 18296 var _globalWindow2 = _interopRequireDefault(_globalWindow); 18297 18298 var _playbackWatcher = require('./playback-watcher'); 18299 18300 var _playbackWatcher2 = _interopRequireDefault(_playbackWatcher); 18301 18302 var _reloadSourceOnError = require('./reload-source-on-error'); 18303 18304 var _reloadSourceOnError2 = _interopRequireDefault(_reloadSourceOnError); 18305 18306 var Hls = { 18307 PlaylistLoader: _playlistLoader2['default'], 18308 Playlist: _playlist2['default'], 18309 Decrypter: _aesDecrypter.Decrypter, 18310 AsyncStream: _aesDecrypter.AsyncStream, 18311 decrypt: _aesDecrypter.decrypt, 18312 utils: _binUtils2['default'], 18313 xhr: (0, _xhr2['default'])() 18314 }; 18315 18316 Object.defineProperty(Hls, 'GOAL_BUFFER_LENGTH', { 18317 get: function get() { 18318 _videoJs2['default'].log.warn('using Hls.GOAL_BUFFER_LENGTH is UNSAFE be sure ' + 'you know what you are doing'); 18319 return _config2['default'].GOAL_BUFFER_LENGTH; 18320 }, 18321 set: function set(v) { 18322 _videoJs2['default'].log.warn('using Hls.GOAL_BUFFER_LENGTH is UNSAFE be sure ' + 'you know what you are doing'); 18323 if (typeof v !== 'number' || v <= 0) { 18324 _videoJs2['default'].log.warn('value passed to Hls.GOAL_BUFFER_LENGTH ' + 'must be a number and greater than 0'); 18325 return; 18326 } 18327 _config2['default'].GOAL_BUFFER_LENGTH = v; 18328 } 18329 }); 18330 18331 Object.defineProperty(Hls, 'BANDWIDTH_VARIANCE', { 18332 get: function get() { 18333 _videoJs2['default'].log.warn('using Hls.BANDWIDTH_VARIANCE is UNSAFE be sure ' + 'you know what you are doing'); 18334 return _config2['default'].BANDWIDTH_VARIANCE; 18335 }, 18336 set: function set(v) { 18337 _videoJs2['default'].log.warn('using Hls.BANDWIDTH_VARIANCE is UNSAFE be sure ' + 'you know what you are doing'); 18338 if (typeof v !== 'number' || v <= 0) { 18339 _videoJs2['default'].log.warn('value passed to Hls.BANDWIDTH_VARIANCE ' + 'must be a number and greater than 0'); 18340 return; 18341 } 18342 _config2['default'].BANDWIDTH_VARIANCE = v; 18343 } 18344 }); 18345 18346 /** 18347 * Returns the CSS value for the specified property on an element 18348 * using `getComputedStyle`. Firefox has a long-standing issue where 18349 * getComputedStyle() may return null when running in an iframe with 18350 * `display: none`. 18351 * 18352 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 18353 * @param {HTMLElement} el the htmlelement to work on 18354 * @param {string} the proprety to get the style for 18355 */ 18356 var safeGetComputedStyle = function safeGetComputedStyle(el, property) { 18357 var result = undefined; 18358 18359 if (!el) { 18360 return ''; 18361 } 18362 18363 result = _globalWindow2['default'].getComputedStyle(el); 18364 if (!result) { 18365 return ''; 18366 } 18367 18368 return result[property]; 18369 }; 18370 18371 /** 18372 * Updates the selectedIndex of the QualityLevelList when a mediachange happens in hls. 18373 * 18374 * @param {QualityLevelList} qualityLevels The QualityLevelList to update. 18375 * @param {PlaylistLoader} playlistLoader PlaylistLoader containing the new media info. 18376 * @function handleHlsMediaChange 18377 */ 18378 var handleHlsMediaChange = function handleHlsMediaChange(qualityLevels, playlistLoader) { 18379 var newPlaylist = playlistLoader.media(); 18380 var selectedIndex = -1; 18381 18382 for (var i = 0; i < qualityLevels.length; i++) { 18383 if (qualityLevels[i].id === newPlaylist.uri) { 18384 selectedIndex = i; 18385 break; 18386 } 18387 } 18388 18389 qualityLevels.selectedIndex_ = selectedIndex; 18390 qualityLevels.trigger({ 18391 selectedIndex: selectedIndex, 18392 type: 'change' 18393 }); 18394 }; 18395 18396 /** 18397 * Adds quality levels to list once playlist metadata is available 18398 * 18399 * @param {QualityLevelList} qualityLevels The QualityLevelList to attach events to. 18400 * @param {Object} hls Hls object to listen to for media events. 18401 * @function handleHlsLoadedMetadata 18402 */ 18403 var handleHlsLoadedMetadata = function handleHlsLoadedMetadata(qualityLevels, hls) {
18404 hls.representations().forEach(function (rep) { 18405 qualityLevels.addQualityLevel(rep); 18406 }); 18407 handleHlsMediaChange(qualityLevels, hls.playlists); 18408 }; 18409 18410 /** 18411 * Resuable stable sort function 18412 * 18413 * @param {Playlists} array 18414 * @param {Function} sortFn Different comparators 18415 * @function stableSort 18416 */ 18417 var stableSort = function stableSort(array, sortFn) { 18418 var newArray = array.slice(); 18419 18420 array.sort(function (left, right) { 18421 var cmp = sortFn(left, right); 18422 18423 if (cmp === 0) { 18424 return newArray.indexOf(left) - newArray.indexOf(right); 18425 } 18426 return cmp; 18427 }); 18428 }; 18429 18430 /** 18431 * Chooses the appropriate media playlist based on the current 18432 * bandwidth estimate and the player size. 18433 * 18434 * @return {Playlist} the highest bitrate playlist less than the currently detected 18435 * bandwidth, accounting for some amount of bandwidth variance 18436 */ 18437 Hls.STANDARD_PLAYLIST_SELECTOR = function () { 18438 var sortedPlaylists = this.playlists.master.playlists.slice(); 18439 var bandwidthPlaylists = []; 18440 var bandwidthBestVariant = undefined; 18441 var resolutionPlusOne = undefined; 18442 var resolutionBestVariant = undefined; 18443 var width = undefined; 18444 var height = undefined; 18445 var systemBandwidth = undefined; 18446 var haveResolution = undefined; 18447 var resolutionPlusOneList = []; 18448 var resolutionPlusOneSmallest = []; 18449 var resolutionBestVariantList = []; 18450 18451 stableSort(sortedPlaylists, Hls.comparePlaylistBandwidth); 18452 18453 // filter out any playlists that have been excluded due to 18454 // incompatible configurations or playback errors 18455 sortedPlaylists = sortedPlaylists.filter(_playlist2['default'].isEnabled); 18456 // filter out any variant that has greater effective bitrate 18457 // than the current estimated bandwidth 18458 systemBandwidth = this.systemBandwidth; 18459 bandwidthPlaylists = sortedPlaylists.filter(function (elem) { 18460 return elem.attributes && elem.attributes.BANDWIDTH && elem.attributes.BANDWIDTH * _config2['default'].BANDWIDTH_VARIANCE < systemBandwidth; 18461 }); 18462 18463 // get all of the renditions with the same (highest) bandwidth 18464 // and then taking the very first element 18465 bandwidthBestVariant = bandwidthPlaylists.filter(function (elem) { 18466 return elem.attributes.BANDWIDTH === bandwidthPlaylists[bandwidthPlaylists.length - 1].attributes.BANDWIDTH; 18467 })[0]; 18468 18469 // sort variants by resolution 18470 stableSort(bandwidthPlaylists, Hls.comparePlaylistResolution); 18471 18472 width = parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10); 18473 height = parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10); 18474 18475 // filter out playlists without resolution information 18476 haveResolution = bandwidthPlaylists.filter(function (elem) { 18477 return elem.attributes && elem.attributes.RESOLUTION && elem.attributes.RESOLUTION.width && elem.attributes.RESOLUTION.height; 18478 }); 18479 18480 // if we have the exact resolution as the player use it 18481 resolutionBestVariantList = haveResolution.filter(function (elem) { 18482 return elem.attributes.RESOLUTION.width === width && elem.attributes.RESOLUTION.height === height; 18483 }); 18484 // ensure that we pick the highest bandwidth variant that have exact resolution 18485 resolutionBestVariant = resolutionBestVariantList.filter(function (elem) { 18486 return elem.attributes.BANDWIDTH === resolutionBestVariantList[resolutionBestVariantList.length - 1].attributes.BANDWIDTH; 18487 })[0]; 18488 18489 // find the smallest variant that is larger than the player 18490 // if there is no match of exact resolution 18491 if (!resolutionBestVariant) { 18492 resolutionPlusOneList = haveResolution.filter(function (elem) { 18493 return elem.attributes.RESOLUTION.width > width || elem.attributes.RESOLUTION.height > height; 18494 }); 18495 // find all the variants have the same smallest resolution 18496 resolutionPlusOneSmallest = resolutionPlusOneList.filter(function (elem) { 18497 return elem.attributes.RESOLUTION.width === resolutionPlusOneList[0].attributes.RESOLUTION.width && elem.attributes.RESOLUTION.height === resolutionPlusOneList[0].attributes.RESOLUTION.height; 18498 }); 18499 // ensure that we also pick the highest bandwidth variant that 18500 // is just-larger-than the video player 18501 resolutionPlusOne = resolutionPlusOneSmallest.filter(function (elem) { 18502 return elem.attributes.BANDWIDTH === resolutionPlusOneSmallest[resolutionPlusOneSmallest.length - 1].attributes.BANDWIDTH; 18503 })[0]; 18504 } 18505 18506 // fallback chain of variants 18507 return resolutionPlusOne || resolutionBestVariant || bandwidthBestVariant || sortedPlaylists[0]; 18508 }; 18509 18510// HLS is a source handler, not a tech. Make sure attempts to use it 18511// as one do not cause exceptions.
18512 Hls.canPlaySource = function () { 18513 return _videoJs2['default'].log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 18514 }; 18515 18516 /** 18517 * Whether the browser has built-in HLS support. 18518 */ 18519 Hls.supportsNativeHls = (function () { 18520 var video = _globalDocument2['default'].createElement('video'); 18521 18522 // native HLS is definitely not supported if HTML5 video isn't 18523 if (!_videoJs2['default'].getTech('Html5').isSupported()) { 18524 return false; 18525 } 18526 18527 // HLS manifests can go by many mime-types 18528 var canPlay = [ 18529 // Apple santioned 18530 'application/vnd.apple.mpegurl', 18531 // Apple sanctioned for backwards compatibility 18532 'audio/mpegurl', 18533 // Very common 18534 'audio/x-mpegurl', 18535 // Very common 18536 'application/x-mpegurl',
18537 // Included for completeness 18538 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl']; 18539 18540 return canPlay.some(function (canItPlay) { 18541 return (/maybe|probably/i.test(video.canPlayType(canItPlay)) 18542 ); 18543 }); 18544 })(); 18545 18546 /** 18547 * HLS is a source handler, not a tech. Make sure attempts to use it 18548 * as one do not cause exceptions. 18549 */ 18550 Hls.isSupported = function () { 18551 return _videoJs2['default'].log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.'); 18552 }; 18553 18554 var USER_AGENT = _globalWindow2['default'].navigator && _globalWindow2['default'].navigator.userAgent || ''; 18555 18556 /** 18557 * Determines whether the browser supports a change in the audio configuration 18558 * during playback. Currently only Firefox 48 and below do not support this. 18559 * window.isSecureContext is a propterty that was added to window in firefox 49, 18560 * so we can use it to detect Firefox 49+. 18561 * 18562 * @return {Boolean} Whether the browser supports audio config change during playback 18563 */ 18564 Hls.supportsAudioInfoChange_ = function () { 18565 if (_videoJs2['default'].browser.IS_FIREFOX) { 18566 var firefoxVersionMap = /Firefox\/([\d.]+)/i.exec(USER_AGENT); 18567 var version = parseInt(firefoxVersionMap[1], 10); 18568 18569 return version >= 49; 18570 } 18571 return true; 18572 }; 18573 18574 var Component = _videoJs2['default'].getComponent('Component'); 18575 18576 /** 18577 * The Hls Handler object, where we orchestrate all of the parts 18578 * of HLS to interact with video.js 18579 * 18580 * @class HlsHandler 18581 * @extends videojs.Component 18582 * @param {Object} source the soruce object 18583 * @param {Tech} tech the parent tech object 18584 * @param {Object} options optional and required options 18585 */ 18586 18587 var HlsHandler = (function (_Component) { 18588 _inherits(HlsHandler, _Component); 18589 18590 function HlsHandler(source, tech, options) { 18591 var _this = this; 18592 18593 _classCallCheck(this, HlsHandler); 18594 18595 _get(Object.getPrototypeOf(HlsHandler.prototype), 'constructor', this).call(this, tech); 18596 18597 // tech.player() is deprecated but setup a reference to HLS for 18598 // backwards-compatibility 18599 if (tech.options_ && tech.options_.playerId) { 18600 var _player = (0, _videoJs2['default'])(tech.options_.playerId); 18601 18602 if (!_player.hasOwnProperty('hls')) { 18603 Object.defineProperty(_player, 'hls', { 18604 get: function get() { 18605 _videoJs2['default'].log.warn('player.hls is deprecated. Use player.tech_.hls instead.'); 18606 return _this; 18607 } 18608 }); 18609 } 18610 } 18611 18612 // overriding native HLS only works if audio tracks have been emulated 18613 // error early if we're misconfigured: 18614 if (_videoJs2['default'].options.hls.overrideNative && (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) { 18615 throw new Error('Overriding native HLS requires emulated tracks. ' + 'See https://git.io/vMpjB'); 18616 } 18617 18618 this.tech_ = tech; 18619 this.source_ = source; 18620 this.stats = {}; 18621 this.ignoreNextSeekingEvent_ = false; 18622 18623 // handle global & Source Handler level options 18624 this.options_ = _videoJs2['default'].mergeOptions(_videoJs2['default'].options.hls || {}, options.hls); 18625 this.setOptions_(); 18626 18627 // listen for fullscreenchange events for this player so that we 18628 // can adjust our quality selection quickly 18629 this.on(_globalDocument2['default'], ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) { 18630 var fullscreenElement = _globalDocument2['default'].fullscreenElement || _globalDocument2['default'].webkitFullscreenElement || _globalDocument2['default'].mozFullScreenElement || _globalDocument2['default'].msFullscreenElement; 18631 18632 if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) { 18633 _this.masterPlaylistController_.fastQualityChange_(); 18634 } 18635 }); 18636 18637 this.on(this.tech_, 'seeking', function () { 18638 if (this.ignoreNextSeekingEvent_) { 18639 this.ignoreNextSeekingEvent_ = false;
18640 return; 18641 } 18642 18643 this.setCurrentTime(this.tech_.currentTime()); 18644 }); 18645 this.on(this.tech_, 'error', function () { 18646 if (this.masterPlaylistController_) { 18647 this.masterPlaylistController_.pauseLoading(); 18648 } 18649 }); 18650 18651 this.audioTrackChange_ = function () { 18652 _this.masterPlaylistController_.setupAudio(); 18653 }; 18654 18655 this.textTrackChange_ = function () { 18656 _this.masterPlaylistController_.setupSubtitles(); 18657 }; 18658 18659 this.on(this.tech_, 'play', this.play); 18660 } 18661 18662 /** 18663 * The Source Handler object, which informs video.js what additional 18664 * MIME types are supported and sets up playback. It is registered 18665 * automatically to the appropriate tech based on the capabilities of 18666 * the browser it is running in. It is not necessary to use or modify 18667 * this object in normal usage. 18668 */ 18669 18670 _createClass(HlsHandler, [{ 18671 key: 'setOptions_', 18672 value: function setOptions_() { 18673 var _this2 = this; 18674 18675 // defaults 18676 this.options_.withCredentials = this.options_.withCredentials || false; 18677 18678 if (typeof this.options_.blacklistDuration !== 'number') { 18679 this.options_.blacklistDuration = 5 * 60; 18680 } 18681 18682 // start playlist selection at a reasonable bandwidth for 18683 // broadband internet 18684 // 0.5 MB/s 18685 if (typeof this.options_.bandwidth !== 'number') { 18686 this.options_.bandwidth = 4194304; 18687 } 18688 18689 // grab options passed to player.src 18690 ['withCredentials', 'bandwidth'].forEach(function (option) { 18691 if (typeof _this2.source_[option] !== 'undefined') { 18692 _this2.options_[option] = _this2.source_[option]; 18693 } 18694 }); 18695 18696 this.bandwidth = this.options_.bandwidth; 18697 } 18698 18699 /** 18700 * called when player.src gets called, handle a new source 18701 * 18702 * @param {Object} src the source object to handle 18703 */ 18704 }, { 18705 key: 'src', 18706 value: function src(_src) { 18707 var _this3 = this; 18708 18709 // do nothing if the src is falsey 18710 if (!_src) { 18711 return; 18712 } 18713 this.setOptions_(); 18714 // add master playlist controller options 18715 this.options_.url = this.source_.src; 18716 this.options_.tech = this.tech_; 18717 this.options_.externHls = Hls; 18718 18719 this.masterPlaylistController_ = new _masterPlaylistController.MasterPlaylistController(this.options_); 18720 this.playbackWatcher_ = new _playbackWatcher2['default'](_videoJs2['default'].mergeOptions(this.options_, { 18721 seekable: function seekable() { 18722 return _this3.seekable(); 18723 } 18724 })); 18725 18726 this.masterPlaylistController_.on('error', function () { 18727 var player = _videoJs2['default'].players[_this3.tech_.options_.playerId]; 18728 18729 player.error(_this3.masterPlaylistController_.error); 18730 }); 18731 18732 // `this` in selectPlaylist should be the HlsHandler for backwards 18733 // compatibility with < v2 18734 this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.selectPlaylist.bind(this) : Hls.STANDARD_PLAYLIST_SELECTOR.bind(this); 18735 18736 // re-expose some internal objects for backwards compatibility with < v2 18737 this.playlists = this.masterPlaylistController_.masterPlaylistLoader_; 18738 this.mediaSource = this.masterPlaylistController_.mediaSource; 18739 18740 // Proxy assignment of some properties to the master playlist 18741 // controller. Using a custom property for backwards compatibility 18742 // with < v2 18743 Object.defineProperties(this, { 18744 selectPlaylist: { 18745 get: function get() { 18746 return this.masterPlaylistController_.selectPlaylist; 18747 }, 18748 set: function set(selectPlaylist) { 18749 this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this); 18750 } 18751 }, 18752 throughput: { 18753 get: function get() { 18754 return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate; 18755 }, 18756 set: function set(throughput) { 18757 this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; 18758 // By setting `count` to 1 the throughput value becomes the starting value 18759 // for the cumulative average 18760 this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1; 18761 } 18762 }, 18763 bandwidth: { 18764 get: function get() { 18765 return this.masterPlaylistController_.mainSegmentLoader_.bandwidth; 18766 }, 18767 set: function set(bandwidth) { 18768 this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; 18769 // setting the bandwidth manually resets the throughput counter 18770 // `count` is set to zero that current value of `rate` isn't included 18771 // in the cumulative average 18772 this.masterPlaylistController_.mainSegmentLoader_.throughput = { rate: 0, count: 0 }; 18773 } 18774 }, 18775 /** 18776 * `systemBandwidth` is a combination of two serial processes bit-rates. The first 18777 * is the network bitrate provided by `bandwidth` and the second is the bitrate of 18778 * the entire process after that - decryption, transmuxing, and appending - provided 18779 * by `throughput`. 18780 * 18781 * Since the two process are serial, the overall system bandwidth is given by: 18782 * sysBandwidth = 1 / (1 / bandwidth + 1 / throughput) 18783 */ 18784 systemBandwidth: { 18785 get: function get() { 18786 var invBandwidth = 1 / (this.bandwidth || 1); 18787 var invThroughput = undefined; 18788 18789 if (this.throughput > 0) { 18790 invThroughput = 1 / this.throughput; 18791 } else { 18792 invThroughput = 0; 18793 } 18794 18795 var systemBitrate = Math.floor(1 / (invBandwidth + invThroughput)); 18796 18797 return systemBitrate; 18798 }, 18799 set: function set() { 18800 _videoJs2['default'].log.error('The "systemBandwidth" property is read-only'); 18801 } 18802 } 18803 }); 18804 18805 Object.defineProperties(this.stats, { 18806 bandwidth: { 18807 get: function get() { 18808 return _this3.bandwidth || 0; 18809 }, 18810 enumerable: true 18811 }, 18812 mediaRequests: { 18813 get: function get() { 18814 return _this3.masterPlaylistController_.mediaRequests_() || 0; 18815 }, 18816 enumerable: true 18817 }, 18818 mediaRequestsAborted: { 18819 get: function get() { 18820 return _this3.masterPlaylistController_.mediaRequestsAborted_() || 0; 18821 }, 18822 enumerable: true 18823 }, 18824 mediaRequestsTimedout: { 18825 get: function get() { 18826 return _this3.masterPlaylistController_.mediaRequestsTimedout_() || 0; 18827 }, 18828 enumerable: true 18829 }, 18830 mediaRequestsErrored: { 18831 get: function get() { 18832 return _this3.masterPlaylistController_.mediaRequestsErrored_() || 0; 18833 }, 18834 enumerable: true 18835 }, 18836 mediaTransferDuration: { 18837 get: function get() { 18838 return _this3.masterPlaylistController_.mediaTransferDuration_() || 0; 18839 }, 18840 enumerable: true 18841 }, 18842 mediaBytesTransferred: { 18843 get: function get() { 18844 return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0; 18845 }, 18846 enumerable: true 18847 }, 18848 mediaSecondsLoaded: { 18849 get: function get() { 18850 return _this3.masterPlaylistController_.mediaSecondsLoaded_() || 0; 18851 }, 18852 enumerable: true 18853 } 18854 }); 18855 18856 this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_)); 18857 18858 this.masterPlaylistController_.on('sourceopen', function () { 18859 _this3.tech_.audioTracks().addEventListener('change', _this3.audioTrackChange_); 18860 _this3.tech_.remoteTextTracks().addEventListener('change', _this3.textTrackChange_); 18861 }); 18862 18863 this.masterPlaylistController_.on('selectedinitialmedia', function () { 18864 // Add the manual rendition mix-in to HlsHandler 18865 (0, _renditionMixin2['default'])(_this3); 18866 }); 18867 18868 this.masterPlaylistController_.on('audioupdate', function () { 18869 // clear current audioTracks 18870 _this3.tech_.clearTracks('audio'); 18871 _this3.masterPlaylistController_.activeAudioGroup().forEac
18871h(function (audioTrack) { 18872 _this3.tech_.audioTracks().addTrack(audioTrack); 18873 }); 18874 }); 18875 18876 // the bandwidth of the primary segment loader is our best 18877 // estimate of overall bandwidth 18878 this.on(this.masterPlaylistController_, 'progress', function () { 18879 this.tech_.trigger('progress'); 18880 }); 18881 18882 // In the live case, we need to ignore the very first `seeking` event since 18883 // that will be the result of the seek-to-live behavior 18884 this.on(this.masterPlaylistController_, 'firstplay', function () { 18885 this.ignoreNextSeekingEvent_ = true; 18886 }); 18887 18888 this.tech_.ready(function () { 18889 return _this3.setupQualityLevels_(); 18890 }); 18891 18892 // do nothing if the tech has been disposed already 18893 // this can occur if someone sets the src in player.ready(), for instance 18894 if (!this.tech_.el()) { 18895 return; 18896 } 18897 18898 this.tech_.src(_videoJs2['default'].URL.createObjectURL(this.masterPlaylistController_.mediaSource)); 18899 } 18900 18901 /** 18902 * Initializes the quality levels and sets listeners to update them. 18903 * 18904 * @method setupQualityLevels_ 18905 * @private 18906 */ 18907 }, { 18908 key: 'setupQualityLevels_', 18909 value: function setupQualityLevels_() { 18910 var _this4 = this; 18911 18912 var player = _videoJs2['default'].players[this.tech_.options_.playerId]; 18913 18914 if (player && player.qualityLevels) { 18915 this.qualityLevels_ = player.qualityLevels(); 18916 18917 this.masterPlaylistController_.on('selectedinitialmedia', function () { 18918 handleHlsLoadedMetadata(_this4.qualityLevels_, _this4); 18919 }); 18920 18921 this.playlists.on('mediachange', function () { 18922 handleHlsMediaChange(_this4.qualityLevels_, _this4.playlists); 18923 }); 18924 } 18925 } 18926 18927 /** 18928 * a helper for grabbing the active audio group from MasterPlaylistController 18929 * 18930 * @private 18931 */ 18932 }, { 18933 key: 'activeAudioGroup_', 18934 value: function activeAudioGroup_() { 18935 return this.masterPlaylistController_.activeAudioGroup(); 18936 } 18937 18938 /** 18939 * Begin playing the video. 18940 */ 18941 }, { 18942 key: 'play', 18943 value: function play() { 18944 this.masterPlaylistController_.play(); 18945 } 18946 18947 /** 18948 * a wrapper around the function in MasterPlaylistController 18949 */ 18950 }, { 18951 key: 'setCurrentTime', 18952 value: function setCurrentTime(currentTime) { 18953 this.masterPlaylistController_.setCurrentTime(currentTime); 18954 } 18955 18956 /** 18957 * a wrapper around the function in MasterPlaylistController 18958 */ 18959 }, { 18960 key: 'duration', 18961 value: function duration() { 18962 return this.masterPlaylistController_.duration(); 18963 } 18964 18965 /** 18966 * a wrapper around the function in MasterPlaylistController 18967 */ 18968 }, { 18969 key: 'seekable', 18970 value: function seekable() { 18971 return this.masterPlaylistController_.seekable(); 18972 } 18973 18974 /** 18975 * Abort all outstanding work and cleanup. 18976 */ 18977 }, { 18978 key: 'dispose', 18979 value: function dispose() { 18980 if (this.playbackWatcher_) { 18981 this.playbackWatcher_.dispose(); 18982 } 18983 if (this.masterPlaylistController_) { 18984 this.masterPlaylistController_.dispose(); 18985 }
18986 if (this.qualityLevels_) { 18987 this.qualityLevels_.dispose(); 18988 } 18989 this.tech_.audioTracks().removeEventListener('change', this.audioTrackChange_); 18990 this.tech_.remoteTextTracks().removeEventListener('change', this.textTrackChange_); 18991 _get(Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this); 18992 } 18993 }]); 18994 18995 return HlsHandler; 18996 })(Component); 18997 18998 var HlsSourceHandler = function HlsSourceHandler(mode) { 18999 return { 19000 canHandleSource: function canHandleSource(srcObj) { 19001 // this forces video.js to skip this tech/mode if its not the one we have been 19002 // overriden to use, by returing that we cannot handle the source. 19003 if (_videoJs2['default'].options.hls && _videoJs2['default'].options.hls.mode && _videoJs2['default'].options.hls.mode !== mode) { 19004 return false; 19005 } 19006 return HlsSourceHandler.canPlayType(srcObj.type); 19007 }, 19008 handleSource: function handleSource(source, tech, options) { 19009 if (mode === 'flash') { 19010 // We need to trigger this asynchronously to give others the chance 19011 // to bind to the event when a source is set at player creation 19012 tech.setTimeout(function () { 19013 tech.trigger('loadstart'); 19014 }, 1); 19015 } 19016 19017 var settings = _videoJs2['default'].mergeOptions(options, { hls: { mode: mode } }); 19018 19019 tech.hls = new HlsHandler(source, tech, settings); 19020 tech.hls.xhr = (0, _xhr2['default'])(); 19021 19022 tech.hls.src(source.src); 19023 return tech.hls; 19024 }, 19025 canPlayType: function canPlayType(type) { 19026 if (HlsSourceHandler.canPlayType(type)) { 19027 return 'maybe'; 19028 } 19029 return ''; 19030 } 19031 }; 19032 }; 19033 19034 /** 19035 * A comparator function to sort two playlist object by bandwidth. 19036 * 19037 * @param {Object} left a media playlist object 19038 * @param {Object} right a media playlist object 19039 * @return {Number} Greater than zero if the bandwidth attribute of 19040 * left is greater than the corresponding attribute of right. Less 19041 * than zero if the bandwidth of right is greater than left and 19042 * exactly zero if the two are equal. 19043 */ 19044 Hls.comparePlaylistBandwidth = function (left, right) { 19045 var leftBandwidth = undefined; 19046 var rightBandwidth = undefined; 19047 19048 if (left.attributes && left.attributes.BANDWIDTH) { 19049 leftBandwidth = left.attributes.BANDWIDTH; 19050 } 19051 leftBandwidth = leftBandwidth || _globalWindow2['default'].Number.MAX_VALUE; 19052 if (right.attributes && right.attributes.BANDWIDTH) { 19053 rightBandwidth = right.attributes.BANDWIDTH; 19054 } 19055 rightBandwidth = rightBandwidth || _globalWindow2['default'].Number.MAX_VALUE; 19056 19057 return leftBandwidth - rightBandwidth; 19058 }; 19059 19060 /** 19061 * A comparator function to sort two playlist object by resolution (width). 19062 * @param {Object} left a media playlist object 19063 * @param {Object} right a media playlist object 19064 * @return {Number} Greater than zero if the resolution.width attribute of 19065 * left is greater than the corresponding attribute of right. Less 19066 * than zero if the resolution.width of right is greater than left and 19067 * exactly zero if the two are equal. 19068 */
19069 Hls.comparePlaylistResolution = function (left, right) { 19070 var leftWidth = undefined; 19071 var rightWidth = undefined; 19072 19073 if (left.attributes && left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) { 19074 leftWidth = left.attributes.RESOLUTION.width; 19075 } 19076 19077 leftWidth = leftWidth || _globalWindow2['default'].Number.MAX_VALUE; 19078 19079 if (right.attributes && right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) { 19080 rightWidth = right.attributes.RESOLUTION.width; 19081 } 19082 19083 rightWidth = rightWidth || _globalWindow2['default'].Number.MAX_VALUE; 19084 19085 // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions 19086 // have the same media dimensions/ resolution 19087 if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) { 19088 return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH; 19089 } 19090 return leftWidth - rightWidth; 19091 }; 19092 19093 HlsSourceHandler.canPlayType = function (type) { 19094 // No support for IE 10 or below 19095 if (_videoJs2['default'].browser.IE_VERSION && _videoJs2['default'].browser.IE_VERSION <= 10) { 19096 return false; 19097 } 19098 19099 var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i; 19100 19101 // favor native HLS support if it's available 19102 if (!_videoJs2['default'].options.hls.overrideNative && Hls.supportsNativeHls) { 19103 return false; 19104 } 19105 return mpegurlRE.test(type); 19106 }; 19107 19108 if (typeof _videoJs2['default'].MediaSource === 'undefined' || typeof _videoJs2['default'].URL === 'undefined') { 19109 _videoJs2['default'].MediaSource = _videojsContribMediaSources.MediaSource; 19110 _videoJs2['default'].URL = _videojsContribMediaSources.URL; 19111 } 19112 19113 var flashTech = _videoJs2['default'].getTech('Flash'); 19114 19115// register source handlers with the appropriate techs 19116 if (_videojsContribMediaSources.MediaSource.supportsNativeMediaSources()) { 19117 _videoJs2['default'].getTech('Html5').registerSourceHandler(HlsSourceHandler('html5'), 0); 19118 } 19119 if (_globalWindow2['default'].Uint8Array && flashTech) { 19120 flashTech.registerSourceHandler(HlsSourceHandler('flash')); 19121 } 19122 19123 _videoJs2['default'].HlsHandler = HlsHandler; 19124 _videoJs2['default'].HlsSourceHandler = HlsSourceHandler; 19125 _videoJs2['default'].Hls = Hls; 19126 if (!_videoJs2['default'].use) { 19127 _videoJs2['default'].registerComponent('Hls', Hls); 19128 } 19129 _videoJs2['default'].m3u8 = _m3u8Parser2['default']; 19130 _videoJs2['default'].options.hls = _videoJs2['default'].options.hls || {}; 19131 19132 if (_videoJs2['default'].registerPlugin) { 19133 _videoJs2['default'].registerPlugin('reloadSourceOnError', _reloadSourceOnError2['default']); 19134 } else { 19135 _videoJs2['default'].plugin('reloadSourceOnError', _reloadSourceOnError2['default']); 19136 } 19137 19138 module.exports = { 19139 Hls: Hls, 19140 HlsHandler: HlsHandler, 19141 HlsSourceHandler: HlsSourceHandler 19142 }; 19143 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 19144 },{"./bin-utils":2,"./config":3,"./master-playlist-controller":5,"./playback-watcher":7,"./playlist":9,"./playlist-loader":8,"./reload-source-on-error":11,"./rendition-mixin":12,"./xhr":19,"aes-decrypter":23,"global/document":29,"global/window":30,"m3u8-parser":31,"videojs-contrib-media-sources":73}]},{},[76])(76) 19145});
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.