Browse Source

working webrtc

split-pipe
Hendrik Langer 4 years ago
parent
commit
c484270c24
  1. 79
      raspberry/roberto/views/websocket/routes.py
  2. 429
      raspberry/roberto/views/websocket/templates/camera.html

79
raspberry/roberto/views/websocket/routes.py

@ -1,7 +1,7 @@
from . import websocket_blueprint from . import websocket_blueprint
from flask import current_app, render_template from flask import current_app, render_template, request
from flask_socketio import SocketIO, emit from flask_socketio import SocketIO, emit, join_room, leave_room
from roberto import socketio from roberto import socketio
@ -53,69 +53,30 @@ def applyDeadZone(value, threshold):
return new_value return new_value
connected_particpants = {} # https://pfertyk.me/2020/03/webrtc-a-working-example/
ROOM = 'default'
@websocket_blueprint.route('/camera') @websocket_blueprint.route('/camera')
def camera(): def camera():
return render_template('camera.html', room='default') return render_template('camera.html', room='default')
@socketio.on('message', namespace='/webrtc') @socketio.on('data', namespace='/webrtc')
def webrtc_message(sid, data): def webrtc_message(data):
sio.emit('message', data=data) sid = request.sid
print('Message from {}: {}'.format(sid, data))
socketio.emit('data', data=data, room=ROOM, namespace='/webrtc', skip_sid=sid)
@socketio.on('disconnect', namespace='/webrtc') @socketio.on('disconnect', namespace='/webrtc')
def disconnect(sid): def disconnect():
sid = request.sid
print("Received Disconnect message from %s" % sid) print("Received Disconnect message from %s" % sid)
for room, clients in connected_particpants.iteritems(): leave_room(ROOM)
try:
clients.remove(sid) @socketio.on('connect', namespace='/webrtc')
print("Removed %s from %s \n list of left participants is %s" %(sid, room, clients)) def connect():
except ValueError: sid = request.sid
print("Remove %s from %s \n list of left participants is %s has failed" %(sid, room, clients)) print("Received Connect message from %s" % sid)
socketio.emit('ready', room=ROOM, namespace='/webrtc', skip_sid=sid)
@socketio.on('create or join', namespace='/webrtc') join_room(ROOM)
def create_or_join(sid, data):
sio.enter_room(sid, data)
try:
connected_particpants[data].append(sid)
except KeyError:
connected_particpants[data] = [sid]
numClients = len(connected_particpants[data])
if numClients == 1:
sio.emit('created', data)
elif numClients > 2:
sio.emit('full')
elif numClients == 2:
sio.emit('joined')
sio.emit('join')
print (sid, data, len(connected_particpants[data]))
@websocket_blueprint.route('/<room>')
def room(room):
return render_template('camera.html', room=room)

429
raspberry/roberto/views/websocket/templates/camera.html

@ -1,9 +1,9 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Video Streaming Demonstration</title> <title>WebRTC working example</title>
<!-- Bootstrap --> <!-- Bootstrap -->
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet"> <link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
<!-- jQuery and Bootstrap --> <!-- jQuery and Bootstrap -->
@ -22,379 +22,118 @@
<script type="text/javascript"> <script type="text/javascript">
'use strict'; 'use strict';
const SIGNALING_SERVER_URL = 'wss://' + document.domain + ':' + location.port + '/webrtc';
const PC_CONFIG = {};
var room = '{{room}}'; var room = '{{room}}';
var isChannelReady = false; var socket = io.connect(SIGNALING_SERVER_URL, { autoConnect: 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 = { socket.on('connect', function(){
'iceServers': [{ console.log("Connected...!", socket.connected)
'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'; const video = document.querySelector("#stream");
// 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) { socket.on('data', (data) => {
console.log('joined: ' + room); console.log('Data received: ',data);
isChannelReady = true; handleSignalingData(data);
}); });
socket.on('log', function(array) { socket.on('ready', () => {
console.log.apply(console, array); console.log('Ready');
createPeerConnection();
sendOffer();
}); });
//////////////////////////////////////////////// let sendData = (data) => {
socket.emit('data', data);
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); // WebRTC methods
let pc;
if (false) { let localStream;
requestTurn( let remoteStreamElement = document.querySelector('#stream');
'https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913'
); let getLocalStream = () => {
navigator.mediaDevices.getUserMedia({ audio: true, video: true })
.then((stream) => {
console.log('Stream found');
localStream = stream;
// Connect after making sure that local stream is availble
socket.connect();
})
.catch(error => {
console.error('Stream not found: ', error);
});
} }
function maybeStart() { let createPeerConnection = () => {
console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady); try {
if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) { pc = new RTCPeerConnection(PC_CONFIG);
console.log('>>>>>> creating peer connection'); pc.onicecandidate = onIceCandidate;
createPeerConnection(); pc.onaddstream = onAddStream;
pc.addStream(localStream); pc.addStream(localStream);
isStarted = true; console.log('PeerConnection created');
console.log('isInitiator', isInitiator); } catch (error) {
if (isInitiator) { console.error('PeerConnection failed: ', error);
doCall();
}
} }
}
window.onbeforeunload = function() {
socket.emit('disconnect', room)
console.log("sending disconnect")
}; };
///////////////////////////////////////////////////////// let sendOffer = () => {
console.log('Send offer');
function createPeerConnection() { pc.createOffer().then(
try { setAndSendLocalDescription,
pc = new RTCPeerConnection(pcConfig); (error) => { console.error('Send offer failed: ', error); }
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) { let sendAnswer = () => {
var text = document.createElement("P"); console.log('Send answer');
text.appendChild(document.createTextNode(event.data)) pc.createAnswer().then(
setAndSendLocalDescription,
(error) => { console.error('Send answer failed: ', error); }
);
};
chatlog.appendChild(text); let setAndSendLocalDescription = (sessionDescription) => {
} pc.setLocalDescription(sessionDescription);
console.log('Local description set');
sendData(sessionDescription);
};
function handleIceCandidate(event) { let onIceCandidate = (event) => {
console.log('icecandidate event: ', event);
if (event.candidate) { if (event.candidate) {
sendMessage({ console.log('ICE candidate');
sendData({
type: 'candidate', type: 'candidate',
label: event.candidate.sdpMLineIndex, candidate: event.candidate
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) { let onAddStream = (event) => {
//trace('Failed to create session description: ' + error.toString()); console.log('Add stream');
} remoteStreamElement.srcObject = event.stream;
};
function requestTurn(turnURL) { let handleSignalingData = (data) => {
var turnExists = false; switch (data.type) {
for (var i in pcConfig.iceServers) { case 'offer':
if (pcConfig.iceServers[i].urls.substr(0, 5) === 'turn:') { createPeerConnection();
turnExists = true; pc.setRemoteDescription(new RTCSessionDescription(data));
turnReady = true; sendAnswer();
break; break;
} case 'answer':
} pc.setRemoteDescription(new RTCSessionDescription(data));
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; break;
} case 'candidate':
} pc.addIceCandidate(new RTCIceCandidate(data.candidate));
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; 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(' '); // Start connection
return sdpLines; getLocalStream();
}
</script> </script>
</body> </body>

Loading…
Cancel
Save