Skip to content

Commit a6fb9f8

Browse files
committed
Fix webcam example shutdown
When serving a webcam using `MediaRelay`, closing the peer connections will not close the webcam - by design. We need to explicitly do so ourselves when the application shuts down in response to CTRL-C. While we are at it, add full type annotations to this example. Fixes: aiortc#688
1 parent 03f0d49 commit a6fb9f8

File tree

1 file changed

+28
-13
lines changed

1 file changed

+28
-13
lines changed

examples/webcam/webcam.py

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,39 @@
55
import os
66
import platform
77
import ssl
8+
from typing import Optional
89

910
from aiohttp import web
10-
from aiortc import RTCPeerConnection, RTCRtpSender, RTCSessionDescription
11+
from aiortc import (
12+
MediaStreamTrack,
13+
RTCPeerConnection,
14+
RTCRtpSender,
15+
RTCSessionDescription,
16+
)
1117
from aiortc.contrib.media import MediaPlayer, MediaRelay
1218

1319
ROOT = os.path.dirname(__file__)
1420

15-
21+
pcs = set()
1622
relay = None
1723
webcam = 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

118133
if __name__ == "__main__":
119134
parser = argparse.ArgumentParser(description="WebRTC webcam demo")

0 commit comments

Comments
 (0)