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