Last active
February 24, 2020 05:25
-
-
Save isao/1a8c28ccf2378f39bd03 to your computer and use it in GitHub Desktop.
est3k hackday files with code liberally borrowed from various places too hastily to credit :/
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
var RTCPeerConnection = null; | |
var getUserMedia = null; | |
var attachMediaStream = null; | |
var reattachMediaStream = null; | |
var webrtcDetectedBrowser = null; | |
var webrtcDetectedVersion = null; | |
function trace(text) { | |
// This function is used for logging. | |
if (text[text.length - 1] == '\n') { | |
text = text.substring(0, text.length - 1); | |
} | |
console.log((performance.now() / 1000).toFixed(3) + ": " + text); | |
} | |
if (navigator.mozGetUserMedia) { | |
console.log("This appears to be Firefox"); | |
webrtcDetectedBrowser = "firefox"; | |
webrtcDetectedVersion = | |
parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1]); | |
// The RTCPeerConnection object. | |
RTCPeerConnection = mozRTCPeerConnection; | |
// The RTCSessionDescription object. | |
RTCSessionDescription = mozRTCSessionDescription; | |
// The RTCIceCandidate object. | |
RTCIceCandidate = mozRTCIceCandidate; | |
// Get UserMedia (only difference is the prefix). | |
// Code from Adam Barth. | |
getUserMedia = navigator.mozGetUserMedia.bind(navigator); | |
// Creates iceServer from the url for FF. | |
createIceServer = function(url, username, password) { | |
var iceServer = null; | |
var url_parts = url.split(':'); | |
if (url_parts[0].indexOf('stun') === 0) { | |
// Create iceServer with stun url. | |
iceServer = { 'url': url }; | |
} else if (url_parts[0].indexOf('turn') === 0 && | |
(url.indexOf('transport=udp') !== -1 || | |
url.indexOf('?transport') === -1)) { | |
// Create iceServer with turn url. | |
// Ignore the transport parameter from TURN url. | |
var turn_url_parts = url.split("?"); | |
iceServer = { 'url': turn_url_parts[0], | |
'credential': password, | |
'username': username }; | |
} | |
return iceServer; | |
}; | |
// Attach a media stream to an element. | |
attachMediaStream = function(element, stream) { | |
console.log("Attaching media stream"); | |
element.mozSrcObject = stream; | |
element.play(); | |
}; | |
reattachMediaStream = function(to, from) { | |
console.log("Reattaching media stream"); | |
to.mozSrcObject = from.mozSrcObject; | |
to.play(); | |
}; | |
// Fake get{Video,Audio}Tracks | |
MediaStream.prototype.getVideoTracks = function() { | |
return []; | |
}; | |
MediaStream.prototype.getAudioTracks = function() { | |
return []; | |
}; | |
} else if (navigator.webkitGetUserMedia) { | |
console.log("This appears to be Chrome"); | |
webrtcDetectedBrowser = "chrome"; | |
webrtcDetectedVersion = | |
parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2]); | |
// Creates iceServer from the url for Chrome. | |
createIceServer = function(url, username, password) { | |
var iceServer = null; | |
var url_parts = url.split(':'); | |
if (url_parts[0].indexOf('stun') === 0) { | |
// Create iceServer with stun url. | |
iceServer = { 'url': url }; | |
} else if (url_parts[0].indexOf('turn') === 0) { | |
if (webrtcDetectedVersion < 28) { | |
// For pre-M28 chrome versions use old TURN format. | |
var url_turn_parts = url.split("turn:"); | |
iceServer = { 'url': 'turn:' + username + '@' + url_turn_parts[1], | |
'credential': password }; | |
} else { | |
// For Chrome M28 & above use new TURN format. | |
iceServer = { 'url': url, | |
'credential': password, | |
'username': username }; | |
} | |
} | |
return iceServer; | |
}; | |
// The RTCPeerConnection object. | |
RTCPeerConnection = webkitRTCPeerConnection; | |
// Get UserMedia (only difference is the prefix). | |
// Code from Adam Barth. | |
getUserMedia = navigator.webkitGetUserMedia.bind(navigator); | |
// Attach a media stream to an element. | |
attachMediaStream = function(element, stream) { | |
if (typeof element.srcObject !== 'undefined') { | |
element.srcObject = stream; | |
} else if (typeof element.mozSrcObject !== 'undefined') { | |
element.mozSrcObject = stream; | |
} else if (typeof element.src !== 'undefined') { | |
element.src = URL.createObjectURL(stream); | |
} else { | |
console.log('Error attaching stream to element.'); | |
} | |
}; | |
reattachMediaStream = function(to, from) { | |
to.src = from.src; | |
}; | |
// The representation of tracks in a stream is changed in M26. | |
// Unify them for earlier Chrome versions in the coexisting period. | |
if (!webkitMediaStream.prototype.getVideoTracks) { | |
webkitMediaStream.prototype.getVideoTracks = function() { | |
return this.videoTracks; | |
}; | |
webkitMediaStream.prototype.getAudioTracks = function() { | |
return this.audioTracks; | |
}; | |
} | |
// New syntax of getXXXStreams method in M26. | |
if (!webkitRTCPeerConnection.prototype.getLocalStreams) { | |
webkitRTCPeerConnection.prototype.getLocalStreams = function() { | |
return this.localStreams; | |
}; | |
webkitRTCPeerConnection.prototype.getRemoteStreams = function() { | |
return this.remoteStreams; | |
}; | |
} | |
} else { | |
console.log("Browser does not appear to be WebRTC-capable"); | |
} |
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> | |
<meta name='viewport' content='width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1'> | |
<base target='_blank'> | |
<title>WebRTC client</title> | |
</head> | |
<body> | |
<div id='container'> | |
<div id='videos'> | |
<video id='localVideo' autoplay hidden muted></video> | |
<video id='remoteVideo' autoplay hidden></video> | |
</div> | |
</div> | |
<script src='/socket.io/socket.io.js'></script> | |
<script src='js/adapter.js'></script> | |
<script src='js/main.js'></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
'use strict'; | |
var isChannelReady; | |
var isInitiator = false; | |
var isStarted = false; | |
var localStream; | |
var pc; | |
var remoteStream; | |
var turnReady; | |
var pc_config = {'iceServers': [{'url': 'stun:stun.l.google.com:19302'}]}; | |
var pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': true}]}; | |
// Set up audio and video regardless of what devices are present. | |
var sdpConstraints = {'mandatory': { | |
'OfferToReceiveAudio':true, | |
'OfferToReceiveVideo':true }}; | |
///////////////////////////////////////////// | |
var room = location.pathname.substring(1); | |
if (room === '') { | |
// room = prompt('Enter room name:'); | |
room = 'foo'; | |
} else { | |
// | |
} | |
var socket = io.connect(); | |
if (room !== '') { | |
console.log('Create or join room', room); | |
socket.emit('create or join', room); | |
} | |
socket.on('created', function (room){ | |
console.log('Created room ' + room); | |
isInitiator = true; | |
}); | |
socket.on('full', function (room){ | |
console.log('Room ' + room + ' is full'); | |
}); | |
socket.on('join', function (room){ | |
console.log('Another peer made a request to join room ' + room); | |
console.log('This peer is the initiator of room ' + room + '!'); | |
isChannelReady = true; | |
}); | |
socket.on('joined', function (room){ | |
console.log('This peer has joined room ' + room); | |
isChannelReady = true; | |
}); | |
socket.on('log', function (array){ | |
console.log.apply(console, array); | |
}); | |
//////////////////////////////////////////////// | |
function sendMessage(message){ | |
console.log('Client sending message: ', message); | |
// if (typeof message === 'object') { | |
// message = JSON.stringify(message); | |
// } | |
socket.emit('message', message); | |
} | |
socket.on('message', function (message){ | |
console.log('Client received message:', message); | |
if (message === 'got user media') { | |
maybeStart(); | |
} else if (message.type === 'offer') { | |
if (!isInitiator && !isStarted) { | |
maybeStart(); | |
} | |
pc.setRemoteDescription(new RTCSessionDescription(message)); | |
doAnswer(); | |
} else if (message.type === 'answer' && isStarted) { | |
pc.setRemoteDescription(new RTCSessionDescription(message)); | |
} else if (message.type === 'candidate' && isStarted) { | |
var candidate = new RTCIceCandidate({ | |
sdpMLineIndex: message.label, | |
candidate: message.candidate | |
}); | |
pc.addIceCandidate(candidate); | |
} else if (message === 'bye' && isStarted) { | |
handleRemoteHangup(); | |
} | |
}); | |
//////////////////////////////////////////////////// | |
var localVideo = document.querySelector('#localVideo'); | |
var remoteVideo = document.querySelector('#remoteVideo'); | |
function handleUserMedia(stream) { | |
console.log('Adding local stream.'); | |
localVideo.src = window.URL.createObjectURL(stream); | |
localStream = stream; | |
sendMessage('got user media'); | |
if (isInitiator) { | |
maybeStart(); | |
} | |
} | |
function handleUserMediaError(error){ | |
console.log('getUserMedia error: ', error); | |
} | |
var constraints = {video: false, audio: true}; | |
getUserMedia(constraints, handleUserMedia, handleUserMediaError); | |
console.log('Getting user media with constraints', constraints); | |
// if (location.hostname != "localhost") { | |
// requestTurn('https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913'); | |
// } | |
function maybeStart() { | |
if (!isStarted && typeof localStream != 'undefined' && isChannelReady) { | |
createPeerConnection(); | |
pc.addStream(localStream); | |
isStarted = true; | |
console.log('isInitiator', isInitiator); | |
if (isInitiator) { | |
doCall(); | |
} | |
} | |
} | |
window.onbeforeunload = function(e){ | |
sendMessage('bye'); | |
} | |
///////////////////////////////////////////////////////// | |
function createPeerConnection() { | |
try { | |
pc = new RTCPeerConnection(null); | |
pc.onicecandidate = handleIceCandidate; | |
pc.onaddstream = handleRemoteStreamAdded; | |
pc.onremovestream = handleRemoteStreamRemoved; | |
console.log('Created RTCPeerConnnection'); | |
} catch (e) { | |
console.log('Failed to create PeerConnection, exception: ' + e.message); | |
alert('Cannot create RTCPeerConnection object.'); | |
return; | |
} | |
} | |
function handleIceCandidate(event) { | |
console.log('handleIceCandidate event: ', event); | |
if (event.candidate) { | |
sendMessage({ | |
type: 'candidate', | |
label: event.candidate.sdpMLineIndex, | |
id: event.candidate.sdpMid, | |
candidate: event.candidate.candidate}); | |
} else { | |
console.log('End of candidates.'); | |
} | |
} | |
function handleRemoteStreamAdded(event) { | |
console.log('Remote stream added.'); | |
remoteVideo.src = window.URL.createObjectURL(event.stream); | |
remoteStream = event.stream; | |
} | |
function handleCreateOfferError(event){ | |
console.log('createOffer() error: ', e); | |
} | |
function doCall() { | |
console.log('Sending offer to peer'); | |
pc.createOffer(setLocalAndSendMessage, handleCreateOfferError); | |
} | |
function doAnswer() { | |
console.log('Sending answer to peer.'); | |
pc.createAnswer(setLocalAndSendMessage, null, sdpConstraints); | |
} | |
function setLocalAndSendMessage(sessionDescription) { | |
// Set Opus as the preferred codec in SDP if Opus is present. | |
sessionDescription.sdp = preferOpus(sessionDescription.sdp); | |
pc.setLocalDescription(sessionDescription); | |
console.log('setLocalAndSendMessage sending message' , sessionDescription); | |
sendMessage(sessionDescription); | |
} | |
function requestTurn(turn_url) { | |
var turnExists = false; | |
for (var i in pc_config.iceServers) { | |
if (pc_config.iceServers[i].url.substr(0, 5) === 'turn:') { | |
turnExists = true; | |
turnReady = true; | |
break; | |
} | |
} | |
if (!turnExists) { | |
console.log('Getting TURN server from ', turn_url); | |
// No TURN server. Get one from computeengineondemand.appspot.com: | |
var xhr = new XMLHttpRequest(); | |
xhr.onreadystatechange = function(){ | |
if (xhr.readyState === 4 && xhr.status === 200) { | |
var turnServer = JSON.parse(xhr.responseText); | |
console.log('Got TURN server: ', turnServer); | |
pc_config.iceServers.push({ | |
'url': 'turn:' + turnServer.username + '@' + turnServer.turn, | |
'credential': turnServer.password | |
}); | |
turnReady = true; | |
} | |
}; | |
xhr.open('GET', turn_url, true); | |
xhr.send(); | |
} | |
} | |
function handleRemoteStreamAdded(event) { | |
console.log('Remote stream added.'); | |
remoteVideo.src = window.URL.createObjectURL(event.stream); | |
remoteStream = event.stream; | |
} | |
function handleRemoteStreamRemoved(event) { | |
console.log('Remote stream removed. Event: ', event); | |
} | |
function hangup() { | |
console.log('Hanging up.'); | |
stop(); | |
sendMessage('bye'); | |
} | |
function handleRemoteHangup() { | |
// console.log('Session terminated.'); | |
// stop(); | |
// isInitiator = false; | |
} | |
function stop() { | |
isStarted = false; | |
// isAudioMuted = false; | |
// isVideoMuted = false; | |
pc.close(); | |
pc = null; | |
} | |
/////////////////////////////////////////// | |
// Set Opus as the default audio codec if it's present. | |
function preferOpus(sdp) { | |
var sdpLines = sdp.split('\r\n'); | |
var mLineIndex; | |
// Search for m line. | |
for (var i = 0; i < sdpLines.length; i++) { | |
if (sdpLines[i].search('m=audio') !== -1) { | |
mLineIndex = i; | |
break; | |
} | |
} | |
if (mLineIndex === null) { | |
return sdp; | |
} | |
// If Opus is available, set it as the default in m line. | |
for (i = 0; i < sdpLines.length; i++) { | |
if (sdpLines[i].search('opus/48000') !== -1) { | |
var opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i); | |
if (opusPayload) { | |
sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex], opusPayload); | |
} | |
break; | |
} | |
} | |
// Remove CN in m line and sdp. | |
sdpLines = removeCN(sdpLines, mLineIndex); | |
sdp = sdpLines.join('\r\n'); | |
return sdp; | |
} | |
function extractSdp(sdpLine, pattern) { | |
var result = sdpLine.match(pattern); | |
return result && result.length === 2 ? result[1] : null; | |
} | |
// Set the selected codec to the first in m line. | |
function setDefaultCodec(mLine, payload) { | |
var elements = mLine.split(' '); | |
var newLine = []; | |
var index = 0; | |
for (var i = 0; i < elements.length; i++) { | |
if (index === 3) { // Format of media starts from the fourth. | |
newLine[index++] = payload; // Put target payload to the first. | |
} | |
if (elements[i] !== payload) { | |
newLine[index++] = elements[i]; | |
} | |
} | |
return newLine.join(' '); | |
} | |
// Strip CN from sdp before CN constraints is ready. | |
function removeCN(sdpLines, mLineIndex) { | |
var mLineElements = sdpLines[mLineIndex].split(' '); | |
// Scan from end for the convenience of removing an item. | |
for (var i = sdpLines.length-1; i >= 0; i--) { | |
var payload = extractSdp(sdpLines[i], /a=rtpmap:(\d+) CN\/\d+/i); | |
if (payload) { | |
var cnPos = mLineElements.indexOf(payload); | |
if (cnPos !== -1) { | |
// Remove CN payload from m line. | |
mLineElements.splice(cnPos, 1); | |
} | |
// Remove CN line in sdp | |
sdpLines.splice(i, 1); | |
} | |
} | |
sdpLines[mLineIndex] = mLineElements.join(' '); | |
return sdpLines; | |
} | |
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
var static = require('node-static'), | |
http = require('http'), | |
cors = { | |
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', | |
'Access-Control-Allow-Credentials': 'true', | |
'Access-Control-Allow-Headers': '*', | |
'Access-Control-Allow-Origin': '*' | |
}, | |
file = new(static.Server)('.', {headers: cors}); | |
var app = http.createServer(function (req, res) { | |
console.log('HI!!'); | |
file.serve(req, res); | |
}).listen(2013); | |
console.log('running on 2013'); | |
var io = require('socket.io').listen(app); | |
io.sockets.on('connection', function (socket){ | |
function log(){ | |
var array = [">>> Message from server: "]; | |
for (var i = 0; i < arguments.length; i++) { | |
array.push(arguments[i]); | |
} | |
socket.emit('log', array); | |
} | |
socket.on('message', function (message) { | |
log('Got message: ', message); | |
// For a real app, should be room only (not broadcast) | |
socket.broadcast.emit('message', message); | |
}); | |
socket.on('create or join', function (room) { | |
var numClients = io.sockets.clients(room).length; | |
log('Room ' + room + ' has ' + numClients + ' client(s)'); | |
log('Request to create or join room', room); | |
if (numClients == 0){ | |
socket.join(room); | |
socket.emit('created', room); | |
} else if (numClients == 1) { | |
io.sockets.in(room).emit('join', room); | |
socket.join(room); | |
socket.emit('joined', room); | |
} else { // max two clients | |
socket.emit('full', room); | |
} | |
socket.emit('emit(): client ' + socket.id + ' joined room ' + room); | |
socket.broadcast.emit('broadcast(): client ' + socket.id + ' joined room ' + room); | |
}); | |
}); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment