Giter Site home page Giter Site logo

elsmr / mp3-mediarecorder Goto Github PK

View Code? Open in Web Editor NEW
82.0 2.0 8.0 5.31 MB

🎙MediaRecorder ponyfill that records audio as mp3

Home Page: https://mp3-mediarecorder.elsmr.dev

License: MIT License

JavaScript 0.44% TypeScript 99.56%
mediarecorder polyfill ponyfill mp3 mediastream audio encoding libmp3lame webassembly vmsg

mp3-mediarecorder's Introduction

mp3-mediarecorder header

🎙 mp3-mediarecorder

Build Status NPM Version Live demo

A MediaRecorder ponyfill that records audio as mp3. It uses the great Kagami/vmsg library under the hood to encode mp3 audio in WebAssembly using LAME.

View the live demo

Features

  • Standard MediaRecorder API
  • Audio encoding off the main thread using Web Workers
  • Consistent MP3 file output in all supported browsers
  • High quality type definitions
  • 9kB main library
  • 80kB Web Worker with WebAssembly module (Loaded async)

Browser Support

  • Chrome 57+
  • Firefox 52+
  • Safari 11+
  • Edge 16+

Installation

Install with npm or yarn.

yarn add mp3-mediarecorder

If you don't want to set up a build environment, you can get mp3-mediarecorder from a CDN like unpkg.com and it will be globally available through the window.mp3MediaRecorder object.

<script src="https://unpkg.com/mp3-mediarecorder"></script>

Usage

We'll have two files: index.js and worker.js. The first is what we import from our app, so it runs on the main thread — it imports our worker (using worker-loader or workerize-loader) and passes it to Mp3MediaRecorder to create a recorder instance around it.

index.js

import { Mp3MediaRecorder } from 'mp3-mediarecorder';
import Mp3RecorderWorker from 'workerize-loader!./worker';

const recorder = new Mp3MediaRecorder(
    mediaStream, // MediaStream instance
    { worker: Mp3RecorderWorker() },
);
recorder.start(); // 🎉

In most cases the MediaStream instance will come from the getUserMedia API. For a usage example, see here.

worker.js

import { initMp3MediaEncoder } from 'mp3-mediarecorder/worker';

initMp3MediaEncoder({ vmsgWasmUrl: '/url/to/vmsg.wasm' });

The second file is our worker code, which runs in the background thread. Here we import initMp3MediaEncoder from mp3-mediarecorder/worker. This sets things up to communicate with the main thread.

API

module:mp3-mediarecorder

Mp3MediaRecorder

Mp3MediaRecorder is a class that has the same API as the standard MediaRecorder. If you want to see the full API please check out the documentation on MDN.

Constructor parameters

The Mp3MediaRecorder constructor parameters differ from the standard API.

  • mediaStream: MediaStream An instance of MediaStream (eg: from getUserMedia)

  • options: Mp3MediaRecorderOptions

    • worker: Worker An instantiated Web Worker (eg: new Worker('./worker.js'))
    • audioContext?: AudioContextAn instantiated AudioContext (eg: new AudioContext()) This might be useful if you want to full control over the AudioContext. Chrome and Safari limit the number of AudioContext objects.

Example

const recorder = new Mp3MediaRecorder(
    mediaStream, // MediaStream instance
    {
        worker: Mp3RecorderWorker(),
        // Optionally supply your own AudioContext
        audioContext: new AudioContext(),
    },
);

module:mp3-mediarecorder/worker

The Web Worker side the of the recorder. The worker will communicate with the main thread to encode the mp3 file.

initMp3MediaEncoder

Sets up the communication with the main thread.

Parameters

  • vmsgWasmUrl: string The URL of the vmsg.wasm file. This could be self-hosted or from a CDN. The Worker fill fetch this URL and instantiate a WebAssembly module from it.

Example

import { initMp3MediaEncoder } from 'mp3-mediarecorder/worker';

initMp3MediaEncoder({ vmsgWasmUrl: '/url/to/vmsg.wasm' });

Why

Browser support for MediaRecorder is lacking.

Even in browsers with support for MediaRecorder, the available audio formats differ between browsers, and are not always compatible with other browsers. MP3 is the only audio format that can be played by all modern browsers.

Kagami/vmsg is a great library but I needed something that doesn't include a UI and/or getUserMedia code.

Limitations

  • In Safari, pause and resume does not work (see #60)
  • The dataavailable event only fires once, when encoding is complete. MediaRecorder.start ignores its optional timeSlice argument. As a result,MediaRecorder.requestData does not trigger a dataavailable event
  • bitsPerSecond is not configurable, the MediaRecorder constructor will ignore this option.

Develop

yarn dev

A development version of the demo will be served on http://localhost:1234.

Related

  • Kagami/vmsg: Use this library if you want a more complete microphone recording library with a built-in UI

mp3-mediarecorder's People

Contributors

alexthewilde avatar dependabot[bot] avatar elsmr avatar renovate-bot avatar renovate[bot] avatar semantic-release-bot avatar timebutt 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

Watchers

 avatar  avatar

mp3-mediarecorder's Issues

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

  • chore(deps): update devdependencies (@babel/core, @babel/preset-env, @rollup/plugin-babel, @rollup/plugin-node-resolve, @rollup/plugin-typescript, @semantic-release/git, @types/dom-mediacapture-record, @types/jest, @types/node, del-cli, jest, prettier, rollup, ts-jest, tslib, typescript)
  • chore(deps): update devdependencies (major) (@rollup/plugin-babel, @rollup/plugin-node-resolve, @rollup/plugin-typescript, @semantic-release/git, @types/jest, @types/node, cpy-cli, del-cli, jest, prettier, rollup, ts-jest, typescript)

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

npm
package.json
  • event-target-shim 6.0.2
  • vmsg 0.4.0
  • @babel/core 7.14.5
  • @babel/preset-env 7.14.5
  • @rollup/plugin-babel 5.3.0
  • @rollup/plugin-node-resolve 13.0.0
  • @rollup/plugin-typescript 8.2.1
  • @semantic-release/git 9.0.0
  • @types/dom-mediacapture-record 1.0.7
  • @types/jest 26.0.23
  • @types/node 15.3.0
  • bundlesize 0.18.1
  • cpy-cli 3.1.1
  • del-cli 4.0.0
  • jest 27.0.4
  • parcel-bundler 1.12.5
  • prettier 2.3.0
  • rollup 2.48.0
  • semantic-release 17.4.3
  • ts-jest 27.0.3
  • tslib 2.2.0
  • typescript 4.3.2
travis
.travis.yml

  • Check this box to trigger a request for Renovate to run again on this repository

Demo page not working

Opening https://mp3-mediarecorder.elsmr.dev/ in Chrome 85 on MacOS gives me this JS error:

Uncaught DOMException: Failed to execute 'importScripts' on 'WorkerGlobalScope': The script at 'https://mp3-mediarecorder.elsmr.dev/worker/index.umd.js' failed to load.
    at https://mp3-mediarecorder.elsmr.dev/worker.js:1:1

When I try to record something, another JS error gets thrown:

index.js:26 Uncaught (in promise) TypeError: Cannot read property 'Mp3MediaRecorder' of undefined
    at index.js:26

Update event-target-shim as soon as they publish a fix for #26

We currently use "event-target-shim": "github:cpmsmith/event-target-shim#sentry-infinite-recursion" to quickly fix an issue when combining this library with the latest @sentry/browser.

As soon as PR is merged we should change that dependency to event-target-shim again.

Add documentation for audioContext option

Mp3MediaRecorderconstructor has following signature:

constructor(stream: MediaStream, options?: { audioContext?: AudioContext });

where the user of the library has the possibility to pass in their own audioContext.

This should be documented in the README.

Problem with pause/resume in Safari

If you pause the recording, resume the recording and the record again, the 2nd part of the recording is muted when you listen to it.
You can even try it in your live demo.

Support for multi-channel recording

I noticed my stereo (2-channel) mediaStream was only getting its first/left channel saved into the MP3s. I also noticed in the audio process that mp3mediarecorder is only getting data from the first (left) channel.

I would like to know if/suggest mp3mediarecorder support multiple channel recordings in the future, especially since some may use this to record stereo audio (such as radio programming) for archive purposes.

Thank you!

image

Recording broken on iOS Firefox/Chrome

Hi, thanks for the great work!

The example is not working on mobile, is it because mobile browser do not support this functionality?

I tested on Mozilla, Chrome and Safari on iOS.

Post-processing

Hi there this library looks dope!

How hard would it be to do the conversion later instead of doing it in real time?
I assume I'd need to derive the solution from https://github.com/elsmr/mp3-mediarecorder/blob/master/src/worker/index.ts

I want to encode an audio file acquired with the native MediaRecorder API, so I am using this library worker with a custom "frontend" see implementation below and the issue right after

// Given:
// recording = [Blob]

const worker = new Worker(new URL("../lib/worker.ts", import.meta.url));
const PostMessageType = {
  DATA_AVAILABLE: "DATA_AVAILABLE",
  START_RECORDING: "START_RECORDING",
  STOP_RECORDING: "STOP_RECORDING",
  ERROR: "ERROR",
  BLOB_READY: "BLOB_READY",
  WORKER_RECORDING: "WORKER_RECORDING",
};

let onComplete;
const audioBlobPromise = new Promise((resolve) => {
  onComplete = resolve;
});

worker.addEventListener("message", (event) => {
  const message = event.data;
  switch (message.type) {
    case PostMessageType.WORKER_RECORDING: {
      // Send the chunks received by the MediaRecorder to the worker for encoding
      // recording = [Blob]
      const context = new AudioContext();
      Promise.all(
        recording.map((r) =>
          r.arrayBuffer().then((arrayBuffer) => {
            let onSuccess, onError;
            const decodeAudioDataPromise = new Promise(
              (resolve, reject) => {
                onSuccess = (buffer) => {
                  const data = buffer.getChannelData(0);
                  worker.postMessage({
                    type: PostMessageType.DATA_AVAILABLE,
                    data,
                  });
                  Promise.resolve().then(() => {
                    resolve();
                  });
                };
                onError = () => {
                  reject();
                };
              }
            );
            context.decodeAudioData(arrayBuffer, onSuccess, onError);
            return decodeAudioDataPromise;
          })
        )
      )
        .then(() => {
          // End the encoding. It will result in PostMessageType.BLOB_READY.
          setTimeout(() => {
            worker.postMessage({ type: PostMessageType.STOP_RECORDING });
          }, 1000);
        })
        .finally(() => {
          context.close();
        });
      break;
    }
    case PostMessageType.BLOB_READY: {
      mimeType = "audio/mpeg";
      onComplete([message.blob]);
      break;
    }
    default: {
      console.error(
        "An error occured while encoding the recording ",
        message.error || "unknown error"
      );
      onComplete(recording);
    }
  }
});

// Start the encoder
worker.postMessage({
  type: PostMessageType.START_RECORDING,
  config: { sampleRate: 44100 },
});

const audioBlob = await audioBlobPromise;
worker.terminate();
console.log({ audioBlob });

new File(audioBlob, `recording.mp3`, {
  lastModified: Date.now(),
  type: mimeType,
})

With this when I send data to the worker, the handler throws an error here because encodedBytesAmount is -1:

const encodedBytesAmount = vmsgInstance.vmsg_encode(vmsgRef, data.length);
if (encodedBytesAmount < 0) {
throw new Error('encoding_failed');
}

{ data: Float32Array(246077)}, dataLength: 246077, encodedBytesAmount: -1 }

Cannot build on Windows

(Sorry, this is probably not a very useful or important bug report, but reporting it just in case.)

The build fails on Windows (even though I'm not interested in running this directly on Windows. I just need it to work in the browser).

$ yarn install
➤ YN0000: ┌ Resolution step
➤ YN0000: └ Completed in 0s 353ms
➤ YN0000: ┌ Fetch step
➤ YN0000: └ Completed in 0s 818ms
➤ YN0000: ┌ Link step
➤ YN0062: │ fsevents@patch:fsevents@npm%3A1.2.13#builtin<compat/fsevents>::version=1.2.13&hash=11e9ea The platform win32 is incompatible with this module, build skipped.
➤ YN0007: │ iltorb@npm:2.4.5 must be built because it never did before or the last one failed
➤ YN0009: │ iltorb@npm:2.4.5 couldn't be built successfully (exit code 1, logs can be found here: C:\Users\tjh238\AppData\Local\Temp\xfs-aaa4fbbb\build.log)
➤ YN0009: │ iltorb@npm:2.4.5 couldn't be built successfully (exit code 1, logs can be found here: C:\Users\tjh238\AppData\Local\Temp\xfs-aaa4fbbb\build.log)
➤ YN0000: └ Completed in 2s 571ms
➤ YN0000: Failed with errors in 4s 40ms

I guess I can fire up a Linux VM as a work-around.

Would it be possible when pausing to get a sample of the recorded audio?

I need to get a sample of the audio recorded when click on the pause option. And click on the resume option, concatenate the new audio with the old, and so on (like Whatsapp Web).
Can you help me with an example or a guide on how to implement this?

I'm also making an audio recorder using the MediaRecorder API, but i'm not having success with the functionality I described above and converting audio to MP3.

Cannot find config.type.ts

mp3-mediarecorder version: 4.0.3


Error log when building Angular project which uses mp3-mediarecorder:

Error: node_modules/mp3-mediarecorder/worker/index.d.ts:1:33 - error TS2307: Cannot find module '../types/config.type' or its corresponding type declarations.

1 import { Mp3WorkerConfig } from '../types/config.type';

My package.json states that mp3-mediarecorder version 4.0.3 is being used. I see the config.type.ts file in this repo, but it is not present in my local node_modules/mp3-mediarecorder directory.

Terminating a worker

Hi! Thanks for the great lib, its awesome :)

How would I terminate a worker so they do not still show up in the Chrome Memory tab.

Thanks again!

[Mobile] Low quality of the recorded mp3

platform : mobile
system : android
browser : chrome browser 97

When recording on a mobile phone I noticed some distortion in the sound.

in computer it's working great.

Problem after 4th recording

Safari does not support more than 4 AudioContexts. Every AudioContext should be closed after each recording. You can try your live demo in Safari and you will see that it crashes on the 5th recording.
This issue is also reported in the Kagami/vmsg library:
Kagami/vmsg#24
The same will happen in Chrome after 30 recordings.

Move away from Promise-based getMp3MediaRecorder API

Ideally usage should be as simple as importing the Mp3MediaRecorder class directly instead of the getMp3MediaRecorder Promise API. The choice to preload the worker can be left to the user, so they have 2 options to use the library.

Option 1

The worker is initialized in the constructor. There might be a longer delay between the user calling .start() and the 'start' event being dispatched because of the time it takes to set up the worker.

import { Mp3MediaRecorder } from 'mp3-mediarecorder';

const recorder = new Mp3MediaRecorder(stream);
recorder.start();

Option 2

Here the worker is initialized by the initWorker Promise. After that there will be (almost) no delay between calling .start() and the 'start' event dispatching. Similar to the current getMp3MediaRecorder API.

import { Mp3MediaRecorder, initWorker } from 'mp3-mediarecorder';

initWorker().then(() => {
  const recorder = new Mp3MediaRecorder(stream);
  recorder.start();
});

Supporting timeSlice

I'd love to see support for timeSlice! I am looking for a way to stream mic audio from a browser to the server to be analyzed there.

Currently I'm using OpusMediaRecorder which works great in itself, but I haven't gotten a Java Opus library on the server to work yet. So I'm looking at mp3-mediarecorder, but without timeSlice I can't use it.

Many greetings

Race condition inside worker

If an instance of the worker is created, attached to a MediaRecorder instance, and MediaRecorder.start() is called (following your example), there is a race condition where the worker will receive a START_RECORDING message and attempt to access the vmsg.wasm in onStartRecording which hasn't been loaded yet by getWasmModule . This will cause it to fail silently and the recorder will stay in 'inactive' state.

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Error type: undefined. Note: this is a nested preset so please contact the preset author if you are unable to fix it yourself.

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.