Built up this cool little feature the other day for a project of mine. I'm going to go through how I got audio files stored on Amazon's S3 to stream down from on high and play in the browser.
If you're only here for the code, here you go.
If you don't have an Amazon Web Service account, you're going to need to head over there to get some creds.
This S3 streamer will be split up into three files along with a loading GIF so users don't get impatient.
- index.html - server.js - config.js
Server Set Up
First, we'll whip up an express server to handle two routes. One route will serve up index.html
and the other will stream our S3 sounds.
var express = require('express');
var app = express();
// Serve up index.html
app.use('/', express.static(__dirname));
app.get('/audio', function(req, res) {
// Add s3 streaming in here
});
// Start Listenin
app.listen(3000, function() {
console.log('makin music on 3000');
});
Next, we'll have to configure an S3 client. I found the s3 npm module to be really easy to use. It lets you set up a client, upload and download files without too much extra work. That said, it's a pretty high level module and if you need finer control over what's going on up there, check out (knox)[https://www.npmjs.com/package/knox] or the official Amazon module aws-sdk.
Make sure and install s3
then require it in server.js
.
npm install s3
S3 will need to check out credentials first.
var s3 = require('s3');
//...
// Set up s3 credentials
var client = s3.createClient({
s3Options: {
accessKeyId: <YOUR ACCESS KEY>,
secretAccessKey: <YOUR SECRET KEY>
}
});
That done, we'll fill in our route. To stream audio, s3
includes a downloadStream
method which will create a read stream pointed at a file stored on S3 and then pipe it down to the client response.
To tell s3
which file we want to download, we need to pass it the name of the S3 Bucket it is stored in as well as its Key value. I have an mp3 file cleverly named test.mp3
stored in a bucket named New-Bucket-1020
. Of course, yours can be named whatever your heart desires. We'll save the bucket and key information in a params
object and pass that to the downloadStream
method.
We'll attach two event listeners onto the download stream. One to check for errors and send a 404 if the files doesn't exist, and the other to listen for incoming headers. When the headers come in, we'll send that information down to the client so it knows what the hell is going on.
Finally, we'll pipe downloadStream
to the response to get the tunes flowing.
app.get('/audio', function(req, res) {
var params = {
Bucket: 'New-Bucket-1020',
Key: 'test.mp3'
};
var downloadStream = client.downloadStream(params);
downloadStream.on('error', function() {
res.status(404).send('Not Found');
});
downloadStream.on('httpHeaders', function(statusCode, headers, resp) {
// Set Headers
res.set({
'Content-Type': headers['content-type']
});
});
// Pipe download stream to response
downloadStream.pipe(res);
});
Set Up the Client
On index.html
we'll render two buttons, play
and stop
, which will do exactly what you hope they would.
We'll include a script that will start by grabbing the browser's AudioContext so we can get access to the speakers.
The play
button will call the playTunes
function which will trigger an ajax request to our server which will start the audio stream. When the data comes through, we'll put it through a helper function, process
which will do most of our work. The process
function will create a buffer source node, decode the Data from our stream, plug it into the user's speakers, and tell the source to start playing.
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext();
// ...
function playTunes() {
var request = new XMLHttpRequest();
request.open("GET", "http://localhost:3000/audio/", true);
request.responseType = "arraybuffer";
spinner.show();
request.onload = function() {
spinner.hide();
var Data = request.response;
process(Data);
};
request.send();
}
function process(Data) {
source = context.createBufferSource(); // Create Sound Source
context.decodeAudioData(Data, function(buffer) {
source.buffer = buffer;
source.connect(context.destination);
source.start(context.currentTime);
});
}
Lastly, we'll add a function to stop the music, and some logic to show/hide a spinner gif while we're waiting for the stream to come on down from the server.
var spinner = document.getElementById('spinner');
spinner.hide = function(){
this.style.display = 'none';
};
spinner.show = function(){
this.style.display = 'block';
}
function stopTunes(){
if(source.stop){
source.stop();
}
}
Our HTML will look like this.
<!DOCTYPE html>
<html>
<head>
<title>S3 Audio</title>
</head>
<body>
<button onclick="playTunes()">Play</button>
<button onclick="stopTunes()">Stop</button>
<div>
<img id="spinner" style="display:none" src="ajax-loader.gif" alt="">
</div>
<script> // app logic </script>
</body>
</html>
So there it is! A quick way to stream audio samples down from S3. Full code is right here.