Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Воспроизведение аудио (https://javascript.ru/forum/misc/81263-vosproizvedenie-audio.html)

him 30.10.2020 17:46

Воспроизведение аудио
 
Есть переменная JS. В ней данные в формате wave. Как их лучше воспроизвести в фоне. Визуализация не нужна. Нужно будет отловить событие - проигрывание завершено.

Nexus 30.10.2020 18:01

Цитата:

Сообщение от him
В ней данные в формате wave.

А тип данных в переменной какой?

him 30.10.2020 18:14

Хороший вопрос. Если честно, пока не знаю. Наверное string. Планируется получать ответ с сервера, из php. Запрос планируется отправлять через Ajax.

Как тогда нужно их, данные, отдавать из php ?

Nexus 30.10.2020 18:34

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

Дальше все просто (со своими нюансами): https://developer.mozilla.org/ru/doc...oElement/Audio
const audio = new Audio(audioFileUrl);
audio.addEventListener('canplaythrough', () => {
    audio.play();
});

him 30.10.2020 19:05

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

Если делать заголовки на php, как ловить на JS ?

Nexus 30.10.2020 19:32

Цитата:

Сообщение от him
На стороне сервера не хочется сохранять файл

:blink: А изначально он где вообще?

him 30.10.2020 19:48

Программно создаю сам на php. Вот описание протокола. Он не сложный.
http://microsin.net/programming/pc/wav-format.html

Да, мы простых путей не ищем. :)

Nexus 30.10.2020 20:33

Цитата:

Сообщение от him
Программно создаю сам на php.

Ну так если он создается "на лету", то сразу же его и отдавайте вашему фронту минуя кеширование на диск.

Я ж сразу об этом написал:
Цитата:

Сообщение от Nexus
В свою переменную затолкайте ссылку [...] на роут, который должен вернуть этот самый аудио-файл с соответствующими заголовками.


him 30.10.2020 21:38

А можно по подробней про ссылку на [...] и роут с заголовками ?

Ссылку я кстати читал, до всех вопросов. Но там src/url который я так понимаю хочет файл, а у меня его нету. :)

Если речь о роуте типа Человека Понятные Урлы, так смысл в нем ?
Хотя ...
Отправляю ссылку уникальную, ее получается надо где-то сохранить, что бы потом ловить к ней обращение ?
Через файл ? Через базу ? Что то реализация не выстраивается ..

Nexus 30.10.2020 22:00

Цитата:

Сообщение от him
Но там src который я так понимаю хочет файл, а у меня его нету.

Вы, вероятно, не понимаете, что клиенту (клиент - ваш js скрипт) глубоко пофигу откуда сервер берет файл, главное, чтобы сервер вернул содержимое этого файла и сказал, что именно это за файл (речь о заголовках ответа).

Google: о передаче файла клиенту
Хабр: тоже самое

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

Прочитайте хотя бы статью на хабре, там даже пример кода есть. Вам только нужно подставить свой content-type и отдавать не файл из файловой системы, а тот, который вы сгенерили.

Опан1 31.10.2020 11:27

Лучше всего создать численный типизированный массив, скажем myArray и поместить в него стандартный заголовок аудиофайла формата wav, в конце за ним поместить данные из переменной JS, преобразовав их перед этим в числа. После этого подключить этот массив в качестве src к элементу аудио так:
audio.src = URL.createObjectURL(new Blob([myArray], {type: audio/wav}));

Nexus 31.10.2020 13:20

Цитата:

Сообщение от Опан1
Лучше всего создать численный типизированный массив, скажем myArray и поместить в него стандартный заголовок аудиофайла формата wav, в конце за ним поместить данные из переменной JS

И чем же это лучше?

// мой вариант
new Audio('/api/get-wav').addEventListener('canplaythrough', function () { this.play(); });

// ваш вариант
fetch('/api/get-wav').then(res => res.blob()).then(blob => {
    const audio = new Audio( URL.createObjectURL(blob) );
    audio.addEventListener('canplaythrough', () => audio.play());
});

Опан1 31.10.2020 15:32

Лучше тем, что без этого заголовка получатся сырые данные, т. е., будет неизвестно, как их следует читать - с какой частотой семплированья, с каким количеством стереоканалов, сколько байт приходится на семпл и т. п. Хот может я забыл предусмотреть, что в переменной js уже есть этот заголовок, но трудно это представить, если там может всё быть в формате string. А если требуется вывести сообщение об окончании воспроизведения, а то для этого подходит событие onended, т. к., canplaythgrough используют для вывода сообщения, что аудио/видео будет воспроизводиться до конца без остановки.

Nexus 31.10.2020 16:11

Цитата:

Сообщение от Опан1
Лучше тем, что без этого заголовка получатся сырые данные

:-?

Цитата:

Сообщение от Опан1
если там может всё быть в формате string

Вы уперлись в то, что у вас данные аудиофайла находятся в переменной, однако у автора пока-что вообще никаких данных нет, т.к. он не смог ответить на вопрос какой тип данных находится в переменной.

Т.е. чтобы имплементировать воспроизведения аудио нужно будет, в любом случае, получить этот аудиофайл от сервера.
Таки вопрос: нахрена нам что-то вручную делать, если можно просто скормить классу Audio ссылку на аудиофайл?

Опан1 31.10.2020 18:41

Nexus,
так было бы просто сделать, если бы этот аудиофайл был статическим. Но изложенная ситуация допускает, что это должен быть не постоянный, а сгенерированный аудиофайл, и возможно с каждым новым ajax-запросом его содержимое должно быть каким-то другим. Т. е., должен каждый раз быть новый звук. Я сам с этим имел дело. Поэтому изложил способ, которым я пользовался чтобы подключить эти сгенерированные данные к элементу аудио, чтобы не приходилось создавать временный файл на жёстком диске.

voraa 31.10.2020 18:48

Ну если его надо один раз получить, проиграть и забыть, то без разницы каким способом.
Другое дело, если его надо получать и проигрывать. Может быть даже используя полноценный элемент <audio>, с возможностью двигать таймкоды. Тогда, конечно в БЛОБ сохранять.

him 02.11.2020 14:00

Пробую отдавать файл на стороне сервера:

header('Content-type: application/octet-stream');
header('Content-Length: '.filesize('wave.wav'));
header('Content-Disposition: attachment; filename="wave.wav"');
readfile('wave.wav');


Смотрю что пересывается. Содержимое похоже на формат wave файла.
Подправляю для случая с переменой:

header('Content-type: application/octet-stream');
header('Content-Length: '.mb_strlen($rawWave, '8bit'));


Смотрю что пересылается. Что прям непонятно что.
Что не так я делаю? Как правильно отправлять данные ?

Опан1 02.11.2020 16:45

Видимо без исследования в шестнадцатеричном редакторе содержимого того, что получается не обойтись. Почему Вы просто не подключаете этот файл к аудио:
<audio src="wave.wav"></audio>

Nexus 02.11.2020 16:46

Такого кода должно быть достаточно, другое дело, что Chrome не воспроизводит wav-файлы.
header('Content-Type: audio/wav');
header('Content-Length: ' . mb_strlen($rawWave, '8bit'));
header('Content-Disposition: attachment; filename="wave.wav"');

echo $rawWave;

him 09.11.2020 17:05

echo $rawWave;

Да помогло, что то не сообразил.


Данные получаю ответом на запрос:
...
    success: function(data) {
    }
...


Это по сути прочитанный файл в переменную.

Как запустить эти данные на воспроизведения, я что то так и не понял до конца,
если вообще такое возможно.

audio = new Audio('/api/get-wav').addEventListener('canplaythrough', function () { this.play(); });
	  audio.src = URL.createObjectURL(new Blob([data], {type: audio/wav}));
    }


Но нужна тогда ссылка /api/get-wav а ее генерить бы не хотелось бы.

him 10.11.2020 11:50

Удалось запустить воспроизведение двумя способами:
var audio = new Audio();
audio.src = URL.createObjectURL(new Blob([data], {type: "audio/x-wav"}));
audio.play();


var wavString = data;
var audio = new Audio();
var len = wavString.length;
var buf = new ArrayBuffer(len);
var view = new Uint8Array(buf);
for (var i = 0; i < len; i++) {
	view[i] = wavString.charCodeAt(i) & 0xff;
}
var blob = new Blob([view], {type: "audio/x-wav"});
audio.src = URL.createObjectURL(blob);
audio.play();


Оба варианта портят звук. Второй вариант портит меньше.
В чем может быть причина ??

him 10.11.2020 15:30

Что бы не портились данные пришлось закодироваться. :)
Но возникла другая проблема, где то обрезаются по длине.

toFile("len.txt", strlen($rawWave));
echo base64_encode($rawWave);


success: function(data) {
var decodeWave = atob(data);
var len = decodeWave.length;
console.log(len);


Получаю:
95384
71538
Куда могли деться 23846 ??

Nexus 10.11.2020 15:38

him, покажите код, который отправляет запрос на сервер и получает содержимое вашего wav-файла.

Nexus 10.11.2020 15:42

Цитата:

Сообщение от him
Получаю:
95384
71538
Куда могли деться 23846 ??


https://wtools.io/php-sandbox/b22Y
<?php
    $string = '������';
    
    var_dump([
        strlen($string), // 4
        mb_strlen($string, 'UTF-8') // 1
    ]);


alert('������'.length);// 2

him 11.11.2020 12:35

header('Content-type: audio/x-wav');
header('Content-Transfer-Encoding: binary');
header('Content-Length: '.mb_strlen($rawWave, '8bit'));

toFile("len.txt", strlen($rawWave));              // 95384
toFile("len2.txt", mb_strlen($rawWave, '8bit'));  // 95384
echo base64_encode($rawWave);


$.ajax({
type: "POST",
..
success: function(data) {
var decodeWave = atob(data);

var len = decodeWave.length;  // 71538
console.log(len);



Как то так ..

him 20.11.2020 09:35

Вобщем, на обычном компьютере все работает. Но радость моя была не долгой.
На iPad-е аудио не проигрывается.
Лазил в интернете. Своя система безопасности (статья от 2012 года). Нужно пользователю что то нажать. Ну у меня вроде нажимает.
Да, у меня нету тега <audio> в html документе. Но он вроде не обязательный.
В чем может быть проблема еще ?
<form>
<input type="button" id="But" value="Проиграть">
</form>
...
$(document).on('click', '#But', function(e) {
	...
	$.ajax({
	..
	success: function(data) {
		var audio = new Audio();
		...
		var blob = new Blob([view], {type: "audio/wav"});
		audio.src = URL.createObjectURL(blob);
		audio.addEventListener('ended', function () { console.log(" ended !! ");});
		audio.play();
		...

Nexus 20.11.2020 12:10

Цитата:

Сообщение от him
В чем может быть проблема еще ?

Проблема, почти уверен, в том, что после нажатия пользователем на кнопку вы сначала отправляете запрос (что требует какого-то времени) и только после этого пытаетесь воспроизвести audio.

Попробуйте предзагружать аудио и воспроизводить его сразу после нажатия пользователем на кнопку.

laimas 20.11.2020 13:50

Цитата:

Сообщение от him
mb_strlen($rawWave, '8bit')

То есть, бинарные данные, это как пить дать в UTF будут? :)

him 20.11.2020 13:56

Цитата:

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

Цитата:

То есть, бинарные данные, это как пить дать в UTF будут?
Наверно. Посути содержимое файла wave.

voraa 20.11.2020 14:02

Цитата:

Сообщение от him
Нужно пользователю что то нажать. Ну у меня вроде нажимает.

Разные браузеры по разному могут трактовать, что play запускается после события вызванного действием пользователя.
Если запустить непосредственно в обработчике 'click' - это одно.
Но вы запускаете уже в обработчике другого события - какого то события XMLHttpRequest.

Надо бы проверить, может быть на fetch они среагируют более благосклонно.

laimas 20.11.2020 14:05

Цитата:

Сообщение от him
Посути содержимое файла wave

Содержимое метаданных файла трактовать будет программа, а доселе вы оперируете бинарными данными, откуда в них UTF, у вас функции и для ascii, и для utf показывают одну и туже длину.

PS. Да и это может относится к mp3, к примеру, но wav таких не содержит.

him 24.11.2020 11:31

Цитата:

Проблема, почти уверен, в том, что после нажатия пользователем на кнопку вы сначала отправляете запрос (что требует какого-то времени) и только после этого пытаетесь воспроизвести audio.
Сократил до 66мс - не помогло.

Цитата:

Надо бы проверить, может быть на fetch они среагируют более благосклонно.
Почитал. Надо переделывать код. Если ничего не выйдет, буду переделывать.

Хорошо. Коментирую строку audio.play();
Добавляю в форму дополнительную кнопку "Проиграть"

Но как это сделать в коде ?
Просто добавить:
$(document).on('click', '#play', function(e) {
	audio.play();
});

не получится. Пропадут
var audio = new Audio();
audio.src = URL.createObjectURL(blob);


Часовой пояс GMT +3, время: 06:34.