0

我有一个 Angular 5 应用程序,我在其中设置了click一个按钮的处理程序来下载音频文件并播放它。我正在使用此代码来执行此操作:

onPreviewPressed(media: Media): void {
    const url = ".....";

    this.httpClient.get(url, {responseType: 'blob'}).subscribe(x => {
        const fileReader = new FileReader();

        fileReader.onloadend = () => {
            const context = new ((<any>window).AudioContext || (<any>window).webkitAudioContext)();
            const source = context.createBufferSource();

            context.decodeAudioData(fileReader.result, buffer => {
                source.buffer = buffer;
                source.connect(context.destination);
                source.start(0);
            }, y => {
                console.info("Error: " + y);
            });
        };

        fileReader.readAsArrayBuffer(x);
    });
}

如果我转到 Chrome 中的页面并按下按钮,音频会立即启动。如果我在 Safari 中执行此操作,则不会发生任何事情。我知道 Safari 锁定了一些东西,但这是对按钮单击的响应,它不是自动播放。

The audio is sent back from the server via a PHP script, and it's sending headers like this, in case it matters:

header("Content-Type: audio/mpeg");
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($_GET['file']));
header('Cache-Control: no-cache');
4

1 回答 1

2

不,它不是“响应按钮点击”。
为响应此单击事件,您将启动一个异步任务。到你打电话的时候source.start(0),你的事件早就死了(或者至少不再是“受信任的用户手势”。所以他们确实会阻止这个电话。

为了避免这种情况,您可以简单地将您的上下文标记为允许静音。然后,当数据可用时,您将能够不受限制地启动它:

function markContextAsAllowed(context) {
  const gain = context.createGain();
  gain.gain.value = 0; // silence
  const osc = context.createOscillator();
  osc.connect(gain);
  gain.connect(context.destination);
  osc.onended = e => gain.disconnect();
  osc.start(0);
  osc.stop(0.01);
}


onPreviewPressed(media: Media): void {
  const url = ".....";
  // declare in the event handler
  const context = new(window.AudioContext || window.webkitAudioContext)();
  const source = context.createBufferSource();
  // allow context synchronously
  markContextAsAllowed(context);


  this.httpClient.get(url, {
    responseType: 'blob'
  }).subscribe(x => {
    const fileReader = new FileReader();

    fileReader.onloadend = () => {
      context.decodeAudioData(fileReader.result, buffer => {
        source.buffer = buffer;
        source.connect(context.destination);
        source.start(0);
      }, y => {
        console.info("Error: " + y);
      });
    };

    fileReader.readAsArrayBuffer(x);
  });
}

作为一个小提琴,因为 Safari 不喜欢过度保护的 StackSnippets®

此外,我的角度知识非常有限,但如果httpClient.get确实支持{responseType: 'arraybuffer'}选项,您可以摆脱这个 FileReader 并避免使用相同的数据填充两倍的内存。

最后,如果您要多次播放此音频,请考虑对其进行预取和预解码,这样您就可以避免整个异步混乱。

于 2018-10-01T02:30:38.497 回答