134

What I ultimately want to do is record from the user's microphone, and upload the file to the server when they're done. So far, I've managed to make a stream to an element with the following code:

var audio = document.getElementById("audio_preview");

navigator.getUserMedia  = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
navigator.getUserMedia({video: false, audio: true}, function(stream) {
   audio.src = window.URL.createObjectURL(stream);
}, onRecordFail);

var onRecordFail = function (e) {
   console.log(e);
}

How do I go from that, to recording to a file?

Fibericon
  • 5,184
  • 12
  • 33
  • 61
  • 2
    http://danml.com/js/recaudio.js is a really short single-file lib (5kb) i cleaned up from code based on this blog post: http://typedarray.org/wp-content/projects/WebAudioRecorder/ unlike the other ones i found (some linked here) the usage is quite simple: recorder.start() and recorder.stop(fnCallbackToCatchWAV_URL) – dandavis Sep 19 '14 at 21:18
  • 1
    From 2016: https://stackoverflow.com/questions/34820578/how-to-capture-audio-in-javascript – Bennett Brown Nov 01 '17 at 03:35
  • https://blog.addpipe.com/using-recorder-js-to-capture-wav-audio-in-your-html5-web-site/ is instructive and has a working demo. – Tushar Gautam Oct 01 '20 at 11:06

7 Answers7

115

There is a fairly complete recording demo available at: http://webaudiodemos.appspot.com/AudioRecorder/index.html

It allows you to record audio in the browser, then gives you the option to export and download what you've recorded.

You can view the source of that page to find links to the javascript, but to summarize, there's a Recorder object that contains an exportWAV method, and a forceDownload method.

Brad Montgomery
  • 2,501
  • 1
  • 22
  • 24
  • That only works if you use Canary or have the flags set in configuration. – Fibericon May 13 '13 at 17:15
  • 3
    @Fibericon not anymore (: Stable Chrome does too now (Version 28.0.1500.71 Mac). – JSmyth Jul 16 '13 at 06:42
  • Which makes me very happy! Now I'm looking very intently at FF to catch up. – Fibericon Jul 16 '13 at 10:27
  • 6
    Doesn't seem to work properly on windows 8, the audio playback is silent. Any ideas? – Mark Murphy Aug 14 '13 at 22:34
  • Thanks for the sample, it works fine on Windows 7 but I am unable to test at iPad, how can I? – Neelam Sharma Dec 23 '13 at 10:31
  • 2
    It is fine when testing online. But if I save all the html files (js, png,...), it does not work locally. – Randy Tang Nov 19 '14 at 07:18
  • 2
    I've tested the demo, it works fine in Chrome and Opera but there is issues with firefox (The microphone is recognized but not the sound).. And for Safari and IE, they doesn't know how to handle that code – Tofandel Jan 19 '15 at 02:43
  • 2
    where can I have the complete code? I tried to extract it but not working in my local server (xampp) – gadss Dec 15 '15 at 00:27
  • Even their Github example folder not working on local. https://github.com/mattdiamond/Recorderjs – Danish Adeel Jun 07 '16 at 05:18
  • 1
    The demo in this answer uses [mattdiamond's Recorder.js](https://addpipe.com/blog/using-recorder-js-to-capture-wav-audio-in-your-html5-web-site/). – Octavian Naicu Jul 09 '18 at 15:36
  • This solution uses the deprecated createScriptProcessor. It looks like this has been replaced by AudioWorklet, but I do not have an example to share. – user12861 Sep 08 '20 at 15:32
43

The code shown below is copyrighted to Matt Diamond and available for use under MIT license. The original files are here:

Save this files and use

(function(window){

      var WORKER_PATH = 'recorderWorker.js';
      var Recorder = function(source, cfg){
        var config = cfg || {};
        var bufferLen = config.bufferLen || 4096;
        this.context = source.context;
        this.node = this.context.createScriptProcessor(bufferLen, 2, 2);
        var worker = new Worker(config.workerPath || WORKER_PATH);
        worker.postMessage({
          command: 'init',
          config: {
            sampleRate: this.context.sampleRate
          }
        });
        var recording = false,
          currCallback;

        this.node.onaudioprocess = function(e){
          if (!recording) return;
          worker.postMessage({
            command: 'record',
            buffer: [
              e.inputBuffer.getChannelData(0),
              e.inputBuffer.getChannelData(1)
            ]
          });
        }

        this.configure = function(cfg){
          for (var prop in cfg){
            if (cfg.hasOwnProperty(prop)){
              config[prop] = cfg[prop];
            }
          }
        }

        this.record = function(){
       
          recording = true;
        }

        this.stop = function(){
        
          recording = false;
        }

        this.clear = function(){
          worker.postMessage({ command: 'clear' });
        }

        this.getBuffer = function(cb) {
          currCallback = cb || config.callback;
          worker.postMessage({ command: 'getBuffer' })
        }

        this.exportWAV = function(cb, type){
          currCallback = cb || config.callback;
          type = type || config.type || 'audio/wav';
          if (!currCallback) throw new Error('Callback not set');
          worker.postMessage({
            command: 'exportWAV',
            type: type
          });
        }

        worker.onmessage = function(e){
          var blob = e.data;
          currCallback(blob);
        }

        source.connect(this.node);
        this.node.connect(this.context.destination);    //this should not be necessary
      };

      Recorder.forceDownload = function(blob, filename){
        var url = (window.URL || window.webkitURL).createObjectURL(blob);
        var link = window.document.createElement('a');
        link.href = url;
        link.download = filename || 'output.wav';
        var click = document.createEvent("Event");
        click.initEvent("click", true, true);
        link.dispatchEvent(click);
      }

      window.Recorder = Recorder;

    })(window);

    //ADDITIONAL JS recorderWorker.js
    var recLength = 0,
      recBuffersL = [],
      recBuffersR = [],
      sampleRate;
    this.onmessage = function(e){
      switch(e.data.command){
        case 'init':
          init(e.data.config);
          break;
        case 'record':
          record(e.data.buffer);
          break;
        case 'exportWAV':
          exportWAV(e.data.type);
          break;
        case 'getBuffer':
          getBuffer();
          break;
        case 'clear':
          clear();
          break;
      }
    };

    function init(config){
      sampleRate = config.sampleRate;
    }

    function record(inputBuffer){

      recBuffersL.push(inputBuffer[0]);
      recBuffersR.push(inputBuffer[1]);
      recLength += inputBuffer[0].length;
    }

    function exportWAV(type){
      var bufferL = mergeBuffers(recBuffersL, recLength);
      var bufferR = mergeBuffers(recBuffersR, recLength);
      var interleaved = interleave(bufferL, bufferR);
      var dataview = encodeWAV(interleaved);
      var audioBlob = new Blob([dataview], { type: type });

      this.postMessage(audioBlob);
    }

    function getBuffer() {
      var buffers = [];
      buffers.push( mergeBuffers(recBuffersL, recLength) );
      buffers.push( mergeBuffers(recBuffersR, recLength) );
      this.postMessage(buffers);
    }

    function clear(){
      recLength = 0;
      recBuffersL = [];
      recBuffersR = [];
    }

    function mergeBuffers(recBuffers, recLength){
      var result = new Float32Array(recLength);
      var offset = 0;
      for (var i = 0; i < recBuffers.length; i++){
        result.set(recBuffers[i], offset);
        offset += recBuffers[i].length;
      }
      return result;
    }

    function interleave(inputL, inputR){
      var length = inputL.length + inputR.length;
      var result = new Float32Array(length);

      var index = 0,
        inputIndex = 0;

      while (index < length){
        result[index++] = inputL[inputIndex];
        result[index++] = inputR[inputIndex];
        inputIndex++;
      }
      return result;
    }

    function floatTo16BitPCM(output, offset, input){
      for (var i = 0; i < input.length; i++, offset+=2){
        var s = Math.max(-1, Math.min(1, input[i]));
        output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
      }
    }

    function writeString(view, offset, string){
      for (var i = 0; i < string.length; i++){
        view.setUint8(offset + i, string.charCodeAt(i));
      }
    }

    function encodeWAV(samples){
      var buffer = new ArrayBuffer(44 + samples.length * 2);
      var view = new DataView(buffer);

      /* RIFF identifier */
      writeString(view, 0, 'RIFF');
      /* file length */
      view.setUint32(4, 32 + samples.length * 2, true);
      /* RIFF type */
      writeString(view, 8, 'WAVE');
      /* format chunk identifier */
      writeString(view, 12, 'fmt ');
      /* format chunk length */
      view.setUint32(16, 16, true);
      /* sample format (raw) */
      view.setUint16(20, 1, true);
      /* channel count */
      view.setUint16(22, 2, true);
      /* sample rate */
      view.setUint32(24, sampleRate, true);
      /* byte rate (sample rate * block align) */
      view.setUint32(28, sampleRate * 4, true);
      /* block align (channel count * bytes per sample) */
      view.setUint16(32, 4, true);
      /* bits per sample */
      view.setUint16(34, 16, true);
      /* data chunk identifier */
      writeString(view, 36, 'data');
      /* data chunk length */
      view.setUint32(40, samples.length * 2, true);

      floatTo16BitPCM(view, 44, samples);

      return view;
    }
<html>
     <body>
      <audio controls autoplay></audio>
      <script type="text/javascript" src="recorder.js"> </script>
                    <fieldset><legend>RECORD AUDIO</legend>
      <input onclick="startRecording()" type="button" value="start recording" />
      <input onclick="stopRecording()" type="button" value="stop recording and play" />
                    </fieldset>
      <script>
       var onFail = function(e) {
        console.log('Rejected!', e);
       };

       var onSuccess = function(s) {
        var context = new webkitAudioContext();
        var mediaStreamSource = context.createMediaStreamSource(s);
        recorder = new Recorder(mediaStreamSource);
        recorder.record();

        // audio loopback
        // mediaStreamSource.connect(context.destination);
       }

       window.URL = window.URL || window.webkitURL;
       navigator.getUserMedia  = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;

       var recorder;
       var audio = document.querySelector('audio');

       function startRecording() {
        if (navigator.getUserMedia) {
         navigator.getUserMedia({audio: true}, onSuccess, onFail);
        } else {
         console.log('navigator.getUserMedia not present');
        }
       }

       function stopRecording() {
        recorder.stop();
        recorder.exportWAV(function(s) {
                                
                                  audio.src = window.URL.createObjectURL(s);
        });
       }
      </script>
     </body>
    </html>
Lewistrick
  • 1,918
  • 5
  • 26
  • 41
Ankit Aranya
  • 1,060
  • 1
  • 10
  • 17
  • 1
    @ Ankit Araynya you provide download code for this audio record file . – Iren Patel Apr 09 '14 at 08:34
  • 2
    @Ankit Araynya this helpful for me. i stuck on this problem since 3 days with heavy googling – Hashir Sheikh Apr 24 '14 at 06:09
  • 1
    i am in need to change the name of blob that is saving. because i am sending blob to server using ajax with form data and while getting file name its giving blob. Is there anything you can help me with that. – Jennifer Dec 04 '15 at 05:15
  • 1
    @Jennifer you can change the name in server side – Yassine Sedrani Nov 02 '16 at 00:09
  • 2
    I'm inclined to drop a downvote here today, because ScriptProcessorNode does processing on the main thread, and will be blocked by layout computations, GC, and other similar stuff, leading to glitches even with high buffer sizes. It's fine in a dead simple demo or as a proof of concept, but not in any reasonably complex real app. – John Weisz Feb 18 '19 at 11:53
22

Update now Chrome also supports MediaRecorder API from v47. The same thing to do would be to use it( guessing native recording method is bound to be faster than work arounds), the API is really easy to use, and you would find tons of answers as to how to upload a blob for the server.

Demo - would work in Chrome and Firefox, intentionally left out pushing blob to server...

Code Source


Currently, there are three ways to do it:

  1. as wav[ all code client-side, uncompressed recording], you can check out --> Recorderjs. Problem: file size is quite big, more upload bandwidth required.
  2. as mp3[ all code client-side, compressed recording], you can check out --> mp3Recorder. Problem: personally, I find the quality bad, also there is this licensing issue.
  3. as ogg [ client+ server(node.js) code, compressed recording, infinite hours of recording without browser crash ], you can check out --> recordOpus, either only client-side recording, or client-server bundling, the choice is yours.

    ogg recording example( only firefox):

    var mediaRecorder = new MediaRecorder(stream);
    mediaRecorder.start();  // to start recording.    
    ...
    mediaRecorder.stop();   // to stop recording.
    mediaRecorder.ondataavailable = function(e) {
        // do something with the data.
    }
    

    Fiddle Demo for ogg recording.

DigitCart
  • 2,770
  • 2
  • 14
  • 27
mido
  • 20,728
  • 10
  • 84
  • 109
  • 1
    Chromium "script.js:33 Uncaught TypeError: navigator.mediaDevices.getUserMedia is not a function" – dikirill Jul 27 '16 at 14:21
  • @dikirill you must be using a server(it works locally), it won't work with files, also it does not work on workers(I had much headache in this), if you don't know how to make a server you should install https://chrome.google.com/webstore/detail/web-server-for-chrome/ofhbbkphhbklhfoeikjpcbhemlocgigb?hl=en – John Balvin Arias Jun 06 '18 at 00:37
  • excellent answer, i find your script easy and simple. however, i was trying to change the start button to do the job of request stream also, any ideas? https://github.com/Mido22/MediaRecorder-sample/issues/6 – Edo Edo Nov 29 '19 at 13:19
15

This is a simple JavaScript sound recorder and editor. You can try it.

https://www.danieldemmel.me/JSSoundRecorder/

Can download from here

https://github.com/daaain/JSSoundRecorder

jhpratt
  • 5,613
  • 16
  • 32
  • 43
  • 16
    Note that [link-only answers](http://meta.stackoverflow.com/tags/link-only-answers/info) are discouraged, SO answers should be the end-point of a search for a solution (vs. yet another stopover of references, which tend to get stale over time). Please consider adding a stand-alone synopsis here, keeping the link as a reference. – kleopatra Oct 16 '13 at 10:30
  • 1
    Appropriately, the first link provided is dead - subdomain rerouting issue. The updated link is [http://www.danieldemmel.me/JSSoundRecorder/](http://www.danieldemmel.me/JSSoundRecorder/) but the example doesn't work anyways (Chrome 60) because the site doesn't support HTTPS. Going to [the secure version](https://www.danieldemmel.me/JSSoundRecorder/) and bypassing the security warning does allow the demo to work though. – brichins Sep 12 '17 at 18:17
8

You can use Recordmp3js from GitHub to achieve your requirements. You can record from user's microphone and then get the file as an mp3. Finally upload it to your server.

I used this in my demo. There is a already a sample available with the source code by the author in this location : https://github.com/Audior/Recordmp3js

The demo is here: http://audior.ec/recordmp3js/

But currently works only on Chrome and Firefox.

Seems to work fine and pretty simple. Hope this helps.

Octavian Naicu
  • 2,731
  • 26
  • 44
7

Here's a gitHub project that does just that.

It records audio from the browser in mp3 format, and it automatically saves it to the webserver. https://github.com/Audior/Recordmp3js

You can also view a detailed explanation of the implementation: http://audior.ec/blog/recording-mp3-using-only-html5-and-javascript-recordmp3-js/

Octavian Naicu
  • 2,731
  • 26
  • 44
Remus Negrota
  • 574
  • 5
  • 11
  • 3
    Based on that project and article I wrote another small tool that refactored the used code and enhanced it to be able to use multiple recorders on one page. It can be found under: https://github.com/icatcher-at/MP3RecorderJS – Vapire Aug 14 '14 at 12:26
5

Stream audio in realtime without waiting for recording to end: https://github.com/noamtcohen/AudioStreamer

This streams PCM data but you could modify the code to stream mp3 or Speex

noamtcohen
  • 3,992
  • 1
  • 17
  • 14