问题
Scenario:
- Audio starts playing from 0:00. At exactly 0:05, the track skips forwards to 0:30.
- The track immediately starts playing at 0:30, and at exactly 0:35, the track skips backwards to 0:05 and plays the rest of the audio file
Summary: Play: 0:00 to 0.05, Skip: 0:05 to 0:30, Play: 0:30 to 0:35, Skip: 0:35 to 0:05, Play: 0.05 to END
The real problem comes when there is the need for a immediate and seamless skip. For example, setTimeout
is very inaccurate and drifts meaning it cannot be used in this case.
I have tried to use the Web Audio API to accomplish this, but I'm still unable to get an immediate transition. I'm currently scheduling segments of a loaded song using AudioBufferSourceNode.start(when, offset, duration);
, initially passing in (0 [when], 0 [offset], 0:05 [duration])
for the example above. After this, I'm still using setTimeout
to call the same function to run just short of 0:30 and scheduling something like (0 [when] + 0:05 [previous duration], 0:30 [offset], 0:05 [duration])
.
Example of this
var AudioContext = window.AudioContext || window.webkitAudioContext,
audioCtx = new AudioContext();
var skipQueue = [0, 5, 30, 35, 5];
// getSong(); // load in the preview song (https://audiojungle.net/item/inspiring/9325839?s_rank=1)
playSound(0, 0);
function getSong() {
console.log("Loading song...");
request = new XMLHttpRequest();
// request.open('GET', "https://preview.s3.envato.com/files/230851789/preview.mp3?response-content-disposition=attachment%3Bfilename%3D20382637_uplifting-cinematic-epic_by_jwaldenmusic_preview.mp3&Expires=1501966849&Signature=HUMfPw3b4ap13cyrc0ZrNNumb0s4AXr7eKHezyIR-rU845u65oQpxjmZDl8AUZU7cR1KuQGV4TLkQ~egPt5hCiw7SUBRApXw3nnrRdtf~M6PXbNqVYhrhfNq4Y~MgvZdd1NEetv2rCjhirLw4OIhkiC2xH2jvbN6mggnhNnw8ZemBzDH3stCVDTEPGuRgUJrwLwsgBHmy5D2Ef0It~oN8LGG~O~LFB5MGHHmRSjejhjnrfSngWNF9SPI3qn7hOE6WDvcEbNe2vBm5TvEx2OTSlYQc1472rrkGDcxzOHGu9jLEizL-sSiV61uVAp5wqKxd2xNBcsUn3EXXnjMIAmUIQ__&Key-Pair-Id=APKAIEEC7ZU2JC6FKENA", true); // free preview from envato
request.responseType = "arraybuffer";
request.onload = function() {
var audioData = request.response;
audioCtx.decodeAudioData(audioData, function(buffer) {
audioBuffer = buffer;
console.log("Ready to play!");
playSong()
}, function(e) {
"Error with decoding audio data" + e.err
});
}
request.send();
}
function playSound(previousPlay, nextPlay) {
// source = audioCtx.createBufferSource();
// source.buffer = audioBuffer;
// source.connect(audioCtx.destination);
skipQueue.shift();
var duration = Math.abs(skipQueue[0] - previousPlay);
// source.start(nextPlay, previousPlay, duration);
console.log("Running: source.start(" + nextPlay + ", " + previousPlay + ", " + duration + ")");
console.log("Current Time: " + previousPlay);
console.log("Next Play in: " + duration + " (Skipping from " + skipQueue[0] + " to " + skipQueue[1] + ")");
if (skipQueue.length > 1) {
setTimeout(function() {
playSound(skipQueue[0], nextPlay + duration);
}, 1000 * duration - 50); // take 50ms off for drift that'll be corrected in scheduling
}
}
<strong>Expected:</strong><br />
Play: 0:00 to 0.05, Skip: 0:05 to 0:30, Play: 0:30 to 0:35, Skip: 0:35 to 0:05, Play: 0.05 to END
I can't get this simplified down example 100% working, but you can see what my attempt is in the console. I've also commented out code since StackOverflow doesn't support AJAX.
I'm open to using any other API or method that you have in mind!
回答1:
You don't ever show when you actually call start() on an AudioBufferSourceNode, so I can't explicitly debug. But in short, you need to schedule the stops and starts AHEAD of when they need to happen, not try to "swap" the active sound playing in a setTimeout callback (or try to get them to align once the sound has stopped playing).
For example, you'd do something like:
var bs1 = audioCtx.createBufferSourceNode();
var bs2 = audioCtx.createBufferSourceNode();
var bs3 = audioCtx.createBufferSourceNode();
var now = audioCtx.currentTime + 0.020; // add 20ms for scheduling slop
bs1.buffer = audioBuffer;
bs1.connect( audioCtx.destination );
bs2.buffer = audioBuffer;
bs2.connect( audioCtx.destination );
bs3.buffer = audioBuffer;
bs3.connect( audioCtx.destination );
bs1.start( now, 0, 5 ); // time, offset, duration
bs2.start( now+5, 30, 5 );
bs3.start( now+10, 5 ); // no duration; play to end
If you want to cancel this playing, you'll have to disconnect the buffersource nodes.
回答2:
The main issue in this will be the accuracy in time and shifting seamlessly from 0:05 to 0:30 and back from 0:35 to 0:05.
I think you should create two different audio sources. Start one from 0:00 and seek the other one at 0:30 but pause it as soon as it starts playing using events.
Then track time using the ontimeupdate event and check it if it is 5 then pause the current and start the second which should now be buffered and when it reaches to 0:35 pause it and start the first one and let it play till the audio finishes.
回答3:
Play: 0:00 to 0.05, Skip: 0:05 to 0:30, Play: 0:30 to 0:35, Skip: 0:35 to 0:05, Play: 0.05 to END
You can use Media Fragments URI and pause
event to render exact media playback in sequence. If necessary pass the HTMLMediaElement
to AudioContext.createMediaElementSource()
const audio = document.querySelector("audio");
const mediaURL = "https://ia600305.us.archive.org/30/items/return_201605/return.mp3";
const timeSlices = ["#t=0,5", "#t=30,35", "#t=5"];
let index = 0;
audio.onpause = e => {
if (index < timeSlices.length) {
audio.src = `${mediaURL}${timeSlices[index++]}`
} else {
console.log("Media fragments playlist completed");
}
}
audio.ontimeupdate = e => console.log(Math.ceil(audio.currentTime));
audio.oncanplay = e => audio.play();
audio.src = `${mediaURL}${timeSlices[index++]}`;
<audio controls></audio>
来源:https://stackoverflow.com/questions/45395073/javascript-seek-audio-to-certain-position-when-at-exact-position-in-audio-trac