1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
|
import argparse
import asyncio
import json
import logging
import os
import platform
import ssl
from aiohttp import web
from aiortc import RTCPeerConnection, RTCRtpSender, RTCSessionDescription
from aiortc.contrib.media import MediaPlayer, MediaRelay
ROOT = os.path.dirname(__file__)
relay = None
webcam = None
def create_local_tracks(play_from, decode):
global relay, webcam
if play_from:
player = MediaPlayer(play_from, decode=decode)
return player.audio, player.video
else:
options = {"framerate": "30", "video_size": "640x480"}
if relay is None:
if platform.system() == "Darwin":
webcam = MediaPlayer(
"default:none", format="avfoundation", options=options
)
elif platform.system() == "Windows":
webcam = MediaPlayer(
"video=Integrated Camera", format="dshow", options=options
)
else:
webcam = MediaPlayer("/dev/video0", format="v4l2", options=options)
relay = MediaRelay()
return None, relay.subscribe(webcam.video)
def force_codec(pc, sender, forced_codec):
kind = forced_codec.split("/")[0]
codecs = RTCRtpSender.getCapabilities(kind).codecs
transceiver = next(t for t in pc.getTransceivers() if t.sender == sender)
transceiver.setCodecPreferences(
[codec for codec in codecs if codec.mimeType == forced_codec]
)
async def index(request):
content = open(os.path.join(ROOT, "index.html"), "r").read()
return web.Response(content_type="text/html", text=content)
async def javascript(request):
content = open(os.path.join(ROOT, "client.js"), "r").read()
return web.Response(content_type="application/javascript", text=content)
async def offer(request):
params = await request.json()
offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])
pc = RTCPeerConnection()
pcs.add(pc)
@pc.on("connectionstatechange")
async def on_connectionstatechange():
print("Connection state is %s" % pc.connectionState)
if pc.connectionState == "failed":
await pc.close()
pcs.discard(pc)
# open media source
audio, video = create_local_tracks(
args.play_from, decode=not args.play_without_decoding
)
if audio:
audio_sender = pc.addTrack(audio)
if args.audio_codec:
force_codec(pc, audio_sender, args.audio_codec)
elif args.play_without_decoding:
raise Exception("You must specify the audio codec using --audio-codec")
if video:
video_sender = pc.addTrack(video)
if args.video_codec:
force_codec(pc, video_sender, args.video_codec)
elif args.play_without_decoding:
raise Exception("You must specify the video codec using --video-codec")
await pc.setRemoteDescription(offer)
answer = await pc.createAnswer()
await pc.setLocalDescription(answer)
return web.Response(
content_type="application/json",
text=json.dumps(
{"sdp": pc.localDescription.sdp, "type": pc.localDescription.type}
),
)
pcs = set()
async def on_shutdown(app):
# close peer connections
coros = [pc.close() for pc in pcs]
await asyncio.gather(*coros)
pcs.clear()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="WebRTC webcam demo")
parser.add_argument("--cert-file", help="SSL certificate file (for HTTPS)")
parser.add_argument("--key-file", help="SSL key file (for HTTPS)")
parser.add_argument("--play-from", help="Read the media from a file and sent it.")
parser.add_argument(
"--play-without-decoding",
help=(
"Read the media without decoding it (experimental). "
"For now it only works with an MPEGTS container with only H.264 video."
),
action="store_true",
)
parser.add_argument(
"--host", default="0.0.0.0", help="Host for HTTP server (default: 0.0.0.0)"
)
parser.add_argument(
"--port", type=int, default=8080, help="Port for HTTP server (default: 8080)"
)
parser.add_argument("--verbose", "-v", action="count")
parser.add_argument(
"--audio-codec", help="Force a specific audio codec (e.g. audio/opus)"
)
parser.add_argument(
"--video-codec", help="Force a specific video codec (e.g. video/H264)"
)
args = parser.parse_args()
if args.verbose:
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
if args.cert_file:
ssl_context = ssl.SSLContext()
ssl_context.load_cert_chain(args.cert_file, args.key_file)
else:
ssl_context = None
app = web.Application()
app.on_shutdown.append(on_shutdown)
app.router.add_get("/", index)
app.router.add_get("/client.js", javascript)
app.router.add_post("/offer", offer)
web.run_app(app, host=args.host, port=args.port, ssl_context=ssl_context)
|