Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

react-record-webcam

Package Overview
Dependencies
Maintainers
1
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-record-webcam - npm Package Compare versions

Comparing version 0.0.18 to 1.0.0-rc.0

276

dist/index.d.ts

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

export * from './component';
export * from './hook';
export * from './types';
type ByIdDevice = {
label: string;
type: 'videoinput' | 'audioinput';
};
type ById = Record<string, ByIdDevice>;
type ByLabelDevice = {
label: string;
deviceId: string;
};
type ByType = {
video: ByLabelDevice[];
audio: ByLabelDevice[];
};
type InitialDevice = {
deviceId: string;
label: string;
};
type InitialDevices = {
video: InitialDevice | null;
audio: InitialDevice | null;
};
type Devices = {
devicesByType: ByType;
devicesById: ById;
initialDevices: InitialDevices;
};
declare function getDevices(): Promise<Devices>;
declare const ERROR_MESSAGES: {
readonly CODEC_NOT_SUPPORTED: "CODEC_NOT_SUPPORTED";
readonly SESSION_EXISTS: "SESSION_EXISTS";
readonly NO_RECORDING_WITH_ID: "NO_RECORDING_WITH_ID";
readonly NO_USER_PERMISSION: "NO_USER_PERMISSION";
};
declare const STATUS: {
readonly INITIAL: "INITIAL";
readonly CLOSED: "CLOSED";
readonly OPEN: "OPEN";
readonly RECORDING: "RECORDING";
readonly STOPPED: "STOPPED";
readonly ERROR: "ERROR";
readonly PAUSED: "PAUSED";
};
type Status = keyof typeof STATUS;
type Recording = {
/**
* @property {string} id - The ID of the recording.
*/
id: string;
/**
* @property {string} id - The ID of the audio device.
*/
audioId: string;
/**
* @property {string} [audioLabel] - The label of the audio device.
*/
audioLabel?: string;
/**
* @property {Blob} [blob] - The blob of the recording.
*/
blob?: Blob;
/**
* @property {Blob[]} blobChunks - Single blob or chunks per timeslice of the recording.
*/
blobChunks: Blob[];
/**
* @property {string} fileName - The name of the file.
*/
fileName: string;
/**
* @property {string} fileType - The type of the file.
*/
fileType: string;
/**
* @property {boolean} isMuted - Whether the recording is muted.
*/
isMuted: boolean;
/**
* @property {string} mimeType - The MIME type of the recording.
*/
mimeType: string;
/**
* @property {string | null} objectURL - The object URL of the recording.
*/
objectURL: string | null;
/**
* @property {React.RefObject<HTMLVideoElement>} previewRef - React Ref for the preview element.
*/
previewRef: React.RefObject<HTMLVideoElement>;
/**
* @property {MediaRecorder | null} recorder - The MediaRecoder instance of the recording.
*/
recorder: MediaRecorder | null;
/**
* @property {Status} status - The status of the recording.
*/
status: Status;
/**
* @property {string} videoId - The ID of the video device.
*/
videoId: string;
/**
* @property {string} [videoLabel] - The label of the video device.
*/
videoLabel?: string;
/**
* @property {React.RefObject<HTMLVideoElement>} webcamRef - React Ref for the webcam element.
*/
webcamRef: React.RefObject<HTMLVideoElement>;
};
type UseRecorder = {
/**
* Array of active recordings.
*/
activeRecordings: Recording[];
/**
* Clears all active recordings.
* @returns A promise that resolves when all recordings are cleared.
*/
clearAllRecordings: () => Promise<void>;
/**
* Applies recording options to a specific recording.
* @param {string} recordingId - The ID of the recording.
* @returns {Promise<Recording | void>} - A promise that resolves to a Recording object or void.
*/
applyRecordingOptions: (recordingId: string) => Promise<Recording | void>;
/**
* Cancels the current recording session.
* @param recordingId The ID of the recording to cancel.
* @returns A promise that resolves when the recording is canceled.
*/
cancelRecording: (recordingId: string) => Promise<void>;
/**
* Clears the preview of a specific recording.
* @param {string} recordingId - The ID of the recording.
* @returns {Promise<Recording | void>} - A promise that resolves to a Recording object or void.
*/
clearPreview: (recordingId: string) => Promise<Recording | void>;
/**
* Downloads a specific recording.
* @param {string} recordingId - The ID of the recording.
* @returns {Promise<void>} - A promise that resolves when the download is complete.
*/
download: (recordingId: string) => Promise<void>;
/**
* Pauses the current recording.
* @param recordingId The ID of the recording to pause.
* @returns A promise that resolves with the updated recording, or void if an error occurs.
*/
pauseRecording: (recordingId: string) => Promise<Recording | void>;
/**
* Resumes a paused recording.
* @param recordingId The ID of the recording to resume.
* @returns A promise that resolves with the updated recording, or void if an error occurs.
*/
resumeRecording: (recordingId: string) => Promise<Recording | void>;
/**
* Starts a new recording session.
* @param recordingId The ID for the new recording session.
* @returns A promise that resolves with the new recording, or void if an error occurs.
*/
startRecording: (recordingId: string) => Promise<Recording | void>;
/**
* Stops the current recording session.
* @param recordingId The ID of the recording to stop.
* @returns A promise that resolves with the stopped recording, or void if an error occurs.
*/
stopRecording: (recordingId: string) => Promise<Recording | void>;
/**
* Mutes or unmutes the recording audio.
* @param recordingId The ID of the recording to mute or unmute.
* @returns A promise that resolves with the updated recording, or void if an error occurs.
*/
muteRecording: (recordingId: string) => Promise<Recording | void>;
/**
* Creates a new recording session with specified video and audio sources.
* @param videoId The ID of the video source device.
* @param audioId The ID of the audio source device.
* @returns A promise that resolves with the created recording, or void if an error occurs.
*/
createRecording: (videoId?: string, audioId?: string) => Promise<Recording | void>;
};
type UseCamera = {
/**
* Applies given constraints to the camera for a specific recording.
* @param recordingId The ID of the recording to apply constraints to.
* @param constraints The new constraints to apply to the camera.
* @returns A promise resolving to the updated recording or void if an error occurs.
*/
applyConstraints: (recordingId: string, constraints: MediaTrackConstraints) => Promise<Recording | void>;
/**
* Closes the camera for a specific recording.
* @param recordingId The ID of the recording for which to close the camera.
* @returns A promise resolving to the updated recording or void if an error occurs.
*/
closeCamera: (recordingId: string) => Promise<Recording | void>;
/**
* Opens the camera for a specific recording with optional constraints.
* @param recordingId The ID of the recording for which to open the camera.
* @returns A promise resolving to the updated recording or void if an error occurs.
*/
openCamera: (recordingId: string) => Promise<Recording | void>;
};
/**
* Options for customizing the recording settings.
*/
type Options = {
/** The name of the output file. */
fileName: string;
/** The MIME type of the output file. */
fileType: string;
/** The time interval (in milliseconds) for splitting the recording into chunks. */
timeSlice: number;
};
/**
* Configuration options for the `useRecordWebcam` hook.
*/
type UseRecordWebcamArgs = {
/** Media track constraints for the camera. */
mediaTrackConstraints?: Partial<MediaTrackConstraints>;
/** Options for the MediaRecorder API. */
mediaRecorderOptions?: Partial<MediaRecorderOptions>;
/** Custom options for recording. */
options?: Partial<Options>;
};
/**
* @typedef {Object} UseRecordWebcam
* The return type of `useRecordWebcam`, providing access to webcam recording functionalities.
*/
type UseRecordWebcam = {
/** Array of active recordings. */
activeRecordings: Recording[];
/** Function to clear all recordings. */
clearAllRecordings: () => Promise<void>;
/** Function to clear the current error message. */
clearError: () => void;
/** Object containing devices by their ID. */
devicesById: ById | undefined;
/** Object categorizing devices by their type. */
devicesByType: ByType | undefined;
/** The current error message, if any, related to recording. */
errorMessage: string | null;
} & UseCamera & UseRecorder;
/**
* React Record Webcam hook.
* @param args Configuration options for the hook.
* @returns {UseRecordWebcam} providing access to webcam recording functionalities.
*/
declare function useRecordWebcam({ mediaRecorderOptions, mediaTrackConstraints, options, }?: Partial<UseRecordWebcamArgs>): UseRecordWebcam;
/**
* Check if the browser supports the codec for recording
* @param {string} codec - The codec to check
* @returns {boolean} - Whether the codec is supported
*/
declare function checkCodecRecordingSupport(codec: string): boolean;
/**
* Check if the browser supports the video codec for playback
* @param {string} codec - The codec to check
* @returns {boolean} - Whether the codec is supported
*/
declare function checkVideoCodecPlaybackSupport(codec: string): boolean;
/**
* Check if the browser supports the audio codec for playback
* @param {string} codec - The codec to check
* @returns {boolean} - Whether the codec is supported
*/
declare function checkAudioCodecPlaybackSupport(codec: string): boolean;
export { type ById, type ByType, type Devices, ERROR_MESSAGES, type InitialDevices, type Options, STATUS, type UseRecordWebcam, type UseRecordWebcamArgs, checkAudioCodecPlaybackSupport, checkCodecRecordingSupport, checkVideoCodecPlaybackSupport, getDevices, useRecordWebcam };

@@ -1,15 +0,727 @@

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
import { useState, useEffect, useMemo, useCallback, createRef } from 'react';
var __defProp = Object.defineProperty;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
exports.__esModule = true;
__exportStar(require("./component"), exports);
__exportStar(require("./hook"), exports);
__exportStar(require("./types"), exports);
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
function createStore(store2) {
return (stateUpdater, callbacks) => {
return new Proxy(store2, {
get(target, prop, receiver) {
if (prop === "size") {
return target.size;
}
const value = Reflect.get(target, prop, receiver);
if (value instanceof Function) {
return function(...args) {
var _a;
const result = value.apply(target, args);
const shouldTriggerUpdate = args[args.length - 1] === true;
if (shouldTriggerUpdate) {
stateUpdater((prev) => prev + 1);
}
if (prop === "set") {
return result.get(args[0]);
}
if (callbacks && callbacks[prop]) {
(_a = callbacks[prop]) == null ? void 0 : _a.call(callbacks, result);
}
return result;
};
}
return value;
}
});
};
}
function useStore(store2, callbacks) {
const [, forceUpdate] = useState(0);
const triggerUpdate = useCallback(() => forceUpdate((prev) => prev + 1), []);
const state = useMemo(() => store2(triggerUpdate, callbacks), []);
return {
state
};
}
// src/useRecordingStore.ts
var ERROR_MESSAGES = {
CODEC_NOT_SUPPORTED: "CODEC_NOT_SUPPORTED",
SESSION_EXISTS: "SESSION_EXISTS",
NO_RECORDING_WITH_ID: "NO_RECORDING_WITH_ID",
NO_USER_PERMISSION: "NO_USER_PERMISSION"
};
var STATUS = {
INITIAL: "INITIAL",
CLOSED: "CLOSED",
OPEN: "OPEN",
RECORDING: "RECORDING",
STOPPED: "STOPPED",
ERROR: "ERROR",
PAUSED: "PAUSED"
};
function createRecording({
videoId,
audioId,
videoLabel,
audioLabel
}) {
const recordingId = `${videoId}-${audioId}`;
const recording = {
id: recordingId,
audioId,
audioLabel,
blobChunks: [],
fileName: String((/* @__PURE__ */ new Date()).getTime()),
fileType: "webm",
isMuted: false,
mimeType: "video/webm;codecs=vp9",
objectURL: null,
previewRef: createRef(),
recorder: null,
status: STATUS.INITIAL,
videoId,
videoLabel,
webcamRef: createRef()
};
return recording;
}
var recordingMap = /* @__PURE__ */ new Map();
var store = createStore(recordingMap);
function useRecordingStore() {
var _a;
const { state } = useStore(store);
const activeRecordings = Array.from((_a = recordingMap == null ? void 0 : recordingMap.values) == null ? void 0 : _a.call(recordingMap));
const clearAllRecordings = () => __async(this, null, function* () {
Array.from(state.values()).forEach((recording) => {
var _a2;
const stream = (_a2 = recording.webcamRef.current) == null ? void 0 : _a2.srcObject;
if (stream) {
stream.getTracks().forEach((track) => track.stop());
}
});
state.clear(true);
});
const isRecordingCreated = (recordingId) => {
const isCreated = state.get(recordingId);
return Boolean(isCreated);
};
const getRecording = (recordingId) => {
const recording = state.get(recordingId);
if (!recording) {
throw new Error(ERROR_MESSAGES.NO_RECORDING_WITH_ID);
}
return recording;
};
const setRecording = (params) => __async(this, null, function* () {
const recording = createRecording(params);
const newRecording = state.set(recording.id, recording, true);
return newRecording;
});
const updateRecording = (recordingId, updatedValues) => __async(this, null, function* () {
const recording = state.get(recordingId);
const updatedRecording = state.set(
recordingId,
__spreadValues(__spreadValues({}, recording), updatedValues),
true
);
return updatedRecording;
});
const deleteRecording = (recordingId) => __async(this, null, function* () {
state.delete(recordingId, true);
});
return {
activeRecordings,
clearAllRecordings,
deleteRecording,
getRecording,
isRecordingCreated,
setRecording,
updateRecording
};
}
// src/devices.ts
function byId(devices) {
return devices.reduce(
(result, { deviceId, kind, label }) => {
if (kind === "videoinput" || kind === "audioinput") {
result[deviceId] = {
label,
type: kind
};
}
return result;
},
{}
);
}
function byType(devices) {
return devices.reduce(
(result, { deviceId, kind, label }) => {
if (kind === "videoinput") {
result.video.push({ label, deviceId });
}
if (kind === "audioinput") {
result.audio.push({ label, deviceId });
}
return result;
},
{
video: [],
audio: []
}
);
}
function getUserPermission() {
return __async(this, null, function* () {
try {
const stream = yield navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
const mediaDevices = yield navigator.mediaDevices.enumerateDevices();
stream.getTracks().forEach((track) => {
track.stop();
});
return mediaDevices;
} catch (error) {
throw new Error(ERROR_MESSAGES.NO_USER_PERMISSION);
}
});
}
var isInit = false;
function getDevices() {
return __async(this, null, function* () {
let devicesByType = {
video: [],
audio: []
};
let devicesById = {};
let initialDevices = {
video: null,
audio: null
};
if (typeof window !== "undefined" && isInit === false) {
isInit = true;
const mediaDevices = yield getUserPermission();
devicesById = byId(mediaDevices);
devicesByType = byType(mediaDevices);
initialDevices = {
video: {
deviceId: devicesByType.video[0].deviceId,
label: devicesByType.video[0].label
},
audio: {
deviceId: devicesByType.audio[0].deviceId,
label: devicesByType.audio[0].label
}
};
}
return { devicesByType, devicesById, initialDevices };
});
}
var DEFAULT_RECORDER_OPTIONS = {
audioBitsPerSecond: 128e3,
videoBitsPerSecond: 25e5,
mimeType: "video/webm;codecs=vp9"
};
function useRecorder({
mediaRecorderOptions,
options,
devices,
handleError
}) {
const {
activeRecordings,
clearAllRecordings,
deleteRecording,
getRecording,
isRecordingCreated,
setRecording,
updateRecording
} = useRecordingStore();
const recorderOptions = useMemo(
() => __spreadValues(__spreadValues({}, DEFAULT_RECORDER_OPTIONS), mediaRecorderOptions),
[mediaRecorderOptions]
);
const startRecording = (recordingId) => __async(this, null, function* () {
var _a;
try {
const recording = getRecording(recordingId);
const stream = (_a = recording.webcamRef.current) == null ? void 0 : _a.srcObject;
recording.mimeType = recorderOptions.mimeType || recording.mimeType;
const isCodecSupported = MediaRecorder.isTypeSupported(
recording.mimeType
);
if (!isCodecSupported) {
console.warn("Codec not supported: ", recording.mimeType);
handleError("startRecording", ERROR_MESSAGES.CODEC_NOT_SUPPORTED);
}
recording.recorder = new MediaRecorder(stream, recorderOptions);
return yield new Promise((resolve) => {
var _a2;
if (recording.recorder) {
recording.recorder.ondataavailable = (event) => {
if (event.data.size) {
recording.blobChunks.push(event.data);
}
};
recording.recorder.onstart = () => __async(this, null, function* () {
recording.status = STATUS.RECORDING;
const updated = yield updateRecording(recording.id, recording);
resolve(updated);
});
recording.recorder.onerror = (error) => {
if (recordingId) {
const recording2 = getRecording(recordingId);
if (recording2)
recording2.status = STATUS.ERROR;
}
handleError("startRecording", error);
};
(_a2 = recording.recorder) == null ? void 0 : _a2.start(options == null ? void 0 : options.timeSlice);
}
});
} catch (error) {
if (recordingId) {
const recording = getRecording(recordingId);
if (recording)
recording.status = STATUS.ERROR;
}
handleError("startRecording", error);
}
});
const pauseRecording = (recordingId) => __async(this, null, function* () {
var _a, _b;
try {
const recording = getRecording(recordingId);
(_a = recording.recorder) == null ? void 0 : _a.pause();
if (((_b = recording.recorder) == null ? void 0 : _b.state) === "paused") {
recording.status = STATUS.PAUSED;
const updated = yield updateRecording(recording.id, recording);
return updated;
}
} catch (error) {
if (recordingId) {
const recording = getRecording(recordingId);
if (recording)
recording.status = STATUS.ERROR;
}
handleError("pauseRecording", error);
}
});
const resumeRecording = (recordingId) => __async(this, null, function* () {
var _a, _b;
try {
const recording = getRecording(recordingId);
(_a = recording.recorder) == null ? void 0 : _a.resume();
if (((_b = recording.recorder) == null ? void 0 : _b.state) === "recording") {
recording.status = STATUS.RECORDING;
const updated = yield updateRecording(recording.id, recording);
return updated;
}
} catch (error) {
if (recordingId) {
const recording = getRecording(recordingId);
if (recording)
recording.status = STATUS.ERROR;
}
if (recordingId) {
const recording = getRecording(recordingId);
if (recording)
recording.status = STATUS.ERROR;
}
handleError("resumeRecording", error);
}
});
const stopRecording = (recordingId) => __async(this, null, function* () {
var _a;
try {
const recording = getRecording(recordingId);
(_a = recording.recorder) == null ? void 0 : _a.stop();
return yield new Promise((resolve) => {
if (recording.recorder) {
recording.recorder.onstop = () => __async(this, null, function* () {
recording.status = STATUS.STOPPED;
const blob = new Blob(recording.blobChunks, {
type: recording.mimeType
});
const url = URL.createObjectURL(blob);
recording.objectURL = url;
if (recording.previewRef.current) {
recording.previewRef.current.src = url;
}
const updated = yield updateRecording(recording.id, recording);
resolve(updated);
});
}
});
} catch (error) {
if (recordingId) {
const recording = getRecording(recordingId);
if (recording)
recording.status = STATUS.ERROR;
}
handleError("stopRecording", error);
}
});
const muteRecording = (recordingId) => __async(this, null, function* () {
var _a;
try {
const recording = getRecording(recordingId);
(_a = recording.recorder) == null ? void 0 : _a.stream.getAudioTracks().forEach((track) => {
track.enabled = !track.enabled;
});
recording.isMuted = !recording.isMuted;
return yield updateRecording(recording.id, recording);
} catch (error) {
if (recordingId) {
const recording = getRecording(recordingId);
if (recording)
recording.status = STATUS.ERROR;
}
handleError("muteRecording", error);
}
});
const cancelRecording = (recordingId) => __async(this, null, function* () {
var _a, _b, _c;
try {
const recording = getRecording(recordingId);
const tracks = (_a = recording == null ? void 0 : recording.recorder) == null ? void 0 : _a.stream.getTracks();
(_b = recording == null ? void 0 : recording.recorder) == null ? void 0 : _b.stop();
tracks == null ? void 0 : tracks.forEach((track) => track.stop());
((_c = recording.recorder) == null ? void 0 : _c.ondataavailable) && (recording.recorder.ondataavailable = null);
if (recording.webcamRef.current) {
const stream = recording.webcamRef.current.srcObject;
stream == null ? void 0 : stream.getTracks().forEach((track) => track.stop());
recording.webcamRef.current.srcObject = null;
recording.webcamRef.current.load();
}
URL.revokeObjectURL(recording.objectURL);
yield deleteRecording(recording.id);
} catch (error) {
if (recordingId) {
const recording = getRecording(recordingId);
if (recording)
recording.status = STATUS.ERROR;
}
handleError("cancelRecording", error);
}
});
const createRecording2 = (videoId, audioId) => __async(this, null, function* () {
var _a, _b, _c, _d, _e, _f;
try {
const { devicesById, initialDevices } = devices || {};
const videoLabel = videoId ? devicesById == null ? void 0 : devicesById[videoId].label : (_a = initialDevices == null ? void 0 : initialDevices.video) == null ? void 0 : _a.label;
const audioLabel = audioId ? devicesById == null ? void 0 : devicesById[audioId].label : (_b = initialDevices == null ? void 0 : initialDevices.audio) == null ? void 0 : _b.label;
const recordingId = `${videoId || ((_c = initialDevices == null ? void 0 : initialDevices.video) == null ? void 0 : _c.deviceId)}-${audioId || ((_d = initialDevices == null ? void 0 : initialDevices.audio) == null ? void 0 : _d.deviceId)}`;
const isCreated = isRecordingCreated(recordingId);
if (isCreated)
throw new Error(ERROR_MESSAGES.SESSION_EXISTS);
const recording = yield setRecording({
videoId: videoId || ((_e = initialDevices == null ? void 0 : initialDevices.video) == null ? void 0 : _e.deviceId),
audioId: audioId || ((_f = initialDevices == null ? void 0 : initialDevices.audio) == null ? void 0 : _f.deviceId),
videoLabel,
audioLabel
});
return recording;
} catch (error) {
handleError("createRecording", error);
}
});
const applyRecordingOptions = (recordingId) => __async(this, null, function* () {
try {
const recording = getRecording(recordingId);
if (options == null ? void 0 : options.fileName) {
recording.fileName = options.fileName;
}
if (options == null ? void 0 : options.fileType) {
recording.fileType = options.fileType;
}
const updatedRecording = yield updateRecording(recording.id, recording);
return updatedRecording;
} catch (error) {
if (recordingId) {
const recording = getRecording(recordingId);
if (recording)
recording.status = STATUS.ERROR;
}
handleError("applyRecordingOptions", error);
}
});
const clearPreview = (recordingId) => __async(this, null, function* () {
try {
const recording = getRecording(recordingId);
if (recording.previewRef.current)
recording.previewRef.current.src = "";
recording.status = STATUS.INITIAL;
URL.revokeObjectURL(recording.objectURL);
const updatedRecording = yield updateRecording(recording.id, recording);
return updatedRecording;
} catch (error) {
if (recordingId) {
const recording = getRecording(recordingId);
if (recording)
recording.status = STATUS.ERROR;
}
handleError("clearPreview", error);
}
});
const download = (recordingId) => __async(this, null, function* () {
try {
const recording = getRecording(recordingId);
const downloadElement = document.createElement("a");
if (recording == null ? void 0 : recording.objectURL) {
downloadElement.href = recording.objectURL;
}
downloadElement.download = `${recording.fileName}.${recording.fileType}`;
downloadElement.click();
} catch (error) {
if (recordingId) {
const recording = getRecording(recordingId);
if (recording)
recording.status = STATUS.ERROR;
}
handleError("download", error);
}
});
return {
activeRecordings,
applyRecordingOptions,
clearAllRecordings,
clearPreview,
download,
cancelRecording,
createRecording: createRecording2,
muteRecording,
pauseRecording,
resumeRecording,
startRecording,
stopRecording
};
}
// src/stream.ts
function startStream(videoId, audioId, constraints) {
return __async(this, null, function* () {
const newStream = yield navigator.mediaDevices.getUserMedia({
video: { deviceId: { exact: videoId } },
audio: {
deviceId: { exact: audioId }
}
});
const tracks = newStream.getTracks();
tracks.forEach((track) => track.applyConstraints(constraints));
return newStream;
});
}
// src/useCamera.ts
var DEFAULT_CONSTRAINTS = {
aspectRatio: 1.7,
echoCancellation: true,
height: 720,
width: 1280
};
function useCamera({
mediaTrackConstraints,
handleError
}) {
const { getRecording, updateRecording } = useRecordingStore();
const constraints = useMemo(
() => __spreadValues(__spreadValues({}, DEFAULT_CONSTRAINTS), mediaTrackConstraints),
[mediaTrackConstraints]
);
const applyConstraints = (recordingId, constraints2) => __async(this, null, function* () {
var _a, _b;
try {
const recording = getRecording(recordingId);
if ((_a = recording.webcamRef.current) == null ? void 0 : _a.srcObject) {
const stream = (_b = recording.webcamRef.current) == null ? void 0 : _b.srcObject;
const tracks = stream.getTracks() || [];
tracks == null ? void 0 : tracks.forEach((track) => {
track.applyConstraints(__spreadValues({}, constraints2));
});
}
return recording;
} catch (error) {
if (recordingId) {
const recording = getRecording(recordingId);
if (recording)
recording.status = STATUS.ERROR;
}
handleError("applyConstraints", error);
}
});
const openCamera = (recordingId) => __async(this, null, function* () {
try {
const recording = getRecording(recordingId);
const stream = yield startStream(
recording.videoId,
recording.audioId,
constraints
);
if (recording.webcamRef.current) {
recording.webcamRef.current.srcObject = stream;
yield recording.webcamRef.current.play();
}
recording.status = STATUS.OPEN;
const updatedRecording = yield updateRecording(recording.id, recording);
return updatedRecording;
} catch (error) {
handleError("openCamera", error);
}
});
const closeCamera = (recordingId) => __async(this, null, function* () {
var _a;
try {
const recording = getRecording(recordingId);
if (recording.webcamRef.current) {
const stream = recording.webcamRef.current.srcObject;
stream == null ? void 0 : stream.getTracks().forEach((track) => track.stop());
((_a = recording.recorder) == null ? void 0 : _a.ondataavailable) && (recording.recorder.ondataavailable = null);
recording.webcamRef.current.srcObject = null;
recording.webcamRef.current.load();
}
recording.status = STATUS.CLOSED;
const updatedRecording = yield updateRecording(recording.id, recording);
return updatedRecording;
} catch (error) {
if (recordingId) {
const recording = getRecording(recordingId);
if (recording)
recording.status = STATUS.ERROR;
}
handleError("closeCamera", error);
}
});
return {
applyConstraints,
closeCamera,
openCamera
};
}
var isInit2 = false;
function useRecordWebcam({
mediaRecorderOptions,
mediaTrackConstraints,
options
} = {}) {
const [devices, setDevices] = useState();
const [errorMessage, setErrorMessage] = useState(null);
function handleError(functionName, error) {
const message = typeof error === "string" ? error : typeof error.message === "string" ? error.message : "";
setErrorMessage(message);
}
function clearError() {
setErrorMessage(null);
}
const { applyConstraints, closeCamera, openCamera } = useCamera({
mediaTrackConstraints,
handleError
});
const {
activeRecordings,
applyRecordingOptions,
cancelRecording,
clearAllRecordings,
clearPreview,
createRecording: createRecording2,
download,
muteRecording,
pauseRecording,
resumeRecording,
startRecording,
stopRecording
} = useRecorder({ mediaRecorderOptions, options, devices, handleError });
function init() {
return __async(this, null, function* () {
try {
const devices2 = yield getDevices();
setDevices(devices2);
} catch (error) {
handleError("init", error);
}
});
}
useEffect(() => {
if (!isInit2) {
isInit2 = true;
init();
}
return () => {
clearAllRecordings();
isInit2 = false;
};
}, []);
return {
activeRecordings,
applyConstraints,
applyRecordingOptions,
cancelRecording,
clearAllRecordings,
clearError,
clearPreview,
closeCamera,
createRecording: createRecording2,
devicesById: devices == null ? void 0 : devices.devicesById,
devicesByType: devices == null ? void 0 : devices.devicesByType,
download,
errorMessage,
muteRecording,
openCamera,
pauseRecording,
resumeRecording,
startRecording,
stopRecording
};
}
// src/codec.ts
function checkCodecRecordingSupport(codec) {
return MediaRecorder.isTypeSupported(codec);
}
function checkVideoCodecPlaybackSupport(codec) {
const video = document.createElement("video");
const canPlay = video.canPlayType(codec);
return canPlay === "maybe" || canPlay === "probably" ? true : false;
}
function checkAudioCodecPlaybackSupport(codec) {
const audio = document.createElement("audio");
const canPlay = audio.canPlayType(codec);
return canPlay === "maybe" || canPlay === "probably" ? true : false;
}
export { ERROR_MESSAGES, STATUS, checkAudioCodecPlaybackSupport, checkCodecRecordingSupport, checkVideoCodecPlaybackSupport, getDevices, useRecordWebcam };

34

package.json
{
"name": "react-record-webcam",
"description": "A React webcam recording hook and component 🎬📹",
"version": "0.0.18",
"description": "Webcam recording tool for React",
"version": "1.0.0-rc.0",
"main": "dist/index.js",
"files": [
"dist"
"dist",
"README.md",
"LICENSE"
],

@@ -12,25 +14,27 @@ "repository": "git@github.com:samuelweckstrom/react-record-webcam.git",

"license": "MIT",
"dependencies": {
"recordrtc": "5.6.2"
},
"peerDependencies": {
"react": "^16.3",
"react-dom": "^16.3"
"react": "^16.3 || ^17.0 || ^18.0 || ^19.0"
},
"scripts": {
"start": "rm -rf ./dist && tsc --watch",
"build": "rm -rf ./dist && tsc"
"dev": "tsup --watch",
"build": "tsup"
},
"keywords": [
"react",
"record",
"webcam",
"record",
"multi",
"recording",
"component",
"concurrent",
"simultaneous",
"hook",
"video"
"video",
"audio"
],
"devDependencies": {
"@types/recordrtc": "^5.6.9"
"sideEffects": false,
"type": "module",
"types": "./dist/index.d.ts",
"exports": {
".": "./dist/index.js"
}
}

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

<div align="center" style="width: 100%; background-color: white;">
<img style="width: 90%;"src="https://s3.eu-central-1.amazonaws.com/samuel.weckstrom.xyz/github/react-record-webcam-logo.jpg">
<div align="center">
<img alt="React Record Webcam Logo" style="width: 90%;" src="https://samuelweckstrom-github.s3.eu-central-1.amazonaws.com/react-record-webcam.svg">
</div>

@@ -8,7 +8,11 @@

[![TypeScript](https://badges.frapsoft.com/typescript/code/typescript.svg?v=101)](https://github.com/ellerbrock/typescript-badges/)
[![Tests](https://github.com/samuelweckstrom/react-record-webcam/actions/workflows/tests.yaml/badge.svg)](https://github.com/samuelweckstrom/react-record-webcam/actions/workflows/tests.yaml)
[![npm version](https://badge.fury.io/js/react-record-webcam.svg)](https://www.npmjs.com/package/react-record-webcam)
Webcam recording hook and component for React. Works in all latest browser versions, although Safari requires MediaRecorder to be enabled in settings under experimental features.
Promise based zero dependency webcam library for React. Select video and audio input for one or multiple concurrent recordings using any mix of video and audio source.
[Demo](https://codesandbox.io/s/react-record-webcam-demo-zog8c)
[DEMO](https://codesandbox.io/p/sandbox/festive-mccarthy-zhkh83)
Note version 1.0 is a complete rewrite so you will need to make some changes if updating.
<br>

@@ -22,19 +26,33 @@

<i>or</i>
```
yarn add react-record-webcam
```
<br>
## Example use for hook
## Usage
```
```javascript
// record a 3s. video
import { useRecordWebcam } from 'react-record-webcam'
function RecordVideo(props) {
const recordWebcam = useRecordWebcam({ frameRate: 60 });
...
const saveFile = async () => {
const blob = await recordWebcam.getRecording();
...
const {
activeRecordings,
createRecording,
openCamera,
startRecording,
stopRecording,
} = useRecordWebcam()
const example = async () => {
try {
const recording = await createRecording();
if (!recording) return;
await openCamera(recording.id);
await startRecording(recording.id);
await new Promise((resolve) => setTimeout(resolve, 3000));
await stopRecording(recording.id);
} catch (error) {
console.error({ error });
}
};

@@ -44,121 +62,132 @@

<div>
<p>Camera status: {recordWebcam.status}</p>
<button onClick={recordWebcam.open}>Open camera</button>
<button onClick={recordWebcam.start}>Start recording</button>
<button onClick={recordWebcam.stop}>Stop recording</button>
<button onClick={recordWebcam.retake}>Retake recording</button>
<button onClick={recordWebcam.download}>Download recording</button>
<button onClick={saveFile}>Save file to server</button>
<video ref={recordWebcam.webcamRef} autoPlay muted />
<video ref={recordWebcam.previewRef} autoPlay muted loop />
<button onClick={example}>Start</button>
{activeRecordings.map(recording => (
<div key={recording.id}>
<video ref={recording.webcamRef} autoPlay muted />
<video ref={recording.previewRef} autoPlay muted loop />
</div>
))}
</div>
)
}
...
```
Import the hook and initialize it in your function. The hook returns refs for preview and recording video elements, functions to control recording (open, start, stop, retake, download) and camera status.
Check the CodeSandbox links for [above demo](https://codesandbox.io/p/sandbox/lingering-haze-sm6jxw) and one with more [features](https://codesandbox.io/p/sandbox/festive-mccarthy-zhkh83).
### Passing options
<br>
Hook and component accept an options object to control things like width/height etc.
## Passing options
`const recordWebcam = useRecordWebcam(<OPTIONS>);`
Pass options either when initializing the hook or at any point in your application logic using `applyOptions`.
or
```typescript
const options = { ... }
const mediaRecorderOptions = { ... }
const mediaTrackConstraints = { ... }
`<RecordWebcam options={<OPTIONS>} />`
const { ... } = useRecordWebcam({
options,
mediaRecorderOptions,
mediaTrackConstraints
})
| Options ||
| ------------- | ------------- |
|`fileName<string>`|Filename for download|
|`recordingLength<number>`|Length of recording in seconds|
|`mimeType<enum>`|`'video/mp4'`<br>`'video/webm'`<br>`'video/webm;codecs=vp9'`<br>`'video/webm;codecs=vp8'`<br>`'video/webm;codecs=h264'`<br>`'video/x-matroska;codecs=avc1'`<br>`'video/mpeg'`
|`aspectRatio<number>`|Video aspect ratio, default is 1.7|
|`width<number>`| Video width|
|`height<number>`| Video height|
|`disableLogs<boolean>`|Disable debug logs, on by default|
// or
Note: file extension and codec is determined from the passed `mimeType` option, so use any of the above ones that are currently supported.
const { applyOptions } = useRecordWebcam() // use utility
applyOptions(recording.id: string, options: Options) // add to your application logic
```
| Option | property | default value|
| ------------- | ------------- | ------------- |
|`fileName`| |`Date.now()`|
|`fileType`| File type for download (will override inferred type from `mimeType`) |`'webm'`|
|`timeSlice`| Recording interval | `undefined`|
<br>
## Example use for component
Both `mediaRecorderOptions` and `mediatrackConstraints` mirror the official API. Please see on MDN for available options:
```
import { RecordWebcam } from 'react-record-webcam'
[MDN: mediaRecorderOptions](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder#options)
[MDN: mediatrackConstraints](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#instance_properties)
### Codec Support
The default codec is set to `video/webm;codecs=vp9`. If you prefer to use another one, you can check the compatibility for playback and recording with the following utilities:
```typescript
const { checkCodecRecordingSupport, checkVideoCodecPlaybackSupport } = useRecordWebcam()
...
const codec = 'video/x-matroska;codecs=avc1'
const isRecordingSupported = checkCodecRecordingSupport(codec)
const isPlayBackSupported = checkVideoCodecPlaybackSupport(codec)
...
```
function RecordVideo(props) {
return (
<RecordWebcam />
)
}
To use a specific codec, pass this in the mediaRecorderOptions:
```typescript
const codec = 'video/webm;codecs=h264'
const mediaRecorderOptions = { mimetype: codec }
const recordWebcam = useRecordWebcam({ mediaRecorderOptions })
```
You can include the component as is and it will render controls, video and preview elements. Alternatively you can use the render prop for more control:
For more info see the codec guide on [MDN](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Video_codecs).
## Error handling
Current error message is passed from the hook, along with a object of error states which you can use for checks.
```typescript
import { useRecordWebcam, ERROR_MESSAGES } from 'react-record-webcam'
const { errorMessage } = useRecordWebcam()
...
const isUserPermissionDenied = errorMessage === ERROR_MESSAGES.NO_USER_PERMISSION;
...
```
function RecordVideo(props) {
return (
<RecordWebcam
render={(props: WebcamRenderProps) => {
return (
<div>
<h1>Component render prop demo</h1>
<p>Camera status: {props.status}</p>
<div>
<button onClick={props.openCamera}>Open camera</button>
<button onClick={props.retake}>Retake</button>
<button onClick={props.start}>Start recording</button>
<button onClick={props.stop}>Stop recording</button>
<button onClick={props.download}>Download</button>
</div>
</div>
);
}}
/>
)
}
```
<br>
|Props||
| ------------- | ------------- |
|`cssNamespace<string>` |Set a namespace for the component CSS classes|
|`downloadFileName<string>` |File name for video download |
|`getStatus<fn:string>` |Callback to get webcam status |
|`options` | Same options as in hook version|
|`controlLabels`|Pass custom labels to control buttons|
||`CLOSE<string \| number>`|
||`DOWNLOAD<string \| number>`|
||`OPEN<string \| number>`|
||`RETAKE<string \| number>`|
||`START<string \| number>`|
||`STOP<string \| number>`|
|`render` |Render prop that passes status and controls|
|| `status<string>`|
|| `closeCamera<fn:void>`|
|| `download<fn:void>`|
|| `getRecording<fn:void>`|
|| `openCamera<fn:void>`|
|| `retake<fn:void>`|
|| `start<fn:void>`|
|| `stop<fn:void>`|
## Full API
<br>
| Method/Property | Arguments | Returns | Description |
|------------------------------|---------------------------------------------------------------|----------------------------------------------|-------------------------------------------------------------------------------------------------------------------|
| `activeRecordings` | | `Recording[]` | Array of active recordings. |
| `applyConstraints` | `recordingId: string, constraints: MediaTrackConstraints` | `Promise<Recording \| void>` | Applies given constraints to the camera for a specific recording. |
| `applyRecordingOptions` | `recordingId: string` | `Promise<Recording \| void>` | Applies recording options to a specific recording. |
| `cancelRecording` | `recordingId: string` | `Promise<void>` | Cancels the current recording session. |
| `clearAllRecordings` | | `Promise<void>` | Clears all active recordings. |
| `clearError` | | `void` | Function to clear the current error message. |
| `clearPreview` | `recordingId: string` | `Promise<Recording \| void>` | Clears the preview of a specific recording. |
| `closeCamera` | `recordingId: string` | `Promise<Recording \| void>` | Closes the camera for a specific recording. |
| `createRecording` | `videoId?: string, audioId?: string` | `Promise<Recording \| void>` | Creates a new recording session with specified video and audio sources. |
| `devicesById` | | `ById` | Object containing devices by their ID, where `ById` is a record of `string` to `{ label: string; type: 'videoinput' \| 'audioinput'; }`. |
| `devicesByType` | | `ByType` | Object categorizing devices by their type, where `ByType` has `video` and `audio` arrays of `{ label: string; deviceId: string; }`. |
| `download` | `recordingId: string` | `Promise<void>` | Downloads a specific recording. |
| `errorMessage` | | `string \| null` | The current error message, if any, related to recording. |
| `muteRecording` | `recordingId: string` | `Promise<Recording \| void>` | Mutes or unmutes the recording audio. |
| `openCamera` | `recordingId: string` | `Promise<Recording \| void>` | Opens the camera for a specific recording with optional constraints. |
| `pauseRecording` | `recordingId: string` | `Promise<Recording \| void>` | Pauses the current recording. |
| `resumeRecording` | `recordingId: string` | `Promise<Recording \| void>` | Resumes a paused recording. |
| `startRecording` | `recordingId: string` | `Promise<Recording \| void>` | Starts a new recording session. |
| `stopRecording` | `recordingId: string` | `Promise<Recording \| void>` | Stops the current recording session. |
|
You can use the below default class names or pass your own namespace to replace the default `react-record-webcam`.
| className |
| ------------- |
|`react-record-webcam__wrapper`
|`react-record-webcam__status`
|`react-record-webcam__video`
|`react-record-webcam__controls`
|`react-record-webcam__controls-button`
## License
[MIT](LICENSE)
## Credits
webcam by iconfield from <a href="https://thenounproject.com/browse/icons/term/webcam/" target="_blank" title="webcam Icons">Noun Project</a> (CC BY 3.0)
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