Transmitting Other Data Types
Using getUserMedia and HTTPS Lauren Chong and I were able to make a simple video streaming site that ties a user’s mouse movement to their video camera feed.
To make the video stream more manageable the video resolution and frame rate are reduced in the user’s browser before being sent to the sever.
In the current iteration the resolution is reduced to 1/6 resolution (106 x 80px) and 8 frames per second.
// Variables to control video size and framerate
let videoSize = 6;
let videoFramerate = 8;
// Draw Loop
function draw() {
context.clearRect(0,0,thecanvas.width,thecanvas.height);
context.drawImage(video,0,0,video.width/videoSize,video.height/videoSize);
//console.log(thecanvas.toDataURL());
var dataToSend = {image: thecanvas.toDataURL(), id: socket.id};
socket.emit('image',dataToSend);
}
setInterval(function() { draw(); }, 1000/videoFramerate);
Based on the graph data from Digital Ocean it appears that even with reduced resolution and frame rate the streams consume a considerable amount of bandwidth and CPU usage on the server.
Video lag is an issue with this setup. From a bit of online digging it seems that this is likely due to the use of TCP over UDP with Socket.io. TCP will only send data in order and repeatedly try to send until it goes through, preventing dropped frames but causing lag. UDP on the other hand would allow for dropped frames and a smoother appearance for the end user.
The full index.html file here:
<html>
<head>
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
var socket = io.connect();
var toggleMovement = true;
socket.on('connect', function() {
console.log("Connected");
});
// Receive from image event
socket.on('image', function(imagedata) {
if(document.getElementById(imagedata.id) != null){
document.getElementById('theimage' + imagedata.id).src = imagedata.image;
console.log('moving image');
//var div = document.getElementById(imagedata.id);
}
});
window.addEventListener('load', function() {
// The video element on the page to display the webcam
let video = document.getElementById('thevideo');
// Constraints - what do we want?
let constraints = { audio: false, video: true }
// Prompt the user for permission, get the stream
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
/* Use the stream */
// Attach to our video object
video.srcObject = stream;
// Wait for the stream to load enough to play
video.onloadedmetadata = function(e) {
video.play();
draw();
};
})
.catch(function(err) {
/* Handle the error */
alert(err);
});
// Hide Cursor
document.body.style.cursor = 'none';
var thecanvas = document.getElementById('mycanvas');
var context = thecanvas.getContext('2d');
context.fillStyle="#0000FF";
thecanvas.addEventListener('mousemove', function(evt) {
context.fillRect(evt.clientX,evt.clientY,20,20);
})
// Variables to control video size and framerate
let videoSize = 6;
let videoFramerate = 8;
// Draw Loop
function draw() {
context.clearRect(0,0,thecanvas.width,thecanvas.height);
context.drawImage(video,0,0,video.width/videoSize,video.height/videoSize);
//console.log(thecanvas.toDataURL());
var dataToSend = {image: thecanvas.toDataURL(), id: socket.id};
socket.emit('image',dataToSend);
//requestAnimationFrame(function() { draw(); });
}
setInterval(function() { draw(); }, 1000/videoFramerate);
// Possibly a better alternative -->
//requestAnimationFrame(function() { draw(); });
// Share mouse position
document.body.addEventListener('mousemove', function(evt) {
//console.log(evt);
if(toggleMovement){
var dataToSend = {x: evt.clientX, y: evt.clientY, id: socket.id};
socket.emit('mouse', dataToSend);
}
});
// Toggle Movement
document.body.addEventListener('click', function(evt) {
//console.log(evt);
toggleMovement = !toggleMovement;
// Hide Cursor
if(toggleMovement){
document.body.style.cursor = 'none';
} else {
document.body.style.cursor = 'pointer';
}
});
});
// When receiving a mouse position tie to video div
socket.on('mouse', function (data) {
//console.log(data);
let newDiv;
if(document.getElementById(data.id) == null) {
// create a new div element
// console.log(data.id + ' does not exist as a DIV and evaluates as null');
newDiv = document.createElement("div");
newDiv.id = data.id;
newDiv.innerHTML = '<img id="theimage' + data.id + '" width="100" height="100" style="-webkit-transform: scaleX(-1);transform: scaleX(-1);">';
newDiv.style.position = "absolute";
newDiv.style.width = "80px";
newDiv.style.height = "80px";
newDiv.style.borderRadius = "40px";
newDiv.style.overflow = "hidden";
newDiv.style.backgroundColor = "#000000";
} else {
// get existing div
newDiv = document.getElementById(data.id);
}
// set div position
newDiv.style.top = data.y + "px";
newDiv.style.left = data.x + "px";
document.body.appendChild(newDiv);
//console.log(newDiv);
});
// User disconnect
socket.on('deleteUser', function(userID) {
var disconnectedUser = document.getElementById(userID);
disconnectedUser.remove();
console.log('User ' + userID + ' disconnected.');
});
</script>
</head>
<body>
<video id="thevideo" width="640" height="480" muted hidden></video>
<canvas width="100" height="100" id="mycanvas" hidden></canvas>
<!-- <img id="theimage" width="100" height="80"> -->
</body>
</html>
And the server.js file:
// We need the file system here
var fs = require('fs');
// Express is a node module for building HTTP servers
var express = require('express');
var app = express();
// Tell Express to look in the "public" folder for any files first
app.use(express.static('public'));
// If the user just goes to the "route" / then run this function
app.get('/', function (req, res) {
res.send('Hello World!')
});
// Here is the actual HTTP server
// In this case, HTTPS (secure) server
var https = require('https');
// Security options - key and certificate
var options = {
key: fs.readFileSync('star_itp_io.key'),
cert: fs.readFileSync('star_itp_io.pem')
};
// We pass in the Express object and the options object
var httpServer = https.createServer(options, app);
// Default HTTPS port
httpServer.listen(443);
// WebSocket Portion
// WebSockets work with the HTTP server
var io = require('socket.io')(httpServer);
var users = [];
// Register a callback function to run when we have an individual connection
// This is run for each individual user that connects
io.sockets.on('connection',
// We are given a websocket object in our function
function (socket) {
console.log("We have a new client: " + socket.id);
users.push(socket);
var usernames = [];
for (var i = 0; i < users.length; i++) {
usernames.push(users[i].username);
}
socket.emit('users',usernames);
socket.on('username', function(data) {
socket.username = data;
var usernames = [];
for (var i = 0; i < users.length; i++) {
usernames.push(users[i].username);
}
io.emit('users',usernames);
});
socket.on('blink', function(data) {
io.emit('blink', data);
});
socket.on('mouse', function(data) {
io.emit('mouse', data);
});
socket.on('image', function(data) {
io.emit('image', data);
});
// When this user emits, client side: socket.emit('otherevent',some data);
socket.on('chatmessage', function(data) {
// Data comes in as whatever was sent, including objects
console.log("Received: 'chatmessage' " + data);
// Send it to all of the clients
//socket.emit()
// Goes to everybody
io.emit('chatmessage', data);
//socket.broadcast.emit('chatmessage', data);
});
socket.on('disconnect', function() {
console.log("Client has disconnected " + socket.id);
io.emit('deleteUser', socket.id);
});
}
);


