Hendrik Langer
4 years ago
8 changed files with 530 additions and 1 deletions
@ -0,0 +1,19 @@ |
|||
-----BEGIN CERTIFICATE----- |
|||
MIIDCTCCAfGgAwIBAgIUaSR0XsMtkf98JniZIgZCiG5cEfUwDQYJKoZIhvcNAQEL |
|||
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIwMDcyNTEwMjc0NFoXDTIxMDcy |
|||
NTEwMjc0NFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF |
|||
AAOCAQ8AMIIBCgKCAQEAw/b9EORjM+5hXkjjg6Fnrzr+QT3m1H/w7W4J08rXsU9Z |
|||
UV3JBgTCNqVWOF+Fv4RRB/kymm/keJ25bmfgrG8MwvF9IIOu2vbppLz2yX0Qsx5F |
|||
5ZEQ+7Mqm7YxCrQPYwk8hftLLBB9rOaxVpIzOlLXv88NnlRQ1yfxu+QYckgiwzrj |
|||
SmKjf7QdksYj8sg3w9zFRuuky/04hWfg1LUn7Mb9r491AUuaHTLqQFlZCgvyvjVJ |
|||
sOJafhOWsLFUVZfQeTp3Fc0w0BPPosFdbKNzHP2Top4b3tdSrdzLxHa1zIpWbys8 |
|||
KGQSW6eL5mwp0RnZsVpaotNqKPtgIYVLIE6xiINeEwIDAQABo1MwUTAdBgNVHQ4E |
|||
FgQU/1bdqVZYHiZIwZooDpVBrB9DE0wwHwYDVR0jBBgwFoAU/1bdqVZYHiZIwZoo |
|||
DpVBrB9DE0wwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAdfm7 |
|||
MxhQUn47NfZYfb5XrwJAoHkek3WX9zNiAwmqI1FzpiFGKt2Jr9VESKZc5aqpRcps |
|||
rVtfe33g544hYakpFh2jsX2nzFGVz61DnhKot61AYOVRWSdgej94+ik5h9n1fLsH |
|||
+aEQnXZ+UvHU9sX8ya1RHZwAbMKYkRZ9NHH4IlVTUdvT3o/uPCG9eF0ayFRk9bsG |
|||
sUlBZ1C6MaDwehUz/3gXtToM3Z1PLllHipyKnRUdhWgh/YZp6MfY8PhWB9BGa1d1 |
|||
TKSaJEyMi0IdvB7e8/dmssGK10TBGNoFCXlzXkt6745fnw+T1f+ZSEzSfM41P/jm |
|||
YYeFaQybnPDAx5rD0Q== |
|||
-----END CERTIFICATE----- |
@ -0,0 +1,28 @@ |
|||
-----BEGIN PRIVATE KEY----- |
|||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDD9v0Q5GMz7mFe |
|||
SOODoWevOv5BPebUf/DtbgnTytexT1lRXckGBMI2pVY4X4W/hFEH+TKab+R4nblu |
|||
Z+CsbwzC8X0gg67a9umkvPbJfRCzHkXlkRD7syqbtjEKtA9jCTyF+0ssEH2s5rFW |
|||
kjM6Ute/zw2eVFDXJ/G75BhySCLDOuNKYqN/tB2SxiPyyDfD3MVG66TL/TiFZ+DU |
|||
tSfsxv2vj3UBS5odMupAWVkKC/K+NUmw4lp+E5awsVRVl9B5OncVzTDQE8+iwV1s |
|||
o3Mc/ZOinhve11Kt3MvEdrXMilZvKzwoZBJbp4vmbCnRGdmxWlqi02oo+2AhhUsg |
|||
TrGIg14TAgMBAAECggEAJ1LFv4EFAyO/uCrKNNzLsJcW9EKWsmemEg71u+FFXRg+ |
|||
JRy7vRBxZIPTBGbusNq9Y+s2o1T2tNl5n1UK+a8jEW9iG4cxLFFF2z/sCcyl8DuP |
|||
7RwqG0f9sddiifJN8CKnWX9uuz3n6i00wtl9nCOJlbzXz8C9pB8o2/pOiYg+KYyq |
|||
mAg+iuZGx8HIqR0TOV3TRk5kKvl4OZ9ah51bpm6/X+Rv8DCXsZ1ADZB5kRJA5lC7 |
|||
lZkCleoan/SVpXccd7ExFF7mByx/6DmfAoWjuoGyPL2fmPmMALYCgc6ZLQ0x53CZ |
|||
lwHgvek4Hc0HIAYnoronldLI5urX3N3hKfGBvdUm8QKBgQDquml/0dfTNS8lBvmi |
|||
7YPCfqpiuK8mbTNiTO3xaXimqom6/riyF3nrWK2Fdg3LuFCxkWHXOR0kWNBkUEzv |
|||
Jb3pCa+B14TqTPCpsXoXerngmxuWnT0BgFGg6HPvvPkYWbT6gpgGOdLv8fY6vrFl |
|||
0zq12ipqLrvdcPhvksnfXp5yqQKBgQDVuUib0lz34vsQOoOSUUuWD5rkBu534atB |
|||
LGzsbbrPMQUHdDAPKjQRSOAIQzEshVhmjkDL3D2VykIK8QNwxsCm7wOwNyG6Uf1V |
|||
YCn03nIXn0Vj3C5bpUyoPqnaNxOWzGU+DdW1H1Hzl2PU7YOdVT3KbpA5i32IutVZ |
|||
YsIMXGk8WwKBgCtPEcAfu66glX5DdzP0lub/7/gfE1IHu/9bKlvslfJKbPcvoGxb |
|||
oIcn6XxCd/EqpNjedir7wsC+ElUv68IEOLISs2tFlKSWZaEpudkzL7Cdbc2wXV01 |
|||
i9ogiaOmPl+bzaSbC+m6KY1UG5ZjMOAUxTRgeIr27HrDKVOMxeqMzrdhAoGAK1FB |
|||
cSui4i8kGbbyYd6ORlzlVOA+xxd7IVuCvCzFVyclUMxwzTINmY8+sQ4FUnO4Zhjg |
|||
8zCxXsG+vv74ZowyEeORyM5zzJK/mOVDu7i2QSlr5ACFeNe5AMSqomrVUpQc1QXy |
|||
0aIGdyuw9UAqk+HqAzSDkNY/3E2Z3mMQ13aHcc8CgYACSCQO7TiOLWZicjcJxbCC |
|||
DYJuDFIvX7omGYxlXXOrp7QHmv2SiBUdZDZiyBGEQPJQv8Md6eS+Rza6h28FA3kA |
|||
Mnewl6gGWX6YerNTZvKtahDqii8ZWFbyys//CYBMlIIohdEYSGaDJG11ysoM5J/E |
|||
OsBiiHTujUhuiOWa3JjT1w== |
|||
-----END PRIVATE KEY----- |
File diff suppressed because one or more lines are too long
@ -0,0 +1,401 @@ |
|||
<!doctype html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|||
<title>Video Streaming Demonstration</title> |
|||
<!-- Bootstrap --> |
|||
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet"> |
|||
<!-- jQuery and Bootstrap --> |
|||
<script src="{{ url_for('static', filename='js/jquery.min.js') }}"></script> |
|||
<script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script> |
|||
<script src="{{ url_for('static', filename='js/socket.io.js') }}"></script> |
|||
</head> |
|||
|
|||
<body> |
|||
<div id="videos"> |
|||
<video id="stream" autoplay playsinline>Your browser does not support video</video> |
|||
</div> |
|||
<div id="chatlog"></div> |
|||
<textarea id="text" name="text"></textarea> |
|||
<button id="sendText" onclick="sendData()">Send</button> |
|||
|
|||
<script type="text/javascript"> |
|||
'use strict'; |
|||
var room = '{{room}}'; |
|||
|
|||
var isChannelReady = false; |
|||
var isInitiator = false; |
|||
var isStarted = false; |
|||
var localStream; |
|||
var pc; |
|||
var remoteStream; |
|||
var turnReady; |
|||
var receiveChannel; |
|||
var sendChannel; |
|||
var sendText = document.querySelector("#text"); |
|||
var chatlog = document.querySelector('#chatlog'); |
|||
|
|||
var pcConfig = { |
|||
'iceServers': [{ |
|||
'urls': 'stun:stun.l.google.com:19302' |
|||
}] |
|||
}; |
|||
|
|||
// Set up audio and video regardless of what devices are present. |
|||
var sdpConstraints = { |
|||
offerToReceiveAudio: true, |
|||
offerToReceiveVideo: true |
|||
}; |
|||
|
|||
///////////////////////////////////////////// |
|||
|
|||
//room = 'foo'; |
|||
// Could prompt for room name: |
|||
// var room = prompt('Enter room name:'); |
|||
|
|||
var socket = io.connect('wss://' + document.domain + ':' + location.port + '/test'); |
|||
|
|||
if (room !== '') { |
|||
socket.emit('create or join', room); |
|||
console.log('Attempted to create or join room', 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('joined: ' + room); |
|||
isChannelReady = true; |
|||
}); |
|||
|
|||
socket.on('log', function(array) { |
|||
console.log.apply(console, array); |
|||
}); |
|||
|
|||
//////////////////////////////////////////////// |
|||
|
|||
function sendMessage(message) { |
|||
console.log('Client sending message: ', message); |
|||
socket.emit('message', message); |
|||
} |
|||
|
|||
// This client receives a 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'); |
|||
|
|||
navigator.mediaDevices.getUserMedia({ |
|||
audio: false, |
|||
video: true |
|||
}) |
|||
.then(gotStream) |
|||
.catch(function(e) { |
|||
alert('getUserMedia() error: ' + e.name); |
|||
}); |
|||
|
|||
function gotStream(stream) { |
|||
console.log('Adding local stream.'); |
|||
localVideo.src = window.URL.createObjectURL(stream); |
|||
localStream = stream; |
|||
sendMessage('got user media'); |
|||
if (isInitiator) { |
|||
maybeStart(); |
|||
} |
|||
} |
|||
|
|||
var constraints = { |
|||
video: true |
|||
}; |
|||
|
|||
console.log('Getting user media with constraints', constraints); |
|||
|
|||
if (false) { |
|||
requestTurn( |
|||
'https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913' |
|||
); |
|||
} |
|||
|
|||
function maybeStart() { |
|||
console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady); |
|||
if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) { |
|||
console.log('>>>>>> creating peer connection'); |
|||
createPeerConnection(); |
|||
pc.addStream(localStream); |
|||
isStarted = true; |
|||
console.log('isInitiator', isInitiator); |
|||
if (isInitiator) { |
|||
doCall(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
window.onbeforeunload = function() { |
|||
socket.emit('disconnect', room) |
|||
console.log("sending disconnect") |
|||
}; |
|||
|
|||
///////////////////////////////////////////////////////// |
|||
|
|||
function createPeerConnection() { |
|||
try { |
|||
pc = new RTCPeerConnection(pcConfig); |
|||
sendChannel = pc.createDataChannel('chat', null); |
|||
pc.onicecandidate = handleIceCandidate; |
|||
pc.onaddstream = handleRemoteStreamAdded; |
|||
pc.onremovestream = handleRemoteStreamRemoved; |
|||
pc.ondatachannel = handleChannelCallback; |
|||
|
|||
console.log('Created RTCPeerConnnection'); |
|||
} catch (e) { |
|||
console.log('Failed to create PeerConnection, exception: ' + e.message); |
|||
alert('Cannot create RTCPeerConnection object.'); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
function handleChannelCallback(event) { |
|||
receiveChannel = event.channel; |
|||
receiveChannel.onmessage = onReceiveMessageCallback; |
|||
} |
|||
|
|||
function onReceiveMessageCallback(event) { |
|||
var text = document.createElement("P"); |
|||
text.appendChild(document.createTextNode(event.data)) |
|||
|
|||
chatlog.appendChild(text); |
|||
} |
|||
|
|||
function handleIceCandidate(event) { |
|||
console.log('icecandidate 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 sendData() { |
|||
var text = document.createElement("P"); |
|||
text.appendChild(document.createTextNode(sendText.value)); |
|||
chatlog.appendChild(text); |
|||
sendChannel.send(sendText.value); |
|||
sendText.value = ''; |
|||
} |
|||
|
|||
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: ', event); |
|||
} |
|||
|
|||
function doCall() { |
|||
console.log('Sending offer to peer'); |
|||
pc.createOffer(setLocalAndSendMessage, handleCreateOfferError); |
|||
} |
|||
|
|||
function doAnswer() { |
|||
console.log('Sending answer to peer.'); |
|||
pc.createAnswer().then( |
|||
setLocalAndSendMessage, |
|||
onCreateSessionDescriptionError |
|||
); |
|||
} |
|||
|
|||
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 onCreateSessionDescriptionError(error) { |
|||
//trace('Failed to create session description: ' + error.toString()); |
|||
} |
|||
|
|||
function requestTurn(turnURL) { |
|||
var turnExists = false; |
|||
for (var i in pcConfig.iceServers) { |
|||
if (pcConfig.iceServers[i].urls.substr(0, 5) === 'turn:') { |
|||
turnExists = true; |
|||
turnReady = true; |
|||
break; |
|||
} |
|||
} |
|||
if (!turnExists) { |
|||
console.log('Getting TURN server from ', turnURL); |
|||
// 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); |
|||
pcConfig.iceServers.push({ |
|||
'url': 'turn:' + turnServer.username + '@' + turnServer.turn, |
|||
'credential': turnServer.password |
|||
}); |
|||
turnReady = true; |
|||
} |
|||
}; |
|||
xhr.open('GET', turnURL, 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; |
|||
} |
|||
</script> |
|||
|
|||
</body> |
|||
</html> |
Loading…
Reference in new issue