Python, WebRTC and You

Post on 15-Jul-2015

1.582 views 5 download

Tags:

Transcript of Python, WebRTC and You

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