LINUX.ORG.RU

Оптимальный способ проигрывания множества звуков на странице (AudioContext, buffer)

 , , ,


0

1

Сразу оговорюсь: я не программист, особенно не программист на js, мне нужно решить одну конкретную задачу и забыть о существовании js на многие годы. Поэтому просьба не посылать меня читать гору толстых учебников.

Собственно, задача: сделать так, чтоб при нажатии определённых клавиш на клавиатуре проигрывались те или иные звуки. Аудиофайлы лежат в той же папке, что и скрипт.

Первоначально делалось так:

const r0 = new Audio("ringing_0.ogg");
r0.currentTime=0; r0.play();

И это работало, но с одной проблемой: при нажатии на кнопку звук, проигрываемый сейчас, мгновенно обрывался, и начинал проигрываться заново. Что сильно режет слух. Но убирать r0.currentTime=0 нельзя: звук вообще не будет воспроизводиться, пока текущее воспроизведение не закончится.

Тогда я поменял код:

r0.cloneNode(true).play();

Теперь играет довольно красиво, но возникла другая проблема: если нажимать кнопку слишком часто, звук начинает захлёбываться.

В интернетах много советов по использованию AudioContext'а, но с ним проблема: почему-то ВСЕ уроки, которые я находил, требуют использовать XMLHttpRequest для запроса звукового файла с сервера. В результате — куча непонятного кода, который ещё и не работает. Например, здесь описан следующий порядок действий:

0. Скачать файл через XMLHttpRequest;

1. Добавить полученный ответ к буферу;

2. Создать bufferSource и проигрывать его при каждом вызове;

Наткнулся на видео, где предлагается httprequest заменить на fetch. В результате код приобретает более простой и короткий вид:

async function getresponse() {
let response2 = await fetch ("http://mapper720.ru/belltowers/transfiguration/ringing_0.ogg");
}

Остаются невыясненными следующие вопросы:

0. Как полученный с помощью fetch аудиофайл добавить к буферу?

1. Как вообще работает этот буфер? Можно ли, например, после загрузки страницы непрерывно проигрывать буфер, в который по нажатию кнопки закидывать некий аудиофайл, или принцип работы иной?

2. И можно ли вообще обойтись без всяких httprequest'ов, fetch'ей и тому подобного? Файл-то вон он, под носом, в одной папке со скриптом, зачем тогда вообще эти сложности?

я не программист, особенно не программист на js, мне нужно решить одну конкретную задачу и забыть о существовании js на многие годы. Поэтому просьба не посылать меня читать гору толстых учебников.

В Job.

anonymous
()

AudioContext, buffer

Тебе это не нужно. Как правило эти штуки юзаются для стримминга аудио. Это оверхэд в твоём случае.

Но убирать r0.currentTime=0 нельзя: звук вообще не будет воспроизводиться, пока текущее воспроизведение не закончится

Это какой-то костыль

Теперь играет довольно красиво, но возникла другая проблема: если нажимать кнопку слишком часто, звук начинает захлёбываться

Почему просто не сделать таймер, мол, если не прошло N секунд, то кнопка play недоступна? Ещё можно добавить fade out, чтобы звук плавно затухал, тем самым скрыв проблему.

Или такие штуки не подходят?

CryNet ★★★★★
()
Ответ на: комментарий от CryNet

Тебе это не нужно. Как правило эти штуки юзаются для стримминга аудио. Это оверхэд в твоём случае.

Эти штуки юзаются в том числе при разработке MIDI и прочих онлайн-музыкальных инструментов, чем ТС и занимается. Они для того и нужны.

anonymous
()
Ответ на: комментарий от anonymous

А, я видимо не понял, что ТС делает, сорян. Да, именно для этого. Согласен.

CryNet ★★★★★
()
  1. И можно ли вообще обойтись без всяких httprequest’ов, fetch’ей и тому подобного? Файл-то вон он, под носом, в одной папке со скриптом, зачем тогда вообще эти сложности?

А он каким-то магическим образом должен в браузер загрузиться? Даже на десктопе нужно было бы как-то прочитать файл.

static_lab ★★★★★
()
Ответ на: комментарий от CryNet

Почему просто не сделать таймер, мол, если не прошло N секунд, то кнопка play недоступна? Ещё можно добавить fade out, чтобы звук плавно затухал, тем самым скрыв проблему.

Потому что юзверь должен иметь возможность долбить кнопку хоть до посинения. Так что временно её блокировать – совсем не вариант.

Я думал о том, чтоб временно (пока не прошло N секунд) блокировать клонирование. Типа, если прошло N секунд - cloneNode, если нет - currentTime=0, play. Стало получше вроде, но всё равно далеко до идеала.

Mapper720
() автор топика
Ответ на: комментарий от static_lab

А он каким-то магическим образом должен в браузер загрузиться? Даже на десктопе нужно было бы как-то прочитать файл.

В данный момент это делается очень легко:

const r1 = new Audio("cas_ringing_1.ogg");

Вот и не могу понять: зачем такие сложности для обычной подгрузки аудиофайла и можно ли обойтись без них.

Mapper720
() автор топика
Ответ на: комментарий от Mapper720

Хм-м-м… а если попробовать сперва отключить звук, а потом с небольшой задёржкой останавливать музыку?

Даже так:

// volume = 0
setTimeout(() => {
  // остановить музыку
}, 1)

Кста, глянь сюда: https://howlerjs.com/

Чекни пример «Audio Sprites». Начни долбить короткий сэмпл. Всё работает как нужно. Может у тебя какие-то костыли отрабатывают не правильно и звук лагает? Можешь заюзать эту либу на крайняк.

CryNet ★★★★★
()
Последнее исправление: CryNet (всего исправлений: 2)
Ответ на: комментарий от Mapper720

Так, погоди. Ты ж вкудахте отписался что всё у тебя получилось!!!

deep-purple ★★★★★
()
Ответ на: комментарий от CryNet

Чекни пример «Audio Sprites»

Хотелось бы, по возможности, избежать использования сторонних библиотек. Хотя, возможно, всё же придётся к ним обратиться.

Может у тебя какие-то костыли отрабатывают не правильно и звук лагает? Можешь заюзать эту либу на крайняк.

Когда я писал этот код на Python, у меня возникла та же проблема. Из-за того, что каждое новое проигрывание звука запускалось в новом потоке и ОС быстро теряла возможность создавать новые потоки. Видимо, в JS ситуация примерно та же. Посему и ищу способ сделать код более оптимальным.

Mapper720
() автор топика
Ответ на: комментарий от Mapper720

В данный момент это делается очень легко

Это только выглядит «очень легко». На самом деле браузер создаёт тег и асинхронно грузит в него аудиофайл:

If a URL is specified, the browser begins to asynchronously load the media resource before returning the new object.

При этом запустить воспроизведение, пока файл не загрузился, нельзя. Нужно отследить, что файл действительно загрузился и распарсился: https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement/Audio#determining_when_playback_can_begin

Ну и самая мякотка там дальше:

If all references to an audio element created using the Audio() constructor are deleted, the element itself won’t be removed from memory by the JavaScript runtime’s garbage collection mechanism if playback is currently underway. Instead, the audio will keep playing and the object will remain in memory until playback ends or is paused (such as by calling pause()). At that time, the object becomes subject to garbage collection.

static_lab ★★★★★
()
Ответ на: комментарий от static_lab

Звуки у меня довольно короткие, так что, полагаю, они быстро будут выгружаться из памяти. А то, что текущий способ не лишён недостатков, мне и так известно. Вопрос, на что его заменить.

Сейчас пробую делать так:

window.AudioContext = window.AudioContext;

var buffer, ctx = new AudioContext(),
gainNode = ctx.createGain();
gainNode.connect(ctx.destination);

async function getResponse(file) {
return await fetch(file);
}

let file = getResponse('http://mapper720.ru/belltowers/transfiguration/ringing_0.ogg');

В итоге файл ringing_0.ogg загрузился (статус 206), но в переменной file оказывается нечто малопонятное:

Promise { <state>: "fulfilled", <value>: Response }
​
<state>: "fulfilled"
​
<value>: Response { type: "basic", url: "http://mapper720.ru/belltowers/transfiguration/ringing_0.ogg", redirected: false, … }
​
<prototype>: Promise.prototype { … }

Не пойму, что с этим дальше-то делать?

Mapper720
() автор топика
Последнее исправление: Mapper720 (всего исправлений: 1)
Ответ на: комментарий от Mapper720
async function getResponse(file) {
    return await fetch(file);
}

Эта функция не имеет смысла. Считай, что async и await взаимно «сокращаются» и остаётся

function getResponse(file) {
    return fetch(file);
}
static_lab ★★★★★
()
Ответ на: комментарий от static_lab

У меня в итоге вот такой вариант, кажется, заработал как нужно:

const ctx = new AudioContext();
let r0, r1, r2, r3, r4, r5, sr0, sr1, sr2, sr3, sr4, sr5, b0, b1, b2;

fetch ('http://mapper720.ru/belltowers/transfiguration/blagovest_1.ogg')
.then(data => data.arrayBuffer())
.then(arrayBuffer => ctx.decodeAudioData(arrayBuffer))
.then(decodedAudio => {
b1 = decodedAudio;
});

function playback(what_to_play) {
    const playSound = ctx.createBufferSource();
    playSound.buffer = what_to_play;
    playSound.connect(ctx.destination);
    playSound.start(ctx.currentTime);
    }

И при нажатии на нужную кнопку:

playback(b1);
Mapper720
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.