Javascript - 在音轨中的确切位置时将音频搜索到特定位置

Javascript - Seek audio to certain position when at exact position in audio track

场景:

  1. 音频从 0:00 开始播放。正好在 0:05,曲目向前跳到 0:30.
  2. 曲目在 0:30 处立即开始播放,恰好在 0:35 处曲目向后跳转0:05 并播放音频文件的其余部分

总结:播放:0:00到0.05跳过:0:05到0:30 , 播放: 0:30 到 0:35, 跳过: 0:35 到 0:05, 播放:0.05 到结束

当需要立即无缝跳过时,真正的问题就来了。例如,setTimeoutvery inaccurate 并且漂移意味着它不能在这种情况下使用。

我已经尝试使用网络音频 API 来完成此操作,但我仍然无法立即进行转换。我目前正在使用 AudioBufferSourceNode.start(when, offset, duration); 安排加载歌曲的片段,最初为上面的示例传入 (0 [when], 0 [offset], 0:05 [duration])。在此之后,我仍然使用 setTimeout 将相同的函数调用到 运行 只是 0:30 并安排类似 (0 [when] + 0:05 [previous duration], 0:30 [offset], 0:05 [duration]).

例子

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

我无法使这个简化的示例 100% 正常工作,但您可以在控制台中看到我的尝试。我还注释掉了代码,因为 Whosebug 不支持 AJAX.

我愿意使用您想到的任何其他 API 或方法!

这里的主要问题是时间的准确性以及从 0:05 到 0:30 以及从 0:35 到 0:05 的无缝转换。

我认为您应该创建两个不同的音频源。从 0:00 开始一个,然后在 0:30 寻找另一个,但是一旦它开始使用事件播放就暂停它。

然后使用 ontimeupdate 事件跟踪时间并检查它是否为 5 然后暂停当前并开始第二个现在应该被缓冲并且当它到达 0:35 暂停它并开始第一个并且让它播放直到音频结束。

当您实际调用 AudioBufferSourceNode 上的 start() 时,您永远不会显示,因此我无法显式调试。但简而言之,您需要提前安排停止和开始的时间,而不是尝试 "swap" 在 setTimeout 回调中播放活动声音(或者尝试让它们在声音停止播放后对齐).

例如,您可以这样做:

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

如果你想取消这次播放,你必须断开buffersource节点。

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

您可以使用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>