Is it possible to mix multiple audio files on top of each other preferably with javascript

前端 未结 1 385
南方客
南方客 2020-12-01 17:40

I want to combine audio clips, layered on top of each other so that they play synchronously and are saved in a new audio file. Any help would be much appreciated. I\'ve done

相关标签:
1条回答
  • 2020-12-01 18:09

    Yes, it is possible using OfflineAudioContext() or AudioContext.createChannelMerger() and creating a MediaStream. See Phonegap mixing audio files , Web Audio API.

    You can use fetch() or XMLHttpRequest() to retrieve audio resource as an ArrayBuffer, AudioContext.decodeAudioData() to create an AudioBufferSourceNode from response; OfflineAudioContext() to render merged audio, AudioContext, AudioContext.createBufferSource(), AudioContext.createMediaStreamDestination() , MediaRecorder() to record stream; Promise.all(), Promise() constructor, .then() to process asynchronous requests to fetch(), AudioContext.decodeAudioData(), pass resulting mixed audio Blob at stop event of MediaRecorder.

    Connect each AudioContext AudioBufferSourceNode to OfflineAudioContext.destination, call .start() on each node; call OfflineAudioContext.startRendering(); create new AudioContext node, connect renderedBuffer; call .createMediaStreamDestination() on AudioContext to create a MediaStream from merged audio buffers, pass .stream to MediaRecorder(), at stop event of MediaRecorder, create Blob URL of Blob of recorded audio mix with URL.createObjectURL(), which can be downloaded using <a> element with download attribute and href set to Blob URL.

    var sources = ["https://upload.wikimedia.org/wikipedia/commons/b/be/"
                   + "Hidden_Tribe_-_Didgeridoo_1_Live.ogg"
                   , "https://upload.wikimedia.org/wikipedia/commons/6/6e/" 
                   + "Micronesia_National_Anthem.ogg"];
    
    var description = "HiddenTribeAnthem";
    var context;
    var recorder;
    var div = document.querySelector("div");
    var duration = 60000;
    var chunks = [];
    var audio = new AudioContext();
    var mixedAudio = audio.createMediaStreamDestination();
    var player = new Audio();
    player.controls = "controls";
    
    function get(src) {
      return fetch(src)
        .then(function(response) {
          return response.arrayBuffer()
        })
    }
    
    function stopMix(duration, ...media) {
      setTimeout(function(media) {
        media.forEach(function(node) {
          node.stop()
        })
      }, duration, media)
    }
    
    Promise.all(sources.map(get)).then(function(data) {
        var len = Math.max.apply(Math, data.map(function(buffer) {
          return buffer.byteLength
        }));
        context = new OfflineAudioContext(2, len, 44100);
        return Promise.all(data.map(function(buffer) {
            return audio.decodeAudioData(buffer)
              .then(function(bufferSource) {
                var source = context.createBufferSource();
                source.buffer = bufferSource;
                source.connect(context.destination);
                return source.start()
              })
          }))
          .then(function() {
            return context.startRendering()
          })
          .then(function(renderedBuffer) {
            return new Promise(function(resolve) {
              var mix = audio.createBufferSource();
              mix.buffer = renderedBuffer;
              mix.connect(audio.destination);
              mix.connect(mixedAudio);              
              recorder = new MediaRecorder(mixedAudio.stream);
              recorder.start(0);
              mix.start(0);
              div.innerHTML = "playing and recording tracks..";
              // stop playback and recorder in 60 seconds
              stopMix(duration, mix, recorder)
    
              recorder.ondataavailable = function(event) {
                chunks.push(event.data);
              };
    
              recorder.onstop = function(event) {
                var blob = new Blob(chunks,  {
                  "type": "audio/ogg; codecs=opus"
                });
                console.log("recording complete");
                resolve(blob)
              };
            })
          })
          .then(function(blob) {
            console.log(blob);
            div.innerHTML = "mixed audio tracks ready for download..";
            var audioDownload = URL.createObjectURL(blob);
            var a = document.createElement("a");
            a.download = description + "." + blob.type.replace(/.+\/|;.+/g, "");
            a.href = audioDownload;
            a.innerHTML = a.download;
            document.body.appendChild(a);
            a.insertAdjacentHTML("afterend", "<br>");
            player.src = audioDownload;
            document.body.appendChild(player);
          })
      })
      .catch(function(e) {
        console.log(e)
      });
    <!DOCTYPE html>
    <html>
    
    <head>
    </head>
    
    <body>
      <div>loading audio tracks.. please wait</div>
    </body>
    
    </html>

    You can alternatively utilize AudioContext.createChannelMerger(), AudioContext.createChannelSplitter()

    var sources = ["/path/to/audoi1", "/path/to/audio2"];    
    var description = "mix";
    var chunks = [];
    var channels = [[0, 1], [1, 0]];
    var audio = new AudioContext();
    var player = new Audio();
    var merger = audio.createChannelMerger(2);
    var splitter = audio.createChannelSplitter(2);
    var mixedAudio = audio.createMediaStreamDestination();
    var duration = 60000;
    var context;
    var recorder;
    var audioDownload;
    
    player.controls = "controls";
    
    function get(src) {
      return fetch(src)
        .then(function(response) {
          return response.arrayBuffer()
        })
    }
    
    function stopMix(duration, ...media) {
      setTimeout(function(media) {
        media.forEach(function(node) {
          node.stop()
        })
      }, duration, media)
    }
    
    Promise.all(sources.map(get)).then(function(data) {
        return Promise.all(data.map(function(buffer, index) {
            return audio.decodeAudioData(buffer)
              .then(function(bufferSource) {
                var channel = channels[index];
                var source = audio.createBufferSource();
                source.buffer = bufferSource;
                source.connect(splitter);
                splitter.connect(merger, channel[0], channel[1]);
                return source
              })
          }))
          .then(function(audionodes) {
            merger.connect(mixedAudio);
            merger.connect(audio.destination);
            recorder = new MediaRecorder(mixedAudio.stream);
            recorder.start(0);
            audionodes.forEach(function(node) {
              node.start(0)
            });
    
            stopMix(duration, ...audionodes, recorder);
    
            recorder.ondataavailable = function(event) {
              chunks.push(event.data);
            };
    
            recorder.onstop = function(event) {
              var blob = new Blob(chunks, {
                "type": "audio/ogg; codecs=opus"
              });
              audioDownload = URL.createObjectURL(blob);
              var a = document.createElement("a");
              a.download = description + "." + blob.type.replace(/.+\/|;.+/g, "");
              a.href = audioDownload;
              a.innerHTML = a.download;
              player.src = audioDownload;
              document.body.appendChild(a);
              document.body.appendChild(player);
            };
          })
      })
      .catch(function(e) {
        console.log(e)
      });
    
    0 讨论(0)
提交回复
热议问题