//CLIENT // Fallbacks for vendor-specific variables until the spec is finalized. var PeerConnection = window.PeerConnection || window.webkitPeerConnection00 || window.webkitRTCPeerConnection; var URL = window.URL || window.webkitURL || window.msURL || window.oURL; var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; (function() { var rtc; if ('undefined' === typeof module) { rtc = this.rtc = {}; } else { rtc = module.exports = {}; } // Holds a connection to the server. rtc._socket = null; // Holds identity for the client rtc._me = null; // Holds callbacks for certain events. rtc._events = {}; rtc.on = function(eventName, callback) { rtc._events[eventName] = rtc._events[eventName] || []; rtc._events[eventName].push(callback); }; rtc.fire = function(eventName, _) { var events = rtc._events[eventName]; var args = Array.prototype.slice.call(arguments, 1); if (!events) { return; } for (var i = 0, len = events.length; i < len; i++) { events[i].apply(null, args); } }; // Holds the STUN/ICE server to use for PeerConnections. rtc.SERVER = {iceServers:[{url:"stun:stun.l.google.com:19302"}]}; // Reference to the lone PeerConnection instance. rtc.peerConnections = {}; // Array of known peer socket ids rtc.connections = []; // Stream-related variables. rtc.streams = []; rtc.numStreams = 0; rtc.initializedStreams = 0; // Reference to the data channels rtc.dataChannels = {}; // PeerConnection datachannel configuration rtc.dataChannelConfig = {optional: [ {RtpDataChannels: true} ] }; // check whether data channel is supported. rtc.checkDataChannelSupport = function() { try { // raises exception if createDataChannel is not supported var pc = new PeerConnection(rtc.SERVER, rtc.dataChannelConfig); channel = pc.createDataChannel('supportCheck', {reliable: false}); channel.close(); return true; } catch(e) { return false; } }; rtc.dataChannelSupport = rtc.checkDataChannelSupport(); /** * Connects to the websocket server. */ rtc.connect = function(server, room) { room = room || ""; // by default, join a room called the blank string rtc._socket = new WebSocket(server); rtc._socket.onopen = function() { rtc._socket.send(JSON.stringify({ "eventName": "join_room", "data":{ "room": room } })); rtc._socket.onmessage = function(msg) { var json = JSON.parse(msg.data); rtc.fire(json.eventName, json.data); }; rtc._socket.onerror = function(err) { console.error('onerror'); console.error(err); }; rtc._socket.onclose = function(data) { rtc.fire('disconnect stream', rtc._socket.id); delete rtc.peerConnections[rtc._socket.id]; }; rtc.on('get_peers', function(data) { rtc.connections = data.connections; rtc._me = data.you; // fire connections event and pass peers rtc.fire('connections', rtc.connections); }); rtc.on('receive_ice_candidate', function(data) { var candidate = new RTCIceCandidate(data); rtc.peerConnections[data.socketId].addIceCandidate(candidate); rtc.fire('receive ice candidate', candidate); }); rtc.on('new_peer_connected', function(data) { rtc.connections.push(data.socketId); var pc = rtc.createPeerConnection(data.socketId); for (var i = 0; i < rtc.streams.length; i++) { var stream = rtc.streams[i]; pc.addStream(stream); } }); rtc.on('remove_peer_connected', function(data) { rtc.fire('disconnect stream', data.socketId); delete rtc.peerConnections[data.socketId]; }); rtc.on('receive_offer', function(data) { rtc.receiveOffer(data.socketId, data.sdp); rtc.fire('receive offer', data); }); rtc.on('receive_answer', function(data) { rtc.receiveAnswer(data.socketId, data.sdp); rtc.fire('receive answer', data); }); rtc.fire('connect'); }; }; rtc.sendOffers = function() { for (var i = 0, len = rtc.connections.length; i < len; i++) { var socketId = rtc.connections[i]; rtc.sendOffer(socketId); } }; rtc.onClose = function(data) { rtc.on('close_stream', function() { rtc.fire('close_stream', data); }); }; rtc.createPeerConnections = function() { for (var i = 0; i < rtc.connections.length; i++) { rtc.createPeerConnection(rtc.connections[i]); } }; rtc.createPeerConnection = function(id) { var config; if (rtc.dataChannelSupport) config = rtc.dataChannelConfig; var pc = rtc.peerConnections[id] = new PeerConnection(rtc.SERVER, config); pc.onicecandidate = function(event) { if (event.candidate) { rtc._socket.send(JSON.stringify({ "eventName": "send_ice_candidate", "data": { "label": event.candidate.label, "candidate": event.candidate.candidate, "socketId": id } })); } rtc.fire('ice candidate', event.candidate); }; pc.onopen = function() { // TODO: Finalize this API rtc.fire('peer connection opened'); }; pc.onaddstream = function(event) { // TODO: Finalize this API rtc.fire('add remote stream', event.stream, id); }; if (rtc.dataChannelSupport) { pc.ondatachannel = function (evt) { console.log('data channel connecting ' + id); rtc.addDataChannel(id, evt.channel); }; } return pc; }; rtc.sendOffer = function(socketId) { var pc = rtc.peerConnections[socketId]; pc.createOffer( function(session_description) { pc.setLocalDescription(session_description); rtc._socket.send(JSON.stringify({ "eventName": "send_offer", "data":{ "socketId": socketId, "sdp": session_description } })); }); }; rtc.receiveOffer = function(socketId, sdp) { var pc = rtc.peerConnections[socketId]; pc.setRemoteDescription(new RTCSessionDescription(sdp)); rtc.sendAnswer(socketId); }; rtc.sendAnswer = function(socketId) { var pc = rtc.peerConnections[socketId]; pc.createAnswer( function(session_description) { pc.setLocalDescription(session_description); rtc._socket.send(JSON.stringify({ "eventName": "send_answer", "data":{ "socketId": socketId, "sdp": session_description } })); var offer = pc.remoteDescription; }); }; rtc.receiveAnswer = function(socketId, sdp) { var pc = rtc.peerConnections[socketId]; pc.setRemoteDescription(new RTCSessionDescription(sdp)); }; rtc.createStream = function(opt, onSuccess, onFail) { var options; onSuccess = onSuccess || function() {}; onFail = onFail || function() {}; options = { video: !!opt.video, audio: !!opt.audio }; if (getUserMedia) { rtc.numStreams++; getUserMedia.call(navigator, options, function(stream) { rtc.streams.push(stream); rtc.initializedStreams++; onSuccess(stream); if (rtc.initializedStreams === rtc.numStreams) { rtc.fire('ready'); } }, function() { alert("Could not connect stream."); onFail(); }); } else { alert('webRTC is not yet supported in this browser.'); } }; rtc.addStreams = function() { for (var i = 0; i < rtc.streams.length; i++) { var stream = rtc.streams[i]; for (var connection in rtc.peerConnections) { rtc.peerConnections[connection].addStream(stream); } } }; rtc.attachStream = function(stream, domId) { document.getElementById(domId).src = URL.createObjectURL(stream); }; rtc.createDataChannel = function(pcOrId, label) { if (!rtc.dataChannelSupport) { alert('webRTC data channel is not yet supported in this browser,' + ' or you must turn on experimental flags'); return; } if (typeof(pcOrId) === 'string') { id = pcOrId; pc = rtc.peerConnections[pcOrId]; } else { pc = pcOrId; id = undefined; for (var key in rtc.peerConnections) { if (rtc.peerConnections[key] === pc) id = key; } } if (!id) throw new Error ('attempt to createDataChannel with unknown id'); if (!pc || !(pc instanceof PeerConnection)) throw new Error ('attempt to createDataChannel without peerConnection'); // need a label label = label || 'fileTransfer' || String(id); // chrome only supports reliable false atm. options = {reliable: false}; try { console.log('createDataChannel ' + id); channel = pc.createDataChannel(label, options); } catch (error) { console.log('seems that DataChannel is NOT actually supported!'); throw error; } return rtc.addDataChannel(id, channel); }; rtc.addDataChannel = function(id, channel) { channel.onopen = function() { console.log('data stream open ' + id); rtc.fire('data stream open', channel); }; channel.onclose = function(event) { delete rtc.dataChannels[id]; console.log('data stream close ' + id); rtc.fire('data stream close', channel); }; channel.onmessage = function(message) { console.log('data stream message ' + id); console.log(message); rtc.fire('data stream data', channel, message.data); }; channel.onerror = function(err) { console.log('data stream error ' + id + ': ' + err); rtc.fire('data stream error', channel, err); }; // track dataChannel rtc.dataChannels[id] = channel; return channel; }; rtc.addDataChannels = function() { if (!rtc.dataChannelSupport) return; for (var connection in rtc.peerConnections) rtc.createDataChannel(connection); }; rtc.on('ready', function() { rtc.createPeerConnections(); rtc.addStreams(); rtc.addDataChannels(); rtc.sendOffers(); }); }).call(this);