Search code examples
javascriptaudioblob

How to slice an audio blob at a specific time?


I have an audio blob and I want to chop it up at a specific time.How should I do that in Javascript ?

Example:

sliceAudioBlob( audio_blob, 0, 10000 ); // Time in milliseconds [ start = 0, end = 10000 ]

Note: I have no clue how to do that, so a little hint would be really appreciated.

Update :

I'm trying to build a simple audio recorder, but the problem is that there are differences in time duration for each browser, some of them adds few seconds ( Firefox ) and others don't ( Chrome ). So, I came up with the idea to code a method that returns only the slice I want.

Full HTML code :

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Audio Recorder</title>
    <style>
        audio{
            display: block;
        }
    </style>
</head>
<body>
    <button type="button" onclick="mediaRecorder.start(1000)">Start</button>
    <button type="button" onclick="mediaRecorder.stop()">Stop</button>


    <script type="text/javascript">
        var mediaRecorder = null,
            chunks = [],
            max_duration = 10000;// in milliseconds.

        function onSuccess( stream ) {

            mediaRecorder = new MediaRecorder( stream );


            mediaRecorder.ondataavailable = function( event ) {
                // chunks.length is the number of recorded seconds
                // since every chunk is 1 second duration.
                if ( chunks.length < max_duration / 1000 ) {
                    chunks.push( event.data );
                } else {
                    if (mediaRecorder.state === 'recording') {
                        mediaRecorder.stop();
                    }
                }
            }

            mediaRecorder.onstop = function() {
                var audio = document.createElement('audio'),
                    audio_blob = new Blob(chunks, {
                        'type' : 'audio/mpeg'
                    });
                audio.controls = 'controls';
                audio.autoplay = 'autoplay';
                audio.src = window.URL.createObjectURL( audio_blob );
                document.body.appendChild(audio);
            };

        }

        var onError = function(err) {
            console.log('Error: ' + err);
        }

        navigator.mediaDevices.getUserMedia({ audio: true }).then(onSuccess, onError);
    </script>
</body>
</html>

Solution

  • There is no straight forward way to slice an Audio media like that, and this is because your file is made of more than sound signal: there are multiple segments in it, with some headers etc, which position can't be determined just by a byteLength. It is like you can't crop a jpeg image just by getting its x firsts bytes.

    There might be ways using the Web Audio API to convert your media File to an AudioBuffer, and then slicing this AudioBuffer's raw PCM data as you wish before packing it back in a media File with the correct new descriptors, but I think you are facing an X-Y problem and if I got it correctly, there is a simple way to fix this X problem.

    Indeed, the problem you describe is that either Chrome or Firefox doesn't produce an 10s media from your code.
    But that is because you are relying on the timeslice argument of MediaRecorder.start(timeslice) to give you chunks of perfect time.
    It won't. This argument should only be understood as a clue you are giving to the browser, but they may well impose their own minimum timeslice and thus not respect your argument. (2.3[Methods].5.4).

    Instead, you'll be better using a simple setTimeout to trigger your recorder's stop() method when you want:

    start_btn.onclick = function() {
      mediaRecorder.start(); // we don't even need timeslice
      // now we'll get similar max duration in every browsers
      setTimeout(stopRecording, max_duration);
    };
    stop_btn.onclick = stopRecording;
    
    function stopRecording() {
      if (mediaRecorder.state === "recording")
        mediaRecorder.stop();
    };
    

    Here is a live example using gUM hosted on jsfiddle.

    And a live snippet using a silent stream from the Web Audio API because StackSnippet's protection doesn't run well with gUM...

    var start_btn = document.getElementById('start'),
      stop_btn = document.getElementById('stop');
    
    var mediaRecorder = null,
      chunks = [],
      max_duration = 10000; // in milliseconds.
    
    start_btn.onclick = function() {
      mediaRecorder.start(); // we don't even need timeslice
      // now we'll get similar max duration in every browsers
      setTimeout(stopRecording, max_duration);
      this.disabled = !(stop_btn.disabled = false);
    };
    stop_btn.onclick = stopRecording;
    
    function stopRecording() {
      if (mediaRecorder.state === "recording")
        mediaRecorder.stop();
      stop_btn.disabled = true;
    };
    
    function onSuccess(stream) {
    
      mediaRecorder = new MediaRecorder(stream);
    
      mediaRecorder.ondataavailable = function(event) {
        // simply always push here, the stop will be controlled by setTimeout
        chunks.push(event.data);
      }
    
      mediaRecorder.onstop = function() {
        var audio_blob = new Blob(chunks);
        var audio = new Audio(URL.createObjectURL(audio_blob));
        audio.controls = 'controls';
        document.body.appendChild(audio);
        // workaround https://crbug.com/642012
        audio.currentTime = 1e12;
        audio.onseeked = function() {
          audio.onseeked = null;
          console.log(audio.duration);
          audio.currentTime = 0;
          audio.play();
        }
      };
      start_btn.disabled = false;
    
    }
    
    var onError = function(err) {
      console.log('Error: ' + err);
    }
    
    onSuccess(SilentStream());
    
    function SilentStream() {
      var ctx = new(window.AudioContext || window.webkitAudioContext),
        gain = ctx.createGain(),
        dest = ctx.createMediaStreamDestination();
      gain.connect(dest);
      return dest.stream;
    }
    <button id="start" disabled>start</button>
    <button id="stop" disabled>stop</button>