Вопрос или проблема
Есть сайт, он подключен к OpenAI Speech API на сервере, сервер принимает POST-запрос с фронтенда, который содержит текст, который нужно озвучить, API озвучивает его, и сервер возвращает фронтенду ссылку на аудиофайл в формате mp3. У Speech API есть функция потоковой передачи аудио в реальном времени, озвучивание передается частями – часть озвучена, передана, часть озвучена, передана.
У меня все работает на стороне запроса, POST-запрос отправляется, я получаю ссылку обратно, но ссылка не хочет озвучиваться. Если вывести ее в консоль и кликнуть по ней, можно увидеть, что она частично загружена и полностью готова к прослушиванию примерно через три секунды после возврата с сервера.
Я написал этот код, в котором функция getPromiseFromEvent оборачивает EventListener в промис и ждет, пока он выполнится. Этот код работает, например, с кликом; если я повешу клик на ту же кнопку таким образом, то код терпеливо ждет клика, а затем продолжает работу в секции ниже, выводя console.log(‘canplay’). Но если речь идет о слушателе событий объекта Audio, то слушатель “canplaythrough” не срабатывает вообще.
speech.addEventListener('click', async (e) => {
e.preventDefault();
let speechText = currentAnswer.replace(/[^\p{L}\d\s.,\-+!?:;]/gu, '');
if (paidVoice === 2) {
function getPromiseFromEvent(item, event) {
return new Promise((resolve) => {
const listener = () => {
item.removeEventListener(event, listener);
resolve();
}
item.addEventListener(event, listener);
})
}
async function waitForAudioLoad() {
const audio = new Audio();
audioURL = await getOpenaiVoice(voiceAPI, voiceID, speechText);
audio.src = audioURL;
audio.addEventListener("progress", () => {
if (audio.readyState > 0) {
console.log('Аудио загружается');
}
})
if (audio.readyState > 0) {
console.log('Аудио загружается-2');
}
console.log(audio.readyState);
await getPromiseFromEvent(audio, "canplaythrough");
audio.play();
console.log('canplay')
}
waitForAudioLoad();
}
Я пытался ‘Blob’ для аудио, пытался добавить слушатель событий без функций, ничего не работает; когда я использовал Blob, я получил ошибку “Не удалось загрузить, так как не найдено подходящего источника.”
Единственное, что у меня работает, это обернуть audio.src = audioURL в setTimeout на 3 секунды; тогда код работает как следует, и озвучивание начинается, а audio.readyState изменяет свое состояние на полную загрузку, вот так:
async function waitForAudioLoad() {
const audio = new Audio();
audioURL = await getOpenaiVoice(voiceAPI, voiceID, speechText);
setTimeout(() => {
audio.src = audioURL;
}, 3000)
audio.addEventListener("progress", () => {
if (audio.readyState > 0) {
console.log('Аудио загружается');
}
})
await getPromiseFromEvent(audio, "canplaythrough");
audio.play();
Если я делаю это, аудио ждет 3 секунды после клика и работает прекрасно. Я думаю, если я заменю setTimeout на грамотный слушатель завершения загрузки, все сработает. Но я не знаю, как это сделать, поэтому надеюсь на вашу помощь.
Это функция getOpenaiVoice, на всякий случай. Я не думаю, что проблема здесь.
async function getOpenaiVoice(voiceAPI, id, answer) {
const requestOptions = {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
"id_answer": id.toString(),
"answer": answer,
}),
};
const response = await fetch(voiceAPI, requestOptions);
if (!response.ok) {
const error = await response.json();
return error;
}
const responseJson = await response.json();
const audioURL = responseJson.audio_url;
return audioURL;
}
Ответ или решение
Чтобы динамически загружать аудио с сервера и воспроизводить его на сайте, вам нужно правильно обработать событие загрузки аудио и использовать подходящие методы для работы с объектом Audio. Давайте разберем ваш код и предложим конкретные улучшения.
Проблема
Вы сталкиваетесь с тем, что аудиофайл не готов к воспроизведению сразу после того, как вы присваиваете ему src
. Ваша проблема заключается в том, что по умолчанию событие canplaythrough
может не сработать, если аудиофайл еще не полностью загружен.
Решение
Вот несколько шагов, которые помогут решить вашу проблему.
1. Исправление обработки событий
Убедитесь, что вы правильно обрабатываете события загрузки аудио. Используйте события canplay
, loadeddata
и canplaythrough
для отслеживания состояний загрузки.
2. Ваша обновлённая функция waitForAudioLoad
Вот пример, как вы можете адаптировать вашу функцию:
async function waitForAudioLoad() {
const audio = new Audio();
const audioURL = await getOpenaiVoice(voiceAPI, voiceID, speechText);
// Устанавливаем источник аудио
audio.src = audioURL;
// Создаем Promise для отслеживания события загрузки
await getPromiseFromEvent(audio, "canplaythrough");
// Начинаем воспроизведение
audio.play();
console.log('Audio is playing');
}
3. Измените вашу функцию getPromiseFromEvent
Убедитесь, что она ожидает разные события, чтобы избежать ситуации, когда аудио должно проигрываться, но не может, потому что не готово:
function getPromiseFromEvent(item, event) {
return new Promise((resolve) => {
const listener = () => {
item.removeEventListener(event, listener);
resolve();
};
item.addEventListener(event, listener);
});
}
Установите дополнительную обработку ошибок
Иногда необходимо обработать ситуации, когда аудиофайл не загружается корректно:
audio.addEventListener('error', (e) => {
console.log('Error loading audio: ', e);
});
Пример полной реализации
Объединяя все вышеперечисленные моменты, вы получите:
speech.addEventListener('click', async (e) => {
e.preventDefault();
let speechText = currentAnswer.replace(/[^\p{L}\d\s.,\-+!?:;]/gu, '');
if (paidVoice === 2) {
async function waitForAudioLoad() {
const audio = new Audio();
const audioURL = await getOpenaiVoice(voiceAPI, voiceID, speechText);
audio.src = audioURL;
audio.addEventListener('error', (e) => {
console.log('Error loading audio: ', e);
});
await getPromiseFromEvent(audio, 'canplaythrough');
audio.play();
console.log('Audio is playing');
}
waitForAudioLoad();
}
});
Заключение
Проблема, с которой вы столкнулись, связана с тем, что аудиофайл не был готов к воспроизведению сразу после его загрузки. Использование события canplaythrough
в сочетании с правильной обработкой событий загрузки должно решить эту проблему. Позаботьтесь о том, чтобы проверять и обрабатывать возможные ошибки при загрузке аудио, чтобы улучшить опыт пользователя.