Fibericon
Fibericon

Reputation: 5793

HTML5 record audio to file

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?

Upvotes: 157

Views: 314230

Answers (10)

Prashant Dey
Prashant Dey

Reputation: 620

The question is very old and many of the answers are not supported in the current version of the browser. I was trying to create the audio recorder using simple html, css, and js. I further went on to use the same code in electron to make a cross platform application.

 <html>
  <head>
    <title>Recorder App</title>
    
  </head>
  <h2>Recorder App</h2>
  <p>
    <button type="button" id="record">Record</button>
    <button type="button" id="stopRecord" disabled>Stop</button>
  </p>
  <p>
    <audio id="recordedAudio"></audio>        
  </p>

  <script> 
    navigator.mediaDevices.getUserMedia({audio:true})
    .then(stream => {handlerFunction(stream)})

    function handlerFunction(stream) {
      rec = new MediaRecorder(stream);
      rec.ondataavailable = e => {
        audioChunks.push(e.data);
        if (rec.state == "inactive"){
          let blob = new Blob(audioChunks,{type:'audio/mp3'});
          recordedAudio.src = URL.createObjectURL(blob);
          recordedAudio.controls=true;
          recordedAudio.autoplay=true;
          sendData(blob)
          }
        }
      }
    
    function sendData(data) {}
      record.onclick = e => {
        record.disabled = true;
        record.style.backgroundColor = "blue"
        stopRecord.disabled=false;
        audioChunks = [];
        rec.start();
        }
      stopRecord.onclick = e => {
        record.disabled = false;
        stopRecord.disabled=true;
        record.style.backgroundColor = "red"
        rec.stop();
        }
  </script>
</html>

The above code works in Windows 10, Mac, Linux, and obviously, on both google chrome and firefox.

Upvotes: 18

Damir Nafikov
Damir Nafikov

Reputation: 332

works on safari Version 16.5.2 (18615.2.9.11.10) macos

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Voice Recorder</title>
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <div class="container">
        <h1>Voice Recorder</h1>
        <div class="recorder">
            <button id="recordBtn" class="button record">Record</button>
            <button id="stopBtn" class="button stop" disabled>Stop</button>
            <audio id="audioPlayback" controls></audio>
            <button id="sendBtn" class="button send" disabled>Send to Server</button>
        </div>
        <p id="statusMessage"></p>
    </div>

    <!-- <script src="app.js"></script> -->
    <script>

        let mediaRecorder;
        let audioChunks = [];
        let audioBlob;

        const sendBtn = document.getElementById('sendBtn');
        sendBtn.disabled = true;

        const recordBtn = document.getElementById('recordBtn');
        const stopBtn = document.getElementById('stopBtn');
        const audioPlayback = document.getElementById('audioPlayback');
        const statusMessage = document.getElementById('statusMessage');

        recordBtn.addEventListener('click', () => {
            navigator.mediaDevices.getUserMedia({ audio: true })
                .then(stream => {
                    mediaRecorder = new MediaRecorder(stream);

                    mediaRecorder.addEventListener("stop", () => {
                        // ... (existing code)
                        sendBtn.disabled = false;
                    });
                    mediaRecorder.start();

                    audioChunks = [];
                    recordBtn.disabled = true;
                    stopBtn.disabled = false;
                    statusMessage.textContent = "Recording...";

                    mediaRecorder.addEventListener("dataavailable", event => {
                        audioChunks.push(event.data);
                    });

                    mediaRecorder.addEventListener("stop", () => {
                        audioBlob = new Blob(audioChunks, { type: 'audio/mp3' });
                        const audioUrl = URL.createObjectURL(audioBlob);
                        audioPlayback.src = audioUrl;
                        statusMessage.textContent = "Recording complete. Click play to listen.";
                        recordBtn.disabled = false;
                        stopBtn.disabled = true;
                    });
                })
                .catch(error => {
                    console.error('Error accessing microphone:', error);
                    statusMessage.textContent = "Error: Unable to access microphone.";
                });
        });

        stopBtn.addEventListener('click', () => {
            if (mediaRecorder && mediaRecorder.state !== "inactive") {
                mediaRecorder.stop();
                statusMessage.textContent = "Recording stopped.";
            }
        });
        const socket = new WebSocket("ws://127.0.0.1:8000/api/realtime/stream");

        socket.onopen = () => {
            console.log("WebSocket connection established");
        };

        socket.onmessage = (event) => {
            console.log("Received message from server:", event.data);
            // Handle incoming messages from the server here
        };

        socket.onclose = () => {
            console.log("WebSocket connection closed");
        };

        sendBtn.addEventListener('click', () => {
            if (audioBlob) {
                const reader = new FileReader();
                reader.onloadend = () => {
                    // Client-side: Convert to base64
                    const base64Audio = btoa(reader.result);

                    const message = JSON.stringify({
                        text: { audio: base64Audio }
                    });
                    socket.send(message);
                    statusMessage.textContent = "Audio sent to server.";
                    sendBtn.disabled = true;
                };
                reader.readAsBinaryString(audioBlob);
            } else {
                statusMessage.textContent = "No audio recorded to send.";
            }
        });

    </script>
</body>

</html>

src: https://gist.github.com/lyf2000/41de00c595b8880c911f7c027e2195e6

Upvotes: 0

ifredom
ifredom

Reputation: 1090

If you only need wav file format, you can use this npm package without any changes. https://www.npmjs.com/package/extendable-media-recorder

import { MediaRecorder, register } from 'extendable-media-recorder';
import { connect } from 'extendable-media-recorder-wav-encoder';

await register(await connect());

const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/wav' });

Upvotes: 0

Ankit Aranya
Ankit Aranya

Reputation: 970

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>

Upvotes: 45

Adithya Kumaranchath
Adithya Kumaranchath

Reputation: 825

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.

Upvotes: 8

Remus Negrota
Remus Negrota

Reputation: 674

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/

Upvotes: 7

mido
mido

Reputation: 25034

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.

Upvotes: 32

user2801382
user2801382

Reputation:

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

Upvotes: 15

noamtcohen
noamtcohen

Reputation: 4612

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

Upvotes: 5

Brad Montgomery
Brad Montgomery

Reputation: 2671

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.

Upvotes: 124

Related Questions