Skip to content

Instantly share code, notes, and snippets.

@iislucas
Last active October 3, 2019 09:18
Show Gist options
  • Save iislucas/d05875216d96bc01632c97448c14898f to your computer and use it in GitHub Desktop.
Save iislucas/d05875216d96bc01632c97448c14898f to your computer and use it in GitHub Desktop.
A very simple audio file and URL player, useful for playing gong sounds in a loop but with a time gap between plays.
<html>
<head>
<title>Simple Web Audio Player</title>
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0' />
<style>
body {
background-color: white;
font-family: sans-serif;
font-size: 16px;
line-height: 1.6;
margin: 10px;
display: flex;
justify-content: center;
}
h1 {
font-size: 1.5em;
}
input {
font-family: sans-serif;
font-size: 14px;
line-height: 1.6;
border: 1px solid #CCC;
}
.main {
max-width: 600px;
min-width: 300px;
margin: 0px;
padding: 20px;
border: 1px solid #CCC;
background: #FFF;
}
#errorMessage {
color: #900;
}
/* Mobile Styles */
@media only screen and (max-width: 400px) {
body {
background-color: #F09A9D; /* Red */
}
}
/* Tablet Styles */
@media only screen and (min-width: 401px) and (max-width: 960px) {
body {
background-color: #F5CF8E; /* Yellow */
}
}
/* Desktop Styles */
@media only screen and (min-width: 961px) {
body {
background-color: #B2D6FF; /* Blue */
}
}
</style>
</head>
<body>
<div class="main">
<h1>Simple Web Audio Player</h1>
<p>
<label for="audioFileChooser">Upload an audio file (e.g. <a href="https://storage.googleapis.com/gong.zxd.fr/sounds/Metal_Gong-Dianakc-109711828.mp3">gong</a>): </label>
<input id="audioFileChooser" type="file" onchange="readFile(this.files);" /> <br>
or load from url <input id="urlToLoadFrom" name="URL to load from" type="text" value="sounds/Metal_Gong-Dianakc-109711828.mp3"/>
<input id="loadUrlButton" type="button" onclick="loadFromUrl();" value="Load & play" />
<div id="errorMessage" hidden="true"></div>
</p>
<p>
Loop every <input id="frequencyText" name="Frequency" type="text" /> (seconds)
<div id="controls" hidden="true">
<div id="soundFileDetails"></div>
<input id="playButton" type="button" onclick="play();" value="Play on Loop" />
<input id="stopButton" type="button" onclick="stop();" value="Stop looping" disabled="true" />
<input id="resetButton" type="button" onclick="reset();" value="Reset" />
</div>
</p>
</div>
</body>
<script>
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint16Array(buf));
}
function str2ab(str) {
if (str === null) {
return null;
}
let buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
let bufView = new Uint16Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
let sound = null;
let playing = false;
let soundDataBuffer;
let soundFileDetails = localStorage.getItem('soundFileDetails');
let frequencyText = localStorage.getItem('frequencyText');
let fileBuffer = str2ab(localStorage.getItem('fileBuffer'));
document.getElementById('frequencyText').value = frequencyText || '60';
document.getElementById('soundFileDetails').innerText = soundFileDetails;
document.getElementById('controls').hidden = (fileBuffer === null);
console.log('fileBuffer', fileBuffer);
function readFile(files) {
document.getElementById('errorMessage').hidden = true;
document.getElementById('controls').hidden = true;
delete fileBuffer;
let fileReader = new FileReader();
fileReader.readAsArrayBuffer(files[0]);
fileReader.onload = (e) => {
fileBuffer = e.target.result;
console.log('file', fileBuffer);
soundFileDetails = ("Filename: '" + files[0].name + "'"),
("(" + ((Math.floor(files[0].size / 1024 / 1024 * 100)) / 100) + " MB)");
localStorage.setItem('fileBuffer', ab2str(fileBuffer));
localStorage.setItem('soundFileDetails', soundFileDetails);
document.getElementById('soundFileDetails').innerText = soundFileDetails;
document.getElementById('controls').hidden = false;
playAudioFile();
}
}
function loadFromUrl() {
document.getElementById('errorMessage').hidden = true;
document.getElementById('controls').hidden = true;
let url = document.getElementById('urlToLoadFrom').value;
let request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
request.onload = function() {
if (request.status !== 200) {
document.getElementById('errorMessage').innerText = request.statusText;
document.getElementById('errorMessage').hidden = false;
return;
}
document.getElementById('errorMessage').hidden = true;
console.log(request);
fileBuffer = request.response;
soundFileDetails = ("Filename: '" + request.responseURL + "'"),
("(" + ((Math.floor(fileBuffer.size / 1024 / 1024 * 100)) / 100) + " MB)");
localStorage.setItem('fileBuffer', ab2str(fileBuffer));
localStorage.setItem('soundFileDetails', soundFileDetails);
document.getElementById('soundFileDetails').innerText = soundFileDetails;
document.getElementById('controls').hidden = false;
playAudioFile();
}
request.send();
}
function reset() {
stop();
localStorage.clear();
delete fileBuffer;
delete soundFileDetails;
delete frequencyText;
document.getElementById('frequencyText').value = '60';
document.getElementById('soundFileDetails').innerText = '';
document.getElementById('controls').hidden = true;
}
function stop() {
playing = false;
document.getElementById('playButton').disabled = false;
document.getElementById('stopButton').disabled = true;
}
function play(frequencyOverride) {
if (playing) {
return true;
}
playing = true;
document.getElementById('playButton').disabled = true;
document.getElementById('stopButton').disabled = false;
playAudioFile(frequencyOverride);
scheduleNextPlay();
}
function scheduleNextPlay() {
if(!playing) {
return;
}
let frequency = parseInt(document.getElementById('frequencyText').value);
// frequency > 0 implies parsing worked, and frequency !== NaN
if (frequency > 0) {
const timeout = setTimeout(() => {
if (!playing) { return; }
playAudioFile();
scheduleNextPlay();
},
frequency * 1000);
console.log(timeout);
}
}
async function playAudioFile(frequencyOverride) {
let context = new window.AudioContext();
// Need to cache the soundDataBuffer because the decodeAudioData operation is
// destructive on the fileBuffer object.
if (!soundDataBuffer) {
soundDataBuffer = await context.decodeAudioData(fileBuffer);
}
let soundsSource = context.createBufferSource();
soundsSource.buffer = soundDataBuffer;
soundsSource.loop = false;
soundsSource.connect(context.destination);
soundsSource.start(0);
console.log('playing at:', Date.now());
}
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment