Last active
August 29, 2015 13:57
-
-
Save richtr/9683905 to your computer and use it in GitHub Desktop.
BroadcastWebSocket: advertising, discovering and communicating between web pages and devices running in the current local network
This file contains hidden or 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
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>BroadcastWebSocket usage demo</title> | |
</head> | |
<body> | |
<h1>BroadcastWebSocket usage demo</h1> | |
<script> | |
// [Constructor(DOMString channel)] | |
// interface BroadcastWebSocket { | |
// // events | |
// attribute EventHandler onconnect; // provides WebSocket objects (one per event) | |
// | |
// // methods | |
// void terminate([Clamp] optional unsigned short code, optional DOMString reason); | |
// } | |
// | |
// Create an object that broadcasts the availability of the provided channel id in the | |
// local network and connect all existing BroadcastWebSocket services that share the same | |
// channel id within the local network. | |
// | |
// The network advertisement broadcast of this BroadcastWebSocket service could | |
// potentially be agnostic to the underlying Discovery Protocol used. | |
// i.e. it could be broadcast over SSDP, Zeroconf and/or DIAL by the UA | |
// as this mechanism is not exposed in the JavaScript API. | |
// | |
// The BroadcastWebSocket service type would be 'web+broadcast:<channel>' where <channel> | |
// is the channel name chosen by the developer. | |
// | |
// Here we build on top of the WebSocket interface rather than MessageChannel or MessagePort | |
// interfaces which do not have well-defined network communication or serialization | |
// features. | |
// | |
var authSocketHandler = new BroadcastWebSocket('auth' + (window.location.search || '?0')); // key the channel name by its URL parameters | |
var connectedSockets = []; | |
authSocketHandler.onconnect = function( webSocket ) { | |
connectedSockets.push( webSocket ); | |
webSocket.onmessage = function (event) { | |
// we only want to allow same-origin cross-document communication in our app. | |
if( event.origin !== location.origin ) | |
return; | |
if (event.data == 'logout') { | |
doLogout(); | |
showLogout(); | |
} | |
} | |
} | |
function logoutRequested() { | |
// called when the user asks us to log them out | |
doLogout(); | |
showLogout(); | |
for(var i in connectedSockets) { | |
connectedSockets[i].send('logout'); | |
} | |
} | |
function doLogout() { | |
// actually log the user out (e.g. clearing cookies) | |
// ... | |
} | |
function showLogout() { | |
// update the UI to indicate we're logged out | |
// ... | |
} | |
</script> | |
</body> | |
</html> |
This file contains hidden or 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
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>DEMO: Pure P2P (signaling + streaming) Screen Casting via WebRTC over BroadcastWebSocket</title> | |
</head> | |
<body> | |
<h1>DEMO: Pure P2P (signaling + streaming) Screen Casting via WebRTC over BroadcastWebSocket</h1> | |
<select disabled> | |
<option disabled>Select a second screen to cast this tab to</option> | |
</select> | |
<script> | |
var castMenu = document.querySelector('select'); | |
function generateUUID() { | |
return Math.floor(Math.random() * 1e16); | |
} | |
// Use a BroadcastWebSocket to collect screencast-capable candidate web pages and devices. | |
// Second screen capable devices advertise 'web+broadcast:screencast_devices' | |
// services on the local network via e.g. DNS-SD/mDNS. | |
var displaysBroadcast = new BroadcastWebSocket('screencast_devices'); | |
var availableDisplays = {}; | |
var availableDisplaysCount = 0; | |
displaysBroadcast.onconnect = function( socket ) { | |
var displayIndex; | |
socket.onmessage = function () { | |
var message = JSON.parse(socket.data); | |
// Connected service is advertising itself as a second screen | |
if( message.action == 'isSecondScreen' ) { | |
displayIndex = generateUUID(); | |
availableDisplays[ displayIndex ] = { | |
'endpointName' : message.name || 'Second Screen', | |
'endpointSocket': socket | |
}; | |
var castOption = document.createElement('option'); | |
castOption.value = castOption.id = displayIndex; | |
castOption.textContent = availableDisplays[ displayIndex ]; | |
castMenu.appendChild( castOption ); | |
availableDisplaysCount++; | |
castMenu.disabled = false; | |
} | |
} | |
socket.onclose = function () { | |
// Remove this previously registered second screen | |
if( displayIndex ) { | |
var castOption = document.querySelector('#' + displayIndex); | |
castOption.parentNode.removeChild( castOption ); | |
delete availableDisplays[ displayIndex ]; | |
availableDisplaysCount--; | |
} | |
if(availableDisplaysCount === 0) { | |
castMenu.disabled = true; | |
} | |
}; | |
} | |
castMenu.onchange = function () { | |
var selectedDisplayIndex = castMenu.options[castMenu.selectedIndex].value; | |
var selectedDisplayObject = availableDisplays[ selectedDisplayIndex ]; | |
var selectedDisplaySocket = selectedDisplayObject.endpointSocket; | |
var streamConfig = { | |
audio: false, | |
video: { | |
mandatory: { | |
mediaSource: 'screen' // share the current screen | |
} | |
} | |
}; | |
// Start a WebRTC screen sharing session | |
navigator.getUserMedia( | |
streamConfig, | |
function( stream ) { startScreenStreaming( stream, selectedDisplaySocket ) }, | |
logError | |
); | |
}; | |
function startScreenStreaming( screenStream, signalingChannel ) { | |
var pc = new RTCPeerConnection({ "iceServers": [{ "url": "stun:stun.example.org" }] }); | |
// Add screen sharing MediaStream object to the PeerConnection | |
pc.addStream( screenStream ); | |
pc.onicecandidate = function ( event ) { | |
if ( event.candidate ) { | |
signalingChannel.send(JSON.stringify({ | |
"action": "rtc_IceCandidate", | |
"candidate": event.candidate | |
})); | |
} | |
}; | |
pc.onnegotiationneeded = function () { | |
// enforce 'sendonly' streaming to second screen | |
pc.createOffer(localSDPCreated, logError, { | |
offerToReceiveVideo: false, | |
offerToReceiveAudio: false | |
}); | |
}; | |
function localSDPCreated( sdp ) { | |
pc.setLocalDescription(sdp, function() { | |
signalingChannel.send(JSON.stringify({ | |
"action": "rtc_SDPDescription", | |
"sdp": pc.localDescription | |
})); | |
}); | |
}; | |
signalingChannel.onmessage = function ( event ) { | |
var message = JSON.parse(event.data); | |
if ( message.action == "rtc_SDPDescription" ) { | |
pc.setRemoteDescription(new RTCSessionDescription(message.sdp), function () { | |
if (pc.remoteDescription.type == "offer") { | |
pc.createAnswer(localSDPCreated, logError); | |
} | |
}, logError); | |
} else if ( message.action == "rtc_IceCandidate" ) { | |
pc.addIceCandidate( new RTCIceCandidate( message.candidate ) ); | |
} | |
}; | |
} | |
function logError( error ) { | |
console.log( error.name + ": " + error.message ); | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment