Socket
Socket
Sign inDemoInstall

extendable-media-recorder

Package Overview
Dependencies
Maintainers
1
Versions
380
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

extendable-media-recorder - npm Package Compare versions

Comparing version 8.1.0 to 9.0.0

14

build/es2019/factories/is-supported-promise.js

@@ -44,7 +44,18 @@ export const createIsSupportedPromise = (window) => {

* 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((resolve) => {
const mediaRecorder = new window.MediaRecorder(mediaStream);
let hasDispatchedDataAvailableEvent = false;
let hasDispatchedStopEvent = false;
mediaRecorder.addEventListener('dataavailable', () => (hasDispatchedDataAvailableEvent = true));
mediaRecorder.addEventListener('error', (event) => {
resolve('error' in event &&
resolve(!hasDispatchedDataAvailableEvent &&
!hasDispatchedStopEvent &&
'error' in event &&
event.error !== null &&

@@ -55,2 +66,3 @@ typeof event.error === 'object' &&

});
mediaRecorder.addEventListener('stop', () => (hasDispatchedStopEvent = true));
mediaRecorder.start();

@@ -57,0 +69,0 @@ context.fillRect(0, 0, 1, 1);

4

build/es2019/factories/native-media-recorder.d.ts

@@ -1,3 +0,3 @@

import { TNativeMediaRecorderFactoryFactory } from '../types';
export declare const createNativeMediaRecorderFactory: TNativeMediaRecorderFactoryFactory;
import { TNativeMediaRecorderFactory } from '../types';
export declare const createNativeMediaRecorder: TNativeMediaRecorderFactory;
//# sourceMappingURL=native-media-recorder.d.ts.map

@@ -1,115 +0,97 @@

export const createNativeMediaRecorderFactory = (createNotSupportedError) => {
return (nativeMediaRecorderConstructor, stream, mediaRecorderOptions) => {
const bufferedBlobs = [];
const dataAvailableListeners = new WeakMap();
const errorListeners = new WeakMap();
const nativeMediaRecorder = new nativeMediaRecorderConstructor(stream, mediaRecorderOptions);
const stopListeners = new WeakMap();
let isActive = true;
nativeMediaRecorder.addEventListener = ((addEventListener) => {
return (type, listener, options) => {
let patchedEventListener = listener;
if (typeof listener === 'function') {
if (type === 'dataavailable') {
// Bug #7 & #8: Chrome fires the dataavailable and stop events before it fires the error event.
patchedEventListener = (event) => {
setTimeout(() => {
if (isActive && nativeMediaRecorder.state === 'inactive') {
bufferedBlobs.push(event.data);
export const createNativeMediaRecorder = (nativeMediaRecorderConstructor, stream, mediaRecorderOptions) => {
const bufferedBlobEventListeners = new Map();
const dataAvailableListeners = new WeakMap();
const errorListeners = new WeakMap();
const nativeMediaRecorder = new nativeMediaRecorderConstructor(stream, mediaRecorderOptions);
const stopListeners = new WeakMap();
let isSliced = false;
nativeMediaRecorder.addEventListener = ((addEventListener) => {
return (type, listener, options) => {
let patchedEventListener = listener;
if (typeof listener === 'function') {
if (type === 'dataavailable') {
const bufferedBlobEvents = [];
// Bug #20: Firefox dispatches multiple dataavailable events while being inactive.
patchedEventListener = (event) => {
if (isSliced && nativeMediaRecorder.state === 'inactive') {
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 = (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 = (event) => {
for (const [dataAvailableListener, bufferedBlobEvents] of bufferedBlobEventListeners.entries()) {
if (bufferedBlobEvents.length > 0) {
const [blobEvent] = bufferedBlobEvents;
if (bufferedBlobEvents.length > 1) {
Object.defineProperty(blobEvent, 'data', {
value: new Blob(bufferedBlobEvents.map(({ data }) => data), { type: blobEvent.data.type })
});
}
else {
if (bufferedBlobs.length > 0) {
const blob = event.data;
Object.defineProperty(event, 'data', {
value: new Blob([...bufferedBlobs, blob], { type: blob.type })
});
bufferedBlobs.length = 0;
}
listener.call(nativeMediaRecorder, event);
}
});
};
dataAvailableListeners.set(listener, patchedEventListener);
}
else if (type === 'error') {
// Bug #12 & #13: Firefox fires a regular event with an error property.
patchedEventListener = (event) => {
if (event instanceof ErrorEvent) {
listener.call(nativeMediaRecorder, event);
bufferedBlobEvents.length = 0;
dataAvailableListener.call(nativeMediaRecorder, blobEvent);
}
else {
listener.call(nativeMediaRecorder, new ErrorEvent('error', { error: event.error }));
}
};
errorListeners.set(listener, patchedEventListener);
}
isSliced = false;
listener.call(nativeMediaRecorder, event);
};
stopListeners.set(listener, patchedEventListener);
}
}
return addEventListener.call(nativeMediaRecorder, type, patchedEventListener, options);
};
})(nativeMediaRecorder.addEventListener);
nativeMediaRecorder.removeEventListener = ((removeEventListener) => {
return (type, listener, options) => {
let patchedEventListener = listener;
if (typeof listener === 'function') {
if (type === 'dataavailable') {
bufferedBlobEventListeners.delete(listener);
const dataAvailableListener = dataAvailableListeners.get(listener);
if (dataAvailableListener !== undefined) {
patchedEventListener = dataAvailableListener;
}
else if (type === 'stop') {
// Bug #7 & #8: Chrome fires the dataavailable and stop events before it fires the error event.
patchedEventListener = (event) => {
isActive = false;
setTimeout(() => listener.call(nativeMediaRecorder, event));
};
stopListeners.set(listener, patchedEventListener);
}
else if (type === 'error') {
const errorListener = errorListeners.get(listener);
if (errorListener !== undefined) {
patchedEventListener = errorListener;
}
}
return addEventListener.call(nativeMediaRecorder, type, patchedEventListener, options);
};
})(nativeMediaRecorder.addEventListener);
nativeMediaRecorder.dispatchEvent = ((dispatchEvent) => {
// Bug #7 & #8: Chrome fires the dataavailable and stop events before it fires the error event.
return (event) => {
let wasActive;
setTimeout(() => {
wasActive = isActive;
isActive = false;
});
const returnValue = dispatchEvent.call(nativeMediaRecorder, event);
setTimeout(() => (isActive = wasActive));
return returnValue;
};
})(nativeMediaRecorder.dispatchEvent);
nativeMediaRecorder.removeEventListener = ((removeEventListener) => {
return (type, listener, options) => {
let patchedEventListener = listener;
if (typeof listener === 'function') {
if (type === 'dataavailable') {
const dataAvailableListener = dataAvailableListeners.get(listener);
if (dataAvailableListener !== undefined) {
patchedEventListener = dataAvailableListener;
}
else if (type === 'stop') {
const stopListener = stopListeners.get(listener);
if (stopListener !== undefined) {
patchedEventListener = stopListener;
}
else if (type === 'error') {
const errorListener = errorListeners.get(listener);
if (errorListener !== undefined) {
patchedEventListener = errorListener;
}
}
else if (type === 'stop') {
const stopListener = stopListeners.get(listener);
if (stopListener !== undefined) {
patchedEventListener = stopListener;
}
}
}
return removeEventListener.call(nativeMediaRecorder, type, patchedEventListener, options);
};
})(nativeMediaRecorder.removeEventListener);
nativeMediaRecorder.start = ((start) => {
return (timeslice) => {
/*
* Bug #6: Chrome 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();
}
isActive = timeslice !== undefined;
return timeslice === undefined ? start.call(nativeMediaRecorder) : start.call(nativeMediaRecorder, timeslice);
};
})(nativeMediaRecorder.start);
return nativeMediaRecorder;
};
}
return removeEventListener.call(nativeMediaRecorder, type, patchedEventListener, options);
};
})(nativeMediaRecorder.removeEventListener);
nativeMediaRecorder.start = ((start) => {
return (timeslice) => {
isSliced = timeslice !== undefined;
return timeslice === undefined ? start.call(nativeMediaRecorder) : start.call(nativeMediaRecorder, timeslice);
};
})(nativeMediaRecorder.start);
return nativeMediaRecorder;
};
//# sourceMappingURL=native-media-recorder.js.map

@@ -122,6 +122,3 @@ import { encode, instantiate } from 'media-encoder-host';

const audioTracks = mediaStream.getAudioTracks();
// @todo TypeScript v4.4.2 removed the channelCount property from the MediaTrackSettings interface.
const channelCount = audioTracks.length === 0
? 2
: (_a = audioTracks[0].getSettings().channelCount) !== null && _a !== void 0 ? _a : 2;
const channelCount = audioTracks.length === 0 ? 2 : (_a = audioTracks[0].getSettings().channelCount) !== null && _a !== void 0 ? _a : 2;
promisedAudioNodesAndEncoderId = Promise.all([

@@ -128,0 +125,0 @@ resume(),

import { encode, instantiate } from 'media-encoder-host';
import { MultiBufferDataView } from 'multi-buffer-data-view';
import { on } from 'subscribable-things';
export const createWebmPcmMediaRecorderFactory = (createBlobEvent, createNotSupportedError, decodeWebMChunk, readVariableSizeInteger) => {
export const createWebmPcmMediaRecorderFactory = (createBlobEvent, decodeWebMChunk, readVariableSizeInteger) => {
return (eventTarget, nativeMediaRecorderConstructor, mediaStream, mimeType) => {
const audioTracks = mediaStream.getAudioTracks();
const bufferedArrayBuffers = [];

@@ -61,15 +60,6 @@ const nativeMediaRecorder = new nativeMediaRecorderConstructor(mediaStream, { mimeType: 'audio/webm;codecs=pcm' });

start(timeslice) {
var _a, _b;
/*
* Bug #6: Chrome will emit a blob without any data when asked to encode a MediaStream with a video track into an audio
* codec.
*/
if (mediaStream.getVideoTracks().length > 0) {
throw createNotSupportedError();
}
if (nativeMediaRecorder.state === 'inactive') {
const [audioTrack] = mediaStream.getAudioTracks();
if (audioTrack !== undefined && nativeMediaRecorder.state === 'inactive') {
// Bug #19: Chrome does not expose the correct channelCount property right away.
// @todo TypeScript v4.4.2 removed the channelCount property from the MediaTrackSettings interface.
const channelCount = ((_a = audioTracks[0]) === null || _a === void 0 ? void 0 : _a.getSettings()).channelCount;
const sampleRate = (_b = audioTracks[0]) === null || _b === void 0 ? void 0 : _b.getSettings().sampleRate;
const { channelCount, sampleRate } = audioTrack.getSettings();
if (channelCount === undefined) {

@@ -76,0 +66,0 @@ throw new Error('The channelCount is not defined.');

@@ -11,3 +11,3 @@ import { register as rgstr } from 'media-encoder-host';

import { createNativeBlobEventConstructor } from './factories/native-blob-event-constructor';
import { createNativeMediaRecorderFactory } from './factories/native-media-recorder';
import { createNativeMediaRecorder } from './factories/native-media-recorder';
import { createNativeMediaRecorderConstructor } from './factories/native-media-recorder-constructor';

@@ -38,6 +38,5 @@ import { createNotSupportedError } from './factories/not-supported-error';

const decodeWebMChunk = createDecodeWebMChunk(readElementContent, readElementType);
const createWebmPcmMediaRecorder = createWebmPcmMediaRecorderFactory(createBlobEvent, createNotSupportedError, decodeWebMChunk, readVariableSizeInteger);
const createWebmPcmMediaRecorder = createWebmPcmMediaRecorderFactory(createBlobEvent, decodeWebMChunk, readVariableSizeInteger);
const createEventTarget = createEventTargetFactory(window);
const nativeMediaRecorderConstructor = createNativeMediaRecorderConstructor(window);
const createNativeMediaRecorder = createNativeMediaRecorderFactory(createNotSupportedError);
const mediaRecorderConstructor = createMediaRecorderConstructor(createNativeMediaRecorder, createNotSupportedError, createWebAudioMediaRecorder, createWebmPcmMediaRecorder, encoderRegexes, createEventTargetConstructor(createEventTarget, wrapEventListener), nativeMediaRecorderConstructor);

@@ -44,0 +43,0 @@ export { mediaRecorderConstructor as MediaRecorder };

@@ -24,3 +24,2 @@ export * from './blob-event-factory';

export * from './native-media-recorder-factory';
export * from './native-media-recorder-factory-factory';
export * from './not-supported-error-factory';

@@ -27,0 +26,0 @@ export * from './promised-data-view-element-type-encoder-id-and-port';

@@ -24,3 +24,2 @@ export * from './blob-event-factory';

export * from './native-media-recorder-factory';
export * from './native-media-recorder-factory-factory';
export * from './not-supported-error-factory';

@@ -27,0 +26,0 @@ export * from './promised-data-view-element-type-encoder-id-and-port';

import { TBlobEventFactory } from './blob-event-factory';
import { TDecodeWebMChunkFunction } from './decode-web-m-chunk-function';
import { TNotSupportedErrorFactory } from './not-supported-error-factory';
import { TReadVariableSizeIntegerFunction } from './read-variable-size-integer-function';
import { TWebmPcmMediaRecorderFactory } from './webm-pcm-media-recorder-factory';
export type TWebmPcmMediaRecorderFactoryFactory = (createBlobEvent: TBlobEventFactory, createNotSupportedError: TNotSupportedErrorFactory, decodeWebMChunk: TDecodeWebMChunkFunction, readVariableSizeInteger: TReadVariableSizeIntegerFunction) => TWebmPcmMediaRecorderFactory;
export type TWebmPcmMediaRecorderFactoryFactory = (createBlobEvent: TBlobEventFactory, decodeWebMChunk: TDecodeWebMChunkFunction, readVariableSizeInteger: TReadVariableSizeIntegerFunction) => TWebmPcmMediaRecorderFactory;
//# sourceMappingURL=webm-pcm-media-recorder-factory-factory.d.ts.map

@@ -172,8 +172,22 @@ (function (global, factory) {

* 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('error' in event && event.error !== null && _typeof(event.error) === 'object' && 'name' in event.error && event.error.name !== 'UnknownError');
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();

@@ -386,115 +400,113 @@ context.fillRect(0, 0, 1, 1);

var createNativeMediaRecorderFactory = function createNativeMediaRecorderFactory(createNotSupportedError) {
return function (nativeMediaRecorderConstructor, stream, mediaRecorderOptions) {
var bufferedBlobs = [];
var dataAvailableListeners = new WeakMap();
var errorListeners = new WeakMap();
var nativeMediaRecorder = new nativeMediaRecorderConstructor(stream, mediaRecorderOptions);
var stopListeners = new WeakMap();
var isActive = true;
nativeMediaRecorder.addEventListener = function (addEventListener) {
return function (type, listener, options) {
var patchedEventListener = listener;
if (typeof listener === 'function') {
if (type === 'dataavailable') {
// Bug #7 & #8: Chrome fires the dataavailable and stop events before it fires the error event.
patchedEventListener = function patchedEventListener(event) {
setTimeout(function () {
if (isActive && nativeMediaRecorder.state === 'inactive') {
bufferedBlobs.push(event.data);
} else {
if (bufferedBlobs.length > 0) {
var blob = event.data;
Object.defineProperty(event, 'data', {
value: new Blob([].concat(bufferedBlobs, [blob]), {
type: blob.type
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, 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 normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
var createNativeMediaRecorder = function createNativeMediaRecorder(nativeMediaRecorderConstructor, stream, mediaRecorderOptions) {
var bufferedBlobEventListeners = new Map();
var dataAvailableListeners = new WeakMap();
var errorListeners = new WeakMap();
var nativeMediaRecorder = new nativeMediaRecorderConstructor(stream, mediaRecorderOptions);
var stopListeners = new WeakMap();
var isSliced = false;
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) {
if (isSliced && nativeMediaRecorder.state === 'inactive') {
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 (_ref) {
var data = _ref.data;
return data;
}), {
type: blobEvent.data.type
})
});
bufferedBlobs.length = 0;
}
listener.call(nativeMediaRecorder, event);
_bufferedBlobEvents.length = 0;
dataAvailableListener.call(nativeMediaRecorder, blobEvent);
}
});
};
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 #7 & #8: Chrome fires the dataavailable and stop events before it fires the error event.
patchedEventListener = function patchedEventListener(event) {
isActive = false;
setTimeout(function () {
return listener.call(nativeMediaRecorder, event);
});
};
stopListeners.set(listener, patchedEventListener);
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
isSliced = false;
listener.call(nativeMediaRecorder, event);
};
stopListeners.set(listener, patchedEventListener);
}
return addEventListener.call(nativeMediaRecorder, type, patchedEventListener, options);
};
}(nativeMediaRecorder.addEventListener);
nativeMediaRecorder.dispatchEvent = function (dispatchEvent) {
// Bug #7 & #8: Chrome fires the dataavailable and stop events before it fires the error event.
return function (event) {
var wasActive;
setTimeout(function () {
wasActive = isActive;
isActive = false;
});
var returnValue = dispatchEvent.call(nativeMediaRecorder, event);
setTimeout(function () {
return isActive = wasActive;
});
return returnValue;
};
}(nativeMediaRecorder.dispatchEvent);
nativeMediaRecorder.removeEventListener = function (removeEventListener) {
return function (type, listener, options) {
var patchedEventListener = listener;
if (typeof listener === 'function') {
if (type === 'dataavailable') {
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 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: Chrome 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();
}
isActive = timeslice !== undefined;
return timeslice === undefined ? start.call(nativeMediaRecorder) : start.call(nativeMediaRecorder, timeslice);
};
}(nativeMediaRecorder.start);
return nativeMediaRecorder;
};
}
return removeEventListener.call(nativeMediaRecorder, type, patchedEventListener, options);
};
}(nativeMediaRecorder.removeEventListener);
nativeMediaRecorder.start = function (start) {
return function (timeslice) {
isSliced = timeslice !== undefined;
return timeslice === undefined ? start.call(nativeMediaRecorder) : start.call(nativeMediaRecorder, timeslice);
};
}(nativeMediaRecorder.start);
return nativeMediaRecorder;
};

@@ -815,3 +827,2 @@

var audioTracks = mediaStream.getAudioTracks();
// @todo TypeScript v4.4.2 removed the channelCount property from the MediaTrackSettings interface.
var channelCount = audioTracks.length === 0 ? 2 : (_a = audioTracks[0].getSettings().channelCount) !== null && _a !== void 0 ? _a : 2;

@@ -880,5 +891,4 @@ promisedAudioNodesAndEncoderId = Promise.all([_resume(), promisedAudioWorkletModule.then(function () {

var createWebmPcmMediaRecorderFactory = function createWebmPcmMediaRecorderFactory(createBlobEvent, createNotSupportedError, decodeWebMChunk, readVariableSizeInteger) {
var createWebmPcmMediaRecorderFactory = function createWebmPcmMediaRecorderFactory(createBlobEvent, decodeWebMChunk, readVariableSizeInteger) {
return function (eventTarget, nativeMediaRecorderConstructor, mediaStream, mimeType) {
var audioTracks = mediaStream.getAudioTracks();
var bufferedArrayBuffers = [];

@@ -966,15 +976,10 @@ var nativeMediaRecorder = new nativeMediaRecorderConstructor(mediaStream, {

start: function start(timeslice) {
var _a, _b;
/*
* Bug #6: Chrome will emit a blob without any data when asked to encode a MediaStream with a video track into an audio
* codec.
*/
if (mediaStream.getVideoTracks().length > 0) {
throw createNotSupportedError();
}
if (nativeMediaRecorder.state === 'inactive') {
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.
// @todo TypeScript v4.4.2 removed the channelCount property from the MediaTrackSettings interface.
var channelCount = ((_a = audioTracks[0]) === null || _a === void 0 ? void 0 : _a.getSettings()).channelCount;
var sampleRate = (_b = audioTracks[0]) === null || _b === void 0 ? void 0 : _b.getSettings().sampleRate;
var _audioTrack$getSettin = audioTrack.getSettings(),
channelCount = _audioTrack$getSettin.channelCount,
sampleRate = _audioTrack$getSettin.sampleRate;
if (channelCount === undefined) {

@@ -1151,6 +1156,5 @@ throw new Error('The channelCount is not defined.');

var decodeWebMChunk = createDecodeWebMChunk(readElementContent, readElementType);
var createWebmPcmMediaRecorder = createWebmPcmMediaRecorderFactory(createBlobEvent, createNotSupportedError, decodeWebMChunk, readVariableSizeInteger);
var createWebmPcmMediaRecorder = createWebmPcmMediaRecorderFactory(createBlobEvent, decodeWebMChunk, readVariableSizeInteger);
var createEventTarget = createEventTargetFactory(window$1);
var nativeMediaRecorderConstructor = createNativeMediaRecorderConstructor(window$1);
var createNativeMediaRecorder = createNativeMediaRecorderFactory(createNotSupportedError);
var mediaRecorderConstructor = createMediaRecorderConstructor(createNativeMediaRecorder, createNotSupportedError, createWebAudioMediaRecorder, createWebmPcmMediaRecorder, encoderRegexes, createEventTargetConstructor(createEventTarget, wrapEventListener), nativeMediaRecorderConstructor);

@@ -1157,0 +1161,0 @@ var isSupported = function isSupported() {

@@ -39,3 +39,3 @@ {

"eslint": "^8.46.0",
"eslint-config-holy-grail": "^57.2.19",
"eslint-config-holy-grail": "^57.2.20",
"extendable-media-recorder-wav-encoder": "^7.0.96",

@@ -59,3 +59,3 @@ "grunt": "^1.6.1",

"rimraf": "^5.0.1",
"rollup": "^3.27.0",
"rollup": "^3.27.2",
"sinon": "^15.2.0",

@@ -100,3 +100,3 @@ "sinon-chai": "^3.7.0",

"types": "build/es2019/module.d.ts",
"version": "8.1.0"
"version": "9.0.0"
}

@@ -54,2 +54,8 @@ import { TIsSupportedPromiseFactory } from '../types';

* 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.
*/

@@ -59,5 +65,11 @@ new Promise((resolve) => {

let hasDispatchedDataAvailableEvent = false;
let hasDispatchedStopEvent = false;
mediaRecorder.addEventListener('dataavailable', () => (hasDispatchedDataAvailableEvent = true));
mediaRecorder.addEventListener('error', (event) => {
resolve(
'error' in event &&
!hasDispatchedDataAvailableEvent &&
!hasDispatchedStopEvent &&
'error' in event &&
event.error !== null &&

@@ -69,2 +81,3 @@ typeof event.error === 'object' &&

});
mediaRecorder.addEventListener('stop', () => (hasDispatchedStopEvent = true));
mediaRecorder.start();

@@ -71,0 +84,0 @@ context.fillRect(0, 0, 1, 1);

import { IBlobEvent, IMediaRecorder } from '../interfaces';
import { TEventHandler, TNativeMediaRecorderFactoryFactory } from '../types';
import { TEventHandler, TNativeMediaRecorderFactory } from '../types';
export const createNativeMediaRecorderFactory: TNativeMediaRecorderFactoryFactory = (createNotSupportedError) => {
return (nativeMediaRecorderConstructor, stream, mediaRecorderOptions) => {
const bufferedBlobs: Blob[] = [];
const dataAvailableListeners = new WeakMap<EventListener, (this: IMediaRecorder, event: IBlobEvent) => void>();
const errorListeners = new WeakMap<EventListener, (this: IMediaRecorder, event: ErrorEvent) => void>();
const nativeMediaRecorder = new nativeMediaRecorderConstructor(stream, mediaRecorderOptions);
const stopListeners = new WeakMap<EventListener, (this: IMediaRecorder, event: Event) => void>();
export const createNativeMediaRecorder: TNativeMediaRecorderFactory = (nativeMediaRecorderConstructor, stream, mediaRecorderOptions) => {
const bufferedBlobEventListeners: Map<EventListener, IBlobEvent[]> = new Map();
const dataAvailableListeners = new WeakMap<EventListener, (this: IMediaRecorder, event: IBlobEvent) => void>();
const errorListeners = new WeakMap<EventListener, (this: IMediaRecorder, event: ErrorEvent) => void>();
const nativeMediaRecorder = new nativeMediaRecorderConstructor(stream, mediaRecorderOptions);
const stopListeners = new WeakMap<EventListener, (this: IMediaRecorder, event: Event) => void>();
let isActive = true;
let isSliced = false;
nativeMediaRecorder.addEventListener = ((addEventListener) => {
return (
type: string,
listener: null | TEventHandler<IMediaRecorder> | EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions
) => {
let patchedEventListener = listener;
nativeMediaRecorder.addEventListener = ((addEventListener) => {
return (
type: string,
listener: null | TEventHandler<IMediaRecorder> | EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions
) => {
let patchedEventListener = listener;
if (typeof listener === 'function') {
if (type === 'dataavailable') {
// Bug #7 & #8: Chrome fires the dataavailable and stop events before it fires the error event.
patchedEventListener = (event: IBlobEvent) => {
setTimeout(() => {
if (isActive && nativeMediaRecorder.state === 'inactive') {
bufferedBlobs.push(event.data);
} else {
if (bufferedBlobs.length > 0) {
const blob = event.data;
if (typeof listener === 'function') {
if (type === 'dataavailable') {
const bufferedBlobEvents: IBlobEvent[] = [];
Object.defineProperty(event, 'data', {
value: new Blob([...bufferedBlobs, blob], { type: blob.type })
});
// Bug #20: Firefox dispatches multiple dataavailable events while being inactive.
patchedEventListener = (event: IBlobEvent) => {
if (isSliced && nativeMediaRecorder.state === 'inactive') {
bufferedBlobEvents.push(event);
} else {
listener.call(nativeMediaRecorder, event);
}
};
bufferedBlobs.length = 0;
}
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 = (event: ErrorEvent | (Event & { error?: Error })) => {
if (event instanceof ErrorEvent) {
listener.call(nativeMediaRecorder, event);
} else {
listener.call(nativeMediaRecorder, new ErrorEvent('error', { error: event.error }));
}
};
listener.call(nativeMediaRecorder, event);
errorListeners.set(listener, patchedEventListener);
} else if (type === 'stop') {
// Bug #20: Firefox dispatches multiple dataavailable events while being inactive.
patchedEventListener = (event: Event) => {
for (const [dataAvailableListener, bufferedBlobEvents] of bufferedBlobEventListeners.entries()) {
if (bufferedBlobEvents.length > 0) {
const [blobEvent] = bufferedBlobEvents;
if (bufferedBlobEvents.length > 1) {
Object.defineProperty(blobEvent, 'data', {
value: new Blob(
bufferedBlobEvents.map(({ data }) => data),
{ type: blobEvent.data.type }
)
});
}
});
};
dataAvailableListeners.set(listener, patchedEventListener);
} else if (type === 'error') {
// Bug #12 & #13: Firefox fires a regular event with an error property.
patchedEventListener = (event: ErrorEvent | (Event & { error?: Error })) => {
if (event instanceof ErrorEvent) {
listener.call(nativeMediaRecorder, event);
} else {
listener.call(nativeMediaRecorder, new ErrorEvent('error', { error: event.error }));
bufferedBlobEvents.length = 0;
dataAvailableListener.call(nativeMediaRecorder, blobEvent);
}
};
}
errorListeners.set(listener, patchedEventListener);
} else if (type === 'stop') {
// Bug #7 & #8: Chrome fires the dataavailable and stop events before it fires the error event.
patchedEventListener = (event: Event) => {
isActive = false;
isSliced = false;
setTimeout(() => listener.call(nativeMediaRecorder, event));
};
listener.call(nativeMediaRecorder, event);
};
stopListeners.set(listener, patchedEventListener);
}
stopListeners.set(listener, patchedEventListener);
}
}
return addEventListener.call(nativeMediaRecorder, type, <EventListenerOrEventListenerObject>patchedEventListener, options);
};
})(nativeMediaRecorder.addEventListener);
return addEventListener.call(nativeMediaRecorder, type, <EventListenerOrEventListenerObject>patchedEventListener, options);
};
})(nativeMediaRecorder.addEventListener);
nativeMediaRecorder.dispatchEvent = ((dispatchEvent) => {
// Bug #7 & #8: Chrome fires the dataavailable and stop events before it fires the error event.
return (event: Event) => {
let wasActive: boolean;
nativeMediaRecorder.removeEventListener = ((removeEventListener) => {
return (
type: string,
listener: null | TEventHandler<IMediaRecorder> | EventListenerOrEventListenerObject,
options?: boolean | EventListenerOptions
) => {
let patchedEventListener = listener;
setTimeout(() => {
wasActive = isActive;
isActive = false;
});
if (typeof listener === 'function') {
if (type === 'dataavailable') {
bufferedBlobEventListeners.delete(listener);
const returnValue = dispatchEvent.call(nativeMediaRecorder, event);
const dataAvailableListener = dataAvailableListeners.get(listener);
setTimeout(() => (isActive = wasActive));
if (dataAvailableListener !== undefined) {
patchedEventListener = dataAvailableListener;
}
} else if (type === 'error') {
const errorListener = errorListeners.get(listener);
return returnValue;
};
})(nativeMediaRecorder.dispatchEvent);
if (errorListener !== undefined) {
patchedEventListener = errorListener;
}
} else if (type === 'stop') {
const stopListener = stopListeners.get(listener);
nativeMediaRecorder.removeEventListener = ((removeEventListener) => {
return (
type: string,
listener: null | TEventHandler<IMediaRecorder> | EventListenerOrEventListenerObject,
options?: boolean | EventListenerOptions
) => {
let patchedEventListener = listener;
if (typeof listener === 'function') {
if (type === 'dataavailable') {
const dataAvailableListener = dataAvailableListeners.get(listener);
if (dataAvailableListener !== undefined) {
patchedEventListener = dataAvailableListener;
}
} else if (type === 'error') {
const errorListener = errorListeners.get(listener);
if (errorListener !== undefined) {
patchedEventListener = errorListener;
}
} else if (type === 'stop') {
const stopListener = stopListeners.get(listener);
if (stopListener !== undefined) {
patchedEventListener = stopListener;
}
if (stopListener !== undefined) {
patchedEventListener = stopListener;
}
}
}
return removeEventListener.call(
nativeMediaRecorder,
type,
<EventListenerOrEventListenerObject>patchedEventListener,
options
);
};
})(nativeMediaRecorder.removeEventListener);
return removeEventListener.call(nativeMediaRecorder, type, <EventListenerOrEventListenerObject>patchedEventListener, options);
};
})(nativeMediaRecorder.removeEventListener);
nativeMediaRecorder.start = ((start) => {
return (timeslice?: number) => {
/*
* Bug #6: Chrome 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();
}
nativeMediaRecorder.start = ((start) => {
return (timeslice?: number) => {
isSliced = timeslice !== undefined;
isActive = timeslice !== undefined;
return timeslice === undefined ? start.call(nativeMediaRecorder) : start.call(nativeMediaRecorder, timeslice);
};
})(nativeMediaRecorder.start);
return timeslice === undefined ? start.call(nativeMediaRecorder) : start.call(nativeMediaRecorder, timeslice);
};
})(nativeMediaRecorder.start);
return nativeMediaRecorder;
};
return nativeMediaRecorder;
};

@@ -180,7 +180,3 @@ import { encode, instantiate } from 'media-encoder-host';

const audioTracks = mediaStream.getAudioTracks();
// @todo TypeScript v4.4.2 removed the channelCount property from the MediaTrackSettings interface.
const channelCount =
audioTracks.length === 0
? 2
: (<MediaTrackSettings & { channelCount?: number }>audioTracks[0].getSettings()).channelCount ?? 2;
const channelCount = audioTracks.length === 0 ? 2 : audioTracks[0].getSettings().channelCount ?? 2;

@@ -187,0 +183,0 @@ promisedAudioNodesAndEncoderId = Promise.all([

@@ -8,3 +8,2 @@ import { encode, instantiate } from 'media-encoder-host';

createBlobEvent,
createNotSupportedError,
decodeWebMChunk,

@@ -14,3 +13,2 @@ readVariableSizeInteger

return (eventTarget, nativeMediaRecorderConstructor, mediaStream, mimeType) => {
const audioTracks = mediaStream.getAudioTracks();
const bufferedArrayBuffers: ArrayBuffer[] = [];

@@ -86,15 +84,7 @@ const nativeMediaRecorder = new nativeMediaRecorderConstructor(mediaStream, { mimeType: 'audio/webm;codecs=pcm' });

start(timeslice?: number): void {
/*
* Bug #6: Chrome will emit a blob without any data when asked to encode a MediaStream with a video track into an audio
* codec.
*/
if (mediaStream.getVideoTracks().length > 0) {
throw createNotSupportedError();
}
const [audioTrack] = mediaStream.getAudioTracks();
if (nativeMediaRecorder.state === 'inactive') {
if (audioTrack !== undefined && nativeMediaRecorder.state === 'inactive') {
// Bug #19: Chrome does not expose the correct channelCount property right away.
// @todo TypeScript v4.4.2 removed the channelCount property from the MediaTrackSettings interface.
const channelCount = (<MediaTrackSettings & { channelCount?: number }>audioTracks[0]?.getSettings()).channelCount;
const sampleRate = audioTracks[0]?.getSettings().sampleRate;
const { channelCount, sampleRate } = audioTrack.getSettings();

@@ -101,0 +91,0 @@ if (channelCount === undefined) {

@@ -11,3 +11,3 @@ import { register as rgstr } from 'media-encoder-host';

import { createNativeBlobEventConstructor } from './factories/native-blob-event-constructor';
import { createNativeMediaRecorderFactory } from './factories/native-media-recorder';
import { createNativeMediaRecorder } from './factories/native-media-recorder';
import { createNativeMediaRecorderConstructor } from './factories/native-media-recorder-constructor';

@@ -47,12 +47,6 @@ import { createNotSupportedError } from './factories/not-supported-error';

const decodeWebMChunk = createDecodeWebMChunk(readElementContent, readElementType);
const createWebmPcmMediaRecorder = createWebmPcmMediaRecorderFactory(
createBlobEvent,
createNotSupportedError,
decodeWebMChunk,
readVariableSizeInteger
);
const createWebmPcmMediaRecorder = createWebmPcmMediaRecorderFactory(createBlobEvent, decodeWebMChunk, readVariableSizeInteger);
const createEventTarget = createEventTargetFactory(window);
const nativeMediaRecorderConstructor = createNativeMediaRecorderConstructor(window);
const createNativeMediaRecorder = createNativeMediaRecorderFactory(createNotSupportedError);
const mediaRecorderConstructor: IMediaRecorderConstructor = createMediaRecorderConstructor(

@@ -59,0 +53,0 @@ createNativeMediaRecorder,

@@ -24,3 +24,2 @@ export * from './blob-event-factory';

export * from './native-media-recorder-factory';
export * from './native-media-recorder-factory-factory';
export * from './not-supported-error-factory';

@@ -27,0 +26,0 @@ export * from './promised-data-view-element-type-encoder-id-and-port';

import { TBlobEventFactory } from './blob-event-factory';
import { TDecodeWebMChunkFunction } from './decode-web-m-chunk-function';
import { TNotSupportedErrorFactory } from './not-supported-error-factory';
import { TReadVariableSizeIntegerFunction } from './read-variable-size-integer-function';

@@ -9,5 +8,4 @@ import { TWebmPcmMediaRecorderFactory } from './webm-pcm-media-recorder-factory';

createBlobEvent: TBlobEventFactory,
createNotSupportedError: TNotSupportedErrorFactory,
decodeWebMChunk: TDecodeWebMChunkFunction,
readVariableSizeInteger: TReadVariableSizeIntegerFunction
) => TWebmPcmMediaRecorderFactory;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc