Socket.IO 1.0 gives us the ability to stream binary data between the server and the client. We will use this ability to transport various forms of binary data, such as images, audio, and video.

Broadcasting an image to other sockets

Typically, the src attribute for an HTML image tag will be a link to the location of the image. However, instead of a link to the image, we can actually provide the binary data for the image itself. This ability allows us to store and load actual images and not just the link to the image location.

We can actually use Socket.IO to send images from a browser to a server and then display them in another browser without ever storing them on a server, in a filesystem, or in a database of any kind. In instances where we don't need the data to be stored, this can be really useful.

Simple code for express

var express = require('express'), 
    app = express(), 
    http = require('http'), 
    socketIO = require('socket.io'), 
    fs = require('fs'), 
    path = require('path'), 
    server, 
    io; 

app.get('/', function (req, res) { 
 res.sendFile(__dirname + '/index.html'); 
});  

server = http.Server(app); 
server.listen(5000);  
io = socketIO(server);  
io.on('connection', function (socket) { 
    var readStream = fs.createReadStream(path.resolve(__dirname, './woodchuck.jpg'), { encoding: 'binary'     }), chunks = []; 

    readStream.on('readable', function () { 
    	console.log('Image loading');     
    });  

    readStream.on('data', function (chunk) { 
    	chunks.push(chunk); 
    	socket.emit('img-chunk', chunk);     
    });  

    readStream.on('end', function () { 
    	console.log('Image loaded');     
    }); 
});

Now, we need to create an index.html page to render the image in pieces as data is chunked in: just focus on the socket part..

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>
	<img id="img-stream2" src="" />
    <scriptsrc="/socket.io/socket.io.js"></script>
    <script type="text/javascript">
    var socket = io.connect('http://localhost:5000');

    varimgChunks = [];

    socket.on('img-chunk', function (chunk) {
        var img = document.getElementById('img-stream2');
        imgChunks.push(chunk);
        img.setAttribute('src', 'data:image/jpeg;base64,' + window.btoa(imgChunks));
    });
    </script>
</body>
</html>

When a user connects to our server, we immediately begin to read the contents of the image file using a read stream. This allows us to emit parts of the image as they are read instead of emitting it all at once. This means the image will load progressively as the data is chunked.

The opposite is also possible, to upload an image to your server using Socket.io than normal HTTP POST.

Sample server.js(express)

var express = require('express'),
app = express(),
http = require('http'),
socketIO = require('socket.io'),
fs = require('fs'),
path = require('path'),
server, io;

app.use(express.static(__dirname));

server = http.Server(app);
server.listen(5000);

console.log('Listening on port 5000');

io = socketIO(server);

io.on('connection', function (socket) {
socket.on('upload-image', function (message) {

var writer = fs.createWriteStream(path.resolve(__dirname, './tmp/' + message.name), {
encoding: 'base64'
        });


writer.write(message.data);
writer.end();

writer.on('finish', function () {

socket.emit('image-uploaded', {
name: '/tmp/' + message.name
            });

        });

    });
});

Then in your Frontend, i.e index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>

<hr />
<input type="file" id="my-file" />
<hr />

<scriptsrc="/socket.io/socket.io.js"></script>
<script type="text/javascript">
var socket = io.connect('http://localhost:5000');

var file = document.getElementById('my-file');

file.addEventListener('change', function () {
if (!file.files.length) {
return;
            }

var firstFile = file.files[0],
reader = new FileReader();

reader.onloadend = function () {
socket.emit('upload-image', {
name: firstFile.name,
data: reader.result
                });
            };

reader.readAsArrayBuffer(firstFile);
        });

socket.on('image-uploaded', function (message) {
varimg = document.createElement('img');
img.setAttribute('src', message.name);
img.setAttribute('height', '100px');
document.body.appendChild(img);
        });
</script>
</body>
</html>

Now, if you start your server and go to localhost:5000/index.html, you should see an input field for uploading a file to the server. Just choose a file and it will upload to your /tmp directory and display the image

Socket.IO can pass any kind of data, including binary file data. Our client-side JavaScript allows us to access the file using the FileReader() API. We can pass the data we extract from the FileReader to the server and let the server write the file. When the file is added to the filesystem, the server emits a message to let us know that the upload is complete. At that point, we can display the newly-uploaded file on the client.

Uploading to AWS

Uploading to aws for frontend is as uploading to your server as shown in index.html file above, you can make changes to server.js as well as creating new aws file for uploading received files from your server to aws

The aws-service.js file. You must have AWS credentials as well as install aws-sdk npm module via npm install aws-sdk

var AWS = require('aws-sdk'),
    _ = require('lodash'),
q = require('q');

var service = {}, s3;

var AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID,
    AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY,
    AWS_BUCKET_NAME = process.env.AWS_BUCKET_NAME,
    AWS_BUCKET_PATH = process.env.AWS_BUCKET_PATH;

AWS.config.update({
accessKeyId: AWS_ACCESS_KEY_ID,
secretAccessKey: AWS_SECRET_ACCESS_KEY
});

s3 = new AWS.S3();

function write (path, file) {
vardeffered = q.defer();

s3.putObject({
        Bucket: AWS_BUCKET_NAME,
        Key: AWS_BUCKET_PATH + '/' + path,
        Body: file
    }, function (err, data) {
deffered.resolve(data || err);
    });

returndeffered.promise;
}

functionreadFile (path) {
vardeffered = q.defer();

path = path.replace(AWS_BUCKET_PATH + '/', '');

s3.getObject({
        Bucket: AWS_BUCKET_NAME,
        Key: AWS_BUCKET_PATH + '/' + path
    }, function (err, data) {
if (!data) {
deffered.resolve(err);
        } else {
deffered.resolve(_.extend(data, {
path: path
            }));
        }
    });

returndeffered.promise;
}

function read (path) {
vardeffered = q.defer();

readFile(path).then(function (data) {
if (data.Body) {
varbuf = new Buffer(data.Body);
deffered.resolve(buf.toString());
        } else {
deffered.resolve(null);
        }
    });

returndeffered.promise;
}

module.exports = {
write: write,
read: read,
readFile: readFile
};

Now, in server.js. The server.js file will be responsible for listening for Socket.IO events and uploading the images as they come in via the AWS service that we created. After each file is uploaded, we will read it as a base64-encoded string and emit the result back to the client so that the client can display the image

var express = require('express'),
app = express(),
http = require('http'),
socketIO = require('socket.io'),
fs = require('fs'),
path = require('path'),
aws = require('./aws.service'),
server, io;

app.use(express.static(__dirname));

app.get('/', function (req, res) {
res.sendFile(__dirname + '/index.html');
});

server = http.Server(app);
server.listen(5000);

console.log('Listening on port 5000');

io = socketIO(server);

io.on('connection', function (socket) {
socket.on('upload-image', function (message) {
var path = 'socketio/' + message.name;

aws.write(path, message.data).then(function (response) {
return aws.readFile(path);
        }).then(function (response) {

var base64 = response.Body.toString('base64');

socket.emit('image-uploaded', {
name: 'data:image/jpeg;base64,' +  base64
            });

        });

    });
});

When we upload binary image data to the server with Socket.IO, we call the aws.write() function to save data to AWS. The service hides most of the business logic involved in writing files to Amazon so that the Socket.IO requests and responses are able to stay slim. This service also makes the AWS reading and writing functions reusable for other endpoints to call.

The AWS SDK provides methods to read, write, listen to, and delete files, so we are able to use these methods in our service to pass files to Amazon. As long as our environmental variables are set correctly and we are sending files to Amazon S3, everything should work.

Streaming audio

Streaming images with Socket.IO is great. However, we can use WebSockets in combination with WebRTC to stream audio from one user's microphone to another.

WebRTC (Web Real-Time Communication) is an API that supports browser-to-browser real-time media sharing for applications such as voice calling, video chat, and peer-to-peer file sharing. WebRTC is still a relatively new technology. While WebRTC has support in most browsers, at the time of writing this book, Internet Explorer and Safari do not yet support it.

For two browsers to directly communicate over WebRTC, there is a handshake process that needs to take place. This means that one client makes an offer containing a description of the offer. The second client must then accept the offer and pass a reciprocal description. When the first client receives the answer to their offer, it must set the remote description that is contained in the offer answer. At that point, both clients have agreed to create a WebRTC connection, and they are free to openly communicate.

Socket.IO is an ideal candidate for sending offers and answers to the offers before the connection is securely established.

First, we will create our server.js file. This file will be responsible for facilitating the connection of two clients such that they can communicate with WebRTC:

var express = require('express'),
app = express(),
http = require('http'),
socketIO = require('socket.io'),
server, io;

app.use(express.static(__dirname));

server = http.Server(app);
server.listen(5000);

console.log('Listening on port 5000');

io = socketIO(server);

io.on('connection', function (socket) {

socket.on('make-offer', function (data) {
socket.broadcast.emit('offer-made', {
offer: data.offer,
socket: socket.id
        });
    });

socket.on('make-answer', function (data) {
socket.to(data.to).emit('answer-made', {
socket: socket.id,
answer: data.answer
        });
    });

});

Now, we will create our sender.html file. This file will simply display a button to broadcast our audio to the other client:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<linkrel="stylesheet" href="/style.css" media="screen" charset="utf-8" />
</head>
<body>
<button id="broadcast" class="play">Broadcast</button>

<scriptsrc="/socket.io/socket.io.js"></script>
<script type="text/javascript" src="/shared.js"></script>
<script type="text/javascript" src="/sender.js"></script>
</body>
</html>

Since we will create two separate pages, we will use a shared.js file to include common variables and functions that both pages can use. This file will mostly allow the WebRTC variables to fall back on browser vendor prefixes, since support for the unprefixed namespaces is still spotty. We will also create our own peer connection to transmit data:

varpeerConnection = window.RTCPeerConnection ||
window.mozRTCPeerConnection ||
window.webkitRTCPeerConnection ||
window.msRTCPeerConnection;

varsessionDescription = window.RTCSessionDescription ||
window.mozRTCSessionDescription ||
window.webkitRTCSessionDescription ||
window.msRTCSessionDescription;

navigator.getUserMedia  = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia;

var socket = io.connect('http://localhost:5000');

var pc = new peerConnection({ iceServers: [{ url: 'stun:stun.services.mozilla.com',
username: 'myuser',
credential: 'mycreds'
 }]
});

function error (err) {
console.warn(err);
}

Now, we will need a sender.js file to pair with our sender.html template. This file will allow the user to click on the Broadcast button to establish a peer-to-peer connection with other clients and stream data once the connection is created:

varanswersFrom = {};

navigator.getUserMedia({ audio: true }, function (stream) {
pc.addStream(stream);
}, error);

functioncreateOffer () {
pc.createOffer(function(offer) {
pc.setLocalDescription(new sessionDescription(offer), function () {
socket.emit('make-offer', {
offer: offer
            });
        }, error);
    }, error);
}

socket.on('answer-made', function (data) {
pc.setRemoteDescription(new sessionDescription(data.answer), function () {
if (!answersFrom[data.socket]) {
createOffer(data.socket);
answersFrom[data.socket] = true;
        }
    }, error);
});

varbtn = document.getElementById('broadcast');
btn.addEventListener('click', function () {
if (btn.getAttribute('class') === 'stop') {
btn.setAttribute('class', 'play');
btn.innerHTML = 'Broadcast';
    } else {
btn.setAttribute('class', 'stop');
btn.innerHTML = 'Broadcasting...';
createOffer();
    }
});

Now, we will create a receiver.html file. Users can go to this file to listen to the stream that will be created in our sender page. The receiver will wait for a connection to be requested by the sender. Then, the user can click on the single button to allow the connection to be created:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<linkrel="stylesheet" href="/style.css" media="screen" charset="utf-8" />
</head>
<body>
<button id="btn" class="muted">No Station...</button>

<scriptsrc="/socket.io/socket.io.js"></script>
<script type="text/javascript" src="/shared.js"></script>
<script type="text/javascript" src="/reciever.js"></script>
</body>
</html>

The receiver.js file will handle listening for offers being created in Socket.IO and allow the user to accept the offer and begin streaming the audio feed from the sender:

varofferData,
player = new Audio(),
btn = document.getElementById('btn');

btn.addEventListener('click', function () {
if (btn.getAttribute('class') === 'play') {
listen();
player.play();
    } else if (btn.getAttribute('class') === 'stop') {
player.pause();
btn.setAttribute('class', 'muted');
btn.innerHTML = 'No Station...';
    }
});

function listen () {
btn.setAttribute('class', 'stop');
btn.innerHTML = 'Listening';

pc.setRemoteDescription(new sessionDescription(offerData.offer), function () {
pc.createAnswer(function (answer) {
pc.setLocalDescription(new sessionDescription(answer), function () {
socket.emit('make-answer', {
answer: answer,
to: offerData.socket
              });
          }, error);
        }, error);
    }, error);
}

pc.onaddstream = function (obj) {
console.log('addStream');
player.src = window.URL.createObjectURL(obj.stream);
};

socket.on('offer-made', function (data) {
btn.setAttribute('class', 'play');
btn.innerHTML = 'Listen';
offerData = data;
});

Finally, we'll add some CSS in a style.css file to change the color of our button as needed:

button {
font-size: 2em;
border: 0px;
color: #FFF;
}

button.play {
background: green;
}

button.stop {
background: red;
}

button.muted {
background: #CCC;
}

Now, we can start our server and go to localhost:5000/sender.html in one browser and localhost:5000/receiver.html in a different browser. When you navigate to the sender.html page, it will prompt you to allow the browser to use your microphone. When you enable the microphone and click on the green button, it will make an offer to establish a WebRTC connection with the other browser.

WebRTC and WebSockets work together really nicely to create a real-time streaming experience. In our example, we used Socket.IO to facilitate the creation of the connection between our clients, and then we let WebRTC take it from there.

The WebRTC API allowed us to generate session descriptions, which we emitted over Socket.IO to authenticate the two browsers with each other. The descriptions are just basic JavaScript objects with some metadata describing the type of connection we are trying to create, so they are easily transported over WebSockets.

Streaming live video

While streaming audio is great, live video is even more gratifying. Using the WebRTC protocol, we can stream video in addition to audio and simply pipe it into an HTML video element instead of an audio element.

First, we need to create a server.js file. This file will be responsible for managing sockets as they join or leave. It will also be responsible for allowing the sockets to connect to one another to initiate a WebRTC session:

var express = require('express'),
app = express(),
http = require('http'),
socketIO = require('socket.io'),
fs = require('fs'),
path = require('path'),
server, io, sockets = [];

app.use(express.static(__dirname));

app.get('/', function (req, res) {
res.sendFile(__dirname + '/index.html');
});

server = http.Server(app);
server.listen(5000);

console.log('Listening on port 5000');

io = socketIO(server);

io.on('connection', function (socket) {

socket.emit('add-users', {
users: sockets
    });

socket.broadcast.emit('add-users', {
users: [socket.id]
    });

socket.on('make-offer', function (data) {
socket.to(data.to).emit('offer-made', {
offer: data.offer,
socket: socket.id
        });
    });

socket.on('make-answer', function (data) {
socket.to(data.to).emit('answer-made', {
socket: socket.id,
answer: data.answer
        });
    });

socket.on('disconnect', function () {
sockets.splice(sockets.indexOf(socket.id), 1);
io.emit('remove-user', socket.id);
    });

sockets.push(socket.id);

});

Now, we will create the index.html template to display our client-side code

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<linkrel="stylesheet" href="/style.css" media="screen" charset="utf-8" />
</head>
<body>
<div class="container">
<video class="video-large" autoplay></video>
<div class="users-container" id="users-container">
<h4>Users</h4>
<div id="users"></div>
</div>
<div>

<scriptsrc="/socket.io/socket.io.js"></script>
<script type="text/javascript" src="/index.js"></script>
</body>
</html>

Next, we will create our index.js file, which will include our client-side JavaScript. This file will be responsible for creating new WebRTC connections and responding to WebRTC connection requests from other users:

var socket = io.connect('http://localhost:5000');

varanswersFrom = {}, offer;

varpeerConnection = window.RTCPeerConnection ||
window.mozRTCPeerConnection ||
window.webkitRTCPeerConnection ||
window.msRTCPeerConnection;

varsessionDescription = window.RTCSessionDescription ||
window.mozRTCSessionDescription ||
window.webkitRTCSessionDescription ||
window.msRTCSessionDescription;

navigator.getUserMedia  = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia;

var pc = new peerConnection({ iceServers: [{ url: "stun:stun.services.mozilla.com",
username: "somename",
credential: "somecredentials" }]
});

pc.onaddstream = function (obj) {
var vid = document.createElement('video');
vid.setAttribute('class', 'video-small');
vid.setAttribute('autoplay', 'autoplay');
vid.setAttribute('id', 'video-small');
document.getElementById('users-container').appendChild(vid);
vid.src = window.URL.createObjectURL(obj.stream);
}

navigator.getUserMedia({video: true}, function (stream) {
var video = document.querySelector('video');
video.src = window.URL.createObjectURL(stream);
pc.addStream(stream);
}, error);

function error (err) {
console.warn('Error', err);
}

functioncreateOffer (id) {
pc.createOffer(function(offer) {
pc.setLocalDescription(new sessionDescription(offer), function () {
socket.emit('make-offer', {
offer: offer,
to: id
            });
        }, error);
    }, error);
}

socket.on('answer-made', function (data) {
pc.setRemoteDescription(new sessionDescription(data.answer), function () {
document.getElementById(data.socket).setAttribute('class', 'active');
if (!answersFrom[data.socket]) {
createOffer(data.socket);
answersFrom[data.socket] = true;
        }
    }, error);
});

socket.on('offer-made', function (data) {
offer = data.offer;

pc.setRemoteDescription(new sessionDescription(data.offer), function () {
pc.createAnswer(function (answer) {
pc.setLocalDescription(new sessionDescription(answer), function () {
socket.emit('make-answer', {
answer: answer,
to: data.socket
              });
          }, error);
        }, error);
    }, error);
});

socket.on('add-users', function (data) {
for (vari = 0; i<data.users.length; i++) {
var el = document.createElement('div'),
id = data.users[i];

el.setAttribute('id', id);
el.innerHTML = id;
el.addEventListener('click', function () {
createOffer(id);
        });
document.getElementById('users').appendChild(el);
    }
});

socket.on('remove-user', function (id) {
var div = document.getElementById(id);
document.getElementById('users').removeChild(div);
});

Finally, we need to add some CSS in style.css to make our page look nice:

html, body {
padding: 0px;
margin: 0px;
}

video {
background: #CCC;
}

.container {
width: 100%;
}

.video-large {
width: 75%;
float: left;
}

.users-container {
width: 21%;
float: left;
padding: 2%;
position: relative;
}

.video-small {
margin-top: 20px;
width: 100%;
}

#users div {
color: red;
text-decoration: underline;
cursor: pointer;
}

#users .active {
color: #000;
cursor: default;
}

Most of the magic here is happening through WebRTC. Socket.IO simply handles the handshake process to authenticate the handshake on both ends.

Once the connection is established, we are piping data over the WebRTC connection with the addStream() method on the peer connection. We can create a data URL to pass as the src of our video element by calling window.URL.createObjectURL(). If the WebRTC session is successfully authenticated, the video will stream as expected.