python, webrtc and you
TRANSCRIPT
Python, WebRTC and YouSaúl Ibarra Corretgé
@saghul
print(“hello!”)
@saghul
FOSDEM
Open Source
github.com/saghul
Listen at your own risk
Do you know about WebRTC?
Have you ever used it?
What is WebRTC?
WebRTC (Web Real-Time Communication) is an API definition drafted by the World Wide Web Consortium (W3C) that supports browser-to-browser applications for voice calling, video chat, and P2P file sharing without the need of either internal or external plugins.
You need an adaptorImplementation in browsers is currently inconsistent
Some APIs are still in flux
I’ll be using rtcninja
https://github.com/eface2face/rtcninja.js
WebRTC APIs
getUserMedia
RTCPeerConnection
RTCDataChannel
getUserMediaif (!rtcninja.hasWebRTC()) { console.log('Are you from the past?!'); return;}!rtcninja.getUserMedia( // constraints {video: true, audio: true},! // successCallback function(localMediaStream) { var video = document.querySelector('video'); rtcninja.attachMediaStream(video, localMediaStream); },! // errorCallback function(err) { console.log("The following error occured: " + err); });
RTCPeerConnection
Handles streaming of media between 2 peers
Uses state of the art technology
ICE for NAT traversal
RTCPeerConnection (2)Get local media
Send SDP offer Get local media
Send SDP answer
Audio / Video
RTCDataChannelP2P, message boundary based channel for arbitrary data
Implemented using SCTP, different reliability choices possible
This is the game-changer
Did I mention it’s P2P?
What about the signalling?
It’s not specified!
Use SIP, XMPP, or your own!
Call Roulette
Saghul’s Imbecile Protocol
The ProtocolUsers enter the roulette when they connect over WebSocket
3 types of messages: offer_request, offer and answer
No end message, just disconnect the WebSocket
Shopping for a framework
Python >= 3.3, because future!
WebSocket support built-in
Async, because blocking is so 2001
New, because hype!
asyncio + aiohttp
@asyncio.coroutinedef init(loop): app = web.Application(loop=loop) app.router.add_route('GET', '/', LazyFileHandler(INDEX_FILE, 'text/html')) app.router.add_route('GET', '/ws', WebSocketHandler()) app.router.add_route('GET', '/static/{path:.*}', StaticFilesHandler(STATIC_FILES))! handler = app.make_handler() server = yield from loop.create_server(handler, '0.0.0.0', 8080) print("Server started at http://0.0.0.0:8080") return server, handler
class StaticFilesHandler: def __init__(self, base_path): self.base_path = base_path self.cache = {}! @asyncio.coroutine def __call__(self, request): path = request.match_info['path'] try: data, content_type = self.cache[path] except KeyError: full_path = os.path.join(self.base_path, path) try: with open(full_path, 'rb') as f: content_type, encoding = mimetypes.guess_type(full_path, strict=False) data = f.read() except IOError: log.warning('Could not open %s file' % path) raise web.HTTPNotFound() self.cache[path] = data, content_type log.debug('Loaded file %s (%s)' % (path, content_type)) return web.Response(body=data, content_type=content_type)
class WebSocketHandler: def __init__(self): self.waiter = None! @asyncio.coroutine def __call__(self, request): ws = web.WebSocketResponse(protocols=('callroulette',)) ws.start(request)! conn = Connection(ws) if self.waiter is None: self.waiter = asyncio.Future() fs = [conn.read(), self.waiter] done, pending = yield from asyncio.wait(fs, return_when=asyncio.FIRST_COMPLETED) if self.waiter not in done: # the connection was most likely closed self.waiter = None return ws other = self.waiter.result() self.waiter = None reading_task = pending.pop() asyncio.async(self.run_roulette(conn, other, reading_task)) else: self.waiter.set_result(conn)! yield from conn.wait_closed()! return ws
@asyncio.coroutine def run_roulette(self, peerA, peerB, initial_reading_task): log.info('Running roulette: %s, %s' % (peerA, peerB))! def _close_connections(): peerA.close() peerB.close()! # request offer data = dict(type='offer_request'); peerA.write(json.dumps(data))! # get offer # I cannot seem to cancel the reading task that was started before, which is the # only way one can know if the connection was closed, so use if for the initial # reading try: data = yield from asyncio.wait_for(initial_reading_task, READ_TIMEOUT) except asyncio.TimeoutError: data = '' if not data: return _close_connections()! data = json.loads(data) if data.get('type') != 'offer' or not data.get('sdp'): log.warning('Invalid offer received') return _close_connections()
# send offer data = dict(type='offer', sdp=data['sdp']); peerB.write(json.dumps(data))! # wait for answer data = yield from peerB.read(timeout=READ_TIMEOUT) if not data: return _close_connections()! data = json.loads(data) if data.get('type') != 'answer' or not data.get('sdp'): log.warning('Invalid answer received') return _close_connections()! # dispatch answer data = dict(type='answer', sdp=data['sdp']); peerA.write(json.dumps(data))! # wait for end fs = [peerA.read(), peerB.read()] yield from asyncio.wait(fs, return_when=asyncio.FIRST_COMPLETED)! # close connections return _close_connections()
Questions?
bettercallsaghul.com@saghul