extendable-media-recorder
Version:
An extendable drop-in replacement for the native MediaRecorder.
1,025 lines (1,008 loc) • 60.3 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@babel/runtime/helpers/asyncToGenerator'), require('@babel/runtime/regenerator'), require('media-encoder-host'), require('@babel/runtime/helpers/classCallCheck'), require('@babel/runtime/helpers/createClass'), require('@babel/runtime/helpers/typeof'), require('@babel/runtime/helpers/assertThisInitialized'), require('@babel/runtime/helpers/possibleConstructorReturn'), require('@babel/runtime/helpers/getPrototypeOf'), require('@babel/runtime/helpers/inherits'), require('@babel/runtime/helpers/slicedToArray'), require('@babel/runtime/helpers/toConsumableArray'), require('recorder-audio-worklet'), require('standardized-audio-context'), require('multi-buffer-data-view'), require('subscribable-things')) :
typeof define === 'function' && define.amd ? define(['exports', '@babel/runtime/helpers/asyncToGenerator', '@babel/runtime/regenerator', 'media-encoder-host', '@babel/runtime/helpers/classCallCheck', '@babel/runtime/helpers/createClass', '@babel/runtime/helpers/typeof', '@babel/runtime/helpers/assertThisInitialized', '@babel/runtime/helpers/possibleConstructorReturn', '@babel/runtime/helpers/getPrototypeOf', '@babel/runtime/helpers/inherits', '@babel/runtime/helpers/slicedToArray', '@babel/runtime/helpers/toConsumableArray', 'recorder-audio-worklet', 'standardized-audio-context', 'multi-buffer-data-view', 'subscribable-things'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.extendableMediaRecorder = {}, global._asyncToGenerator, global._regeneratorRuntime, global.mediaEncoderHost, global._classCallCheck, global._createClass, global._typeof, global._assertThisInitialized, global._possibleConstructorReturn, global._getPrototypeOf, global._inherits, global._slicedToArray, global._toConsumableArray, global.recorderAudioWorklet, global.standardizedAudioContext, global.multiBufferDataView, global.subscribableThings));
})(this, (function (exports, _asyncToGenerator, _regeneratorRuntime, mediaEncoderHost, _classCallCheck, _createClass, _typeof, _assertThisInitialized, _possibleConstructorReturn, _getPrototypeOf, _inherits, _slicedToArray, _toConsumableArray, recorderAudioWorklet, standardizedAudioContext, multiBufferDataView, subscribableThings) { 'use strict';
var createBlobEventFactory = function createBlobEventFactory(nativeBlobEventConstructor) {
return function (type, blobEventInit) {
if (nativeBlobEventConstructor === null) {
throw new Error('A native BlobEvent could not be created.');
}
return new nativeBlobEventConstructor(type, blobEventInit);
};
};
var createDecodeWebMChunk = function createDecodeWebMChunk(readElementContent, readElementType) {
return function (dataView, elementType, channelCount) {
var contents = [];
var currentElementType = elementType;
var offset = 0;
while (offset < dataView.byteLength) {
if (currentElementType === null) {
var lengthAndType = readElementType(dataView, offset);
if (lengthAndType === null) {
break;
}
var length = lengthAndType.length,
type = lengthAndType.type;
currentElementType = type;
offset += length;
} else {
var contentAndLength = readElementContent(dataView, offset, currentElementType, channelCount);
if (contentAndLength === null) {
break;
}
var content = contentAndLength.content,
_length = contentAndLength.length;
currentElementType = null;
offset += _length;
if (content !== null) {
contents.push(content);
}
}
}
return {
contents: contents,
currentElementType: currentElementType,
offset: offset
};
};
};
var createEventTargetConstructor = function createEventTargetConstructor(createEventTarget, wrapEventListener) {
return /*#__PURE__*/function () {
function EventTarget() {
var nativeEventTarget = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
_classCallCheck(this, EventTarget);
this._listeners = new WeakMap();
this._nativeEventTarget = nativeEventTarget === null ? createEventTarget() : nativeEventTarget;
}
return _createClass(EventTarget, [{
key: "addEventListener",
value: function addEventListener(type, listener, options) {
if (listener !== null) {
var wrappedEventListener = this._listeners.get(listener);
if (wrappedEventListener === undefined) {
wrappedEventListener = wrapEventListener(this, listener);
if (typeof listener === 'function') {
this._listeners.set(listener, wrappedEventListener);
}
}
this._nativeEventTarget.addEventListener(type, wrappedEventListener, options);
}
}
}, {
key: "dispatchEvent",
value: function dispatchEvent(event) {
return this._nativeEventTarget.dispatchEvent(event);
}
}, {
key: "removeEventListener",
value: function removeEventListener(type, listener, options) {
var wrappedEventListener = listener === null ? undefined : this._listeners.get(listener);
this._nativeEventTarget.removeEventListener(type, wrappedEventListener === undefined ? null : wrappedEventListener, options);
}
}]);
}();
};
var createEventTargetFactory = function createEventTargetFactory(window) {
return function () {
if (window === null) {
throw new Error('A native EventTarget could not be created.');
}
return window.document.createElement('p');
};
};
var createInvalidModificationError = function createInvalidModificationError() {
var message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
try {
return new DOMException(message, 'InvalidModificationError');
} catch (err) {
// @todo Edge is the only browser that does not yet allow to construct a DOMException.
err.code = 13;
err.message = message;
err.name = 'InvalidModificationError';
return err;
}
};
var createInvalidStateError = function createInvalidStateError() {
try {
return new DOMException('', 'InvalidStateError');
} catch (err) {
// Bug #122: Edge is the only browser that does not yet allow to construct a DOMException.
err.code = 11;
err.name = 'InvalidStateError';
return err;
}
};
var createIsSupportedPromise = function createIsSupportedPromise(window) {
if (window !== null &&
// Bug #14: Before v14.1 Safari did not support the BlobEvent.
window.BlobEvent !== undefined && window.MediaStream !== undefined && (
/*
* Bug #10: An early experimental implemenation in Safari v14 did not provide the isTypeSupported() function.
*
* Bug #17: Safari up to v14.1.2 throttled the processing on hidden tabs if there was no active audio output. This is not tested
* here but should be covered by the following test, too.
*/
window.MediaRecorder === undefined || window.MediaRecorder.isTypeSupported !== undefined)) {
// Bug #11 Safari up to v14.1.2 did not support the MediaRecorder but that isn't tested here.
if (window.MediaRecorder === undefined) {
return Promise.resolve(true);
}
var canvasElement = window.document.createElement('canvas');
var context = canvasElement.getContext('2d');
if (context === null || typeof canvasElement.captureStream !== 'function') {
return Promise.resolve(false);
}
var mediaStream = canvasElement.captureStream();
return Promise.all([
/*
* Bug #5: Up until v70 Firefox did emit a blob of type video/webm when asked to encode a MediaStream with a video track into an
* audio codec.
*/
new Promise(function (resolve) {
var mimeType = 'audio/webm';
try {
var mediaRecorder = new window.MediaRecorder(mediaStream, {
mimeType: mimeType
});
mediaRecorder.addEventListener('dataavailable', function (_ref) {
var data = _ref.data;
return resolve(data.type === mimeType);
});
mediaRecorder.start();
setTimeout(function () {
return mediaRecorder.stop();
}, 10);
} catch (err) {
resolve(err.name === 'NotSupportedError');
}
}),
/*
* Bug #1 & #2: Up until v83 Firefox fired an error event with an UnknownError when adding or removing a track.
*
* Bug #3 & #4: Up until v112 Chrome dispatched an error event without any error.
*
* Bug #6: Up until v113 Chrome emitted a blob without any data when asked to encode a MediaStream with a video track as audio.
* This is not directly tested here as it can only be tested by recording something for a short time. It got fixed at the same
* time as #7 and #8.
*
* Bug #7 & #8: Up until v113 Chrome dispatched the dataavailable and stop events before it dispatched the error event.
*/
new Promise(function (resolve) {
var mediaRecorder = new window.MediaRecorder(mediaStream);
var hasDispatchedDataAvailableEvent = false;
var hasDispatchedStopEvent = false;
mediaRecorder.addEventListener('dataavailable', function () {
return hasDispatchedDataAvailableEvent = true;
});
mediaRecorder.addEventListener('error', function (event) {
resolve(!hasDispatchedDataAvailableEvent && !hasDispatchedStopEvent && 'error' in event && event.error !== null && _typeof(event.error) === 'object' && 'name' in event.error && event.error.name !== 'UnknownError');
});
mediaRecorder.addEventListener('stop', function () {
return hasDispatchedStopEvent = true;
});
mediaRecorder.start();
context.fillRect(0, 0, 1, 1);
mediaStream.removeTrack(mediaStream.getVideoTracks()[0]);
})]).then(function (results) {
return results.every(function (result) {
return result;
});
});
}
return Promise.resolve(false);
};
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
var createMediaRecorderConstructor = function createMediaRecorderConstructor(createNativeMediaRecorder, createNotSupportedError, createWebAudioMediaRecorder, createWebmPcmMediaRecorder, encoderRegexes, eventTargetConstructor, nativeMediaRecorderConstructor) {
return /*#__PURE__*/function (_eventTargetConstruct) {
function MediaRecorder(stream) {
var _this;
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
_classCallCheck(this, MediaRecorder);
var mimeType = options.mimeType;
if (nativeMediaRecorderConstructor !== null && (
// Bug #10: Safari does not yet implement the isTypeSupported() method.
mimeType === undefined || nativeMediaRecorderConstructor.isTypeSupported !== undefined && nativeMediaRecorderConstructor.isTypeSupported(mimeType))) {
var internalMediaRecorder = createNativeMediaRecorder(nativeMediaRecorderConstructor, stream, options);
_this = _callSuper(this, MediaRecorder, [internalMediaRecorder]);
_this._internalMediaRecorder = internalMediaRecorder;
} else if (mimeType !== undefined && encoderRegexes.some(function (regex) {
return regex.test(mimeType);
})) {
_this = _callSuper(this, MediaRecorder);
// Bug #10: Safari does not yet implement the isTypeSupported() method.
if (nativeMediaRecorderConstructor !== null && nativeMediaRecorderConstructor.isTypeSupported !== undefined && nativeMediaRecorderConstructor.isTypeSupported('audio/webm;codecs=pcm')) {
_this._internalMediaRecorder = createWebmPcmMediaRecorder(_assertThisInitialized(_this), nativeMediaRecorderConstructor, stream, mimeType);
} else {
_this._internalMediaRecorder = createWebAudioMediaRecorder(_assertThisInitialized(_this), stream, mimeType);
}
} else {
// This is creating a native MediaRecorder just to provoke it to throw an error.
if (nativeMediaRecorderConstructor !== null) {
createNativeMediaRecorder(nativeMediaRecorderConstructor, stream, options);
}
throw createNotSupportedError();
}
_this._ondataavailable = null;
_this._onerror = null;
_this._onpause = null;
_this._onresume = null;
_this._onstart = null;
_this._onstop = null;
return _assertThisInitialized(_this);
}
_inherits(MediaRecorder, _eventTargetConstruct);
return _createClass(MediaRecorder, [{
key: "mimeType",
get: function get() {
return this._internalMediaRecorder.mimeType;
}
}, {
key: "ondataavailable",
get: function get() {
return this._ondataavailable === null ? this._ondataavailable : this._ondataavailable[0];
},
set: function set(value) {
if (this._ondataavailable !== null) {
this.removeEventListener('dataavailable', this._ondataavailable[1]);
}
if (typeof value === 'function') {
var boundListener = value.bind(this);
this.addEventListener('dataavailable', boundListener);
this._ondataavailable = [value, boundListener];
} else {
this._ondataavailable = null;
}
}
}, {
key: "onerror",
get: function get() {
return this._onerror === null ? this._onerror : this._onerror[0];
},
set: function set(value) {
if (this._onerror !== null) {
this.removeEventListener('error', this._onerror[1]);
}
if (typeof value === 'function') {
var boundListener = value.bind(this);
this.addEventListener('error', boundListener);
this._onerror = [value, boundListener];
} else {
this._onerror = null;
}
}
}, {
key: "onpause",
get: function get() {
return this._onpause === null ? this._onpause : this._onpause[0];
},
set: function set(value) {
if (this._onpause !== null) {
this.removeEventListener('pause', this._onpause[1]);
}
if (typeof value === 'function') {
var boundListener = value.bind(this);
this.addEventListener('pause', boundListener);
this._onpause = [value, boundListener];
} else {
this._onpause = null;
}
}
}, {
key: "onresume",
get: function get() {
return this._onresume === null ? this._onresume : this._onresume[0];
},
set: function set(value) {
if (this._onresume !== null) {
this.removeEventListener('resume', this._onresume[1]);
}
if (typeof value === 'function') {
var boundListener = value.bind(this);
this.addEventListener('resume', boundListener);
this._onresume = [value, boundListener];
} else {
this._onresume = null;
}
}
}, {
key: "onstart",
get: function get() {
return this._onstart === null ? this._onstart : this._onstart[0];
},
set: function set(value) {
if (this._onstart !== null) {
this.removeEventListener('start', this._onstart[1]);
}
if (typeof value === 'function') {
var boundListener = value.bind(this);
this.addEventListener('start', boundListener);
this._onstart = [value, boundListener];
} else {
this._onstart = null;
}
}
}, {
key: "onstop",
get: function get() {
return this._onstop === null ? this._onstop : this._onstop[0];
},
set: function set(value) {
if (this._onstop !== null) {
this.removeEventListener('stop', this._onstop[1]);
}
if (typeof value === 'function') {
var boundListener = value.bind(this);
this.addEventListener('stop', boundListener);
this._onstop = [value, boundListener];
} else {
this._onstop = null;
}
}
}, {
key: "state",
get: function get() {
return this._internalMediaRecorder.state;
}
}, {
key: "pause",
value: function pause() {
return this._internalMediaRecorder.pause();
}
}, {
key: "resume",
value: function resume() {
return this._internalMediaRecorder.resume();
}
}, {
key: "start",
value: function start(timeslice) {
return this._internalMediaRecorder.start(timeslice);
}
}, {
key: "stop",
value: function stop() {
return this._internalMediaRecorder.stop();
}
}], [{
key: "isTypeSupported",
value: function isTypeSupported(mimeType) {
return nativeMediaRecorderConstructor !== null &&
// Bug #10: Safari does not yet implement the isTypeSupported() method.
nativeMediaRecorderConstructor.isTypeSupported !== undefined && nativeMediaRecorderConstructor.isTypeSupported(mimeType) || encoderRegexes.some(function (regex) {
return regex.test(mimeType);
});
}
}]);
}(eventTargetConstructor);
};
var createNativeBlobEventConstructor = function createNativeBlobEventConstructor(window) {
if (window !== null && window.BlobEvent !== undefined) {
return window.BlobEvent;
}
return null;
};
var createNativeMediaRecorderConstructor = function createNativeMediaRecorderConstructor(window) {
if (window === null) {
return null;
}
return window.MediaRecorder === undefined ? null : window.MediaRecorder;
};
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: true } : { done: false, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = true, u = false; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = true, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
var createNativeMediaRecorderFactory = function createNativeMediaRecorderFactory(createNotSupportedError) {
return function (nativeMediaRecorderConstructor, stream, mediaRecorderOptions) {
var bufferedBlobEventListeners = new Map();
var dataAvailableListeners = new WeakMap();
var errorListeners = new WeakMap();
var flags = [];
var nativeMediaRecorder = new nativeMediaRecorderConstructor(stream, mediaRecorderOptions);
var stopListeners = new WeakMap();
nativeMediaRecorder.addEventListener('stop', function (_ref) {
var isTrusted = _ref.isTrusted;
if (isTrusted) {
setTimeout(function () {
return flags.shift();
});
}
});
nativeMediaRecorder.addEventListener = function (addEventListener) {
return function (type, listener, options) {
var patchedEventListener = listener;
if (typeof listener === 'function') {
if (type === 'dataavailable') {
var bufferedBlobEvents = [];
// Bug #20: Firefox dispatches multiple dataavailable events while being inactive.
patchedEventListener = function patchedEventListener(event) {
var _flags$ = flags[0],
_flags$2 = _flags$ === void 0 ? [false, false] : _flags$,
_flags$3 = _slicedToArray(_flags$2, 2),
isSliced = _flags$3[0],
isActive = _flags$3[1];
if (isSliced && !isActive) {
bufferedBlobEvents.push(event);
} else {
listener.call(nativeMediaRecorder, event);
}
};
bufferedBlobEventListeners.set(listener, bufferedBlobEvents);
dataAvailableListeners.set(listener, patchedEventListener);
} else if (type === 'error') {
// Bug #12 & #13: Firefox fires a regular event with an error property.
patchedEventListener = function patchedEventListener(event) {
if (event instanceof ErrorEvent) {
listener.call(nativeMediaRecorder, event);
} else {
listener.call(nativeMediaRecorder, new ErrorEvent('error', {
error: event.error
}));
}
};
errorListeners.set(listener, patchedEventListener);
} else if (type === 'stop') {
// Bug #20: Firefox dispatches multiple dataavailable events while being inactive.
patchedEventListener = function patchedEventListener(event) {
var _iterator = _createForOfIteratorHelper(bufferedBlobEventListeners.entries()),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var _step$value = _slicedToArray(_step.value, 2),
dataAvailableListener = _step$value[0],
_bufferedBlobEvents = _step$value[1];
if (_bufferedBlobEvents.length > 0) {
var _bufferedBlobEvents2 = _slicedToArray(_bufferedBlobEvents, 1),
blobEvent = _bufferedBlobEvents2[0];
if (_bufferedBlobEvents.length > 1) {
Object.defineProperty(blobEvent, 'data', {
value: new Blob(_bufferedBlobEvents.map(function (_ref2) {
var data = _ref2.data;
return data;
}), {
type: blobEvent.data.type
})
});
}
_bufferedBlobEvents.length = 0;
dataAvailableListener.call(nativeMediaRecorder, blobEvent);
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
listener.call(nativeMediaRecorder, event);
};
stopListeners.set(listener, patchedEventListener);
}
}
return addEventListener.call(nativeMediaRecorder, type, patchedEventListener, options);
};
}(nativeMediaRecorder.addEventListener);
nativeMediaRecorder.removeEventListener = function (removeEventListener) {
return function (type, listener, options) {
var patchedEventListener = listener;
if (typeof listener === 'function') {
if (type === 'dataavailable') {
bufferedBlobEventListeners["delete"](listener);
var dataAvailableListener = dataAvailableListeners.get(listener);
if (dataAvailableListener !== undefined) {
patchedEventListener = dataAvailableListener;
}
} else if (type === 'error') {
var errorListener = errorListeners.get(listener);
if (errorListener !== undefined) {
patchedEventListener = errorListener;
}
} else if (type === 'stop') {
var stopListener = stopListeners.get(listener);
if (stopListener !== undefined) {
patchedEventListener = stopListener;
}
}
}
return removeEventListener.call(nativeMediaRecorder, type, patchedEventListener, options);
};
}(nativeMediaRecorder.removeEventListener);
nativeMediaRecorder.start = function (start) {
return function (timeslice) {
/*
* Bug #6: Safari will emit a blob without any data when asked to encode a MediaStream with a video track into an audio
* codec.
*/
if (mediaRecorderOptions.mimeType !== undefined && mediaRecorderOptions.mimeType.startsWith('audio/') && stream.getVideoTracks().length > 0) {
throw createNotSupportedError();
}
if (nativeMediaRecorder.state === 'inactive') {
flags.push([timeslice !== undefined, true]);
}
return timeslice === undefined ? start.call(nativeMediaRecorder) : start.call(nativeMediaRecorder, timeslice);
};
}(nativeMediaRecorder.start);
nativeMediaRecorder.stop = function (stop) {
return function () {
if (nativeMediaRecorder.state !== 'inactive') {
flags[0][1] = false;
}
stop.call(nativeMediaRecorder);
};
}(nativeMediaRecorder.stop);
return nativeMediaRecorder;
};
};
var createNotSupportedError = function createNotSupportedError() {
try {
return new DOMException('', 'NotSupportedError');
} catch (err) {
// @todo Edge is the only browser that does not yet allow to construct a DOMException.
err.code = 9;
err.name = 'NotSupportedError';
return err;
}
};
var createReadElementContent = function createReadElementContent(readVariableSizeInteger) {
return function (dataView, offset, type) {
var channelCount = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 2;
var lengthAndValue = readVariableSizeInteger(dataView, offset);
if (lengthAndValue === null) {
return lengthAndValue;
}
var length = lengthAndValue.length,
value = lengthAndValue.value;
if (type === 'master') {
return {
content: null,
length: length
};
}
if (offset + length + value > dataView.byteLength) {
return null;
}
if (type === 'binary') {
var numberOfSamples = (value / Float32Array.BYTES_PER_ELEMENT - 1) / channelCount;
var content = Array.from({
length: channelCount
}, function () {
return new Float32Array(numberOfSamples);
});
for (var i = 0; i < numberOfSamples; i += 1) {
var elementOffset = i * channelCount + 1;
for (var j = 0; j < channelCount; j += 1) {
content[j][i] = dataView.getFloat32(offset + length + (elementOffset + j) * Float32Array.BYTES_PER_ELEMENT, true);
}
}
return {
content: content,
length: length + value
};
}
return {
content: null,
length: length + value
};
};
};
var createReadElementType = function createReadElementType(readVariableSizeInteger) {
return function (dataView, offset) {
var lengthAndValue = readVariableSizeInteger(dataView, offset);
if (lengthAndValue === null) {
return lengthAndValue;
}
var length = lengthAndValue.length,
value = lengthAndValue.value;
if (value === 35) {
return {
length: length,
type: 'binary'
};
}
if (value === 46 || value === 97 || value === 88713574 || value === 106212971 || value === 139690087 || value === 172351395 || value === 256095861) {
return {
length: length,
type: 'master'
};
}
return {
length: length,
type: 'unknown'
};
};
};
var createReadVariableSizeInteger = function createReadVariableSizeInteger(readVariableSizeIntegerLength) {
return function (dataView, offset) {
var length = readVariableSizeIntegerLength(dataView, offset);
if (length === null) {
return length;
}
var firstDataByteOffset = offset + Math.floor((length - 1) / 8);
if (firstDataByteOffset + length > dataView.byteLength) {
return null;
}
var firstDataByte = dataView.getUint8(firstDataByteOffset);
var value = firstDataByte & (1 << 8 - length % 8) - 1; // tslint:disable-line:no-bitwise
for (var i = 1; i < length; i += 1) {
value = (value << 8) + dataView.getUint8(firstDataByteOffset + i); // tslint:disable-line:no-bitwise
}
return {
length: length,
value: value
};
};
};
var ERROR_MESSAGE = 'Missing AudioWorklet support. Maybe this is not running in a secure context.';
// @todo This should live in a separate file.
var createPromisedAudioNodesEncoderInstanceIdAndPort = /*#__PURE__*/function () {
var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(audioBuffer, audioContext, channelCount, mediaStream, mimeType) {
var _yield$instantiate, encoderInstanceId, port, audioBufferSourceNode, mediaStreamAudioSourceNode, recorderAudioWorkletNode;
return _regeneratorRuntime.wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return mediaEncoderHost.instantiate(mimeType, audioContext.sampleRate);
case 2:
_yield$instantiate = _context.sent;
encoderInstanceId = _yield$instantiate.encoderInstanceId;
port = _yield$instantiate.port;
if (!(standardizedAudioContext.AudioWorkletNode === undefined)) {
_context.next = 7;
break;
}
throw new Error(ERROR_MESSAGE);
case 7:
audioBufferSourceNode = new standardizedAudioContext.AudioBufferSourceNode(audioContext, {
buffer: audioBuffer
});
mediaStreamAudioSourceNode = new standardizedAudioContext.MediaStreamAudioSourceNode(audioContext, {
mediaStream: mediaStream
});
recorderAudioWorkletNode = recorderAudioWorklet.createRecorderAudioWorkletNode(standardizedAudioContext.AudioWorkletNode, audioContext, {
channelCount: channelCount
});
return _context.abrupt("return", {
audioBufferSourceNode: audioBufferSourceNode,
encoderInstanceId: encoderInstanceId,
mediaStreamAudioSourceNode: mediaStreamAudioSourceNode,
port: port,
recorderAudioWorkletNode: recorderAudioWorkletNode
});
case 11:
case "end":
return _context.stop();
}
}, _callee);
}));
return function createPromisedAudioNodesEncoderInstanceIdAndPort(_x, _x2, _x3, _x4, _x5) {
return _ref.apply(this, arguments);
};
}();
var createWebAudioMediaRecorderFactory = function createWebAudioMediaRecorderFactory(createBlobEvent, createInvalidModificationError, createInvalidStateError, createNotSupportedError) {
return function (eventTarget, mediaStream, mimeType) {
var _a;
var sampleRate = (_a = mediaStream.getAudioTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().sampleRate;
var audioContext = new standardizedAudioContext.MinimalAudioContext({
latencyHint: 'playback',
sampleRate: sampleRate
});
var length = Math.max(1024, Math.ceil(audioContext.baseLatency * audioContext.sampleRate));
var audioBuffer = new standardizedAudioContext.AudioBuffer({
length: length,
sampleRate: audioContext.sampleRate
});
var bufferedArrayBuffers = [];
var promisedAudioWorkletModule = recorderAudioWorklet.addRecorderAudioWorkletModule(function (url) {
if (standardizedAudioContext.addAudioWorkletModule === undefined) {
throw new Error(ERROR_MESSAGE);
}
return standardizedAudioContext.addAudioWorkletModule(audioContext, url);
});
var abortRecording = null;
var intervalId = null;
var promisedAudioNodesAndEncoderInstanceId = null;
var promisedPartialRecording = null;
var isAudioContextRunning = true;
var dispatchDataAvailableEvent = function dispatchDataAvailableEvent(arrayBuffers) {
eventTarget.dispatchEvent(createBlobEvent('dataavailable', {
data: new Blob(arrayBuffers, {
type: mimeType
})
}));
};
var _requestNextPartialRecording = /*#__PURE__*/function () {
var _ref2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee2(encoderInstanceId, timeslice) {
var arrayBuffers;
return _regeneratorRuntime.wrap(function _callee2$(_context2) {
while (1) switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 2;
return mediaEncoderHost.encode(encoderInstanceId, timeslice);
case 2:
arrayBuffers = _context2.sent;
if (promisedAudioNodesAndEncoderInstanceId === null) {
bufferedArrayBuffers.push.apply(bufferedArrayBuffers, _toConsumableArray(arrayBuffers));
} else {
dispatchDataAvailableEvent(arrayBuffers);
promisedPartialRecording = _requestNextPartialRecording(encoderInstanceId, timeslice);
}
case 4:
case "end":
return _context2.stop();
}
}, _callee2);
}));
return function requestNextPartialRecording(_x6, _x7) {
return _ref2.apply(this, arguments);
};
}();
var _resume = function resume() {
isAudioContextRunning = true;
return audioContext.resume();
};
var stop = function stop() {
if (promisedAudioNodesAndEncoderInstanceId === null) {
return;
}
if (abortRecording !== null) {
mediaStream.removeEventListener('addtrack', abortRecording);
mediaStream.removeEventListener('removetrack', abortRecording);
}
if (intervalId !== null) {
clearTimeout(intervalId);
}
promisedAudioNodesAndEncoderInstanceId.then(/*#__PURE__*/function () {
var _ref4 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee3(_ref3) {
var encoderInstanceId, mediaStreamAudioSourceNode, recorderAudioWorkletNode, arrayBuffers;
return _regeneratorRuntime.wrap(function _callee3$(_context3) {
while (1) switch (_context3.prev = _context3.next) {
case 0:
encoderInstanceId = _ref3.encoderInstanceId, mediaStreamAudioSourceNode = _ref3.mediaStreamAudioSourceNode, recorderAudioWorkletNode = _ref3.recorderAudioWorkletNode;
if (promisedPartialRecording !== null) {
promisedPartialRecording["catch"](function () {
/* @todo Only catch the errors caused by a duplicate call to encode. */
});
promisedPartialRecording = null;
}
_context3.next = 4;
return recorderAudioWorkletNode.stop();
case 4:
mediaStreamAudioSourceNode.disconnect(recorderAudioWorkletNode);
_context3.next = 7;
return mediaEncoderHost.encode(encoderInstanceId, null);
case 7:
arrayBuffers = _context3.sent;
if (!(promisedAudioNodesAndEncoderInstanceId === null)) {
_context3.next = 11;
break;
}
_context3.next = 11;
return suspend();
case 11:
dispatchDataAvailableEvent([].concat(bufferedArrayBuffers, _toConsumableArray(arrayBuffers)));
bufferedArrayBuffers.length = 0;
eventTarget.dispatchEvent(new Event('stop'));
case 14:
case "end":
return _context3.stop();
}
}, _callee3);
}));
return function (_x8) {
return _ref4.apply(this, arguments);
};
}());
promisedAudioNodesAndEncoderInstanceId = null;
};
var suspend = function suspend() {
isAudioContextRunning = false;
return audioContext.suspend();
};
suspend();
return {
get mimeType() {
return mimeType;
},
get state() {
return promisedAudioNodesAndEncoderInstanceId === null ? 'inactive' : isAudioContextRunning ? 'recording' : 'paused';
},
pause: function pause() {
if (promisedAudioNodesAndEncoderInstanceId === null) {
throw createInvalidStateError();
}
if (isAudioContextRunning) {
suspend();
eventTarget.dispatchEvent(new Event('pause'));
}
},
resume: function resume() {
if (promisedAudioNodesAndEncoderInstanceId === null) {
throw createInvalidStateError();
}
if (!isAudioContextRunning) {
_resume();
eventTarget.dispatchEvent(new Event('resume'));
}
},
start: function start(timeslice) {
var _a;
if (promisedAudioNodesAndEncoderInstanceId !== null) {
throw createInvalidStateError();
}
if (mediaStream.getVideoTracks().length > 0) {
throw createNotSupportedError();
}
eventTarget.dispatchEvent(new Event('start'));
var audioTracks = mediaStream.getAudioTracks();
var channelCount = audioTracks.length === 0 ? 2 : (_a = audioTracks[0].getSettings().channelCount) !== null && _a !== void 0 ? _a : 2;
promisedAudioNodesAndEncoderInstanceId = Promise.all([_resume(), promisedAudioWorkletModule.then(function () {
return createPromisedAudioNodesEncoderInstanceIdAndPort(audioBuffer, audioContext, channelCount, mediaStream, mimeType);
})]).then(/*#__PURE__*/function () {
var _ref6 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee4(_ref5) {
var _ref7, _ref7$, audioBufferSourceNode, encoderInstanceId, mediaStreamAudioSourceNode, port, recorderAudioWorkletNode;
return _regeneratorRuntime.wrap(function _callee4$(_context4) {
while (1) switch (_context4.prev = _context4.next) {
case 0:
_ref7 = _slicedToArray(_ref5, 2), _ref7$ = _ref7[1], audioBufferSourceNode = _ref7$.audioBufferSourceNode, encoderInstanceId = _ref7$.encoderInstanceId, mediaStreamAudioSourceNode = _ref7$.mediaStreamAudioSourceNode, port = _ref7$.port, recorderAudioWorkletNode = _ref7$.recorderAudioWorkletNode;
mediaStreamAudioSourceNode.connect(recorderAudioWorkletNode);
_context4.next = 4;
return new Promise(function (resolve) {
audioBufferSourceNode.onended = resolve;
audioBufferSourceNode.connect(recorderAudioWorkletNode);
audioBufferSourceNode.start(audioContext.currentTime + length / audioContext.sampleRate);
});
case 4:
audioBufferSourceNode.disconnect(recorderAudioWorkletNode);
_context4.next = 7;
return recorderAudioWorkletNode.record(port);
case 7:
if (timeslice !== undefined) {
promisedPartialRecording = _requestNextPartialRecording(encoderInstanceId, timeslice);
}
return _context4.abrupt("return", {
encoderInstanceId: encoderInstanceId,
mediaStreamAudioSourceNode: mediaStreamAudioSourceNode,
recorderAudioWorkletNode: recorderAudioWorkletNode
});
case 9:
case "end":
return _context4.stop();
}
}, _callee4);
}));
return function (_x9) {
return _ref6.apply(this, arguments);
};
}());
var tracks = mediaStream.getTracks();
abortRecording = function abortRecording() {
stop();
eventTarget.dispatchEvent(new ErrorEvent('error', {
error: createInvalidModificationError()
}));
};
mediaStream.addEventListener('addtrack', abortRecording);
mediaStream.addEventListener('removetrack', abortRecording);
intervalId = setInterval(function () {
var currentTracks = mediaStream.getTracks();
if ((currentTracks.length !== tracks.length || currentTracks.some(function (track, index) {
return track !== tracks[index];
})) && abortRecording !== null) {
abortRecording();
}
}, 1000);
},
stop: stop
};
};
};
var createWebmPcmMediaRecorderFactory = function createWebmPcmMediaRecorderFactory(createBlobEvent, decodeWebMChunk, readVariableSizeInteger) {
return function (eventTarget, nativeMediaRecorderConstructor, mediaStream, mimeType) {
var bufferedArrayBuffers = [];
var nativeMediaRecorder = new nativeMediaRecorderConstructor(mediaStream, {
mimeType: 'audio/webm;codecs=pcm'
});
var promisedPartialRecording = null;
var stopRecording = function stopRecording() {}; // tslint:disable-line:no-empty
var dispatchDataAvailableEvent = function dispatchDataAvailableEvent(arrayBuffers) {
eventTarget.dispatchEvent(createBlobEvent('dataavailable', {
data: new Blob(arrayBuffers, {
type: mimeType
})
}));
};
var _requestNextPartialRecording = /*#__PURE__*/function () {
var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(encoderInstanceId, timeslice) {
var arrayBuffers;
return _regeneratorRuntime.wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return mediaEncoderHost.encode(encoderInstanceId, timeslice);
case 2:
arrayBuffers = _context.sent;
if (nativeMediaRecorder.state === 'inactive') {
bufferedArrayBuffers.push.apply(bufferedArrayBuffers, _toConsumableArray(arrayBuffers));
} else {
dispatchDataAvailableEvent(arrayBuffers);
promisedPartialRecording = _requestNextPartialRecording(encoderInstanceId, timeslice);
}
case 4:
case "end":
return _context.stop();
}
}, _callee);
}));
return function requestNextPartialRecording(_x, _x2) {
return _ref.apply(this, arguments);
};
}();
var stop = function stop() {
if (nativeMediaRecorder.state === 'inactive') {
return;
}
if (promisedPartialRecording !== null) {
promisedPartialRecording["catch"](function () {
/* @todo Only catch the errors caused by a duplicate call to encode. */
});
promisedPartialRecording = null;
}
stopRecording();
stopRecording = function stopRecording() {}; // tslint:disable-line:no-empty
nativeMediaRecorder.stop();
};
nativeMediaRecorder.addEventListener('error', function (event) {
stop();
eventTarget.dispatchEvent(new ErrorEvent('error', {
error: event.error
}));
});
nativeMediaRecorder.addEventListener('pause', function () {
return eventTarget.dispatchEvent(new Event('pause'));
});
nativeMediaRecorder.addEventListener('resume', function () {
return eventTarget.dispatchEvent(new Event('resume'));
});
nativeMediaRecorder.addEventListener('start', function () {
return eventTarget.dispatchEvent(new Event('start'));
});
return {
get mimeType() {
return mimeType;
},
get state() {
return nativeMediaRecorder.state;
},
pause: function pause() {
return nativeMediaRecorder.pause();
},
resume: function resume() {
return nativeMediaRecorder.resume();
},
start: function start(timeslice) {
var _mediaStream$getAudio = mediaStream.getAudioTracks(),
_mediaStream$getAudio2 = _slicedToArray(_mediaStream$getAudio, 1),
audioTrack = _mediaStream$getAudio2[0];
if (audioTrack !== undefined && nativeMediaRecorder.state === 'inactive') {
// Bug #19: Chrome does not expose the correct channelCount property right away.
var _audioTrack$getSettin = audioTrack.getSettings(),
channelCount = _audioTrack$getSettin.channelCount,
sampleRate = _audioTrack$getSettin.sampleRate;
if (channelCount === undefined) {
throw new Error('The channelCount is not defined.');
}
if (sampleRate === undefined) {
throw new Error('The sampleRate is not defined.');
}
var isRecording = false;
var isStopped = false;
// Bug #9: Chrome sometimes fires more than one dataavailable event while being inactive.
var pendingInvocations = 0;
var promisedDataViewElementTypeEncoderInstanceIdAndPort = mediaEncoderHost.instantiate(mimeType, sampleRate);
stopRecording = function stopRecording() {
isStopped = true;
};
var removeEventListener = subscribableThings.on(nativeMediaRecorder, 'dataavailable')(function (_ref2) {
var data = _ref2.data;
pendingInvocations += 1;
var promisedArrayBuffer = data.arrayBuffer();
prom