问题
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