55import os
66import platform
77import ssl
8+ from typing import Optional
89
910from aiohttp import web
10- from aiortc import RTCPeerConnection , RTCRtpSender , RTCSessionDescription
11+ from aiortc import (
12+ MediaStreamTrack ,
13+ RTCPeerConnection ,
14+ RTCRtpSender ,
15+ RTCSessionDescription ,
16+ )
1117from aiortc .contrib .media import MediaPlayer , MediaRelay
1218
1319ROOT = os .path .dirname (__file__ )
1420
15-
21+ pcs = set ()
1622relay = None
1723webcam = None
1824
1925
20- def create_local_tracks (play_from , decode ):
26+ def create_local_tracks (
27+ play_from : str , decode : bool
28+ ) -> tuple [Optional [MediaStreamTrack ], Optional [MediaStreamTrack ]]:
2129 global relay , webcam
2230
2331 if play_from :
32+ # If a file name was given, play from that file.
2433 player = MediaPlayer (play_from , decode = decode )
2534 return player .audio , player .video
2635 else :
36+ # Otherwise, play from the system's default webcam.
37+ #
38+ # In order to serve the same webcam to multiple users we make use of
39+ # a `MediaRelay`. The webcam will stay open, so it is our responsability
40+ # to stop the webcam when the application shuts down in `on_shutdown`.
2741 options = {"framerate" : "30" , "video_size" : "640x480" }
2842 if relay is None :
2943 if platform .system () == "Darwin" :
@@ -40,7 +54,7 @@ def create_local_tracks(play_from, decode):
4054 return None , relay .subscribe (webcam .video )
4155
4256
43- def force_codec (pc , sender , forced_codec ) :
57+ def force_codec (pc : RTCPeerConnection , sender : RTCRtpSender , forced_codec : str ) -> None :
4458 kind = forced_codec .split ("/" )[0 ]
4559 codecs = RTCRtpSender .getCapabilities (kind ).codecs
4660 transceiver = next (t for t in pc .getTransceivers () if t .sender == sender )
@@ -49,25 +63,25 @@ def force_codec(pc, sender, forced_codec):
4963 )
5064
5165
52- async def index (request ) :
66+ async def index (request : web . Request ) -> web . Response :
5367 content = open (os .path .join (ROOT , "index.html" ), "r" ).read ()
5468 return web .Response (content_type = "text/html" , text = content )
5569
5670
57- async def javascript (request ) :
71+ async def javascript (request : web . Request ) -> web . Response :
5872 content = open (os .path .join (ROOT , "client.js" ), "r" ).read ()
5973 return web .Response (content_type = "application/javascript" , text = content )
6074
6175
62- async def offer (request ) :
76+ async def offer (request : web . Request ) -> web . Response :
6377 params = await request .json ()
6478 offer = RTCSessionDescription (sdp = params ["sdp" ], type = params ["type" ])
6579
6680 pc = RTCPeerConnection ()
6781 pcs .add (pc )
6882
6983 @pc .on ("connectionstatechange" )
70- async def on_connectionstatechange ():
84+ async def on_connectionstatechange () -> None :
7185 print ("Connection state is %s" % pc .connectionState )
7286 if pc .connectionState == "failed" :
7387 await pc .close ()
@@ -105,15 +119,16 @@ async def on_connectionstatechange():
105119 )
106120
107121
108- pcs = set ()
109-
110-
111- async def on_shutdown (app ):
112- # close peer connections
122+ async def on_shutdown (app : web .Application ) -> None :
123+ # Close peer connections.
113124 coros = [pc .close () for pc in pcs ]
114125 await asyncio .gather (* coros )
115126 pcs .clear ()
116127
128+ # If a shared webcam was opened, stop it.
129+ if webcam is not None :
130+ webcam .video .stop ()
131+
117132
118133if __name__ == "__main__" :
119134 parser = argparse .ArgumentParser (description = "WebRTC webcam demo" )
0 commit comments