On the following url:
https://www.tophtml.com/snl/15.mp3
there is one audio I want to play using pure Web Audio API
on the following range
:
range from: second: 306.6
range to: second: 311.8
total: 5.2 seconds
I downloaded that file to my desktop (I'm using Windows 10
), then opened it with VLC
and got the following file info:
number of channels: 2
sample rate: 44100 Hz
bits per sample: 32 (float32)
Here you have info about concepts on this:
from where I got the following excerpt:
I want to play the range
commented above (also pasting it here):
range from: second: 306.6
range to: second: 311.8
total: 5.2 seconds
by downloading just that fragment from the server, which supports the request header: Range
.
Then I tried the following code:
...
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);
...
My questions are:
I need to find the right value for variable:
range_from
so it starts playing from second:306.6
.I want to know if the value specified above for:
range_length
is correct or not since probably there are bytes used for headers, etc., I mean:headers
+data
.
Here you have the code I have so far:
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>
Here you have the corresponding CodePen.io
:
https://codepen.io/anon/pen/RYXKmP
Could you please, provide the right value for: range_from
and use it on a forked code on CodePen.io
?
Related question: https://engineering.stackexchange.com/questions/23929
[EDIT 1]
Here is a simpler CodePen.io
: https://codepen.io/anon/pen/YJKVde, which is focused on check the ability of the browser to move, given a random position, to the next valid frame.
On a quick experiment I did, using combinations of { Windows 10, Android, iPhone } x { Native browser, Chrome, Firefox }
, the right above code only works on: { (Windows 10, Chrome), (Android, Chrome), (Android, Native browser) }
.
It's a pity it doesn't work on:
{ (iPhone, Safari), (iPhone, Chrome), (Windows 10, Firefox), (Android, Firefox) }
Is there a way we can submit a request to the browser developers to pay attention to this?
Google Chrome
is doing really well on Windows 10
and Android
.
It would be interesting that the rest of the browsers do the same.
Thanks!
Frame length (sec) = frame samples / sample rate which makes 38.28 frames/sec.
Fram length (byte) = 144*bitrate/sample rate
So, your fetch() should work now (I changed range length too):
EDIT I added basic frame header search and, my'o'my, even old fox eats that. For a stabile solution you'll have to parse file header to get metadata, to compare that against frame header data. And do something when header is not found and... ...