Потоковый микрофонный вход с помощью getUserMedia под iOS Safari - PullRequest
0 голосов
/ 27 сентября 2019

Я разрабатываю веб-сайт, на котором пользователь может отправлять звуковые команды, которые записываются с помощью getUserMedia (только аудио) и интерпретируются в бэкэнде с помощью службы преобразования речи в текст.Чтобы задержка была как можно ниже, я отправляю небольшие аудиоблоки на свой сервер.Это прекрасно работает на Chrome / Firefox и даже Edge.Однако я борюсь с iOS Safari.Я знаю, что Safari - мой единственный выбор на устройствах Apple из-за отсутствия поддержки WebRTC в iOS Chrome / Firefox.

Проблема в том, что я обычно получаю голос пользователя пару раз (для некоторых команд).Но без какого-либо паттерна поток вдруг содержит только пустые байты.Я испробовал много разных стратегий, но в целом я придерживался следующего плана:

  • После того, как пользователь нажмет кнопку, вызовите getUserMedia (с ограничением звука) и сохраните поток в переменную
  • Зарегистрируйте прослушиватель событий в ScriptProcessor и отправьте аудиоблоки в обратном вызове на сервер
  • Когдарезультат возвращается с сервера close AudioContext и MediaStream аудио

Интересная часть - теперь то, что происходит после последующей пользовательской команды.Я пробовал разные вещи: снова вызывать getUserMedia для каждого вызова и каждый раз закрывать трек MediaStream, использовать изначально созданный MediaStream и повторно подключать EventHandler каждый раз, закрывать AudioContext после каждого вызова, использовать только один изначально созданный AudioContext ... Все мои попыткидо сих пор не удалось, потому что я либо получил пустые байты из потока, либо AudioContext был создан в "приостановленном" состоянии.Только закрытие MediaStream / AudioContext и его создание каждый раз кажется более стабильным, но загрузка MediaStream с помощью getUserMedia занимает довольно много времени на iOS (~ 1,5-2 с), что дает плохой пользовательский опыт.Я покажу вам свою последнюю попытку, в которой я пытался отключить / отключить поток между пользовательскими командами и оставить AudioContext открытым:

var audioStream: MediaStream;
var audioContext: AudioContext;

var startButton = document.getElementById("startButton");
startButton.onclick = () => {
    if (!audioStream) {
        getUserAudioStream();
    } else {
        // mute/disable stream
        audioStream.getAudioTracks()[0].enabled = true; 
    }
}

var stopButton = document.getElementById("stopButton");
stopButton.onclick = () => {
    // unmute/enable stream
    audioStream.getAudioTracks()[0].enabled = false;
}

function getUserAudioStream(): Promise<any> {
    return navigator.mediaDevices.getUserMedia({
        audio: true
        } as MediaTrackConstraints,
    }).then((stream: MediaStream) => {
        audioStream = stream;
        startRecording();
    }).catch((e) => { ... });
}

const startRecording = () => {
    const ctx = (window as any).AudioContext || (window as any).webkitAudioContext;
    if (!ctx) {
        console.error("No Audio Context available in browser.");
        return;
    } else {
        audioContext = new ctx();
    }

    const inputPoint = audioContext.createGain();
    const microphone = audioContext.createMediaStreamSource(audioStream);
    scriptProcessor = inputPoint.context.createScriptProcessor(4096, 1, 1);

    microphone.connect(inputPoint);
    inputPoint.connect(scriptProcessor);
    scriptProcessor.connect(inputPoint.context.destination);

    scriptProcessor.addEventListener("audioprocess", streamCallback);
};

const streamCallback = (e) => {
    const samples = e.inputBuffer.getChannelData(0);

    // Here I stream audio chunks to the server and 
    // observe that buffer sometimes only contains empty bytes...
}

Я надеюсь, что фрагмент имеет смысл для вас, потому что я позволил некоторые вещичтобы он был читабельным.Я думаю, что ясно дал понять, что это только одна из многих попыток, и на самом деле мой вопрос: есть ли какая-то особенность в WebRTC / getUserMedia на iOS, которую я пропустил до сих пор?Почему iOS обрабатывает MediaStream иначе, чем Chrome / Firefox в Windows?В качестве последнего комментария: я знаю, что ScriptProcessorNode больше не рекомендуется.На самом деле, я бы хотел использовать MediaRecorder для этого, но это также пока не поддерживается на iOS.Кроме того, я знаю, что polyfill не очень подходит, потому что он поддерживает только ogg для потоковой передачи аудио, что также приводит к проблемам, потому что для этого нужно установить фиксированную частоту дискретизации.

...