Web Audio API - multiple synchronized tracks - stopping previous track when new track starts

可紊 提交于 2019-12-02 05:07:46

问题


I'm trying to mimic the Web Audio API multitrack demo from the 'Mozilla Web Audio API for games' Web doc.

https://developer.mozilla.org/en-US/docs/Games/Techniques/Audio_for_Web_Games#Web_Audio_API_for_games

The only caveat I have is that I want the previous track to stop, once a new track is clicked (instead of playing layered on top of each other).

An example would be, click drums, the drums start playing, then click guitar, the drums stop and the guitar starts right where the drums left off.

Any ideas? Are there better tools/libraries for handling web audio?

http://jsfiddle.net/c87z11jj/1/

<ul>
  <li><a class="track" href="http://jPlayer.org/audio/mp3/gbreggae-leadguitar.mp3">Lead Guitar</a></li>
  <li><a class="track" href="http://jPlayer.org/audio/mp3/gbreggae-drums.mp3">Drums</a></li>
  <li><a class="track" href="http://jPlayer.org/audio/mp3/gbreggae-bassguitar.mp3">Bass Guitar</a></li>
  <li><a class="track" href="http://jPlayer.org/audio/mp3/gbreggae-horns.mp3">Horns</a></li>
  <li><a class="track" href="http://jPlayer.org/audio/mp3/gbreggae-clav.mp3">Clavi</a></li>
</ul>

    window.AudioContext = window.AudioContext || window.webkitAudioContext;

    var offset = 0;
    var context = new AudioContext();

    function playTrack(url) {
      var request = new XMLHttpRequest();
      request.open('GET', url, true);
      request.responseType = 'arraybuffer';

      var audiobuffer;

      // Decode asynchronously
      request.onload = function() {

        if (request.status == 200) {

          context.decodeAudioData(request.response, function(buffer) {
            var source = context.createBufferSource();
            source.buffer = buffer;
            source.connect(context.destination);
            console.log('context.currentTime '+context.currentTime);

            if (offset == 0) {
              source.start();
              offset = context.currentTime;
            } else {
              source.start(0,context.currentTime - offset);
            }

          }, function(e) {
            console.log('Error decoding audio data:' + e);
          });
        } else {


     console.log('Audio didn\'t load successfully; error code:' + request.statusText);
    }
  }
  request.send();
}

var tracks = document.getElementsByClassName('track');

for (var i = 0, len = tracks.length; i < len; i++) {
  tracks[i].addEventListener('click', function(e){
    console.log(this.href);
    playTrack(this.href);
    e.preventDefault();
  });
}

回答1:


Simply store the BufferSources somewhere in the outer scope and then call their stop() method.

I took the liberty to rewrite a bit your loading logic, you shouldn't create a new request every time you start a new track, in that case you loose the main advantages of AudioBuffers against Audio element: they're truly fast to instantiate.

var active_source = null;

function stopActiveSource() {
  if (active_source) {
    active_source.onended = null; // manual stop, no event
    active_source.stop(0);
  }
}
// instead of requesting a new ArrayBuffer every time
// store them in a dictionnary
var buffers = {};
var context = new(window.AudioContext || window.webkitAudioContext)();

function playTrack(url) {
  // get fom our dictionnary
  var buffer = buffers[url];
  // stop the active one if any
  stopActiveSource();
  // create a new BufferSource
  var source = context.createBufferSource();
  // it is now the active one
  active_source = source;
  source.onended = function() {
    active_source = null;
  };

  source.buffer = buffer;
  source.connect(context.destination);

  source.start(0);
}

// start by getting all AudioBuffers
var tracks = document.getElementsByClassName('track');

for (var i = 0, len = tracks.length; i < len; i++) {
  tracks[i].addEventListener('click', function(e) {
    playTrack(this.href);
    e.preventDefault();
  });
  getBuffer(tracks[i].href);
}


function getBuffer(url) {
  var request = new XMLHttpRequest();
  request.open('GET', url, true);
  request.responseType = 'arraybuffer';
  request.onload = function(evt) {
    context.decodeAudioData(request.response, store);
  };
  request.send();

  function store(buffer) {
    buffers[url] = buffer;
  }
}
<base href="https://dl.dropboxusercontent.com/s/">
<ul>
  <li><a class="track" href="kbgd2jm7ezk3u3x/hihat.mp3">HiHat</a></li>
  <li><a class="track" href="h2j6vm17r07jf03/snare.mp3">Snare</a></li>
  <li><a class="track" href="1cdwpm3gca9mlo0/kick.mp3">Kick</a></li>
  <li><a class="track" href="h8pvqqol3ovyle8/tom.mp3">Tom</a></li>
</ul>



回答2:


Figured it out with help from @Kaiido

Example with both synchronization and starting a new track where a previous track stops:

<ul>
  <li><a class="track" href="http://jPlayer.org/audio/mp3/gbreggae-leadguitar.mp3">Lead Guitar</a></li>
  <li><a class="track" href="http://jPlayer.org/audio/mp3/gbreggae-drums.mp3">Drums</a></li>
  <li><a class="track" href="http://jPlayer.org/audio/mp3/gbreggae-bassguitar.mp3">Bass Guitar</a></li>
  <li><a class="track" href="http://jPlayer.org/audio/mp3/gbreggae-horns.mp3">Horns</a></li>
  <li><a class="track" href="http://jPlayer.org/audio/mp3/gbreggae-clav.mp3">Clavi</a></li>
</ul>

let active_source = null;
let buffers = {};
const context = new(window.AudioContext || window.webkitAudioContext)();
let offset = 0;
const tempo = 3.074074076;
const tracks = document.getElementsByClassName('track');

function playTrack(url) {
  let buffer = buffers[url];
  let source = context.createBufferSource();

  source.buffer = buffer;
  source.connect(context.destination);
  source.loop = true;

  if (offset == 0) {
    source.start();
    offset = context.currentTime;
    active_source = source;
  } else {
    let relativeTime = context.currentTime - offset;
    let beats = relativeTime / tempo;
    let remainder = beats - Math.floor(beats);
    let delay = tempo - (remainder*tempo);
    let when = context.currentTime+delay;

    stopActiveSource(when);
    source.start(context.currentTime+delay,relativeTime+delay);
    active_source = source;
    source.onended = function() {
      active_source = null;
    };
  }
}

for (var i = 0, len = tracks.length; i < len; i++) {
  tracks[i].addEventListener('click', function(e) {
    playTrack(this.href);
    e.preventDefault();
  });
  getBuffer(tracks[i].href);
}

function getBuffer(url) {
  const request = new XMLHttpRequest();
  request.open('GET', url, true);
  request.responseType = 'arraybuffer';
  request.onload = function(evt) {
    context.decodeAudioData(request.response, store);
  };
  request.send();

  function store(buffer) {
    buffers[url] = buffer;
  }
}

function stopActiveSource(when) {
  if (active_source) {
    active_source.onended = null;
    active_source.stop(when);
  }
}

http://jsfiddle.net/mdq2c1wv/1/



来源:https://stackoverflow.com/questions/52436015/web-audio-api-multiple-synchronized-tracks-stopping-previous-track-when-new

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!