Звук воспроизводится в Chrome, но не в Safari

У меня есть угловое приложение, где я установил 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 заблокировал все, но это в ответ на нажатие кнопки, это не автоматическое воспроизведение.

Аудио отправляется обратно с сервера через скрипт PHP, и он отправляет заголовки следующим образом, если это имеет значение:

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

1 ответ

Решение

Нет, это не "в ответ на нажатие кнопки".
В ответ на это событие щелчка вы запускаете асинхронную задачу. К тому времени как ты позвонишь 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 и избежать заполнения дважды памяти одними и теми же данными.

Наконец, если вы собираетесь воспроизводить этот звук более одного раза, рассмотрите возможность его предварительной выборки и предварительного декодирования, тогда вы сможете избежать всего асинхронного беспорядка.

Другие вопросы по тегам