1

设想:

  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 到 END

当需要立即和无缝跳过时,真正的问题就出现了。例如,setTimeout非常不准确和漂移意味着它不能在这种情况下使用。

我尝试使用 Web Audio 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% 正常工作,但您可以在控制台中看到我的尝试。由于 StackOverflow 不支持 AJAX,我还注释掉了代码。

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

4

3 回答 3

3

当您在 AudioBufferSourceNode 上实际调用 start() 时,您永远不会显示,因此我无法显式调试。但简而言之,您需要安排停止并在需要发生时提前开始,而不是尝试在 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

如果要取消此播放,则必须断开缓冲源节点。

于 2017-07-30T00:05:09.897 回答
1

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

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

然后使用 ontimeupdate 事件跟踪时间并检查它是否为 5 然后暂停当前并开始现在应该缓冲的第二个,当它达到 0:35 时暂停它并启动第一个并让它播放直到音频完成.

于 2017-07-30T00:02:57.090 回答
1

播放:0:00 到 0.05,跳过:0:05 到 0:30,播放:0:30 到 0:35,跳过:0:35 到 0:05,播放:0.05 到 END

您可以使用媒体片段 URIpause事件按顺序呈现精确的媒体播放。如有必要,传递HTMLMediaElementAudioContext.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>

于 2017-07-30T00:23:49.550 回答