Browse Source

working webrtc

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

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

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

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

@ -1,9 +1,9 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Video Streaming Demonstration</title>
<title>WebRTC working example</title>
<!-- Bootstrap -->
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
<!-- jQuery and Bootstrap -->
@ -22,379 +22,118 @@
<script type="text/javascript">
'use strict';
const SIGNALING_SERVER_URL = 'wss://' + document.domain + ':' + location.port + '/webrtc';
const PC_CONFIG = {};
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 socket = io.connect(SIGNALING_SERVER_URL, { autoConnect: false });
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
};
/////////////////////////////////////////////
socket.on('connect', function(){
console.log("Connected...!", socket.connected)
});
//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');
const video = document.querySelector("#stream");
socket.on('data', (data) => {
console.log('Data received: ',data);
handleSignalingData(data);
});
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);
socket.on('ready', () => {
console.log('Ready');
createPeerConnection();
sendOffer();
});
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
let sendData = (data) => {
socket.emit('data', data);
};
console.log('Getting user media with constraints', constraints);
if (false) {
requestTurn(
'https://computeengineondemand.appspot.com/turn?username=41784574&key=4080218913'
);
// WebRTC methods
let pc;
let localStream;
let remoteStreamElement = document.querySelector('#stream');
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() {
console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady);
if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) {
console.log('>>>>>> creating peer connection');
createPeerConnection();
let createPeerConnection = () => {
try {
pc = new RTCPeerConnection(PC_CONFIG);
pc.onicecandidate = onIceCandidate;
pc.onaddstream = onAddStream;
pc.addStream(localStream);
isStarted = true;
console.log('isInitiator', isInitiator);
if (isInitiator) {
doCall();
}
console.log('PeerConnection created');
} catch (error) {
console.error('PeerConnection failed: ', error);
}
}
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;
}
let sendOffer = () => {
console.log('Send offer');
pc.createOffer().then(
setAndSendLocalDescription,
(error) => { console.error('Send offer failed: ', error); }
);
};
function onReceiveMessageCallback(event) {
var text = document.createElement("P");
text.appendChild(document.createTextNode(event.data))
let sendAnswer = () => {
console.log('Send answer');
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) {
console.log('icecandidate event: ', event);
let onIceCandidate = (event) => {
if (event.candidate) {
sendMessage({
console.log('ICE candidate');
sendData({
type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate
candidate: event.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());
}
let onAddStream = (event) => {
console.log('Add stream');
remoteStreamElement.srcObject = event.stream;
};
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;
let handleSignalingData = (data) => {
switch (data.type) {
case 'offer':
createPeerConnection();
pc.setRemoteDescription(new RTCSessionDescription(data));
sendAnswer();
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;
case 'answer':
pc.setRemoteDescription(new RTCSessionDescription(data));
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);
}
case 'candidate':
pc.addIceCandidate(new RTCIceCandidate(data.candidate));
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;
}
// Start connection
getLocalStream();
</script>
</body>

Loading…
Cancel
Save