/**
* @license
- * Video.js 7.17.0 <http://videojs.com/>
+ * Video.js 7.18.1 <http://videojs.com/>
* Copyright Brightcove, Inc. <https://www.brightcove.com/>
* Available under Apache License Version 2.0
* <https://github.com/videojs/video.js/blob/main/LICENSE>
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojs = factory());
}(this, (function () { 'use strict';
- var version$5 = "7.17.0";
+ var version$5 = "7.18.1";
/**
* An Object that contains lifecycle hooks as keys which point to an array
var Set = window.Set ? window.Set : SetSham;
+ var keycode = createCommonjsModule(function (module, exports) {
+ // Source: http://jsfiddle.net/vWx8V/
+ // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes
+
+ /**
+ * Conenience method returns corresponding value for given keyName or keyCode.
+ *
+ * @param {Mixed} keyCode {Number} or keyName {String}
+ * @return {Mixed}
+ * @api public
+ */
+ function keyCode(searchInput) {
+ // Keyboard Events
+ if (searchInput && 'object' === typeof searchInput) {
+ var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode;
+ if (hasKeyCode) searchInput = hasKeyCode;
+ } // Numbers
+
+
+ if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string)
+
+ var search = String(searchInput); // check codes
+
+ var foundNamedKey = codes[search.toLowerCase()];
+ if (foundNamedKey) return foundNamedKey; // check aliases
+
+ var foundNamedKey = aliases[search.toLowerCase()];
+ if (foundNamedKey) return foundNamedKey; // weird character?
+
+ if (search.length === 1) return search.charCodeAt(0);
+ return undefined;
+ }
+ /**
+ * Compares a keyboard event with a given keyCode or keyName.
+ *
+ * @param {Event} event Keyboard event that should be tested
+ * @param {Mixed} keyCode {Number} or keyName {String}
+ * @return {Boolean}
+ * @api public
+ */
+
+
+ keyCode.isEventKey = function isEventKey(event, nameOrCode) {
+ if (event && 'object' === typeof event) {
+ var keyCode = event.which || event.keyCode || event.charCode;
+
+ if (keyCode === null || keyCode === undefined) {
+ return false;
+ }
+
+ if (typeof nameOrCode === 'string') {
+ // check codes
+ var foundNamedKey = codes[nameOrCode.toLowerCase()];
+
+ if (foundNamedKey) {
+ return foundNamedKey === keyCode;
+ } // check aliases
+
+
+ var foundNamedKey = aliases[nameOrCode.toLowerCase()];
+
+ if (foundNamedKey) {
+ return foundNamedKey === keyCode;
+ }
+ } else if (typeof nameOrCode === 'number') {
+ return nameOrCode === keyCode;
+ }
+
+ return false;
+ }
+ };
+
+ exports = module.exports = keyCode;
+ /**
+ * Get by name
+ *
+ * exports.code['enter'] // => 13
+ */
+
+ var codes = exports.code = exports.codes = {
+ 'backspace': 8,
+ 'tab': 9,
+ 'enter': 13,
+ 'shift': 16,
+ 'ctrl': 17,
+ 'alt': 18,
+ 'pause/break': 19,
+ 'caps lock': 20,
+ 'esc': 27,
+ 'space': 32,
+ 'page up': 33,
+ 'page down': 34,
+ 'end': 35,
+ 'home': 36,
+ 'left': 37,
+ 'up': 38,
+ 'right': 39,
+ 'down': 40,
+ 'insert': 45,
+ 'delete': 46,
+ 'command': 91,
+ 'left command': 91,
+ 'right command': 93,
+ 'numpad *': 106,
+ 'numpad +': 107,
+ 'numpad -': 109,
+ 'numpad .': 110,
+ 'numpad /': 111,
+ 'num lock': 144,
+ 'scroll lock': 145,
+ 'my computer': 182,
+ 'my calculator': 183,
+ ';': 186,
+ '=': 187,
+ ',': 188,
+ '-': 189,
+ '.': 190,
+ '/': 191,
+ '`': 192,
+ '[': 219,
+ '\\': 220,
+ ']': 221,
+ "'": 222
+ }; // Helper aliases
+
+ var aliases = exports.aliases = {
+ 'windows': 91,
+ '⇧': 16,
+ '⌥': 18,
+ '⌃': 17,
+ '⌘': 91,
+ 'ctl': 17,
+ 'control': 17,
+ 'option': 18,
+ 'pause': 19,
+ 'break': 19,
+ 'caps': 20,
+ 'return': 13,
+ 'escape': 27,
+ 'spc': 32,
+ 'spacebar': 32,
+ 'pgup': 33,
+ 'pgdn': 34,
+ 'ins': 45,
+ 'del': 46,
+ 'cmd': 91
+ };
+ /*!
+ * Programatically add the following
+ */
+ // lower case chars
+
+ for (i = 97; i < 123; i++) {
+ codes[String.fromCharCode(i)] = i - 32;
+ } // numbers
+
+
+ for (var i = 48; i < 58; i++) {
+ codes[i - 48] = i;
+ } // function keys
+
+
+ for (i = 1; i < 13; i++) {
+ codes['f' + i] = i + 111;
+ } // numpad keys
+
+
+ for (i = 0; i < 10; i++) {
+ codes['numpad ' + i] = i + 96;
+ }
+ /**
+ * Get by code
+ *
+ * exports.name[13] // => 'Enter'
+ */
+
+
+ var names = exports.names = exports.title = {}; // title for backward compat
+ // Create reverse mapping
+
+ for (i in codes) {
+ names[codes[i]] = i;
+ } // Add aliases
+
+
+ for (var alias in aliases) {
+ codes[alias] = aliases[alias];
+ }
+ });
+ keycode.code;
+ keycode.codes;
+ keycode.aliases;
+ keycode.names;
+ keycode.title;
+
/**
* Player Component - Base class for all UI objects
*
_proto.handleKeyDown = function handleKeyDown(event) {
if (this.player_) {
// We only stop propagation here because we want unhandled events to fall
- // back to the browser.
- event.stopPropagation();
+ // back to the browser. Exclude Tab for focus trapping.
+ if (!keycode.isEventKey(event, 'Tab')) {
+ event.stopPropagation();
+ }
+
this.player_.handleKeyDown(event);
}
}
trackToJson_: trackToJson_
};
- var keycode = createCommonjsModule(function (module, exports) {
- // Source: http://jsfiddle.net/vWx8V/
- // http://stackoverflow.com/questions/5603195/full-list-of-javascript-keycodes
-
- /**
- * Conenience method returns corresponding value for given keyName or keyCode.
- *
- * @param {Mixed} keyCode {Number} or keyName {String}
- * @return {Mixed}
- * @api public
- */
- function keyCode(searchInput) {
- // Keyboard Events
- if (searchInput && 'object' === typeof searchInput) {
- var hasKeyCode = searchInput.which || searchInput.keyCode || searchInput.charCode;
- if (hasKeyCode) searchInput = hasKeyCode;
- } // Numbers
-
-
- if ('number' === typeof searchInput) return names[searchInput]; // Everything else (cast to string)
-
- var search = String(searchInput); // check codes
-
- var foundNamedKey = codes[search.toLowerCase()];
- if (foundNamedKey) return foundNamedKey; // check aliases
-
- var foundNamedKey = aliases[search.toLowerCase()];
- if (foundNamedKey) return foundNamedKey; // weird character?
-
- if (search.length === 1) return search.charCodeAt(0);
- return undefined;
- }
- /**
- * Compares a keyboard event with a given keyCode or keyName.
- *
- * @param {Event} event Keyboard event that should be tested
- * @param {Mixed} keyCode {Number} or keyName {String}
- * @return {Boolean}
- * @api public
- */
-
-
- keyCode.isEventKey = function isEventKey(event, nameOrCode) {
- if (event && 'object' === typeof event) {
- var keyCode = event.which || event.keyCode || event.charCode;
-
- if (keyCode === null || keyCode === undefined) {
- return false;
- }
-
- if (typeof nameOrCode === 'string') {
- // check codes
- var foundNamedKey = codes[nameOrCode.toLowerCase()];
-
- if (foundNamedKey) {
- return foundNamedKey === keyCode;
- } // check aliases
-
-
- var foundNamedKey = aliases[nameOrCode.toLowerCase()];
-
- if (foundNamedKey) {
- return foundNamedKey === keyCode;
- }
- } else if (typeof nameOrCode === 'number') {
- return nameOrCode === keyCode;
- }
-
- return false;
- }
- };
-
- exports = module.exports = keyCode;
- /**
- * Get by name
- *
- * exports.code['enter'] // => 13
- */
-
- var codes = exports.code = exports.codes = {
- 'backspace': 8,
- 'tab': 9,
- 'enter': 13,
- 'shift': 16,
- 'ctrl': 17,
- 'alt': 18,
- 'pause/break': 19,
- 'caps lock': 20,
- 'esc': 27,
- 'space': 32,
- 'page up': 33,
- 'page down': 34,
- 'end': 35,
- 'home': 36,
- 'left': 37,
- 'up': 38,
- 'right': 39,
- 'down': 40,
- 'insert': 45,
- 'delete': 46,
- 'command': 91,
- 'left command': 91,
- 'right command': 93,
- 'numpad *': 106,
- 'numpad +': 107,
- 'numpad -': 109,
- 'numpad .': 110,
- 'numpad /': 111,
- 'num lock': 144,
- 'scroll lock': 145,
- 'my computer': 182,
- 'my calculator': 183,
- ';': 186,
- '=': 187,
- ',': 188,
- '-': 189,
- '.': 190,
- '/': 191,
- '`': 192,
- '[': 219,
- '\\': 220,
- ']': 221,
- "'": 222
- }; // Helper aliases
-
- var aliases = exports.aliases = {
- 'windows': 91,
- '⇧': 16,
- '⌥': 18,
- '⌃': 17,
- '⌘': 91,
- 'ctl': 17,
- 'control': 17,
- 'option': 18,
- 'pause': 19,
- 'break': 19,
- 'caps': 20,
- 'return': 13,
- 'escape': 27,
- 'spc': 32,
- 'spacebar': 32,
- 'pgup': 33,
- 'pgdn': 34,
- 'ins': 45,
- 'del': 46,
- 'cmd': 91
- };
- /*!
- * Programatically add the following
- */
- // lower case chars
-
- for (i = 97; i < 123; i++) {
- codes[String.fromCharCode(i)] = i - 32;
- } // numbers
-
-
- for (var i = 48; i < 58; i++) {
- codes[i - 48] = i;
- } // function keys
-
-
- for (i = 1; i < 13; i++) {
- codes['f' + i] = i + 111;
- } // numpad keys
-
-
- for (i = 0; i < 10; i++) {
- codes['numpad ' + i] = i + 96;
- }
- /**
- * Get by code
- *
- * exports.name[13] // => 'Enter'
- */
-
-
- var names = exports.names = exports.title = {}; // title for backward compat
- // Create reverse mapping
-
- for (i in codes) {
- names[codes[i]] = i;
- } // Add aliases
-
-
- for (var alias in aliases) {
- codes[alias] = aliases[alias];
- }
- });
- keycode.code;
- keycode.codes;
- keycode.aliases;
- keycode.names;
- keycode.title;
-
var MODAL_CLASS_NAME = 'vjs-modal-dialog';
/**
* The `ModalDialog` displays over the video and its controls, which blocks
_proto.createEl = function createEl$1() {
var el = _TimeDisplay.prototype.createEl.call(this);
- el.insertBefore(createEl('span', {}, {
- 'aria-hidden': true
- }, '-'), this.contentEl_);
+ if (this.options_.displayNegative !== false) {
+ el.insertBefore(createEl('span', {}, {
+ 'aria-hidden': true
+ }, '-'), this.contentEl_);
+ }
+
return el;
}
/**
this.on(doc, 'mouseup', this.handleMouseUp_);
this.on(doc, 'touchmove', this.handleMouseMove_);
this.on(doc, 'touchend', this.handleMouseUp_);
- this.handleMouseMove(event);
+ this.handleMouseMove(event, true);
}
/**
* Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
* @param {EventTarget~Event} event
* `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
* this function
+ * @param {boolean} mouseDown this is a flag that should be set to true if `handleMouseMove` is called directly. It allows us to skip things that should not happen if coming from mouse down but should happen on regular mouse move handler. Defaults to false.
*
* @listens mousemove
* @listens touchmove
event.stopPropagation();
- this.player_.scrubbing(true);
this.videoWasPlaying = !this.player_.paused();
this.player_.pause();
*
* @param {EventTarget~Event} event
* The `mousemove` event that caused this to run.
+ * @param {boolean} mouseDown this is a flag that should be set to true if `handleMouseMove` is called directly. It allows us to skip things that should not happen if coming from mouse down but should happen on regular mouse move handler. Defaults to false
*
* @listens mousemove
*/
;
- _proto.handleMouseMove = function handleMouseMove(event) {
+ _proto.handleMouseMove = function handleMouseMove(event, mouseDown) {
+ if (mouseDown === void 0) {
+ mouseDown = false;
+ }
+
if (!isSingleLeftClick(event)) {
return;
}
+ if (!mouseDown && !this.player_.scrubbing()) {
+ this.player_.scrubbing(true);
+ }
+
var newTime;
var distance = this.calculateDistance(event);
var liveTracker = this.player_.liveTracker;
var _proto = AudioTrackMenuItem.prototype;
- _proto.createEl = function createEl(type, props, attrs) {
+ _proto.createEl = function createEl$1(type, props, attrs) {
var el = _MenuItem.prototype.createEl.call(this, type, props, attrs);
var parentSpan = el.querySelector('.vjs-menu-item-text');
if (this.options_.track.kind === 'main-desc') {
- parentSpan.appendChild(_MenuItem.prototype.createEl.call(this, 'span', {
+ parentSpan.appendChild(createEl('span', {
className: 'vjs-icon-placeholder'
}, {
'aria-hidden': true
}));
- parentSpan.appendChild(_MenuItem.prototype.createEl.call(this, 'span', {
+ parentSpan.appendChild(createEl('span', {
className: 'vjs-control-text',
- textContent: this.localize('Descriptions')
+ textContent: ' ' + this.localize('Descriptions')
}));
}
// off for us.
- this.track.enabled = true;
+ this.track.enabled = true; // when native audio tracks are used, we want to make sure that other tracks are turned off
+
+ if (this.player_.tech_.featuresNativeAudioTracks) {
+ var tracks = this.player_.audioTracks();
+
+ for (var i = 0; i < tracks.length; i++) {
+ var track = tracks[i]; // skip the current track since we enabled it above
+
+ if (track === this.track) {
+ continue;
+ }
+
+ track.enabled = track === this.track;
+ }
+ }
}
/**
* Handle any {@link AudioTrack} change.
_proto.handleClick = function handleClick(event) {
// select next rate option
var currentRate = this.player().playbackRate();
- var rates = this.playbackRates(); // this will select first one if the last one currently selected
-
- var newRate = rates[0];
-
- for (var i = 0; i < rates.length; i++) {
- if (rates[i] > currentRate) {
- newRate = rates[i];
- break;
- }
- }
+ var rates = this.playbackRates();
+ var currentIndex = rates.indexOf(currentRate); // this get the next rate and it will select first one if the last one currently selected
- this.player().playbackRate(newRate);
+ var newIndex = (currentIndex + 1) % rates.length;
+ this.player().playbackRate(rates[newIndex]);
}
/**
* On playbackrateschange, update the menu to account for the new items.
// may not have the proper values for things like seekableEnd until then
- _this.one(_this.player_, 'canplay', function () {
+ _this.on(_this.player_, 'canplay', function () {
return _this.toggleTracking();
}); // we don't need to track live playback if the document is hidden,
// also, tracking when the document is hidden can
var endFn = function endFn() {
this.trigger('fullscreenchange', {
isFullscreen: false
- });
+ }); // Safari will sometimes set controls on the videoelement when existing fullscreen.
+
+ if (this.el_.controls && !this.options_.nativeControlsForTouch && this.controls()) {
+ this.el_.controls = false;
+ }
};
var beginFn = function beginFn() {
try {
var volume = Html5.TEST_VID.volume;
Html5.TEST_VID.volume = volume / 2 + 0.1;
- return volume !== Html5.TEST_VID.volume;
+ var canControl = volume !== Html5.TEST_VID.volume; // With the introduction of iOS 15, there are cases where the volume is read as
+ // changed but reverts back to its original state at the start of the next tick.
+ // To determine whether volume can be controlled on iOS,
+ // a timeout is set and the volume is checked asynchronously.
+ // Since `features` doesn't currently work asynchronously, the value is manually set.
+
+ if (canControl && IS_IOS) {
+ window.setTimeout(function () {
+ if (Html5 && Html5.prototype) {
+ Html5.prototype.featuresVolumeControl = volume !== Html5.TEST_VID.volume;
+ }
+ }); // default iOS to false, which will be updated in the timeout above.
+
+ return false;
+ }
+
+ return canControl;
} catch (e) {
return false;
}
* @default {@link Html5.supportsNativeAudioTracks}
*/
- [['featuresVolumeControl', 'canControlVolume'], ['featuresMuteControl', 'canMuteVolume'], ['featuresPlaybackRate', 'canControlPlaybackRate'], ['featuresSourceset', 'canOverrideAttributes'], ['featuresNativeTextTracks', 'supportsNativeTextTracks'], ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'], ['featuresNativeAudioTracks', 'supportsNativeAudioTracks']].forEach(function (_ref) {
+ [['featuresMuteControl', 'canMuteVolume'], ['featuresPlaybackRate', 'canControlPlaybackRate'], ['featuresSourceset', 'canOverrideAttributes'], ['featuresNativeTextTracks', 'supportsNativeTextTracks'], ['featuresNativeVideoTracks', 'supportsNativeVideoTracks'], ['featuresNativeAudioTracks', 'supportsNativeAudioTracks']].forEach(function (_ref) {
var key = _ref[0],
fn = _ref[1];
defineLazyProperty(Html5.prototype, key, function () {
return Html5[fn]();
}, true);
});
+ Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
/**
* Boolean indicating whether the `HTML5` tech currently supports the media element
* moving in the DOM. iOS breaks if you move the media element, so this is set this to
;
_proto.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
+ var _this9 = this;
+
if (data) {
if (data.nativeIOSFullscreen) {
- this.toggleClass('vjs-ios-native-fs');
+ this.addClass('vjs-ios-native-fs');
+ this.tech_.one('webkitendfullscreen', function () {
+ _this9.removeClass('vjs-ios-native-fs');
+ });
}
this.isFullscreen(data.isFullscreen);
;
_proto.play = function play() {
- var _this9 = this;
+ var _this10 = this;
var PromiseClass = this.options_.Promise || window.Promise;
if (PromiseClass) {
return new PromiseClass(function (resolve) {
- _this9.play_(resolve);
+ _this10.play_(resolve);
});
}
;
_proto.play_ = function play_(callback) {
- var _this10 = this;
+ var _this11 = this;
if (callback === void 0) {
callback = silencePromise;
if (!this.isReady_ || !isSrcReady) {
this.waitToPlay_ = function (e) {
- _this10.play_();
+ _this11.play_();
};
this.one(['ready', 'loadstart'], this.waitToPlay_); // if we are in Safari, there is a high chance that loadstart will trigger after the gesture timeperiod
};
_proto.requestFullscreenHelper_ = function requestFullscreenHelper_(fullscreenOptions) {
- var _this11 = this;
+ var _this12 = this;
var fsOptions; // Only pass fullscreen options to requestFullscreen in spec-compliant browsers.
// Use defaults or player configured option unless passed directly to this method.
if (promise) {
promise.then(function () {
- return _this11.isFullscreen(true);
+ return _this12.isFullscreen(true);
}, function () {
- return _this11.isFullscreen(false);
+ return _this12.isFullscreen(false);
});
}
};
_proto.exitFullscreenHelper_ = function exitFullscreenHelper_() {
- var _this12 = this;
+ var _this13 = this;
if (this.fsApi_.requestFullscreen) {
var promise = document[this.fsApi_.exitFullscreen]();
// we're splitting the promise here, so, we want to catch the
// potential error so that this chain doesn't have unhandled errors
silencePromise(promise.then(function () {
- return _this12.isFullscreen(false);
+ return _this13.isFullscreen(false);
}));
}
;
_proto.selectSource = function selectSource(sources) {
- var _this13 = this;
+ var _this14 = this;
// Get only the techs specified in `techOrder` that exist and are supported by the
// current platform
var techName = _ref2[0],
tech = _ref2[1];
- if (tech.canPlaySource(source, _this13.options_[techName.toLowerCase()])) {
+ if (tech.canPlaySource(source, _this14.options_[techName.toLowerCase()])) {
return {
source: source,
tech: techName
;
_proto.handleSrc_ = function handleSrc_(source, isRetry) {
- var _this14 = this;
+ var _this15 = this;
// getter usage
if (typeof source === 'undefined') {
this.updateSourceCaches_(sources[0]); // middlewareSource is the source after it has been changed by middleware
setSource(this, sources[0], function (middlewareSource, mws) {
- _this14.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since
+ _this15.middleware_ = mws; // since sourceSet is async we have to update the cache again after we select a source since
// the source that is selected could be out of order from the cache update above this callback.
if (!isRetry) {
- _this14.cache_.sources = sources;
+ _this15.cache_.sources = sources;
}
- _this14.updateSourceCaches_(middlewareSource);
+ _this15.updateSourceCaches_(middlewareSource);
- var err = _this14.src_(middlewareSource);
+ var err = _this15.src_(middlewareSource);
if (err) {
if (sources.length > 1) {
- return _this14.handleSrc_(sources.slice(1));
+ return _this15.handleSrc_(sources.slice(1));
}
- _this14.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers
+ _this15.changingSrc_ = false; // We need to wrap this in a timeout to give folks a chance to add error event handlers
- _this14.setTimeout(function () {
+ _this15.setTimeout(function () {
this.error({
code: 4,
message: this.localize(this.options_.notSupportedMessage)
// this needs a better comment about why this is needed
- _this14.triggerReady();
+ _this15.triggerReady();
return;
}
- setTech(mws, _this14.tech_);
+ setTech(mws, _this15.tech_);
}); // Try another available source if this one fails before playback.
if (this.options_.retryOnError && sources.length > 1) {
var retry = function retry() {
// Remove the error modal
- _this14.error(null);
+ _this15.error(null);
- _this14.handleSrc_(sources.slice(1), true);
+ _this15.handleSrc_(sources.slice(1), true);
};
var stopListeningForErrors = function stopListeningForErrors() {
- _this14.off('error', retry);
+ _this15.off('error', retry);
};
this.one('error', retry);
this.one('playing', stopListeningForErrors);
this.resetRetryOnError_ = function () {
- _this14.off('error', retry);
+ _this15.off('error', retry);
- _this14.off('playing', stopListeningForErrors);
+ _this15.off('playing', stopListeningForErrors);
};
}
}
;
_proto.src_ = function src_(source) {
- var _this15 = this;
+ var _this16 = this;
var sourceTech = this.selectSource([source]);
this.loadTech_(sourceTech.tech, sourceTech.source);
this.tech_.ready(function () {
- _this15.changingSrc_ = false;
+ _this16.changingSrc_ = false;
});
return false;
} // wait until the tech is ready to set the source
;
_proto.reset = function reset() {
- var _this16 = this;
+ var _this17 = this;
var PromiseClass = this.options_.Promise || window.Promise;
} else {
var playPromise = this.play();
silencePromise(playPromise.then(function () {
- return _this16.doReset_();
+ return _this17.doReset_();
}));
}
};
;
_proto.error = function error(err) {
- var _this17 = this;
+ var _this18 = this;
if (err === undefined) {
return this.error_ || null;
hooks('beforeerror').forEach(function (hookFunction) {
- var newErr = hookFunction(_this17, err);
+ var newErr = hookFunction(_this18, err);
if (!(isObject$1(newErr) && !Array.isArray(newErr) || typeof newErr === 'string' || typeof newErr === 'number' || newErr === null)) {
- _this17.log.error('please return a value that MediaError expects in beforeerror hooks');
+ _this18.log.error('please return a value that MediaError expects in beforeerror hooks');
return;
}
this.trigger('error'); // notify hooks of the per player error
hooks('error').forEach(function (hookFunction) {
- return hookFunction(_this17, _this17.error_);
+ return hookFunction(_this18, _this18.error_);
});
return;
}
;
_proto.createModal = function createModal(content, options) {
- var _this18 = this;
+ var _this19 = this;
options = options || {};
options.content = content || '';
var modal = new ModalDialog(this, options);
this.addChild(modal);
modal.on('dispose', function () {
- _this18.removeChild(modal);
+ _this19.removeChild(modal);
});
modal.open();
return modal;
;
_proto.loadMedia = function loadMedia(media, ready) {
- var _this19 = this;
+ var _this20 = this;
if (!media || typeof media !== 'object') {
return;
if (Array.isArray(textTracks)) {
textTracks.forEach(function (tt) {
- return _this19.addRemoteTextTrack(tt, false);
+ return _this20.addRemoteTextTrack(tt, false);
});
}
})();
});
- var DEFAULT_LOCATION$1 = 'http://example.com';
+ var DEFAULT_LOCATION = 'http://example.com';
- var resolveUrl$2 = function resolveUrl(baseUrl, relativeUrl) {
+ var resolveUrl$1 = function resolveUrl(baseUrl, relativeUrl) {
// return early if we don't need to resolve
if (/^[a-z]+:/i.test(relativeUrl)) {
return relativeUrl;
var removeLocation = !window.location && !/\/\//i.test(baseUrl); // if the base URL is relative then combine with the current location
if (nativeURL) {
- baseUrl = new window.URL(baseUrl, window.location || DEFAULT_LOCATION$1);
+ baseUrl = new window.URL(baseUrl, window.location || DEFAULT_LOCATION);
} else if (!/\/\//i.test(baseUrl)) {
baseUrl = urlToolkit.buildAbsoluteURL(window.location && window.location.href || '', baseUrl);
}
// otherwise, return the url unmodified
if (removeLocation) {
- return newUrl.href.slice(DEFAULT_LOCATION$1.length);
+ return newUrl.href.slice(DEFAULT_LOCATION.length);
} else if (protocolLess) {
return newUrl.href.slice(newUrl.protocol.length);
}
return null;
};
- var DEFAULT_LOCATION = 'http://example.com';
-
- var resolveUrl$1 = function resolveUrl(baseUrl, relativeUrl) {
- // return early if we don't need to resolve
- if (/^[a-z]+:/i.test(relativeUrl)) {
- return relativeUrl;
- } // if baseUrl is a data URI, ignore it and resolve everything relative to window.location
-
-
- if (/^data:/.test(baseUrl)) {
- baseUrl = window.location && window.location.href || '';
- } // IE11 supports URL but not the URL constructor
- // feature detect the behavior we want
-
-
- var nativeURL = typeof window.URL === 'function';
- var protocolLess = /^\/\//.test(baseUrl); // remove location if window.location isn't available (i.e. we're in node)
- // and if baseUrl isn't an absolute url
-
- var removeLocation = !window.location && !/\/\//i.test(baseUrl); // if the base URL is relative then combine with the current location
-
- if (nativeURL) {
- baseUrl = new window.URL(baseUrl, window.location || DEFAULT_LOCATION);
- } else if (!/\/\//i.test(baseUrl)) {
- baseUrl = urlToolkit.buildAbsoluteURL(window.location && window.location.href || '', baseUrl);
- }
-
- if (nativeURL) {
- var newUrl = new URL(relativeUrl, baseUrl); // if we're a protocol-less url, remove the protocol
- // and if we're location-less, remove the location
- // otherwise, return the url unmodified
-
- if (removeLocation) {
- return newUrl.href.slice(DEFAULT_LOCATION.length);
- } else if (protocolLess) {
- return newUrl.href.slice(newUrl.protocol.length);
+ /**
+ * Loops through all supported media groups in master and calls the provided
+ * callback for each group
+ *
+ * @param {Object} master
+ * The parsed master manifest object
+ * @param {string[]} groups
+ * The media groups to call the callback for
+ * @param {Function} callback
+ * Callback to call for each media group
+ */
+ var forEachMediaGroup$1 = function forEachMediaGroup(master, groups, callback) {
+ groups.forEach(function (mediaType) {
+ for (var groupKey in master.mediaGroups[mediaType]) {
+ for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
+ var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
+ callback(mediaProperties, mediaType, groupKey, labelKey);
+ }
}
-
- return newUrl.href;
- }
-
- return urlToolkit.buildAbsoluteURL(baseUrl, relativeUrl);
+ });
};
/**
var DOMParser = domParser.DOMParser;
- /*! @name mpd-parser @version 0.19.2 @license Apache-2.0 */
+ /*! @name mpd-parser @version 0.21.0 @license Apache-2.0 */
var isObject = function isObject(obj) {
return !!obj && typeof obj === 'object';
return a;
}, []);
};
+ /**
+ * Returns the first index that satisfies the matching function, or -1 if not found.
+ *
+ * Only necessary because of IE11 support.
+ *
+ * @param {Array} list - the list to search through
+ * @param {Function} matchingFunction - the matching function
+ *
+ * @return {number} the matching index or -1 if not found
+ */
+
+
+ var findIndex = function findIndex(list, matchingFunction) {
+ for (var i = 0; i < list.length; i++) {
+ if (matchingFunction(list[i])) {
+ return i;
+ }
+ }
+
+ return -1;
+ };
+ /**
+ * Returns a union of the included lists provided each element can be identified by a key.
+ *
+ * @param {Array} list - list of lists to get the union of
+ * @param {Function} keyFunction - the function to use as a key for each element
+ *
+ * @return {Array} the union of the arrays
+ */
+
+
+ var union = function union(lists, keyFunction) {
+ return values(lists.reduce(function (acc, list) {
+ list.forEach(function (el) {
+ acc[keyFunction(el)] = el;
+ });
+ return acc;
+ }, {}));
+ };
var errors = {
INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
if (range || indexRange) {
var rangeStr = range ? range : indexRange;
- var ranges = rangeStr.split('-');
- var startRange = parseInt(ranges[0], 10);
- var endRange = parseInt(ranges[1], 10); // byterange should be inclusive according to
+ var ranges = rangeStr.split('-'); // default to parsing this as a BigInt if possible
+
+ var startRange = window.BigInt ? window.BigInt(ranges[0]) : parseInt(ranges[0], 10);
+ var endRange = window.BigInt ? window.BigInt(ranges[1]) : parseInt(ranges[1], 10); // convert back to a number if less than MAX_SAFE_INTEGER
+
+ if (startRange < Number.MAX_SAFE_INTEGER && typeof startRange === 'bigint') {
+ startRange = Number(startRange);
+ }
+
+ if (endRange < Number.MAX_SAFE_INTEGER && typeof endRange === 'bigint') {
+ endRange = Number(endRange);
+ }
+
+ var length;
+
+ if (typeof endRange === 'bigint' || typeof startRange === 'bigint') {
+ length = window.BigInt(endRange) - window.BigInt(startRange) + window.BigInt(1);
+ } else {
+ length = endRange - startRange + 1;
+ }
+
+ if (typeof length === 'bigint' && length < Number.MAX_SAFE_INTEGER) {
+ length = Number(length);
+ } // byterange should be inclusive according to
// RFC 2616, Clause 14.35.1
+
segment.byterange = {
- length: endRange - startRange + 1,
+ length: length,
offset: startRange
};
}
var byteRangeToString = function byteRangeToString(byterange) {
// `endRange` is one less than `offset + length` because the HTTP range
// header uses inclusive ranges
- var endRange = byterange.offset + byterange.length - 1;
+ var endRange;
+
+ if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') {
+ endRange = window.BigInt(byterange.offset) + window.BigInt(byterange.length) - window.BigInt(1);
+ } else {
+ endRange = byterange.offset + byterange.length - 1;
+ }
+
return byterange.offset + "-" + endRange;
};
/**
_attributes$timescale2 = attributes.timescale,
timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
duration = attributes.duration,
- _attributes$start = attributes.start,
- start = _attributes$start === void 0 ? 0 : _attributes$start,
+ _attributes$periodSta = attributes.periodStart,
+ periodStart = _attributes$periodSta === void 0 ? 0 : _attributes$periodSta,
_attributes$minimumUp = attributes.minimumUpdatePeriod,
minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp,
_attributes$timeShift = attributes.timeShiftBufferDepth,
timeShiftBufferDepth = _attributes$timeShift === void 0 ? Infinity : _attributes$timeShift;
- var endNumber = parseEndNumber(attributes.endNumber);
- var now = (NOW + clientOffset) / 1000;
- var periodStartWC = availabilityStartTime + start;
+ var endNumber = parseEndNumber(attributes.endNumber); // clientOffset is passed in at the top level of mpd-parser and is an offset calculated
+ // after retrieving UTC server time.
+
+ var now = (NOW + clientOffset) / 1000; // WC stands for Wall Clock.
+ // Convert the period start time to EPOCH.
+
+ var periodStartWC = availabilityStartTime + periodStart; // Period end in EPOCH is manifest's retrieval time + time until next update.
+
var periodEndWC = now + minimumUpdatePeriod;
var periodDuration = periodEndWC - periodStartWC;
var segmentCount = Math.ceil(periodDuration * timescale / duration);
*/
var toSegments = function toSegments(attributes) {
- return function (number, index) {
+ return function (number) {
var duration = attributes.duration,
_attributes$timescale3 = attributes.timescale,
timescale = _attributes$timescale3 === void 0 ? 1 : _attributes$timescale3,
- periodIndex = attributes.periodIndex,
+ periodStart = attributes.periodStart,
_attributes$startNumb = attributes.startNumber,
startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb;
return {
number: startNumber + number,
duration: duration / timescale,
- timeline: periodIndex,
- time: index * duration
+ timeline: periodStart,
+ time: number * duration
};
};
};
sourceDuration = attributes.sourceDuration,
_attributes$indexRang = attributes.indexRange,
indexRange = _attributes$indexRang === void 0 ? '' : _attributes$indexRang,
+ periodStart = attributes.periodStart,
+ presentationTime = attributes.presentationTime,
+ _attributes$number = attributes.number,
+ number = _attributes$number === void 0 ? 0 : _attributes$number,
duration = attributes.duration; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
if (!baseUrl) {
}
} else if (sourceDuration) {
segment.duration = sourceDuration;
- segment.timeline = 0;
- } // This is used for mediaSequence
+ segment.timeline = periodStart;
+ } // If presentation time is provided, these segments are being generated by SIDX
+ // references, and should use the time provided. For the general case of SegmentBase,
+ // there should only be one segment in the period, so its presentation time is the same
+ // as its period start.
- segment.number = 0;
+ segment.presentationTime = presentationTime || periodStart;
+ segment.number = number;
return [segment];
};
/**
*/
- var addSidxSegmentsToPlaylist = function addSidxSegmentsToPlaylist(playlist, sidx, baseUrl) {
+ var addSidxSegmentsToPlaylist$1 = function addSidxSegmentsToPlaylist(playlist, sidx, baseUrl) {
// Retain init segment information
var initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial main manifest parsing
return r.referenceType !== 1;
});
var segments = [];
- var type = playlist.endList ? 'static' : 'dynamic'; // firstOffset is the offset from the end of the sidx box
+ var type = playlist.endList ? 'static' : 'dynamic';
+ var periodStart = playlist.sidx.timeline;
+ var presentationTime = periodStart;
+ var number = playlist.mediaSequence || 0; // firstOffset is the offset from the end of the sidx box
- var startIndex = sidxEnd + sidx.firstOffset;
+ var startIndex; // eslint-disable-next-line
+
+ if (typeof sidx.firstOffset === 'bigint') {
+ startIndex = window.BigInt(sidxEnd) + sidx.firstOffset;
+ } else {
+ startIndex = sidxEnd + sidx.firstOffset;
+ }
for (var i = 0; i < mediaReferences.length; i++) {
var reference = sidx.references[i]; // size of the referenced (sub)segment
var duration = reference.subsegmentDuration; // should be an inclusive range
- var endIndex = startIndex + size - 1;
+ var endIndex = void 0; // eslint-disable-next-line
+
+ if (typeof startIndex === 'bigint') {
+ endIndex = startIndex + window.BigInt(size) - window.BigInt(1);
+ } else {
+ endIndex = startIndex + size - 1;
+ }
+
var indexRange = startIndex + "-" + endIndex;
var attributes = {
baseUrl: baseUrl,
timescale: timescale,
timeline: timeline,
- // this is used in parseByDuration
- periodIndex: timeline,
+ periodStart: periodStart,
+ presentationTime: presentationTime,
+ number: number,
duration: duration,
sourceDuration: sourceDuration,
indexRange: indexRange,
}
segments.push(segment);
- startIndex += size;
+
+ if (typeof startIndex === 'bigint') {
+ startIndex += window.BigInt(size);
+ } else {
+ startIndex += size;
+ }
+
+ presentationTime += duration / timescale;
+ number++;
}
playlist.segments = segments;
return playlist;
};
+ var SUPPORTED_MEDIA_TYPES = ['AUDIO', 'SUBTITLES']; // allow one 60fps frame as leniency (arbitrarily chosen)
+
+ var TIME_FUDGE = 1 / 60;
+ /**
+ * Given a list of timelineStarts, combines, dedupes, and sorts them.
+ *
+ * @param {TimelineStart[]} timelineStarts - list of timeline starts
+ *
+ * @return {TimelineStart[]} the combined and deduped timeline starts
+ */
+
+ var getUniqueTimelineStarts = function getUniqueTimelineStarts(timelineStarts) {
+ return union(timelineStarts, function (_ref) {
+ var timeline = _ref.timeline;
+ return timeline;
+ }).sort(function (a, b) {
+ return a.timeline > b.timeline ? 1 : -1;
+ });
+ };
+ /**
+ * Finds the playlist with the matching NAME attribute.
+ *
+ * @param {Array} playlists - playlists to search through
+ * @param {string} name - the NAME attribute to search for
+ *
+ * @return {Object|null} the matching playlist object, or null
+ */
+
+
+ var findPlaylistWithName = function findPlaylistWithName(playlists, name) {
+ for (var i = 0; i < playlists.length; i++) {
+ if (playlists[i].attributes.NAME === name) {
+ return playlists[i];
+ }
+ }
+
+ return null;
+ };
+ /**
+ * Gets a flattened array of media group playlists.
+ *
+ * @param {Object} manifest - the main manifest object
+ *
+ * @return {Array} the media group playlists
+ */
+
+
+ var getMediaGroupPlaylists = function getMediaGroupPlaylists(manifest) {
+ var mediaGroupPlaylists = [];
+ forEachMediaGroup$1(manifest, SUPPORTED_MEDIA_TYPES, function (properties, type, group, label) {
+ mediaGroupPlaylists = mediaGroupPlaylists.concat(properties.playlists || []);
+ });
+ return mediaGroupPlaylists;
+ };
+ /**
+ * Updates the playlist's media sequence numbers.
+ *
+ * @param {Object} config - options object
+ * @param {Object} config.playlist - the playlist to update
+ * @param {number} config.mediaSequence - the mediaSequence number to start with
+ */
+
+
+ var updateMediaSequenceForPlaylist = function updateMediaSequenceForPlaylist(_ref2) {
+ var playlist = _ref2.playlist,
+ mediaSequence = _ref2.mediaSequence;
+ playlist.mediaSequence = mediaSequence;
+ playlist.segments.forEach(function (segment, index) {
+ segment.number = playlist.mediaSequence + index;
+ });
+ };
+ /**
+ * Updates the media and discontinuity sequence numbers of newPlaylists given oldPlaylists
+ * and a complete list of timeline starts.
+ *
+ * If no matching playlist is found, only the discontinuity sequence number of the playlist
+ * will be updated.
+ *
+ * Since early available timelines are not supported, at least one segment must be present.
+ *
+ * @param {Object} config - options object
+ * @param {Object[]} oldPlaylists - the old playlists to use as a reference
+ * @param {Object[]} newPlaylists - the new playlists to update
+ * @param {Object} timelineStarts - all timelineStarts seen in the stream to this point
+ */
+
+
+ var updateSequenceNumbers = function updateSequenceNumbers(_ref3) {
+ var oldPlaylists = _ref3.oldPlaylists,
+ newPlaylists = _ref3.newPlaylists,
+ timelineStarts = _ref3.timelineStarts;
+ newPlaylists.forEach(function (playlist) {
+ playlist.discontinuitySequence = findIndex(timelineStarts, function (_ref4) {
+ var timeline = _ref4.timeline;
+ return timeline === playlist.timeline;
+ }); // Playlists NAMEs come from DASH Representation IDs, which are mandatory
+ // (see ISO_23009-1-2012 5.3.5.2).
+ //
+ // If the same Representation existed in a prior Period, it will retain the same NAME.
+
+ var oldPlaylist = findPlaylistWithName(oldPlaylists, playlist.attributes.NAME);
+
+ if (!oldPlaylist) {
+ // Since this is a new playlist, the media sequence values can start from 0 without
+ // consequence.
+ return;
+ } // TODO better support for live SIDX
+ //
+ // As of this writing, mpd-parser does not support multiperiod SIDX (in live or VOD).
+ // This is evident by a playlist only having a single SIDX reference. In a multiperiod
+ // playlist there would need to be multiple SIDX references. In addition, live SIDX is
+ // not supported when the SIDX properties change on refreshes.
+ //
+ // In the future, if support needs to be added, the merging logic here can be called
+ // after SIDX references are resolved. For now, exit early to prevent exceptions being
+ // thrown due to undefined references.
+
+
+ if (playlist.sidx) {
+ return;
+ } // Since we don't yet support early available timelines, we don't need to support
+ // playlists with no segments.
+
+
+ var firstNewSegment = playlist.segments[0];
+ var oldMatchingSegmentIndex = findIndex(oldPlaylist.segments, function (oldSegment) {
+ return Math.abs(oldSegment.presentationTime - firstNewSegment.presentationTime) < TIME_FUDGE;
+ }); // No matching segment from the old playlist means the entire playlist was refreshed.
+ // In this case the media sequence should account for this update, and the new segments
+ // should be marked as discontinuous from the prior content, since the last prior
+ // timeline was removed.
+
+ if (oldMatchingSegmentIndex === -1) {
+ updateMediaSequenceForPlaylist({
+ playlist: playlist,
+ mediaSequence: oldPlaylist.mediaSequence + oldPlaylist.segments.length
+ });
+ playlist.segments[0].discontinuity = true;
+ playlist.discontinuityStarts.unshift(0); // No matching segment does not necessarily mean there's missing content.
+ //
+ // If the new playlist's timeline is the same as the last seen segment's timeline,
+ // then a discontinuity can be added to identify that there's potentially missing
+ // content. If there's no missing content, the discontinuity should still be rather
+ // harmless. It's possible that if segment durations are accurate enough, that the
+ // existence of a gap can be determined using the presentation times and durations,
+ // but if the segment timing info is off, it may introduce more problems than simply
+ // adding the discontinuity.
+ //
+ // If the new playlist's timeline is different from the last seen segment's timeline,
+ // then a discontinuity can be added to identify that this is the first seen segment
+ // of a new timeline. However, the logic at the start of this function that
+ // determined the disconinuity sequence by timeline index is now off by one (the
+ // discontinuity of the newest timeline hasn't yet fallen off the manifest...since
+ // we added it), so the disconinuity sequence must be decremented.
+ //
+ // A period may also have a duration of zero, so the case of no segments is handled
+ // here even though we don't yet support early available periods.
+
+ if (!oldPlaylist.segments.length && playlist.timeline > oldPlaylist.timeline || oldPlaylist.segments.length && playlist.timeline > oldPlaylist.segments[oldPlaylist.segments.length - 1].timeline) {
+ playlist.discontinuitySequence--;
+ }
+
+ return;
+ } // If the first segment matched with a prior segment on a discontinuity (it's matching
+ // on the first segment of a period), then the discontinuitySequence shouldn't be the
+ // timeline's matching one, but instead should be the one prior, and the first segment
+ // of the new manifest should be marked with a discontinuity.
+ //
+ // The reason for this special case is that discontinuity sequence shows how many
+ // discontinuities have fallen off of the playlist, and discontinuities are marked on
+ // the first segment of a new "timeline." Because of this, while DASH will retain that
+ // Period while the "timeline" exists, HLS keeps track of it via the discontinuity
+ // sequence, and that first segment is an indicator, but can be removed before that
+ // timeline is gone.
+
+
+ var oldMatchingSegment = oldPlaylist.segments[oldMatchingSegmentIndex];
+
+ if (oldMatchingSegment.discontinuity && !firstNewSegment.discontinuity) {
+ firstNewSegment.discontinuity = true;
+ playlist.discontinuityStarts.unshift(0);
+ playlist.discontinuitySequence--;
+ }
+
+ updateMediaSequenceForPlaylist({
+ playlist: playlist,
+ mediaSequence: oldPlaylist.segments[oldMatchingSegmentIndex].number
+ });
+ });
+ };
+ /**
+ * Given an old parsed manifest object and a new parsed manifest object, updates the
+ * sequence and timing values within the new manifest to ensure that it lines up with the
+ * old.
+ *
+ * @param {Array} oldManifest - the old main manifest object
+ * @param {Array} newManifest - the new main manifest object
+ *
+ * @return {Object} the updated new manifest object
+ */
+
+
+ var positionManifestOnTimeline = function positionManifestOnTimeline(_ref5) {
+ var oldManifest = _ref5.oldManifest,
+ newManifest = _ref5.newManifest; // Starting from v4.1.2 of the IOP, section 4.4.3.3 states:
+ //
+ // "MPD@availabilityStartTime and Period@start shall not be changed over MPD updates."
+ //
+ // This was added from https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/160
+ //
+ // Because of this change, and the difficulty of supporting periods with changing start
+ // times, periods with changing start times are not supported. This makes the logic much
+ // simpler, since periods with the same start time can be considerred the same period
+ // across refreshes.
+ //
+ // To give an example as to the difficulty of handling periods where the start time may
+ // change, if a single period manifest is refreshed with another manifest with a single
+ // period, and both the start and end times are increased, then the only way to determine
+ // if it's a new period or an old one that has changed is to look through the segments of
+ // each playlist and determine the presentation time bounds to find a match. In addition,
+ // if the period start changed to exceed the old period end, then there would be no
+ // match, and it would not be possible to determine whether the refreshed period is a new
+ // one or the old one.
+
+ var oldPlaylists = oldManifest.playlists.concat(getMediaGroupPlaylists(oldManifest));
+ var newPlaylists = newManifest.playlists.concat(getMediaGroupPlaylists(newManifest)); // Save all seen timelineStarts to the new manifest. Although this potentially means that
+ // there's a "memory leak" in that it will never stop growing, in reality, only a couple
+ // of properties are saved for each seen Period. Even long running live streams won't
+ // generate too many Periods, unless the stream is watched for decades. In the future,
+ // this can be optimized by mapping to discontinuity sequence numbers for each timeline,
+ // but it may not become an issue, and the additional info can be useful for debugging.
+
+ newManifest.timelineStarts = getUniqueTimelineStarts([oldManifest.timelineStarts, newManifest.timelineStarts]);
+ updateSequenceNumbers({
+ oldPlaylists: oldPlaylists,
+ newPlaylists: newPlaylists,
+ timelineStarts: newManifest.timelineStarts
+ });
+ return newManifest;
+ };
+
var generateSidxKey = function generateSidxKey(sidx) {
return sidx && sidx.uri + '-' + byteRangeToString(sidx.byterange);
};
// assuming playlist IDs are the same across periods
// TODO: handle multiperiod where representation sets are not the same
// across periods
- var name = playlist.attributes.id + (playlist.attributes.lang || ''); // Periods after first
+ var name = playlist.attributes.id + (playlist.attributes.lang || '');
- if (acc[name]) {
- var _acc$name$segments; // first segment of subsequent periods signal a discontinuity
+ if (!acc[name]) {
+ // First Period
+ acc[name] = playlist;
+ acc[name].attributes.timelineStarts = [];
+ } else {
+ // Subsequent Periods
+ if (playlist.segments) {
+ var _acc$name$segments; // first segment of subsequent periods signal a discontinuity
- if (playlist.segments[0]) {
- playlist.segments[0].discontinuity = true;
- }
+ if (playlist.segments[0]) {
+ playlist.segments[0].discontinuity = true;
+ }
- (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments); // bubble up contentProtection, this assumes all DRM content
+ (_acc$name$segments = acc[name].segments).push.apply(_acc$name$segments, playlist.segments);
+ } // bubble up contentProtection, this assumes all DRM content
// has the same contentProtection
if (playlist.attributes.contentProtection) {
acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
}
- } else {
- // first Period
- acc[name] = playlist;
}
+ acc[name].attributes.timelineStarts.push({
+ // Although they represent the same number, it's important to have both to make it
+ // compatible with HLS potentially having a similar attribute.
+ start: playlist.attributes.periodStart,
+ timeline: playlist.attributes.periodStart
+ });
return acc;
}, {}));
return mergedPlaylists.map(function (playlist) {
- playlist.discontinuityStarts = findIndexes(playlist.segments, 'discontinuity');
+ playlist.discontinuityStarts = findIndexes(playlist.segments || [], 'discontinuity');
return playlist;
});
};
- var addSidxSegmentsToPlaylist$1 = function addSidxSegmentsToPlaylist$1(playlist, sidxMapping) {
+ var addSidxSegmentsToPlaylist = function addSidxSegmentsToPlaylist(playlist, sidxMapping) {
var sidxKey = generateSidxKey(playlist.sidx);
var sidxMatch = sidxKey && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
if (sidxMatch) {
- addSidxSegmentsToPlaylist(playlist, sidxMatch, playlist.sidx.resolvedUri);
+ addSidxSegmentsToPlaylist$1(playlist, sidxMatch, playlist.sidx.resolvedUri);
}
return playlist;
}
for (var i in playlists) {
- playlists[i] = addSidxSegmentsToPlaylist$1(playlists[i], sidxMapping);
+ playlists[i] = addSidxSegmentsToPlaylist(playlists[i], sidxMapping);
}
return playlists;
var attributes = _ref.attributes,
segments = _ref.segments,
- sidx = _ref.sidx;
+ sidx = _ref.sidx,
+ mediaSequence = _ref.mediaSequence,
+ discontinuitySequence = _ref.discontinuitySequence,
+ discontinuityStarts = _ref.discontinuityStarts;
var playlist = {
attributes: (_attributes = {
NAME: attributes.id,
}, _attributes['PROGRAM-ID'] = 1, _attributes),
uri: '',
endList: attributes.type === 'static',
- timeline: attributes.periodIndex,
+ timeline: attributes.periodStart,
resolvedUri: '',
targetDuration: attributes.duration,
- segments: segments,
- mediaSequence: segments.length ? segments[0].number : 1
+ discontinuitySequence: discontinuitySequence,
+ discontinuityStarts: discontinuityStarts,
+ timelineStarts: attributes.timelineStarts,
+ mediaSequence: mediaSequence,
+ segments: segments
};
if (attributes.contentProtection) {
var _m3u8Attributes;
var attributes = _ref2.attributes,
- segments = _ref2.segments;
+ segments = _ref2.segments,
+ mediaSequence = _ref2.mediaSequence,
+ discontinuityStarts = _ref2.discontinuityStarts,
+ discontinuitySequence = _ref2.discontinuitySequence;
if (typeof segments === 'undefined') {
// vtt tracks may use single file in BaseURL
segments = [{
uri: attributes.baseUrl,
- timeline: attributes.periodIndex,
+ timeline: attributes.periodStart,
resolvedUri: attributes.baseUrl || '',
duration: attributes.sourceDuration,
number: 0
attributes: m3u8Attributes,
uri: '',
endList: attributes.type === 'static',
- timeline: attributes.periodIndex,
+ timeline: attributes.periodStart,
resolvedUri: attributes.baseUrl || '',
targetDuration: attributes.duration,
- segments: segments,
- mediaSequence: segments.length ? segments[0].number : 1
+ timelineStarts: attributes.timelineStarts,
+ discontinuityStarts: discontinuityStarts,
+ discontinuitySequence: discontinuitySequence,
+ mediaSequence: mediaSequence,
+ segments: segments
};
};
};
}
- var formatted = addSidxSegmentsToPlaylist$1(formatAudioPlaylist(playlist, isAudioOnly), sidxMapping);
+ var formatted = addSidxSegmentsToPlaylist(formatAudioPlaylist(playlist, isAudioOnly), sidxMapping);
a[label].playlists.push(formatted);
if (typeof mainPlaylist === 'undefined' && role === 'main') {
};
}
- a[label].playlists.push(addSidxSegmentsToPlaylist$1(formatVttPlaylist(playlist), sidxMapping));
+ a[label].playlists.push(addSidxSegmentsToPlaylist(formatVttPlaylist(playlist), sidxMapping));
return a;
}, {});
};
var attributes = _ref3.attributes,
segments = _ref3.segments,
- sidx = _ref3.sidx;
+ sidx = _ref3.sidx,
+ discontinuityStarts = _ref3.discontinuityStarts;
var playlist = {
attributes: (_attributes2 = {
NAME: attributes.id,
}, _attributes2['PROGRAM-ID'] = 1, _attributes2),
uri: '',
endList: attributes.type === 'static',
- timeline: attributes.periodIndex,
+ timeline: attributes.periodStart,
resolvedUri: '',
targetDuration: attributes.duration,
- segments: segments,
- mediaSequence: segments.length ? segments[0].number : 1
+ discontinuityStarts: discontinuityStarts,
+ timelineStarts: attributes.timelineStarts,
+ segments: segments
};
if (attributes.contentProtection) {
var attributes = _ref6.attributes;
return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
};
+ /**
+ * Contains start and timeline properties denoting a timeline start. For DASH, these will
+ * be the same number.
+ *
+ * @typedef {Object} TimelineStart
+ * @property {number} start - the start time of the timeline
+ * @property {number} timeline - the timeline number
+ */
- var toM3u8 = function toM3u8(dashPlaylists, locations, sidxMapping) {
- var _mediaGroups;
+ /**
+ * Adds appropriate media and discontinuity sequence values to the segments and playlists.
+ *
+ * Throughout mpd-parser, the `number` attribute is used in relation to `startNumber`, a
+ * DASH specific attribute used in constructing segment URI's from templates. However, from
+ * an HLS perspective, the `number` attribute on a segment would be its `mediaSequence`
+ * value, which should start at the original media sequence value (or 0) and increment by 1
+ * for each segment thereafter. Since DASH's `startNumber` values are independent per
+ * period, it doesn't make sense to use it for `number`. Instead, assume everything starts
+ * from a 0 mediaSequence value and increment from there.
+ *
+ * Note that VHS currently doesn't use the `number` property, but it can be helpful for
+ * debugging and making sense of the manifest.
+ *
+ * For live playlists, to account for values increasing in manifests when periods are
+ * removed on refreshes, merging logic should be used to update the numbers to their
+ * appropriate values (to ensure they're sequential and increasing).
+ *
+ * @param {Object[]} playlists - the playlists to update
+ * @param {TimelineStart[]} timelineStarts - the timeline starts for the manifest
+ */
- if (sidxMapping === void 0) {
- sidxMapping = {};
+
+ var addMediaSequenceValues = function addMediaSequenceValues(playlists, timelineStarts) {
+ // increment all segments sequentially
+ playlists.forEach(function (playlist) {
+ playlist.mediaSequence = 0;
+ playlist.discontinuitySequence = findIndex(timelineStarts, function (_ref7) {
+ var timeline = _ref7.timeline;
+ return timeline === playlist.timeline;
+ });
+
+ if (!playlist.segments) {
+ return;
+ }
+
+ playlist.segments.forEach(function (segment, index) {
+ segment.number = index;
+ });
+ });
+ };
+ /**
+ * Given a media group object, flattens all playlists within the media group into a single
+ * array.
+ *
+ * @param {Object} mediaGroupObject - the media group object
+ *
+ * @return {Object[]}
+ * The media group playlists
+ */
+
+
+ var flattenMediaGroupPlaylists = function flattenMediaGroupPlaylists(mediaGroupObject) {
+ if (!mediaGroupObject) {
+ return [];
}
+ return Object.keys(mediaGroupObject).reduce(function (acc, label) {
+ var labelContents = mediaGroupObject[label];
+ return acc.concat(labelContents.playlists);
+ }, []);
+ };
+
+ var toM3u8 = function toM3u8(_ref8) {
+ var _mediaGroups;
+
+ var dashPlaylists = _ref8.dashPlaylists,
+ locations = _ref8.locations,
+ _ref8$sidxMapping = _ref8.sidxMapping,
+ sidxMapping = _ref8$sidxMapping === void 0 ? {} : _ref8$sidxMapping,
+ previousManifest = _ref8.previousManifest;
+
if (!dashPlaylists.length) {
return {};
} // grab all main manifest attributes
minimumUpdatePeriod = _dashPlaylists$0$attr.minimumUpdatePeriod;
var videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
var audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
- var vttPlaylists = dashPlaylists.filter(vttOnly);
+ var vttPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(vttOnly));
var captions = dashPlaylists.map(function (playlist) {
return playlist.attributes.captionServices;
}).filter(Boolean);
}
var isAudioOnly = manifest.playlists.length === 0;
+ var organizedAudioGroup = audioPlaylists.length ? organizeAudioPlaylists(audioPlaylists, sidxMapping, isAudioOnly) : null;
+ var organizedVttGroup = vttPlaylists.length ? organizeVttPlaylists(vttPlaylists, sidxMapping) : null;
+ var formattedPlaylists = videoPlaylists.concat(flattenMediaGroupPlaylists(organizedAudioGroup), flattenMediaGroupPlaylists(organizedVttGroup));
+ var playlistTimelineStarts = formattedPlaylists.map(function (_ref9) {
+ var timelineStarts = _ref9.timelineStarts;
+ return timelineStarts;
+ });
+ manifest.timelineStarts = getUniqueTimelineStarts(playlistTimelineStarts);
+ addMediaSequenceValues(formattedPlaylists, manifest.timelineStarts);
- if (audioPlaylists.length) {
- manifest.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists, sidxMapping, isAudioOnly);
+ if (organizedAudioGroup) {
+ manifest.mediaGroups.AUDIO.audio = organizedAudioGroup;
}
- if (vttPlaylists.length) {
- manifest.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists, sidxMapping);
+ if (organizedVttGroup) {
+ manifest.mediaGroups.SUBTITLES.subs = organizedVttGroup;
}
if (captions.length) {
manifest.mediaGroups['CLOSED-CAPTIONS'].cc = organizeCaptionServices(captions);
}
+ if (previousManifest) {
+ return positionManifestOnTimeline({
+ oldManifest: previousManifest,
+ newManifest: manifest
+ });
+ }
+
return manifest;
};
/**
availabilityStartTime = attributes.availabilityStartTime,
_attributes$timescale = attributes.timescale,
timescale = _attributes$timescale === void 0 ? 1 : _attributes$timescale,
- _attributes$start = attributes.start,
- start = _attributes$start === void 0 ? 0 : _attributes$start,
+ _attributes$periodSta = attributes.periodStart,
+ periodStart = _attributes$periodSta === void 0 ? 0 : _attributes$periodSta,
_attributes$minimumUp = attributes.minimumUpdatePeriod,
minimumUpdatePeriod = _attributes$minimumUp === void 0 ? 0 : _attributes$minimumUp;
var now = (NOW + clientOffset) / 1000;
- var periodStartWC = availabilityStartTime + start;
+ var periodStartWC = availabilityStartTime + periodStart;
var periodEndWC = now + minimumUpdatePeriod;
var periodDuration = periodEndWC - periodStartWC;
return Math.ceil((periodDuration * timescale - time) / duration);
timescale = _attributes$timescale2 === void 0 ? 1 : _attributes$timescale2,
_attributes$startNumb = attributes.startNumber,
startNumber = _attributes$startNumb === void 0 ? 1 : _attributes$startNumb,
- timeline = attributes.periodIndex;
+ timeline = attributes.periodStart;
var segments = [];
var time = -1;
number: attributes.startNumber || 1,
duration: attributes.sourceDuration,
time: 0,
- timeline: attributes.periodIndex
+ timeline: attributes.periodStart
}];
}
* @function
* @param {PeriodInformation} period
* Period object containing necessary period information
- * @param {number} periodIndex
- * Index of the Period within the mpd
+ * @param {number} periodStart
+ * Start time of the Period within the mpd
* @return {RepresentationInformation[]}
* List of objects containing Representaion information
*/
var toAdaptationSets = function toAdaptationSets(mpdAttributes, mpdBaseUrls) {
return function (period, index) {
var periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period.node, 'BaseURL'));
- var parsedPeriodId = parseInt(period.attributes.id, 10); // fallback to mapping index if Period@id is not a number
-
- var periodIndex = window.isNaN(parsedPeriodId) ? index : parsedPeriodId;
var periodAttributes = merge(mpdAttributes, {
- periodIndex: periodIndex,
periodStart: period.attributes.start
});
return attributes;
};
+ /*
+ * Given a DASH manifest string and options, parses the DASH manifest into an object in the
+ * form outputed by m3u8-parser and accepted by videojs/http-streaming.
+ *
+ * For live DASH manifests, if `previousManifest` is provided in options, then the newly
+ * parsed DASH manifest will have its media sequence and discontinuity sequence values
+ * updated to reflect its position relative to the prior manifest.
+ *
+ * @param {string} manifestString - the DASH manifest as a string
+ * @param {options} [options] - any options
+ *
+ * @return {Object} the manifest object
+ */
var parse = function parse(manifestString, options) {
if (options === void 0) {
var parsedManifestInfo = inheritAttributes(stringToMpdXml(manifestString), options);
var playlists = toPlaylists(parsedManifestInfo.representationInfo);
- return toM3u8(playlists, parsedManifestInfo.locations, options.sidxMapping);
+ return toM3u8({
+ dashPlaylists: playlists,
+ locations: parsedManifestInfo.locations,
+ sidxMapping: options.sidxMapping,
+ previousManifest: options.previousManifest
+ });
};
/**
* Parses the manifest for a UTCTiming node, returning the nodes attributes if found
var MAX_UINT32 = Math.pow(2, 32);
+ var getUint64$1 = function getUint64(uint8) {
+ var dv = new DataView(uint8.buffer, uint8.byteOffset, uint8.byteLength);
+ var value;
+
+ if (dv.getBigUint64) {
+ value = dv.getBigUint64(0);
+
+ if (value < Number.MAX_SAFE_INTEGER) {
+ return Number(value);
+ }
+
+ return value;
+ }
+
+ return dv.getUint32(0) * MAX_UINT32 + dv.getUint32(4);
+ };
+
+ var numbers = {
+ getUint64: getUint64$1,
+ MAX_UINT32: MAX_UINT32
+ };
+
+ var getUint64 = numbers.getUint64;
+
var parseSidx = function parseSidx(data) {
var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
result = {
i += 8;
} else {
// read 64 bits
- result.earliestPresentationTime = view.getUint32(i) * MAX_UINT32 + view.getUint32(i + 4);
- result.firstOffset = view.getUint32(i + 8) * MAX_UINT32 + view.getUint32(i + 12);
+ result.earliestPresentationTime = getUint64(data.subarray(i));
+ result.firstOffset = getUint64(data.subarray(i + 8));
i += 16;
}
};
var clock_1 = clock.ONE_SECOND_IN_TS;
- /*! @name @videojs/http-streaming @version 2.12.0 @license Apache-2.0 */
+ /*! @name @videojs/http-streaming @version 2.13.1 @license Apache-2.0 */
/**
* @file resolve-url.js - Handling how URLs are resolved and manipulated
*/
- var resolveUrl = resolveUrl$2;
+ var resolveUrl = resolveUrl$1;
/**
* Checks whether xhr request was redirected and returns correct url depending
* on `handleManifestRedirects` option
var byterangeStr = function byterangeStr(byterange) {
// `byterangeEnd` is one less than `offset + length` because the HTTP range
// header uses inclusive ranges
- var byterangeEnd = byterange.offset + byterange.length - 1;
+ var byterangeEnd;
var byterangeStart = byterange.offset;
+
+ if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') {
+ byterangeEnd = window.BigInt(byterange.offset) + window.BigInt(byterange.length) - window.BigInt(1);
+ } else {
+ byterangeEnd = byterange.offset + byterange.length - 1;
+ }
+
return 'bytes=' + byterangeStart + '-' + byterangeEnd;
};
/**
var masterXml = _ref.masterXml,
srcUrl = _ref.srcUrl,
clientOffset = _ref.clientOffset,
- sidxMapping = _ref.sidxMapping;
- var master = parse(masterXml, {
+ sidxMapping = _ref.sidxMapping,
+ previousManifest = _ref.previousManifest;
+ var manifest = parse(masterXml, {
manifestUri: srcUrl,
clientOffset: clientOffset,
- sidxMapping: sidxMapping
+ sidxMapping: sidxMapping,
+ previousManifest: previousManifest
});
- addPropertiesToMaster(master, srcUrl);
- return master;
+ addPropertiesToMaster(manifest, srcUrl);
+ return manifest;
};
/**
* Returns a new master manifest that is the result of merging an updated master manifest
var update = mergeOptions(oldMaster, {
// These are top level properties that can be updated
duration: newMaster.duration,
- minimumUpdatePeriod: newMaster.minimumUpdatePeriod
+ minimumUpdatePeriod: newMaster.minimumUpdatePeriod,
+ timelineStarts: newMaster.timelineStarts
}); // First update the playlists in playlist list
for (var i = 0; i < newMaster.playlists.length; i++) {
var sidxKey = generateSidxKey(playlist.sidx); // add sidx segments to the playlist if we have all the sidx info already
if (sidxMapping && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx) {
- addSidxSegmentsToPlaylist(playlist, sidxMapping[sidxKey].sidx, playlist.sidx.resolvedUri);
+ addSidxSegmentsToPlaylist$1(playlist, sidxMapping[sidxKey].sidx, playlist.sidx.resolvedUri);
}
}
sidxInfo: playlist.sidx,
sidx: sidx
};
- addSidxSegmentsToPlaylist(playlist, sidx, playlist.sidx.resolvedUri);
+ addSidxSegmentsToPlaylist$1(playlist, sidx, playlist.sidx.resolvedUri);
return cb(true);
};
_proto.handleMaster_ = function handleMaster_() {
// clear media request
this.mediaRequest_ = null;
+ var oldMaster = this.masterPlaylistLoader_.master;
var newMaster = parseMasterXml({
masterXml: this.masterPlaylistLoader_.masterXml_,
srcUrl: this.masterPlaylistLoader_.srcUrl,
clientOffset: this.masterPlaylistLoader_.clientOffset_,
- sidxMapping: this.masterPlaylistLoader_.sidxMapping_
- });
- var oldMaster = this.masterPlaylistLoader_.master; // if we have an old master to compare the new master against
+ sidxMapping: this.masterPlaylistLoader_.sidxMapping_,
+ previousManifest: oldMaster
+ }); // if we have an old master to compare the new master against
if (oldMaster) {
newMaster = updateMaster(oldMaster, newMaster, this.masterPlaylistLoader_.sidxMapping_);
var getWorkerString = function getWorkerString(fn) {
return fn.toString().replace(/^function.+?{/, '').slice(0, -1);
};
- /* rollup-plugin-worker-factory start for worker!/Users/gkatsevman/p/http-streaming-release/src/transmuxer-worker.js */
+ /* rollup-plugin-worker-factory start for worker!/Users/gsinger/repos/clean/http-streaming/src/transmuxer-worker.js */
var workerCode$1 = transform(getWorkerString(function () {
};
var stream = Stream;
- /**
- * mux.js
- *
- * Copyright (c) Brightcove
- * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
- *
- * Functions that generate fragmented MP4s suitable for use with Media
- * Source Extensions.
- */
+ var MAX_UINT32$1 = Math.pow(2, 32);
+
+ var getUint64$2 = function getUint64(uint8) {
+ var dv = new DataView(uint8.buffer, uint8.byteOffset, uint8.byteLength);
+ var value;
+
+ if (dv.getBigUint64) {
+ value = dv.getBigUint64(0);
+
+ if (value < Number.MAX_SAFE_INTEGER) {
+ return Number(value);
+ }
+
+ return value;
+ }
+
+ return dv.getUint32(0) * MAX_UINT32$1 + dv.getUint32(4);
+ };
- var UINT32_MAX = Math.pow(2, 32) - 1;
+ var numbers = {
+ getUint64: getUint64$2,
+ MAX_UINT32: MAX_UINT32$1
+ };
+ var MAX_UINT32 = numbers.MAX_UINT32;
var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, trun$1, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; // pre-calculate constants
(function () {
0x00, 0x00, 0x00, 0x00, // default_sample_size
0x00, 0x00, 0x00, 0x00 // default_sample_flags
]));
- upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
- lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
+ upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / MAX_UINT32);
+ lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % MAX_UINT32);
trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
0x00, 0x00, 0x00, // flags
// baseMediaDecodeTime
var findBox_1 = findBox;
var toUnsigned$1 = bin.toUnsigned;
+ var getUint64$1 = numbers.getUint64;
var tfdt = function tfdt(data) {
var result = {
version: data[0],
- flags: new Uint8Array(data.subarray(1, 4)),
- baseMediaDecodeTime: toUnsigned$1(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7])
+ flags: new Uint8Array(data.subarray(1, 4))
};
if (result.version === 1) {
- result.baseMediaDecodeTime *= Math.pow(2, 32);
- result.baseMediaDecodeTime += toUnsigned$1(data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]);
+ result.baseMediaDecodeTime = getUint64$1(data.subarray(4));
+ } else {
+ result.baseMediaDecodeTime = toUnsigned$1(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]);
}
return result;
};
var parseTfhd = tfhd;
+ var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
+ var win;
+
+ if (typeof window !== "undefined") {
+ win = window;
+ } else if (typeof commonjsGlobal !== "undefined") {
+ win = commonjsGlobal;
+ } else if (typeof self !== "undefined") {
+ win = self;
+ } else {
+ win = {};
+ }
+
+ var window_1 = win;
var discardEmulationPreventionBytes = captionPacketParser.discardEmulationPreventionBytes;
var CaptionStream = captionStream.CaptionStream;
/**
* the absolute presentation and decode timestamps of each sample.
*
* @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
- * @param {Number} baseMediaDecodeTime - base media decode time from tfdt
+ * @param {Number|BigInt} baseMediaDecodeTime - base media decode time from tfdt
@see ISO-BMFF-12/2015, Section 8.8.12
* @param {Object} tfhd - The parsed Track Fragment Header
* @see inspect.parseTfhd
sample.compositionTimeOffset = 0;
}
- sample.pts = currentDts + sample.compositionTimeOffset;
- currentDts += sample.duration;
+ if (typeof currentDts === 'bigint') {
+ sample.pts = currentDts + window_1.BigInt(sample.compositionTimeOffset);
+ currentDts += window_1.BigInt(sample.duration);
+ } else {
+ sample.pts = currentDts + sample.compositionTimeOffset;
+ currentDts += sample.duration;
+ }
});
allSamples = allSamples.concat(samples);
});
var captionParser = CaptionParser;
var toUnsigned = bin.toUnsigned;
var toHexString = bin.toHexString;
+ var getUint64 = numbers.getUint64;
var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks, getTimescaleFromMediaHeader;
/**
* Parses an MP4 initialization segment and extracts the timescale
startTime = function startTime(timescale, fragment) {
- var trafs, baseTimes, result; // we need info from two childrend of each track fragment box
+ var trafs; // we need info from two childrend of each track fragment box
trafs = findBox_1(fragment, ['moof', 'traf']); // determine the start times for each track
- baseTimes = [].concat.apply([], trafs.map(function (traf) {
- return findBox_1(traf, ['tfhd']).map(function (tfhd) {
- var id, scale, baseTime; // get the track id from the tfhd
+ var lowestTime = trafs.reduce(function (acc, traf) {
+ var tfhd = findBox_1(traf, ['tfhd'])[0]; // get the track id from the tfhd
- id = toUnsigned(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
+ var id = toUnsigned(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
- scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
+ var scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
- baseTime = findBox_1(traf, ['tfdt']).map(function (tfdt) {
- var version, result;
- version = tfdt[0];
- result = toUnsigned(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
+ var tfdt = findBox_1(traf, ['tfdt'])[0];
+ var dv = new DataView(tfdt.buffer, tfdt.byteOffset, tfdt.byteLength);
+ var baseTime; // version 1 is 64 bit
- if (version === 1) {
- result *= Math.pow(2, 32);
- result += toUnsigned(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
- }
+ if (tfdt[0] === 1) {
+ baseTime = getUint64(tfdt.subarray(4, 12));
+ } else {
+ baseTime = dv.getUint32(4);
+ } // convert base time to seconds if it is a valid number.
- return result;
- })[0];
- baseTime = typeof baseTime === 'number' && !isNaN(baseTime) ? baseTime : Infinity; // convert base time to seconds
- return baseTime / scale;
- });
- })); // return the minimum
+ var seconds;
+
+ if (typeof baseTime === 'bigint') {
+ seconds = baseTime / window_1.BigInt(scale);
+ } else if (typeof baseTime === 'number' && !isNaN(baseTime)) {
+ seconds = baseTime / scale;
+ }
+
+ if (seconds < Number.MAX_SAFE_INTEGER) {
+ seconds = Number(seconds);
+ }
- result = Math.min.apply(null, baseTimes);
- return isFinite(result) ? result : 0;
+ if (seconds < acc) {
+ acc = seconds;
+ }
+
+ return acc;
+ }, Infinity);
+ return typeof lowestTime === 'bigint' || isFinite(lowestTime) ? lowestTime : 0;
};
/**
* Determine the composition start, in seconds, for an MP4
var timescale = timescales[trackId] || 90e3; // return the composition start time, in seconds
- return (baseMediaDecodeTime + compositionTimeOffset) / timescale;
+ if (typeof baseMediaDecodeTime === 'bigint') {
+ compositionTimeOffset = window_1.BigInt(compositionTimeOffset);
+ timescale = window_1.BigInt(timescale);
+ }
+
+ var result = (baseMediaDecodeTime + compositionTimeOffset) / timescale;
+
+ if (typeof result === 'bigint' && result < Number.MAX_SAFE_INTEGER) {
+ result = Number(result);
+ }
+
+ return result;
};
/**
* Find the trackIds of the video tracks in this source.
};
}));
var TransmuxWorker = factory(workerCode$1);
- /* rollup-plugin-worker-factory end for worker!/Users/gkatsevman/p/http-streaming-release/src/transmuxer-worker.js */
+ /* rollup-plugin-worker-factory end for worker!/Users/gsinger/repos/clean/http-streaming/src/transmuxer-worker.js */
var handleData_ = function handleData_(event, transmuxedData, callback) {
var _event$data$segment = event.data.segment,
return false;
};
- var mediaDuration = function mediaDuration(audioTimingInfo, videoTimingInfo) {
- var audioDuration = audioTimingInfo && typeof audioTimingInfo.start === 'number' && typeof audioTimingInfo.end === 'number' ? audioTimingInfo.end - audioTimingInfo.start : 0;
- var videoDuration = videoTimingInfo && typeof videoTimingInfo.start === 'number' && typeof videoTimingInfo.end === 'number' ? videoTimingInfo.end - videoTimingInfo.start : 0;
- return Math.max(audioDuration, videoDuration);
+ var mediaDuration = function mediaDuration(timingInfos) {
+ var maxDuration = 0;
+ ['video', 'audio'].forEach(function (type) {
+ var typeTimingInfo = timingInfos[type + "TimingInfo"];
+
+ if (!typeTimingInfo) {
+ return;
+ }
+
+ var start = typeTimingInfo.start,
+ end = typeTimingInfo.end;
+ var duration;
+
+ if (typeof start === 'bigint' || typeof end === 'bigint') {
+ duration = window.BigInt(end) - window.BigInt(start);
+ } else if (typeof start === 'number' && typeof end === 'number') {
+ duration = end - start;
+ }
+
+ if (typeof duration !== 'undefined' && duration > maxDuration) {
+ maxDuration = duration;
+ }
+ }); // convert back to a number if it is lower than MAX_SAFE_INTEGER
+ // as we only need BigInt when we are above that.
+
+ if (typeof maxDuration === 'bigint' && maxDuration < Number.MAX_SAFE_INTEGER) {
+ maxDuration = Number(maxDuration);
+ }
+
+ return maxDuration;
};
var segmentTooLong = function segmentTooLong(_ref3) {
return null;
}
- var segmentDuration = mediaDuration(segmentInfo.audioTimingInfo, segmentInfo.videoTimingInfo); // Don't report if we lack information.
+ var segmentDuration = mediaDuration({
+ audioTimingInfo: segmentInfo.audioTimingInfo,
+ videoTimingInfo: segmentInfo.videoTimingInfo
+ }); // Don't report if we lack information.
//
// If the segment has a duration of 0 it is either a lack of information or a
// metadata only segment and shouldn't be reported here.
return TimelineChangeController;
}(videojs.EventTarget);
- /* rollup-plugin-worker-factory start for worker!/Users/gkatsevman/p/http-streaming-release/src/decrypter-worker.js */
+ /* rollup-plugin-worker-factory start for worker!/Users/gsinger/repos/clean/http-streaming/src/decrypter-worker.js */
var workerCode = transform(getWorkerString(function () {
};
}));
var Decrypter = factory(workerCode);
- /* rollup-plugin-worker-factory end for worker!/Users/gkatsevman/p/http-streaming-release/src/decrypter-worker.js */
+ /* rollup-plugin-worker-factory end for worker!/Users/gsinger/repos/clean/http-streaming/src/decrypter-worker.js */
/**
* Convert the properties of an HLS track into an audioTrackKind.
};
_proto.onSyncInfoUpdate_ = function onSyncInfoUpdate_() {
- var audioSeekable; // If we have two source buffers and only one is created then the seekable range will be incorrect.
- // We should wait until all source buffers are created.
+ var audioSeekable; // TODO check for creation of both source buffers before updating seekable
+ //
+ // A fix was made to this function where a check for
+ // this.sourceUpdater_.hasCreatedSourceBuffers
+ // was added to ensure that both source buffers were created before seekable was
+ // updated. However, it originally had a bug where it was checking for a true and
+ // returning early instead of checking for false. Setting it to check for false to
+ // return early though created other issues. A call to play() would check for seekable
+ // end without verifying that a seekable range was present. In addition, even checking
+ // for that didn't solve some issues, as handleFirstPlay is sometimes worked around
+ // due to a media update calling load on the segment loaders, skipping a seek to live,
+ // thereby starting live streams at the beginning of the stream rather than at the end.
+ //
+ // This conditional should be fixed to wait for the creation of two source buffers at
+ // the same time as the other sections of code are fixed to properly seek to live and
+ // not throw an error due to checking for a seekable end when no seekable range exists.
+ //
+ // For now, fall back to the older behavior, with the understanding that the seekable
+ // range may not be completely correct, leading to a suboptimal initial live point.
- if (!this.masterPlaylistLoader_ || this.sourceUpdater_.hasCreatedSourceBuffers()) {
+ if (!this.masterPlaylistLoader_) {
return;
}
return;
}
- if(player == null){
- return;
- }
-
seekTo = player.duration() !== Infinity && player.currentTime() || 0;
player.one('loadedmetadata', loadedMetadataHandler);
player.src(sourceObj);
initPlugin(this, options);
};
- var version$4 = "2.12.0";
- var version$3 = "5.14.1";
- var version$2 = "0.19.2";
+ var version$4 = "2.13.1";
+ var version$3 = "6.0.1";
+ var version$2 = "0.21.0";
var version$1 = "4.7.0";
var version = "3.1.2";
var Vhs = {
this.mediaSourceUrl_ = window.URL.createObjectURL(this.masterPlaylistController_.mediaSource);
this.tech_.src(this.mediaSourceUrl_);
+ };
+
+ _proto.createKeySessions_ = function createKeySessions_() {
+ var _this4 = this;
+
+ var audioPlaylistLoader = this.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader;
+ this.logger_('waiting for EME key session creation');
+ waitForKeySessionCreation({
+ player: this.player_,
+ sourceKeySystems: this.source_.keySystems,
+ audioMedia: audioPlaylistLoader && audioPlaylistLoader.media(),
+ mainPlaylists: this.playlists.master.playlists
+ }).then(function () {
+ _this4.logger_('created EME key session');
+
+ _this4.masterPlaylistController_.sourceUpdater_.initializedEme();
+ })["catch"](function (err) {
+ _this4.logger_('error while creating EME key session', err);
+
+ _this4.player_.error({
+ message: 'Failed to initialize media keys for EME',
+ code: 3
+ });
+ });
+ };
+
+ _proto.handleWaitingForKey_ = function handleWaitingForKey_() {
+ // If waitingforkey is fired, it's possible that the data that's necessary to retrieve
+ // the key is in the manifest. While this should've happened on initial source load, it
+ // may happen again in live streams where the keys change, and the manifest info
+ // reflects the update.
+ //
+ // Because videojs-contrib-eme compares the PSSH data we send to that of PSSH data it's
+ // already requested keys for, we don't have to worry about this generating extraneous
+ // requests.
+ this.logger_('waitingforkey fired, attempting to create any new key sessions');
+ this.createKeySessions_();
}
/**
* If necessary and EME is available, sets up EME options and waits for key session
;
_proto.setupEme_ = function setupEme_() {
- var _this4 = this;
+ var _this5 = this;
var audioPlaylistLoader = this.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader;
var didSetupEmeOptions = setupEmeOptions({
});
this.player_.tech_.on('keystatuschange', function (e) {
if (e.status === 'output-restricted') {
- _this4.masterPlaylistController_.blacklistCurrentPlaylist({
- playlist: _this4.masterPlaylistController_.media(),
+ _this5.masterPlaylistController_.blacklistCurrentPlaylist({
+ playlist: _this5.masterPlaylistController_.media(),
message: "DRM keystatus changed to " + e.status + ". Playlist will fail to play. Check for HDCP content.",
blacklistDuration: Infinity
});
}
- }); // In IE11 this is too early to initialize media keys, and IE11 does not support
+ });
+ this.handleWaitingForKey_ = this.handleWaitingForKey_.bind(this);
+ this.player_.tech_.on('waitingforkey', this.handleWaitingForKey_); // In IE11 this is too early to initialize media keys, and IE11 does not support
// promises.
if (videojs.browser.IE_VERSION === 11 || !didSetupEmeOptions) {
return;
}
- this.logger_('waiting for EME key session creation');
- waitForKeySessionCreation({
- player: this.player_,
- sourceKeySystems: this.source_.keySystems,
- audioMedia: audioPlaylistLoader && audioPlaylistLoader.media(),
- mainPlaylists: this.playlists.master.playlists
- }).then(function () {
- _this4.logger_('created EME key session');
-
- _this4.masterPlaylistController_.sourceUpdater_.initializedEme();
- })["catch"](function (err) {
- _this4.logger_('error while creating EME key session', err);
-
- _this4.player_.error({
- message: 'Failed to initialize media keys for EME',
- code: 3
- });
- });
+ this.createKeySessions_();
}
/**
* Initializes the quality levels and sets listeners to update them.
;
_proto.setupQualityLevels_ = function setupQualityLevels_() {
- var _this5 = this;
+ var _this6 = this;
var player = videojs.players[this.tech_.options_.playerId]; // if there isn't a player or there isn't a qualityLevels plugin
// or qualityLevels_ listeners have already been setup, do nothing.
this.qualityLevels_ = player.qualityLevels();
this.masterPlaylistController_.on('selectedinitialmedia', function () {
- handleVhsLoadedMetadata(_this5.qualityLevels_, _this5);
+ handleVhsLoadedMetadata(_this6.qualityLevels_, _this6);
});
this.playlists.on('mediachange', function () {
- handleVhsMediaChange(_this5.qualityLevels_, _this5.playlists);
+ handleVhsMediaChange(_this6.qualityLevels_, _this6.playlists);
});
}
/**
this.mediaSourceUrl_ = null;
}
+ if (this.tech_) {
+ this.tech_.off('waitingforkey', this.handleWaitingForKey_);
+ }
+
_Component.prototype.dispose.call(this);
};
}
var _videojs$mergeOptions = videojs.mergeOptions(videojs.options, options),
- _videojs$mergeOptions2 = _videojs$mergeOptions.vhs.overrideNative,
- overrideNative = _videojs$mergeOptions2 === void 0 ? !videojs.browser.IS_ANY_SAFARI : _videojs$mergeOptions2;
-
+ _videojs$mergeOptions2 = _videojs$mergeOptions.vhs;
+
+ _videojs$mergeOptions2 = _videojs$mergeOptions2 === void 0 ? {} : _videojs$mergeOptions2;
+ var _videojs$mergeOptions3 = _videojs$mergeOptions2.overrideNative,
+ overrideNative = _videojs$mergeOptions3 === void 0 ? !videojs.browser.IS_ANY_SAFARI : _videojs$mergeOptions3,
+ _videojs$mergeOptions4 = _videojs$mergeOptions.hls;
+ _videojs$mergeOptions4 = _videojs$mergeOptions4 === void 0 ? {} : _videojs$mergeOptions4;
+ var _videojs$mergeOptions5 = _videojs$mergeOptions4.overrideNative,
+ legacyOverrideNative = _videojs$mergeOptions5 === void 0 ? false : _videojs$mergeOptions5;
var supportedType = simpleTypeFromSourceType(type);
- var canUseMsePlayback = supportedType && (!Vhs.supportsTypeNatively(supportedType) || overrideNative);
+ var canUseMsePlayback = supportedType && (!Vhs.supportsTypeNatively(supportedType) || legacyOverrideNative || overrideNative);
return canUseMsePlayback ? 'maybe' : '';
}
};
return videojs;
-})));
+})));
\ No newline at end of file