Last active
October 3, 2019 09:18
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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