Извлечение фрагмента аудио из URL и проигрывание его с помощью чистого Web Audio API - PullRequest
0 голосов
/ 26 сентября 2018

По следующему адресу:

https://www.tophtml.com/snl/15.mp3

есть один звук, который я хочу воспроизвести с использованием чистого Web Audio API на following range:

range from: second: 306.6
  range to: second: 311.8
     total: 5.2 seconds

Я скачал этот файл на рабочий стол (я использую Windows 10), затем открыл его с помощью VLC и получил следующую информацию о файле:

enter image description here

number of channels: 2
       sample rate: 44100 Hz
   bits per sample: 32 (float32)

Здесь у вас есть информация о понятиях по этому вопросу:

https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API#Audio_buffers_frames_samples_and_channels

, откуда я получил следующий отрывок:

enter image description here

Я хочу сыграть range с комментариями выше (также вставив его здесь):

range from: second: 306.6
  range to: second: 311.8
     total: 5.2 seconds

, загрузив только тот фрагмент с сервера, который поддерживает заголовок запроса:Range.

Тогда я попробовал следующий код:

...
let num_channels    = 2;
let sample_rate     = 44100;
let range_from      = 0;                                    // Goal: 306.6 seconds
let range_length    = (sample_rate / num_channels) * 5.2;   // Goal:   5.2 seconds
let range_to        = range_from + (range_length - 1);      // "range_to" is inclusive (confirmed)
request.setRequestHeader("Range", "bytes=" + range_from + "-" + range_to);
...

Мои вопросы:

  1. Мне нужночтобы найти правильное значение для переменной: range_from, чтобы она начала воспроизводиться с секунды: 306.6.

  2. Я хочу знать, является ли указанное выше значение для: range_length правильнымили нет, так как, вероятно, есть байты, используемые для headers и т. д., я имею в виду: headers + data.

Здесь у вас есть код, который у меня есть:

window.AudioContext = window.AudioContext || window.webkitAudioContext; // necessary for iPhone (maybe others). Could change a near future.

const URL = 'https://www.tophtml.com/snl/15.mp3';
const context = new AudioContext();

window.addEventListener('load', function() {

	const button_option_1			= document.querySelector('.button_option_1');
	const button_option_1_play		= document.querySelector('.button_option_1_play');
	button_option_1_play.disabled	= true;

	button_option_1.addEventListener('click', async function() {
		let time_start, duration;
		let buffer;
		log('...', false);
		button_option_1_play.disabled = true;
		button_option_1_play.onclick = () => playBuffer(buffer);
		//---
		time_start = new Date().getTime();
		let arrayBuffer = await fetch(URL);
		// download complete
		duration = sprintf('%.2fs', (new Date().getTime()-time_start)/1000);
		log(sprintf('P2. Delay: +%s for download. Wait...', duration));
		//---
		time_start = new Date().getTime();		
		let audioBuffer = await decodeAudioData(context, arrayBuffer);
		// decoding complete
		duration = sprintf('%.2fs', (new Date().getTime()-time_start)/1000);
		log(sprintf('P3. Delay: +%s for decoding.', duration));
		//---
		button_option_1_play.disabled = false;
		buffer = audioBuffer;
		button_option_1_play.click();
	});

});
function playBuffer(buffer, from, duration) {
	const source = context.createBufferSource(); // type of "source": "AudioBufferSourceNode"
	source.buffer = buffer;
	source.connect(context.destination);
	source.start(context.currentTime, from, duration);
}
function log(text, append = true) {
	let log = document.querySelector('.log');
	if (!append)
		log.innerHTML = '';
	let entry = document.createElement('div');
	entry.innerHTML = text;
	log.appendChild(entry);
}
function decodeAudioData(context, arrayBuffer) {
	return new Promise(async (resolve, reject) => {
		if (false) {}
		else if (context.decodeAudioData.length == 1) {
			// console.log('decodeAudioData / Way 1');
			let audioBuffer = await context.decodeAudioData(arrayBuffer);
			resolve(audioBuffer);
		}
		else if (context.decodeAudioData.length == 2) {
			// necessary for iPhone (Safari, Chrome) and Mac (Safari). Could change a near future.
			// console.log('decodeAudioData / Way 2');
			context.decodeAudioData(arrayBuffer, function onSuccess(audioBuffer) {
				resolve(audioBuffer);
			});
		}
	});
}
function fetch(url) {
	return new Promise((resolve, reject) => {
		var request = new XMLHttpRequest();
		request.open('GET', url, true);
		request.responseType = 'arraybuffer';
		let num_channels	= 2;
		let sample_rate		= 44100;
		let range_from		= 0;									// Goal: 306.6 seconds
		let range_length	= (sample_rate / num_channels) * 5.2;	// Goal:   5.2 seconds
		let range_to		= range_from + (range_length - 1);		// "range_to" is inclusive (confirmed)
		request.setRequestHeader("Range", "bytes=" + range_from + "-" + range_to);
		request.onload = function() {
			let arrayBuffer = request.response;
			let byteArray = new Uint8Array(arrayBuffer);
			// console.log(Array.from(byteArray)); // just logging info
			resolve(arrayBuffer);
		}
		request.send();
	});
}
.log {
	display: inline-block;
	font-family: "Courier New", Courier, monospace;
	font-size: 13px;
	margin-top: 10px;
	padding: 4px;	
	background-color: #d4e4ff;
}
.divider {
	border-top: 1px solid #ccc;
	margin: 10px 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/sprintf/1.1.1/sprintf.min.js"></script>

<button class="button_option_1">Option 1</button>
<button class="button_option_1_play">Play</button><br />
<div class="log">[empty]</div>

Здесь у вас есть соответствующие CodePen.io:

https://codepen.io/anon/pen/RYXKmP

Не могли бы вы предоставитьправильное значение для: range_from и используйте его для разветвленного кода на CodePen.io? ​​

Связанный вопрос: https://engineering.stackexchange.com/questions/23929

[EDIT 1]

Вот более простой CodePen.io: https://codepen.io/anon/pen/YJKVde,, который сфокусирован на проверке способности браузера перемещаться при заданной случайной позиции к следующему действительному кадру.

Набыстрый эксперимент, который я сделал, используя комбинации { Windows 10, Android, iPhone } x { Native browser, Chrome, Firefox }, код выше справа работает только на: { (Windows 10, Chrome), (Android, Chrome), (Android, Native browser) }.

Жаль, что он не работает:

{ (iPhone, Safari), (iPhone, Chrome), (Windows 10, Firefox), (Android, Firefox) }

есть ли способ отправить запрос разработчикам браузера, чтобы обратить на это внимание?

Google Chrome действительно хорошо работает на Windows 10 и Android.

Было бы интересночто остальные браузеры делают то же самое.

Спасибо!

1 Ответ

0 голосов
/ 26 сентября 2018

Длина кадра (сек) = частота кадров / частота дискретизации, которая составляет 38,28 кадра / сек.

Длина кадра (байт) = 144 * битрейт / частота дискретизации

Итак, ваш выбор () теперь должно работать (я тоже изменил длину диапазона):

function fetch(url) {
  return new Promise((resolve, reject) => {
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.responseType = 'arraybuffer';
    let num_channels    = 2;
    let bitrate         = 192000;
    let sample_rate     = 44100;
    let byte_per_sec    = 144 * (bitrate/sample_rate) * 38.28;
    let range_from      = Math.floor(byte_per_sec * 306.6);
    let range_length    = Math.floor(byte_per_sec * 5.2);
    let range_to        = range_from + (range_length - 1);
    request.setRequestHeader("Range", "bytes=" + range_from + "-" + range_to);
    request.onload = function() {
        let arrayBuffer = request.response;
        let byteArray = new Uint8Array(arrayBuffer);
        //******************
            for ( let i = 0; i < byteArray.length; i += 1 ) {
                if (( byteArray[i] === 0b11111111 ) && ( byteArray[ i + 1 ] & 0b11110000 ) === 0b11110000 ){
                    log('we have a winner! Frame header at:'+i, true);
                    console.log((parseInt(byteArray[i], 10)).toString(2)); //frame header 4 bytes
                    console.log((parseInt(byteArray[i+1], 10)).toString(2));
                    console.log((parseInt(byteArray[i+2], 10)).toString(2));
                    console.log((parseInt(byteArray[i+3], 10)).toString(2));
                    resolve(arrayBuffer.slice(i));
                    break;
                }
            }
        //******************
    }
    request.send();
  });
}

EDIT Я добавил базовый поиск по заголовку фрейма, и, черт возьми, даже старая лиса это съела.Для стабильного решения вам нужно проанализировать заголовок файла, чтобы получить метаданные, чтобы сравнить их с данными заголовка кадра.И делать что-то, когда заголовок не найден и ... ...

...