如何在JavaScript中同步播放音频文件?

dac*_*cle 44 javascript synchronous playback html5-audio

我正在制作一个程序,将文本转换为莫尔斯电码音频.

说我输入sos.我的程序将把它变成数组[1, 1, 1, 0, 2, 2, 2, 0, 1, 1, 1].哪里s = dot dot dot(或1,1,1),和o = dash dash dash(或2,2,2).这部分很容易.

接下来,我有两个声音文件:

var dot = new Audio('dot.mp3');
var dash = new Audio('dash.mp3');
Run Code Online (Sandbox Code Playgroud)

我的目标是有一个功能,dot.mp3当它看到a时1,dash.mp3当它看到a时会播放,并在看到a 2时暂停0.

以下类型/有时/有时可行,但我认为它存在根本缺陷,我不知道如何修复它.

function playMorseArr(morseArr) {
  for (let i = 0; i < morseArr.length; i++) {
    setTimeout(function() {
      if (morseArr[i] === 1) {
        dot.play();
      }
      if (morseArr[i] === 2) {
        dash.play();
      }
    }, 250*i);
  }
}
Run Code Online (Sandbox Code Playgroud)

问题:

我可以遍历数组,播放声音文件,但时机是一个挑战.如果我没有setTimeout()恰当地设置间隔,如果最后一个音频文件未完成播放且250ms已经过,则将跳过数组中的下一个元素.所以dash.mp3比...更长dot.mp3.如果我的时间太短,我可能会听到[dot dot dot pause dash dash pause dot dot dot],或者说那种效果.

我想要的效果

我希望程序像这样(在伪代码中):

  1. 看一下ith数组元素
  2. if 1or 2,开始播放声音文件或者创建暂停
  3. 等待声音文件或暂停完成
  4. 增加i并返回步骤1

我想到了什么,但不知道如何实施

因此,我希望循环同步进行.我已经在我有几个函数的情况下使用了promises,我希望按特定顺序执行,但是如何链接未知数量的函数?

我也考虑过使用自定义事件,但我遇到了同样的问题.

Kai*_*ido 43

不要将HTMLAudioElement用于那种应用程序.

HTMLMediaElements本质上是异步的,从play()方法到pause()一个并且通过明显的资源获取和不太明显的currentTime设置的所有内容都是异步的.

这意味着对于需要完美计时的应用程序(如摩尔斯电码阅读器),这些元素纯粹是不可靠的.

而是使用Web Audio API及其AudioBufferSourceNode对象,您可以使用μs精度控制它们.

首先将所有资源作为ArrayBuffers获取,然后在需要时从这些ArrayBuffers生成并播放AudioBufferSourceNodes.

您将能够同步开始播放这些内容,或以比setTimeout为您提供的更高精度安排它们(AudioContext使用自己的时钟).

担心让几个AudioBufferSourceNodes播放你的样本会对内存造成影响吗?不要.数据仅存储在内存中,一次存储在AudioBuffer中.AudioBufferSourceNodes只是对这些数据的观看,并没有任何地方.

// I use a lib for Morse encoding, didn't tested it too much though
// https://github.com/Syncthetic/MorseCode/
const morse = Object.create(MorseCode);

const ctx = new (window.AudioContext || window.webkitAudioContext)();

(async function initMorseData() {
  // our AudioBuffers objects
  const [short, long] = await fetchBuffers();

  btn.onclick = e => {
    let time = 0; // a simple time counter
    const sequence = morse.encode(inp.value);
    console.log(sequence); // dots and dashes
    sequence.split('').forEach(type => {
      if(type === ' ') { // space => 0.5s of silence
        time += 0.5;
        return;
      }
      // create an AudioBufferSourceNode
      let source = ctx.createBufferSource();
      // assign the correct AudioBuffer to it
      source.buffer = type === '-' ? long : short;
      // connect to our output audio
      source.connect(ctx.destination);
      // schedule it to start at the end of previous one
      source.start(ctx.currentTime + time);
      // increment our timer with our sample's duration
      time += source.buffer.duration;
    });
  };
  // ready to go
  btn.disabled = false
})()
  .catch(console.error);

function fetchBuffers() {
  return Promise.all(
    [
      'https://dl.dropboxusercontent.com/s/1cdwpm3gca9mlo0/kick.mp3',
      'https://dl.dropboxusercontent.com/s/h2j6vm17r07jf03/snare.mp3'
    ].map(url => fetch(url)
      .then(r => r.arrayBuffer())
      .then(buf => ctx.decodeAudioData(buf))
    )
  );
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdn.jsdelivr.net/gh/mohayonao/promise-decode-audio-data@eb4b1322113b08614634559bc12e6a8163b9cf0c/build/promise-decode-audio-data.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/Syncthetic/MorseCode@master/morsecode.js"></script>
<input type="text" id="inp" value="sos"><button id="btn" disabled>play</button>
Run Code Online (Sandbox Code Playgroud)

  • -.-.--- --- .- ../-... - - ..-./ - .--- .--/ - .... - - .-.- ...... (2认同)

Cer*_*nce 18

Audio■找的ended,你可以监听事件,这样你就可以await一个Promise解析当事件触发:

const audios = [undefined, dot, dash];
async function playMorseArr(morseArr) {
  for (let i = 0; i < morseArr.length; i++) {
    const item = morseArr[i];
    await new Promise((resolve) => {
      if (item === 0) {
        // insert desired number of milliseconds to pause here
        setTimeout(resolve, 250);
      } else {
        audios[item].onended = resolve;
        audios[item].play();
      }
    });
  }
}
Run Code Online (Sandbox Code Playgroud)


Shi*_*rsz 10

我将使用一种递归方法来监听音频结束事件.因此,每当当前播放音频停止时,再次调用该方法播放下一个音频.

function playMorseArr(morseArr, idx)
{
    // Finish condition.
    if (idx >= morseArr.length)
        return;

    let next = function() {playMorseArr(morseArr, idx + 1)};

    if (morseArr[idx] === 1) {
        dot.onended = next;
        dot.play();
    }
    else if (morseArr[idx] === 2) {
        dash.onended = next;
        dash.play();
    }
    else {
        setTimeout(next, 250);
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以playMorseArr()使用数组和起始索引初始化调用过程:

playMorseArr([1, 1, 1, 0, 2, 2, 2, 0, 1, 1, 1], 0);
Run Code Online (Sandbox Code Playgroud)

一个测试示例(使用mp3来自Kaiido答案的虚拟文件)

let [dot, dash] = [
    new Audio('https://dl.dropboxusercontent.com/s/1cdwpm3gca9mlo0/kick.mp3'),
    new Audio('https://dl.dropboxusercontent.com/s/h2j6vm17r07jf03/snare.mp3')
];

function playMorseArr(morseArr, idx)
{
    // Finish condition.
    if (idx >= morseArr.length)
        return;

    let next = function() {playMorseArr(morseArr, idx + 1)};

    if (morseArr[idx] === 1) {
        dot.onended = next;
        dot.play();
    }
    else if (morseArr[idx] === 2) {
        dash.onended = next;
        dash.play();
    }
    else {
        setTimeout(next, 250);
    }
}

playMorseArr([1,1,1,0,2,2,2,0,1,1,1], 0);
Run Code Online (Sandbox Code Playgroud)