@cycjimmy/jsmpeg-player
Version:
MPEG1 Video Player Based On JSMpeg
1,583 lines (1,264 loc) • 216 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.JSMpeg = factory());
}(this, (function () { 'use strict';
function _extends() {
_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;
};
return _extends.apply(this, arguments);
}
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
function unwrapExports (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var isString = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
/**
* determine a string type
* @param str
* @returns {boolean}
*/
var _default = function _default(str) {
return typeof str === 'string' && str.constructor === String;
};
exports["default"] = _default;
});
var isString$1 = unwrapExports(isString);
var isPromise = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
/**
* determine a promise type
* @param promise
* @returns {boolean}
*/
var _default = function _default(promise) {
return Object.prototype.toString.call(promise).slice(8, -1) === 'Promise';
};
exports["default"] = _default;
});
unwrapExports(isPromise);
var functionToPromise = createCommonjsModule(function (module, exports) {
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _isPromise = _interopRequireDefault(isPromise);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
"default": obj
};
}
/**
* function to promise
* @param normalFunction
* @param timeout
* @returns {Promise<any>}
*/
var _default = function _default(normalFunction) {
var timeout = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
if ((0, _isPromise["default"])(normalFunction)) {
return normalFunction;
} // eslint-disable-next-line no-undef
return new Promise(function (resolve) {
normalFunction();
setTimeout(resolve, timeout);
});
};
exports["default"] = _default;
});
var functionToPromise$1 = unwrapExports(functionToPromise);
function styleInject(css, ref) {
if (ref === void 0) ref = {};
var insertAt = ref.insertAt;
if (!css || typeof document === 'undefined') {
return;
}
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var css_248z = "._2RIcqcDcdd,._9Rxgc3-sVf,.H-DEgIh9Aq,.nk-GvVcgZw{position:absolute;z-index:1;left:0;top:0;width:100%;height:100%}._9Rxgc3-sVf{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}._2RIcqcDcdd,.nk-GvVcgZw{display:block}._2RIcqcDcdd.HE6yLI4_vV{display:none}._9Rxgc3-sVf,.H-DEgIh9Aq{opacity:.7;cursor:pointer;-webkit-tap-highlight-color:rgba(255,0,0,0)}.HE6yLI4_vV._9Rxgc3-sVf,.HE6yLI4_vV.H-DEgIh9Aq{display:none}._9Rxgc3-sVf{z-index:10}._9Rxgc3-sVf>svg{width:12vw;height:12vw;max-width:60px;max-height:60px;fill:#fff}.H-DEgIh9Aq{z-index:10;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end;-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end}.H-DEgIh9Aq>svg{margin:0 15px 15px 0;width:9vw;height:9vw;max-width:40px;max-height:40px;fill:#fff}";
var _style = {"canvas":"nk-GvVcgZw","poster":"_2RIcqcDcdd","playButton":"_9Rxgc3-sVf","unmuteButton":"H-DEgIh9Aq","hidden":"HE6yLI4_vV"};
styleInject(css_248z);
/**
* PLAY_BUTTON HTML
* @type {string}
*/
var PLAY_BUTTON = "\n<svg viewBox=\"0 0 64 64\">\n <path d=\"M26,45.5L44,32L26,18.6v27V45.5L26,45.5z M32,2C15.4,2,2,15.5,2,32c0,16.6,13.4,30,30,30c16.6,0,30-13.4,30-30 C62,15.4,48.5,2,32,2L32,2z M32,56c-9.7,0-18.5-5.9-22.2-14.8C6.1,32.2,8.1,21.9,15,15c6.9-6.9,17.2-8.9,26.2-5.2 C50.1,13.5,56,22.3,56,32C56,45.3,45.2,56,32,56L32,56z\"/>\n</svg>\n";
/**
* UNMUTE_BUTTON HTML
* @type {string}
*/
var UNMUTE_BUTTON = "\n<svg viewBox=\"0 0 64 64\">\n <path d=\"M58.3,45.5l-4.8-4.3c1.4-2.9,2.2-6.2,2.2-9.6c0-11.1-8.2-20.3-18.9-21.9V3.3C50.9,4.9,62,16.9,62,31.6 C62,36.6,60.6,41.4,58.3,45.5L58.3,45.5z M30.4,5.6v15.2l-8.3-7.3L30.4,5.6L30.4,5.6z M36.7,19.9c4.6,1.9,7.9,6.4,7.9,11.7 c0,0.6-0.1,1.1-0.1,1.7l-7.8-6.9V19.9L36.7,19.9z M57.5,60.7l-7.1-6.3c-3.9,2.9-8.6,4.8-13.7,5.4v-6.4c3.2-0.5,6.2-1.7,8.8-3.4 l-8.1-7.2c-0.2,0.1-0.5,0.3-0.7,0.4v-1l-6.3-5.6v20.2L15.4,42.6H2V20.5h10.2l-9.7-8.6l4.2-4.7L61.7,56L57.5,60.7L57.5,60.7z\"/>\n</svg>\n";
var VideoElement = /*#__PURE__*/function () {
function VideoElement(wrapper, videoUrl, _temp, overlayOptions) {
var _ref = _temp === void 0 ? {} : _temp,
_ref$canvas = _ref.canvas,
canvas = _ref$canvas === void 0 ? '' : _ref$canvas,
_ref$poster = _ref.poster,
poster = _ref$poster === void 0 ? '' : _ref$poster,
_ref$autoplay = _ref.autoplay,
autoplay = _ref$autoplay === void 0 ? false : _ref$autoplay,
_ref$autoSetWrapperSi = _ref.autoSetWrapperSize,
autoSetWrapperSize = _ref$autoSetWrapperSi === void 0 ? false : _ref$autoSetWrapperSi,
_ref$loop = _ref.loop,
loop = _ref$loop === void 0 ? false : _ref$loop,
_ref$control = _ref.control,
control = _ref$control === void 0 ? true : _ref$control,
_ref$decodeFirstFrame = _ref.decodeFirstFrame,
decodeFirstFrame = _ref$decodeFirstFrame === void 0 ? true : _ref$decodeFirstFrame,
_ref$picMode = _ref.picMode,
picMode = _ref$picMode === void 0 ? false : _ref$picMode,
_ref$progressive = _ref.progressive,
progressive = _ref$progressive === void 0 ? true : _ref$progressive,
_ref$chunkSize = _ref.chunkSize,
chunkSize = _ref$chunkSize === void 0 ? 1024 * 1024 : _ref$chunkSize,
_ref$hooks = _ref.hooks,
hooks = _ref$hooks === void 0 ? {} : _ref$hooks;
if (overlayOptions === void 0) {
overlayOptions = {};
}
this.options = _extends({
videoUrl: videoUrl,
canvas: canvas,
poster: poster,
picMode: picMode,
autoplay: autoplay,
autoSetWrapperSize: autoSetWrapperSize,
loop: loop,
control: control,
decodeFirstFrame: decodeFirstFrame,
progressive: progressive,
chunkSize: chunkSize,
hooks: _extends({
play: function play() {},
pause: function pause() {},
stop: function stop() {},
load: function load() {}
}, hooks)
}, overlayOptions);
this.options.needPlayButton = this.options.control && !this.options.picMode;
this.player = null; // Setup canvas and play button
this.els = {
wrapper: isString$1(wrapper) ? document.querySelector(wrapper) : wrapper,
canvas: null,
playButton: document.createElement('div'),
unmuteButton: null,
poster: null
};
if (window.getComputedStyle(this.els.wrapper).getPropertyValue('position') === 'static') {
this.els.wrapper.style.position = 'relative';
}
this.els.wrapper.clientRect = this.els.wrapper.getBoundingClientRect();
this.initCanvas();
this.initPlayButton();
this.initPlayer();
}
var _proto = VideoElement.prototype;
_proto.initCanvas = function initCanvas() {
if (this.options.canvas) {
this.els.canvas = isString$1(this.options.canvas) ? document.querySelector(this.options.canvas) : this.options.canvas;
} else {
this.els.canvas = document.createElement('canvas');
this.els.canvas.classList.add(_style.canvas);
this.els.wrapper.appendChild(this.els.canvas);
}
};
_proto.initPlayer = function initPlayer() {
var _this = this;
// Parse the data-options - we try to decode the values as json. This way
// we can get proper boolean and number values. If JSON.parse() fails,
// treat it as a string.
this.options = _extends(this.options, {
canvas: this.els.canvas
}); // eslint-disable-next-line no-underscore-dangle
var _options = _extends({}, this.options, {
autoplay: false
}); // Create the player instance
this.player = new Player(this.options.videoUrl, _options, {
play: function play() {
if (_this.options.needPlayButton) {
_this.els.playButton.classList.add(_style.hidden);
}
if (_this.els.poster) {
_this.els.poster.classList.add(_style.hidden);
}
_this.options.hooks.play();
},
pause: function pause() {
if (_this.options.needPlayButton) {
_this.els.playButton.classList.remove(_style.hidden);
}
_this.options.hooks.pause();
},
stop: function stop() {
if (_this.els.poster) {
_this.els.poster.classList.remove(_style.hidden);
}
_this.options.hooks.stop();
},
load: function load() {
if (_this.options.autoplay) {
_this.play();
}
_this._autoSetWrapperSize();
_this.options.hooks.load();
}
});
this._copyPlayerFuncs();
this.els.wrapper.playerInstance = this.player; // Setup the poster element, if any
if (this.options.poster && !this.options.autoplay && !this.player.options.streaming) {
this.options.decodeFirstFrame = false;
this.els.poster = new Image();
this.els.poster.src = this.options.poster;
this.els.poster.classList.add(_style.poster);
this.els.wrapper.appendChild(this.els.poster);
} // Add the click handler if this video is pausable
if (!this.player.options.streaming) {
this.els.wrapper.addEventListener('click', this.onClick.bind(this));
} // Hide the play button if this video immediately begins playing
if (this.options.autoplay || this.player.options.streaming) {
this.els.playButton.classList.add(_style.hidden);
} // Set up the unlock audio button for iOS devices. iOS only allows us to
// play audio after a user action has initiated playing. For autoplay or
// streaming players we set up a muted speaker icon as the button. For all
// others, we can simply use the play button.
if (this.player.audioOut && !this.player.audioOut.unlocked) {
var unlockAudioElement = this.els.wrapper;
if (this.options.autoplay || this.player.options.streaming) {
this.els.unmuteButton = document.createElement('div');
this.els.unmuteButton.innerHTML = UNMUTE_BUTTON;
this.els.unmuteButton.classList.add(_style.unmuteButton);
this.els.wrapper.appendChild(this.els.unmuteButton);
unlockAudioElement = this.els.unmuteButton;
}
this.unlockAudioBound = this.onUnlockAudio.bind(this, unlockAudioElement);
unlockAudioElement.addEventListener('touchstart', this.unlockAudioBound, false);
unlockAudioElement.addEventListener('click', this.unlockAudioBound, true);
}
};
_proto.initPlayButton = function initPlayButton() {
if (!this.options.needPlayButton) {
return;
}
this.els.playButton.classList.add(_style.playButton);
this.els.playButton.innerHTML = PLAY_BUTTON;
this.els.wrapper.appendChild(this.els.playButton);
};
_proto._autoSetWrapperSize = function _autoSetWrapperSize() {
var _this2 = this;
if (!this.options.autoSetWrapperSize) {
return Promise.resolve();
}
var destination = this.player.video.destination;
if (!destination) {
return Promise.resolve();
}
return Promise.resolve().then(function () {
return functionToPromise$1(function () {
_this2.els.wrapper.style.width = destination.width + "px";
_this2.els.wrapper.style.height = destination.height + "px";
});
});
};
_proto.onUnlockAudio = function onUnlockAudio(element, ev) {
var _this3 = this;
if (this.els.unmuteButton) {
ev.preventDefault();
ev.stopPropagation();
}
this.player.audioOut.unlock(function () {
if (_this3.els.unmuteButton) {
_this3.els.unmuteButton.classList.add(_style.hidden);
}
element.removeEventListener('touchstart', _this3.unlockAudioBound);
element.removeEventListener('click', _this3.unlockAudioBound);
});
};
_proto.onClick = function onClick() {
if (!this.options.control) {
return;
}
if (this.player.isPlaying) {
this.pause();
} else {
this.play();
}
}
/**
* copy player functions
* @private
*/
;
_proto._copyPlayerFuncs = function _copyPlayerFuncs() {
var _this4 = this;
this.play = function () {
return _this4.player.play();
};
this.pause = function () {
return _this4.player.pause();
};
this.stop = function () {
return _this4.player.stop();
};
this.destroy = function () {
_this4.player.destroy();
_this4.els.wrapper.innerHTML = '';
_this4.els.wrapper.playerInstance = null;
};
};
return VideoElement;
}();
var Now = function Now() {
return window.performance ? window.performance.now() / 1000 : Date.now() / 1000;
};
var CreateVideoElements = function CreateVideoElements() {
var elements = document.querySelectorAll('.jsmpeg');
for (var i = 0; i < elements.length; i++) {
// eslint-disable-next-line no-new
new VideoElement(elements[i]);
}
};
var Fill = function Fill(array, value) {
if (array.fill) {
array.fill(value);
} else {
for (var i = 0; i < array.length; i++) {
array[i] = value;
}
}
};
var Base64ToArrayBuffer = function Base64ToArrayBuffer(base64) {
var binary = window.atob(base64);
var length = binary.length;
var bytes = new Uint8Array(length);
for (var i = 0; i < length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes.buffer;
};
/* eslint class-methods-use-this: ["error", { "exceptMethods": ["resume"] }] */
var AjaxSource = /*#__PURE__*/function () {
function AjaxSource(url, options) {
this.url = url;
this.destination = null;
this.request = null;
this.streaming = false;
this.completed = false;
this.established = false;
this.progress = 0;
this.onEstablishedCallback = options.onSourceEstablished;
this.onCompletedCallback = options.onSourceCompleted;
if (options.hookOnEstablished) {
this.hookOnEstablished = options.hookOnEstablished;
}
}
var _proto = AjaxSource.prototype;
_proto.connect = function connect(destination) {
this.destination = destination;
};
_proto.start = function start() {
this.request = new XMLHttpRequest(); // eslint-disable-next-line func-names
this.request.onreadystatechange = function () {
if (this.request.readyState === this.request.DONE && this.request.status === 200) {
this.onLoad(this.request.response);
}
}.bind(this);
this.request.onprogress = this.onProgress.bind(this);
this.request.open('GET', this.url);
this.request.responseType = 'arraybuffer';
this.request.send();
};
_proto.resume = function resume() {// Nothing to do here
};
_proto.destroy = function destroy() {
this.request.abort();
};
_proto.onProgress = function onProgress(ev) {
this.progress = ev.loaded / ev.total;
};
_proto.onLoad = function onLoad(data) {
this.established = true;
this.completed = true;
this.progress = 1;
if (this.hookOnEstablished) {
this.hookOnEstablished();
}
if (this.onEstablishedCallback) {
this.onEstablishedCallback(this);
}
if (this.onCompletedCallback) {
this.onCompletedCallback(this);
}
if (this.destination) {
this.destination.write(data);
}
};
return AjaxSource;
}();
var AjaxProgressiveSource = /*#__PURE__*/function () {
function AjaxProgressiveSource(url, options) {
this.url = url;
this.destination = null;
this.request = null;
this.streaming = false;
this.completed = false;
this.established = false;
this.progress = 0;
this.fileSize = 0;
this.loadedSize = 0;
this.chunkSize = options.chunkSize || 1024 * 1024;
this.isLoading = false;
this.loadStartTime = 0;
this.throttled = options.throttled !== false;
this.aborted = false;
this.onEstablishedCallback = options.onSourceEstablished;
this.onCompletedCallback = options.onSourceCompleted;
if (options.hookOnEstablished) {
this.hookOnEstablished = options.hookOnEstablished;
}
}
var _proto = AjaxProgressiveSource.prototype;
_proto.connect = function connect(destination) {
this.destination = destination;
};
_proto.start = function start() {
var _this = this;
this.request = new XMLHttpRequest();
this.request.onreadystatechange = function () {
if (_this.request.readyState === _this.request.DONE) {
_this.fileSize = parseInt(_this.request.getResponseHeader('Content-Length'), 10);
_this.loadNextChunk();
}
};
this.request.onprogress = this.onProgress.bind(this);
this.request.open('HEAD', this.url);
this.request.send();
};
_proto.resume = function resume(secondsHeadroom) {
if (this.isLoading || !this.throttled) {
return;
} // Guess the worst case loading time with lots of safety margin. This is
// somewhat arbitrary...
var worstCaseLoadingTime = this.loadTime * 8 + 2;
if (worstCaseLoadingTime > secondsHeadroom) {
this.loadNextChunk();
}
};
_proto.destroy = function destroy() {
this.request.abort();
this.aborted = true;
};
_proto.loadNextChunk = function loadNextChunk() {
var _this2 = this;
var start = this.loadedSize;
var end = Math.min(this.loadedSize + this.chunkSize - 1, this.fileSize - 1);
if (start >= this.fileSize || this.aborted) {
this.completed = true;
if (this.onCompletedCallback) {
this.onCompletedCallback(this);
}
return;
}
this.isLoading = true;
this.loadStartTime = Now();
this.request = new XMLHttpRequest();
this.request.onreadystatechange = function () {
if (_this2.request.readyState === _this2.request.DONE && _this2.request.status >= 200 && _this2.request.status < 300) {
_this2.onChunkLoad(_this2.request.response);
} else if (_this2.request.readyState === _this2.request.DONE) {
// Retry?
if (_this2.loadFails++ < 3) {
_this2.loadNextChunk();
}
}
};
if (start === 0) {
this.request.onprogress = this.onProgress.bind(this);
}
this.request.open('GET', this.url + "?" + start + "-" + end);
this.request.setRequestHeader('Range', "bytes=" + start + "-" + end);
this.request.responseType = 'arraybuffer';
this.request.send();
};
_proto.onProgress = function onProgress(ev) {
this.progress = ev.loaded / ev.total;
};
_proto.onChunkLoad = function onChunkLoad(data) {
var isFirstChunk = !this.established;
this.established = true;
this.progress = 1;
this.loadedSize += data.byteLength;
this.loadFails = 0;
this.isLoading = false;
if (isFirstChunk && this.hookOnEstablished) {
this.hookOnEstablished();
}
if (isFirstChunk && this.onEstablishedCallback) {
this.onEstablishedCallback(this);
}
if (this.destination) {
this.destination.write(data);
}
this.loadTime = Now() - this.loadStartTime;
if (!this.throttled) {
this.loadNextChunk();
}
};
return AjaxProgressiveSource;
}();
var WSSource = /*#__PURE__*/function () {
function WSSource(url, options) {
this.url = url;
this.options = options;
this.socket = null;
this.streaming = true;
this.callbacks = {
connect: [],
data: []
};
this.destination = null;
this.reconnectInterval = // eslint-disable-next-line no-undefined
options.reconnectInterval !== undefined ? options.reconnectInterval : 5;
this.shouldAttemptReconnect = !!this.reconnectInterval;
this.completed = false;
this.established = false;
this.progress = 0;
this.reconnectTimeoutId = 0;
this.onEstablishedCallback = options.onSourceEstablished;
this.onCompletedCallback = options.onSourceCompleted; // Never used
if (options.hookOnEstablished) {
this.hookOnEstablished = options.hookOnEstablished;
}
}
var _proto = WSSource.prototype;
_proto.connect = function connect(destination) {
this.destination = destination;
};
_proto.destroy = function destroy() {
clearTimeout(this.reconnectTimeoutId);
this.shouldAttemptReconnect = false;
this.socket.close();
};
_proto.start = function start() {
this.shouldAttemptReconnect = !!this.reconnectInterval;
this.progress = 0;
this.established = false;
this.socket = new WebSocket(this.url, this.options.protocols || null);
this.socket.binaryType = 'arraybuffer';
this.socket.onmessage = this.onMessage.bind(this);
this.socket.onopen = this.onOpen.bind(this);
this.socket.onerror = this.onClose.bind(this);
this.socket.onclose = this.onClose.bind(this);
} // eslint-disable-next-line class-methods-use-this
;
_proto.resume = function resume() {// Nothing to do here
};
_proto.onOpen = function onOpen() {
this.progress = 1;
};
_proto.onClose = function onClose() {
var _this = this;
if (this.shouldAttemptReconnect) {
clearTimeout(this.reconnectTimeoutId);
this.reconnectTimeoutId = setTimeout(function () {
_this.start();
}, this.reconnectInterval * 1000);
}
};
_proto.onMessage = function onMessage(ev) {
var isFirstChunk = !this.established;
this.established = true;
if (isFirstChunk && this.hookOnEstablished) {
this.hookOnEstablished();
}
if (isFirstChunk && this.onEstablishedCallback) {
this.onEstablishedCallback(this);
}
if (this.destination) {
this.destination.write(ev.data);
}
};
return WSSource;
}();
var BitBuffer = /*#__PURE__*/function () {
function BitBuffer(bufferOrLength, mode) {
if (typeof bufferOrLength === 'object') {
this.bytes = bufferOrLength instanceof Uint8Array ? bufferOrLength : new Uint8Array(bufferOrLength);
this.byteLength = this.bytes.length;
} else {
this.bytes = new Uint8Array(bufferOrLength || 1024 * 1024);
this.byteLength = 0;
}
this.mode = mode || BitBuffer.MODE.EXPAND;
this.index = 0;
}
var _proto = BitBuffer.prototype;
_proto.resize = function resize(size) {
var newBytes = new Uint8Array(size);
if (this.byteLength !== 0) {
this.byteLength = Math.min(this.byteLength, size);
newBytes.set(this.bytes, 0, this.byteLength);
}
this.bytes = newBytes;
this.index = Math.min(this.index, this.byteLength << 3);
};
_proto.evict = function evict(sizeNeeded) {
var bytePos = this.index >> 3;
var available = this.bytes.length - this.byteLength; // If the current index is the write position, we can simply reset both
// to 0. Also reset (and throw away yet unread data) if we won't be able
// to fit the new data in even after a normal eviction.
if (this.index === this.byteLength << 3 || sizeNeeded > available + bytePos // emergency evac
) {
this.byteLength = 0;
this.index = 0;
return;
} else if (bytePos === 0) {
// Nothing read yet - we can't evict anything
return;
} // Some browsers don't support copyWithin() yet - we may have to do
// it manually using set and a subarray
if (this.bytes.copyWithin) {
this.bytes.copyWithin(0, bytePos, this.byteLength);
} else {
this.bytes.set(this.bytes.subarray(bytePos, this.byteLength));
}
this.byteLength -= bytePos;
this.index -= bytePos << 3;
};
_proto.write = function write(buffers) {
var isArrayOfBuffers = typeof buffers[0] === 'object';
var totalLength = 0;
var available = this.bytes.length - this.byteLength; // Calculate total byte length
if (isArrayOfBuffers) {
totalLength = 0;
for (var i = 0; i < buffers.length; i++) {
totalLength += buffers[i].byteLength;
}
} else {
totalLength = buffers.byteLength;
} // Do we need to resize or evict?
if (totalLength > available) {
if (this.mode === BitBuffer.MODE.EXPAND) {
var newSize = Math.max(this.bytes.length * 2, totalLength - available);
this.resize(newSize);
} else {
this.evict(totalLength);
}
}
if (isArrayOfBuffers) {
for (var _i = 0; _i < buffers.length; _i++) {
this.appendSingleBuffer(buffers[_i]);
}
} else {
this.appendSingleBuffer(buffers);
}
return totalLength;
};
_proto.appendSingleBuffer = function appendSingleBuffer(buffer) {
buffer = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
this.bytes.set(buffer, this.byteLength);
this.byteLength += buffer.length;
};
_proto.findNextStartCode = function findNextStartCode() {
for (var i = this.index + 7 >> 3; i < this.byteLength; i++) {
if (this.bytes[i] === 0x00 && this.bytes[i + 1] === 0x00 && this.bytes[i + 2] === 0x01) {
this.index = i + 4 << 3;
return this.bytes[i + 3];
}
}
this.index = this.byteLength << 3;
return -1;
};
_proto.findStartCode = function findStartCode(code) {
var current = this.findNextStartCode();
if (current === code || current === -1) {
return current;
}
return -1;
};
_proto.nextBytesAreStartCode = function nextBytesAreStartCode() {
var i = this.index + 7 >> 3;
return i >= this.byteLength || this.bytes[i] === 0x00 && this.bytes[i + 1] === 0x00 && this.bytes[i + 2] === 0x01;
};
_proto.peek = function peek(count) {
var offset = this.index;
var value = 0;
while (count) {
var currentByte = this.bytes[offset >> 3];
var remaining = 8 - (offset & 7); // remaining bits in byte
var read = remaining < count ? remaining : count; // bits in this run
var shift = remaining - read;
var mask = 0xff >> 8 - read;
value = value << read | (currentByte & mask << shift) >> shift;
offset += read;
count -= read;
}
return value;
};
_proto.read = function read(count) {
var value = this.peek(count);
this.index += count;
return value;
};
_proto.skip = function skip(count) {
return this.index += count;
};
_proto.rewind = function rewind(count) {
this.index = Math.max(this.index - count, 0);
};
_proto.has = function has(count) {
return (this.byteLength << 3) - this.index >= count;
};
return BitBuffer;
}();
BitBuffer.MODE = {
EVICT: 1,
EXPAND: 2
};
var TS = /*#__PURE__*/function () {
function TS() {
this.bits = null;
this.leftoverBytes = null;
this.guessVideoFrameEnd = true;
this.pidsToStreamIds = {};
this.pesPacketInfo = {};
this.startTime = 0;
this.currentTime = 0;
}
var _proto = TS.prototype;
_proto.connect = function connect(streamId, destination) {
this.pesPacketInfo[streamId] = {
destination: destination,
currentLength: 0,
totalLength: 0,
pts: 0,
buffers: []
};
};
_proto.write = function write(buffer) {
if (this.leftoverBytes) {
var totalLength = buffer.byteLength + this.leftoverBytes.byteLength;
this.bits = new BitBuffer(totalLength);
this.bits.write([this.leftoverBytes, buffer]);
} else {
this.bits = new BitBuffer(buffer);
} // eslint-disable-next-line no-empty
while (this.bits.has(188 << 3) && this.parsePacket()) {}
var leftoverCount = this.bits.byteLength - (this.bits.index >> 3);
this.leftoverBytes = leftoverCount > 0 ? this.bits.bytes.subarray(this.bits.index >> 3) : null;
};
_proto.parsePacket = function parsePacket() {
// Check if we're in sync with packet boundaries; attempt to resync if not.
if (this.bits.read(8) !== 0x47) {
if (!this.resync()) {
// Couldn't resync; maybe next time...
return false;
}
}
var end = (this.bits.index >> 3) + 187; // eslint-disable-next-line no-unused-vars
var transportError = this.bits.read(1);
var payloadStart = this.bits.read(1); // eslint-disable-next-line no-unused-vars
var transportPriority = this.bits.read(1);
var pid = this.bits.read(13); // eslint-disable-next-line no-unused-vars
var transportScrambling = this.bits.read(2);
var adaptationField = this.bits.read(2); // eslint-disable-next-line no-unused-vars
var continuityCounter = this.bits.read(4); // If this is the start of a new payload; signal the end of the previous
// frame, if we didn't do so already.
var streamId = this.pidsToStreamIds[pid];
if (payloadStart && streamId) {
var pi = this.pesPacketInfo[streamId];
if (pi && pi.currentLength) {
this.packetComplete(pi);
}
} // Extract current payload
if (adaptationField & 0x1) {
if (adaptationField & 0x2) {
var adaptationFieldLength = this.bits.read(8);
this.bits.skip(adaptationFieldLength << 3);
}
if (payloadStart && this.bits.nextBytesAreStartCode()) {
this.bits.skip(24);
streamId = this.bits.read(8);
this.pidsToStreamIds[pid] = streamId;
var packetLength = this.bits.read(16);
this.bits.skip(8);
var ptsDtsFlag = this.bits.read(2);
this.bits.skip(6);
var headerLength = this.bits.read(8);
var payloadBeginIndex = this.bits.index + (headerLength << 3);
var _pi = this.pesPacketInfo[streamId];
if (_pi) {
var pts = 0;
if (ptsDtsFlag & 0x2) {
// The Presentation Timestamp is encoded as 33(!) bit
// integer, but has a "marker bit" inserted at weird places
// in between, making the whole thing 5 bytes in size.
// You can't make this shit up...
this.bits.skip(4);
var p32_30 = this.bits.read(3);
this.bits.skip(1);
var p29_15 = this.bits.read(15);
this.bits.skip(1);
var p14_0 = this.bits.read(15);
this.bits.skip(1); // Can't use bit shifts here; we need 33 bits of precision,
// so we're using JavaScript's double number type. Also
// divide by the 90khz clock to get the pts in seconds.
pts = (p32_30 * 1073741824 + p29_15 * 32768 + p14_0) / 90000;
this.currentTime = pts;
if (this.startTime === -1) {
this.startTime = pts;
}
}
var payloadLength = packetLength ? packetLength - headerLength - 3 : 0;
this.packetStart(_pi, pts, payloadLength);
} // Skip the rest of the header without parsing it
this.bits.index = payloadBeginIndex;
}
if (streamId) {
// Attempt to detect if the PES packet is complete. For Audio (and
// other) packets, we received a total packet length with the PES
// header, so we can check the current length.
// For Video packets, we have to guess the end by detecting if this
// TS packet was padded - there's no good reason to pad a TS packet
// in between, but it might just fit exactly. If this fails, we can
// only wait for the next PES header for that stream.
var _pi2 = this.pesPacketInfo[streamId];
if (_pi2) {
var start = this.bits.index >> 3;
var complete = this.packetAddData(_pi2, start, end);
var hasPadding = !payloadStart && adaptationField & 0x2;
if (complete || this.guessVideoFrameEnd && hasPadding) {
this.packetComplete(_pi2);
}
}
}
}
this.bits.index = end << 3;
return true;
};
_proto.resync = function resync() {
// Check if we have enough data to attempt a resync. We need 5 full packets.
if (!this.bits.has(188 * 6 << 3)) {
return false;
}
var byteIndex = this.bits.index >> 3; // Look for the first sync token in the first 187 bytes
for (var i = 0; i < 187; i++) {
if (this.bits.bytes[byteIndex + i] === 0x47) {
// Look for 4 more sync tokens, each 188 bytes appart
var foundSync = true;
for (var j = 1; j < 5; j++) {
if (this.bits.bytes[byteIndex + i + 188 * j] !== 0x47) {
foundSync = false;
break;
}
}
if (foundSync) {
this.bits.index = byteIndex + i + 1 << 3;
return true;
}
}
} // In theory, we shouldn't arrive here. If we do, we had enough data but
// still didn't find sync - this can only happen if we were fed garbage
// data. Check your source!
console.warn('JSMpeg: Possible garbage data. Skipping.');
this.bits.skip(187 << 3);
return false;
} // eslint-disable-next-line class-methods-use-this
;
_proto.packetStart = function packetStart(pi, pts, payloadLength) {
pi.totalLength = payloadLength;
pi.currentLength = 0;
pi.pts = pts;
};
_proto.packetAddData = function packetAddData(pi, start, end) {
pi.buffers.push(this.bits.bytes.subarray(start, end));
pi.currentLength += end - start;
return pi.totalLength !== 0 && pi.currentLength >= pi.totalLength;
} // eslint-disable-next-line class-methods-use-this
;
_proto.packetComplete = function packetComplete(pi) {
pi.destination.write(pi.pts, pi.buffers);
pi.totalLength = 0;
pi.currentLength = 0;
pi.buffers = [];
};
return TS;
}();
TS.STREAM = {
PACK_HEADER: 0xba,
SYSTEM_HEADER: 0xbb,
PROGRAM_MAP: 0xbc,
PRIVATE_1: 0xbd,
PADDING: 0xbe,
PRIVATE_2: 0xbf,
AUDIO_1: 0xc0,
VIDEO_1: 0xe0,
DIRECTORY: 0xff
};
/* eslint class-methods-use-this: ["error", { "exceptMethods": ["destroy"] }] */
var BaseDecoder = /*#__PURE__*/function () {
function BaseDecoder(options) {
this.destination = null;
this.canPlay = false;
this.collectTimestamps = !options.streaming;
this.bytesWritten = 0;
this.timestamps = [];
this.timestampIndex = 0;
this.startTime = 0;
this.decodedTime = 0;
Object.defineProperty(this, 'currentTime', {
get: this.getCurrentTime
});
}
var _proto = BaseDecoder.prototype;
_proto.destroy = function destroy() {};
_proto.connect = function connect(destination) {
this.destination = destination;
};
_proto.bufferGetIndex = function bufferGetIndex() {
return this.bits.index;
};
_proto.bufferSetIndex = function bufferSetIndex(index) {
this.bits.index = index;
};
_proto.bufferWrite = function bufferWrite(buffers) {
return this.bits.write(buffers);
};
_proto.write = function write(pts, buffers) {
if (this.collectTimestamps) {
if (this.timestamps.length === 0) {
this.startTime = pts;
this.decodedTime = pts;
}
this.timestamps.push({
index: this.bytesWritten << 3,
time: pts
});
}
this.bytesWritten += this.bufferWrite(buffers);
this.canPlay = true;
};
_proto.seek = function seek(time) {
if (!this.collectTimestamps) {
return;
}
this.timestampIndex = 0;
for (var i = 0; i < this.timestamps.length; i++) {
if (this.timestamps[i].time > time) {
break;
}
this.timestampIndex = i;
}
var ts = this.timestamps[this.timestampIndex];
if (ts) {
this.bufferSetIndex(ts.index);
this.decodedTime = ts.time;
} else {
this.bits.index = 0;
this.decodedTime = this.startTime;
}
};
_proto.decode = function decode() {
this.advanceDecodedTime(0);
};
_proto.advanceDecodedTime = function advanceDecodedTime(seconds) {
if (this.collectTimestamps) {
var newTimestampIndex = -1;
var currentIndex = this.bufferGetIndex();
for (var i = this.timestampIndex; i < this.timestamps.length; i++) {
if (this.timestamps[i].index > currentIndex) {
break;
}
newTimestampIndex = i;
} // Did we find a new PTS, different from the last? If so, we don't have
// to advance the decoded time manually and can instead sync it exactly
// to the PTS.
if (newTimestampIndex !== -1 && newTimestampIndex !== this.timestampIndex) {
this.timestampIndex = newTimestampIndex;
this.decodedTime = this.timestamps[this.timestampIndex].time;
return;
}
}
this.decodedTime += seconds;
};
_proto.getCurrentTime = function getCurrentTime() {
return this.decodedTime;
};
return BaseDecoder;
}();
var MPEG1 = /*#__PURE__*/function (_BaseDecoder) {
_inheritsLoose(MPEG1, _BaseDecoder);
function MPEG1(options) {
var _this;
_this = _BaseDecoder.call(this, options) || this;
_this.onDecodeCallback = options.onVideoDecode;
var bufferSize = options.videoBufferSize || 512 * 1024;
var bufferMode = options.streaming ? BitBuffer.MODE.EVICT : BitBuffer.MODE.EXPAND;
_this.bits = new BitBuffer(bufferSize, bufferMode);
_this.customIntraQuantMatrix = new Uint8Array(64);
_this.customNonIntraQuantMatrix = new Uint8Array(64);
_this.blockData = new Int32Array(64);
_this.currentFrame = 0;
_this.decodeFirstFrame = options.decodeFirstFrame !== false;
return _this;
} // eslint-disable-next-line consistent-return
var _proto = MPEG1.prototype;
_proto.write = function write(pts, buffers) {
BaseDecoder.prototype.write.call(this, pts, buffers);
if (!this.hasSequenceHeader) {
if (this.bits.findStartCode(MPEG1.START.SEQUENCE) === -1) {
return false;
}
this.decodeSequenceHeader();
if (this.decodeFirstFrame) {
this.decode();
}
}
};
_proto.decode = function decode() {
var startTime = Now();
if (!this.hasSequenceHeader) {
return false;
}
if (this.bits.findStartCode(MPEG1.START.PICTURE) === -1) {
return false;
}
this.decodePicture();
this.advanceDecodedTime(1 / this.frameRate);
var elapsedTime = Now() - startTime;
if (this.onDecodeCallback) {
this.onDecodeCallback(this, elapsedTime);
}
return true;
};
_proto.readHuffman = function readHuffman(codeTable) {
var state = 0;
do {
state = codeTable[state + this.bits.read(1)];
} while (state >= 0 && codeTable[state] !== 0);
return codeTable[state + 2];
};
_proto.decodeSequenceHeader = function decodeSequenceHeader() {
var newWidth = this.bits.read(12);
var newHeight = this.bits.read(12); // skip pixel aspect ratio
this.bits.skip(4);
this.frameRate = MPEG1.PICTURE_RATE[this.bits.read(4)]; // skip bitRate, marker, bufferSize and constrained bit
this.bits.skip(18 + 1 + 10 + 1);
if (newWidth !== this.width || newHeight !== this.height) {
this.width = newWidth;
this.height = newHeight;
this.initBuffers();
if (this.destination) {
this.destination.resize(newWidth, newHeight);
}
}
if (this.bits.read(1)) {
// load custom intra quant matrix?
for (var i = 0; i < 64; i++) {
this.customIntraQuantMatrix[MPEG1.ZIG_ZAG[i]] = this.bits.read(8);
}
this.intraQuantMatrix = this.customIntraQuantMatrix;
}
if (this.bits.read(1)) {
// load custom non intra quant matrix?
for (var _i = 0; _i < 64; _i++) {
var idx = MPEG1.ZIG_ZAG[_i];
this.customNonIntraQuantMatrix[idx] = this.bits.read(8);
}
this.nonIntraQuantMatrix = this.customNonIntraQuantMatrix;
}
this.hasSequenceHeader = true;
};
_proto.initBuffers = function initBuffers() {
this.intraQuantMatrix = MPEG1.DEFAULT_INTRA_QUANT_MATRIX;
this.nonIntraQuantMatrix = MPEG1.DEFAULT_NON_INTRA_QUANT_MATRIX;
this.mbWidth = this.width + 15 >> 4;
this.mbHeight = this.height + 15 >> 4;
this.mbSize = this.mbWidth * this.mbHeight;
this.codedWidth = this.mbWidth << 4;
this.codedHeight = this.mbHeight << 4;
this.codedSize = this.codedWidth * this.codedHeight;
this.halfWidth = this.mbWidth << 3;
this.halfHeight = this.mbHeight << 3; // Allocated buffers and resize the canvas
this.currentY = new Uint8ClampedArray(this.codedSize);
this.currentY32 = new Uint32Array(this.currentY.buffer);
this.currentCr = new Uint8ClampedArray(this.codedSize >> 2);
this.currentCr32 = new Uint32Array(this.currentCr.buffer);
this.currentCb = new Uint8ClampedArray(this.codedSize >> 2);
this.currentCb32 = new Uint32Array(this.currentCb.buffer);
this.forwardY = new Uint8ClampedArray(this.codedSize);
this.forwardY32 = new Uint32Array(this.forwardY.buffer);
this.forwardCr = new Uint8ClampedArray(this.codedSize >> 2);
this.forwardCr32 = new Uint32Array(this.forwardCr.buffer);
this.forwardCb = new Uint8ClampedArray(this.codedSize >> 2);
this.forwardCb32 = new Uint32Array(this.forwardCb.buffer);
};
_proto.decodePicture = function decodePicture() {
this.currentFrame++;
this.bits.skip(10); // skip temporalReference
this.pictureType = this.bits.read(3);
this.bits.skip(16); // skip vbv_delay
// Skip B and D frames or unknown coding type
if (this.pictureType <= 0 || this.pictureType >= MPEG1.PICTURE_TYPE.B) {
return;
} // full_pel_forward, forward_f_code
if (this.pictureType === MPEG1.PICTURE_TYPE.PREDICTIVE) {
this.fullPelForward = this.bits.read(1);
this.forwardFCode = this.bits.read(3);
if (this.forwardFCode === 0) {
// Ignore picture with zero forward_f_code
return;
}
this.forwardRSize = this.forwardFCode - 1;
this.forwardF = 1 << this.forwardRSize;
}
var code = 0;
do {
code = this.bits.findNextStartCode();
} while (code === MPEG1.START.EXTENSION || code === MPEG1.START.USER_DATA);
while (code >= MPEG1.START.SLICE_FIRST && code <= MPEG1.START.SLICE_LAST) {
this.decodeSlice(code & 0x000000ff);
code = this.bits.findNextStartCode();
}
if (code !== -1) {
// We found the next start code; rewind 32bits and let the main loop
// handle it.
this.bits.rewind(32);
} // Invoke decode callbacks
if (this.destination) {
this.destination.render(this.currentY, this.currentCr, this.currentCb, true);
} // If this is a reference picutre then rotate the prediction pointers
if (this.pictureType === MPEG1.PICTURE_TYPE.INTRA || this.pictureType === MPEG1.PICTURE_TYPE.PREDICTIVE) {
var tmpY = this.forwardY;
var tmpY32 = this.forwardY32;
var tmpCr = this.forwardCr;
var tmpCr32 = this.forwardCr32;
var tmpCb = this.forwardCb;
var tmpCb32 = this.forwardCb32;
this.forwardY = this.currentY;
this.forwardY32 = this.currentY32;
this.forwardCr = this.currentCr;
this.forwardCr32 = this.currentCr32;
this.forwardCb = this.currentCb;
this.forwardCb32 = this.currentCb32;
this.currentY = tmpY;
this.currentY32 = tmpY32;
this.currentCr = tmpCr;
this.currentCr32 = tmpCr32;
this.currentCb = tmpCb;
this.currentCb32 = tmpCb32;
}
};
_proto.decodeSlice = function decodeSlice(slice) {
this.sliceBegin = true;
this.macroblockAddress = (slice - 1) * this.mbWidth - 1; // Reset motion vectors and DC predictors
this.motionFwH = this.motionFwHPrev = 0;
this.motionFwV = this.motionFwVPrev = 0;
this.dcPredictorY = 128;
this.dcPredictorCr = 128;
this.dcPredictorCb = 128;
this.quantizerScale = this.bits.read(5); // skip extra bits
while (this.bits.read(1)) {
this.bits.skip(8);
}
do {
this.decodeMacroblock();
} while (!this.bits.nextBytesAreStartCode());
};
_proto.decodeMacroblock = function decodeMacroblock() {
// Decode macroblock_address_increment
var increment = 0;
var t = this.readHuffman(MPEG1.MACROBLOCK_ADDRESS_INCREMENT);
while (t === 34) {
// macroblock_stuffing
t = this.readHuffman(MPEG1.MACROBLOCK_ADDRESS_INCREMENT);
}
while (t === 35) {
// macroblock_escape
increment += 33;
t = this.readHuffman(MPEG1.MACROBLOCK_ADDRESS_INCREMENT);
}
increment += t; // Process any skipped macroblocks
if (this.sliceBegin) {
// The first macroblock_address_increment of each slice is relative
// to beginning of the preverious row, not the preverious macroblock
this.sliceBegin = false;
this.macroblockAddress += increment;
} else {
if (this.macroblockAddress + increment >= this.mbSize) {
// Illegal (too large) macroblock_address_increment
return;
}
if (increment > 1) {
// Skipped macroblocks reset DC predictors
this.dcPredictorY = 128;
this.dcPredictorCr = 128;
this.dcPredictorCb = 128; // Skipped macroblocks in P-pictures reset motion vectors
if (this.pictureType === MPEG1.PICTURE_TYPE.PREDICTIVE) {
this.motionFwH = this.motionFwHPrev = 0;
this.motionFwV = this.motionFwVPrev = 0;
}
} // Predict skipped macroblocks
while (increment > 1) {
this.macroblockAddress++;
this.mbRow = this.macroblockAddress / this.mbWidth | 0;
this.mbCol = this.macroblockAddress % this.mbWidth;
this.copyMacroblock(this.motionFwH, this.motionFwV, this.forwardY, this.forwardCr, this.forwardCb);
increment--;
}
this.macroblockAddress++;
}
this.mbRow = this.macroblockAddress / this.mbWidth | 0;
this.mbCol = this.macroblockAddress % this.mbWidth; // Process the current macroblock
var mbTable = MPEG1.MACROBLOCK_TYPE[this.pictureType];
this.macroblockType = this.readHuffman(mbTable);
this.macroblockIntra = this.macroblockType & 0x01;
this.macroblockMotFw = this.macroblockType & 0x08; // Quantizer scale
if ((this.macroblockType & 0x10) !== 0) {
this.quantizerScale = this.bits.read(5);
}
if (this.macroblockIntra) {
// Intra-coded macroblocks reset motion vectors
this.motionFwH = this.motionFwHPrev = 0;
this.motionFwV = this.motionFwVPrev = 0;
} else {
// Non-intra macroblocks reset DC predictors
this.dcPredictorY = 128;