Giter Club home page Giter Club logo

use-media-recorder's Introduction

use-media-recorder

React based hooks to utilize the MediaRecorder API for audio, video and screen recording.

Features

  • ๐Ÿ‘€ Familiar API - Extends the MediaRecorder/MediaStream API with minimal abstraction making it easy to use.
  • ๐Ÿ”ด Media recording - Supports audio ๐ŸŽค, video ๐ŸŽฅ & screen ๐Ÿ–ฅ๏ธ recording.
  • ๐ŸŽ›๏ธ Configurable - Adjust settings to match your recording requirements.
  • ๐Ÿ’… Headless - Build your own custom user interface to fit your style.

Installation

npm install @wmik/use-media-recorder

Example

import React from 'react';
import useMediaRecorder from '@wmik/use-media-recorder';

function Player({ srcBlob, audio }) {
  if (!srcBlob) {
    return null;
  }

  if (audio) {
    return <audio src={URL.createObjectURL(srcBlob)} controls />;
  }

  return (
    <video
      src={URL.createObjectURL(srcBlob)}
      width={520}
      height={480}
      controls
    />
  );
}

function ScreenRecorderApp() {
  let {
    error,
    status,
    mediaBlob,
    stopRecording,
    getMediaStream,
    startRecording
  } = useMediaRecorder({
    recordScreen: true,
    blobOptions: { type: 'video/webm' },
    mediaStreamConstraints: { audio: true, video: true }
  });

  return (
    <article>
      <h1>Screen recorder</h1>
      {error ? `${status} ${error.message}` : status}
      <section>
        <button
          type="button"
          onClick={getMediaStream}
          disabled={status === 'ready'}
        >
          Share screen
        </button>
        <button
          type="button"
          onClick={startRecording}
          disabled={status === 'recording'}
        >
          Start recording
        </button>
        <button
          type="button"
          onClick={stopRecording}
          disabled={status !== 'recording'}
        >
          Stop recording
        </button>
      </section>
      <Player srcBlob={mediaBlob} />
    </article>
  );
}

Demo

Live demo example

API

useMediaRecorder (Default export)

Creates a custom media recorder object using the MediaRecorder API.

Parameters (MediaRecorderProps)

Property Type Description
blobOptions BlobPropertyBag Options used for creating a Blob object.
recordScreen boolean Enable/disable screen capture.
customMediaStream MediaStream Custom stream e.g canvas.captureStream
onStart function Callback to run when recording starts.
onStop function Callback to run when recording stops. Accepts a Blob object as a parameter.
onError function Callback to run when an error occurs while recording. Accepts an error object as a parameter.
onDataAvailable function Callback to run when recording data exists.
mediaRecorderOptions object Options used for creating MediaRecorder object.
mediaStreamConstraints* MediaStreamConstraints Options used for creating a MediaStream object from getDisplayMedia and getUserMedia.

NOTE: * means it is required

Returns (MediaRecorderHookOptions)

Property Type Description
error Error Information about an operation failure. Possible exceptions
status string Current state of recorder. One of idle, acquiring_media, ready, recording, paused,stopping, stopped, failed.
mediaBlob Blob Raw media data.
isAudioMuted boolean Indicates whether audio is active/inactive.
stopRecording function End a recording.
getMediaStream function Request for a media source. Camera, mic and/or screen access. Returns instance of requested media source or customMediaStream if was provided in initializing.
clearMediaStream function Resets the media stream object to null.
clearMediaBlob function Resets the media blob to null.
startRecording function(timeSlice?) Begin a recording. Optional argument timeSlice controls chunk size.
pauseRecording function Stop without ending a recording allowing the recording to continue later.
resumeRecording function Continue a recording from a previous pause.
muteAudio function Disable audio.
unMuteAudio function Enable audio.
liveStream MediaStream Real-time stream of current recording.

More examples

function LiveStreamPreview({ stream }) {
  let videoPreviewRef = React.useRef();

  React.useEffect(() => {
    if (videoPreviewRef.current && stream) {
      videoPreviewRef.current.srcObject = stream;
    }
  }, [stream]);

  if (!stream) {
    return null;
  }

  return <video ref={videoPreviewRef} width={520} height={480} autoPlay />;
}

<LiveStreamPreview stream={liveStream} />

Related

License

MIT ยฉ2020

use-media-recorder's People

Contributors

amir-alipour avatar chiefgui avatar eminvergil avatar itsnotrisky avatar janmisker avatar martynassapoka avatar no-1ne avatar slowestmonkey avatar swissspidy avatar wmik avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

use-media-recorder's Issues

onStop callback never called

The onStop callback is never called, so there is no way to get the last audio chunk and it's unfortunately lost.
This is because the event listener for handleStop() is removed in stopRecording(), preventing it to be called by the media recorder.

[line 254] mediaRecorder.current.removeEventListener('stop', handleStop);

[verified on Chrome 116 and FF 116]

add 'paused' status

Would be great to change status when I call pauseRecording(), for example to paused :) I just need it :P

stopRecording() not change status to stopped when mediaChunks.current not available

use-media-recorder/index.js

Lines 187 to 206 in 766484c

function handleStop() {
let blob = new Blob();
let sampleChunk = new Blob();
if (mediaChunks.current.length) {
[sampleChunk] = mediaChunks.current;
let blobPropertyBag = Object.assign(
{ type: sampleChunk.type },
blobOptions
);
blob = new Blob(mediaChunks.current, blobPropertyBag);
cacheMediaBlob(blob);
setStatus('stopped');
}
onStop(blob);
}

I think move setStatus('stopped'); to line 204, before call onStop(blob);

Errors from `MediaRecorder.start()` not caught

Citing MDN:

Errors that can be detected immediately are thrown as DOM exceptions. All other errors are reported through error events sent to the MediaRecorder object. You can implement the onerror event handler to respond to these errors.

The former (thrown DOM exceptions) are not currently caught here:

use-media-recorder/index.js

Lines 172 to 176 in 766484c

mediaRecorder.current.addEventListener('stop', handleStop);
mediaRecorder.current.addEventListener('error', handleError);
mediaRecorder.current.start(timeSlice);
setStatus('recording');
onStart();

Something like this should fix it:

      mediaRecorder.current.addEventListener('stop', handleStop);
      mediaRecorder.current.addEventListener('error', handleError);
-     mediaRecorder.current.start(timeSlice);
-     setStatus('recording');
-     onStart();
+     try {
+       mediaRecorder.current.start(timeSlice);
+       setStatus('recording');
+       onStart();
+     } catch (error) {
+       handleError({ error });
+     }

Recording custom stream for second time fails

In stopRecording all tracks are removed from the stream that is being recorded.
However in case of a custom stream this is not desirable behaviour, because when we want to record that same stream again it fails (on Safari it records but only silence, on Chrome an error is thrown when calling start() on the mediarecorder.current).

I'm not sure how to fix is, I think not taking out the tracks in stopRecording should be enough, when we work on a customStream ?

Custom media stream fails silently

Add warning/throw error if custom media stream is invalid?
Current behavior: fails silently and falls back on internal media stream

use-media-recorder/index.js

Lines 107 to 111 in f3631a7

if (customMediaStream && customMediaStream instanceof MediaStream) {
mediaStream.current = customMediaStream;
return;
}

Corrupted Downloaded File

I am creating audio blob using this code

const {
    error,
    status,
    mediaBlob,
    stopRecording,
    getMediaStream,
    startRecording,
    pauseRecording,
    resumeRecording,
  } = useMediaRecorder({
    recordScreen: false,
    blobOptions: { type: 'audio/wav' },
    mediaStreamConstraints: { audio: true },
  });

But when I download the blob after calling method onStop, it downloads a corrupted file.
Although it is audible but it does not contain proper formatting
image

Anyone plz guide me related to this issue
Thanks

firefox audio problem

When I set
mediaStreamConstraints: { audio: false, video: true } below listener (inside your hook) is not called and doesn't return blob me
mediaRecorder.current.addEventListener('stop', handleStop);
@wmik Do you know what can cause this?

Error when calling stopRecording() immediately after recording starts

If I call stopRecording() immediately after the recording starts (more specifically when status === "recording"), I get the following error: TypeError: undefined is not an object (evaluating 'sampleChunk.type')

If I wait maybe 1 or 2 seconds after the recording starts and call stopRecording(), everything works as intended. It seems like there should be another 'status' between 'ready' and 'recording' that could help us identify when it is safe to call stopRecording().

As a workaround, I've created my own isInitializing state which turns true when status === "ready", and then using a setTimeout, turns back to false 2000ms after status === "recording". Then I simply show a loading spinner instead of the stop button when isInitializing === true. I imagine you could just include a property like this (just not using setTimeout) into the actual status property itself.

I am using the 1.5.0-beta.0 version

Unmounted component behavior

Disable recording and cleanup when a component is no longer mounted.

  • Implementation
  • Testing
  • Documentation

Update
How to replicate:

  1. Render multiple recording apps on separate routes
  2. Visit one app/route
  3. Begin recording
  4. Navigate to the previous route (<- Back)

Expected behavior: recording should stop
Current behavior: recording continues/media recorder is not reset

Example sandbox

handleStop errors if there are no chunks

Affected code:

use-media-recorder/index.js

Lines 187 to 198 in 9660bb1

function handleStop() {
let [sampleChunk] = mediaChunks.current;
let blobPropertyBag = Object.assign(
{ type: sampleChunk.type },
blobOptions
);
let blob = new Blob(mediaChunks.current, blobPropertyBag);
cacheMediaBlob(blob);
setStatus('stopped');
onStop(blob);
}

When mediaChunks.current is an empty array, sampleChunk will be undefined and so accessing sampleChunk.type throws an error.

fix status in clearMediaStream

Hi, I made a fix in clearMediaStream. But I don't have permission to send pull request. Can you review @wmik ?

function clearMediaStream() {
    if (mediaStream.current) {
      mediaStream.current.getTracks().forEach((track) => track.stop());
      mediaStream.current = null;
      setStatus("idle"); // clear the status if it is in ready state
    }
  }

Get AudioBuffer or MediaStream at onDataAvailable()

Hello! Thanks for your great job!
I'd like to get frequency for each chunk to show visualization of sound from microphone each 2-3 seconds.
I try to do:

let { status, mediaBlob, stopRecording, startRecording } = useMediaRecorder({
    onDataAvailable: async (blob) => {
      const context = new AudioContext();
      const arrayBuffer = await blob.arrayBuffer();
      const audioBuffer = await context.decodeAudioData(arrayBuffer);
      console.log("audioBuffer", audioBuffer);
      await context.close();
    },
    mediaStreamConstraints: { audio: true },
  });

But I get errors:

index.js:1 Uncaught Error: The error you provided does not contain a stack trace.
App.tsx:18 Uncaught (in promise) DOMException: Unable to decode audio data

Best regards!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.