You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rtpengine/t/auto-daemon-tests-websocket.py

2095 lines
77 KiB

import asyncio
import json
import os
import re
import socket
import ssl
import subprocess
import sys
import tempfile
import traceback
import unittest
import uuid
import websockets
eventloop = None
async def get_ws(cls, proto):
for _ in range(1, 300):
try:
cls._ws = await websockets.connect(
"ws://127.0.0.1:9191/", subprotocols=[proto]
)
break
except:
await asyncio.sleep(0.1)
async def testIO(self, msg):
await self._ws.send(msg)
self._res = await asyncio.wait_for(self._ws.recv(), timeout=10)
async def testIOJson(self, msg):
await self._ws.send(json.dumps(msg))
self._res = await asyncio.wait_for(self._ws.recv(), timeout=10)
self._res = json.loads(self._res)
async def testIJson(self):
self._res = await asyncio.wait_for(self._ws.recv(), timeout=10)
self._res = json.loads(self._res)
async def testIJanus(self):
self._res = await asyncio.wait_for(self._ws.recv(), timeout=10)
self._res = json.loads(self._res)
self.assertEqual(self._res["transaction"], self._trans)
del self._res["transaction"]
async def testIOJanus(self, msg):
trans = str(uuid.uuid4())
msg["transaction"] = trans
self._trans = trans
await self._ws.send(json.dumps(msg))
await testIJanus(self)
async def testOJanus(self, msg):
trans = str(uuid.uuid4())
msg["transaction"] = trans
self._trans = trans
await self._ws.send(json.dumps(msg))
class TestWSEcho(unittest.TestCase):
@classmethod
def setUpClass(cls):
eventloop.run_until_complete(get_ws(cls, "echo.rtpengine.com"))
@classmethod
def tearDownClass(cls):
eventloop.run_until_complete(cls._ws.close())
def testEcho(self):
eventloop.run_until_complete(testIO(self, b"foobar"))
self.assertEqual(self._res, b"foobar")
def testEchoText(self):
eventloop.run_until_complete(testIO(self, "foobar"))
self.assertEqual(self._res, b"foobar")
class TestWSCli(unittest.TestCase):
@classmethod
def setUpClass(cls):
eventloop.run_until_complete(get_ws(cls, "cli.rtpengine.com"))
@classmethod
def tearDownClass(cls):
eventloop.run_until_complete(cls._ws.close())
def testListNumsessions(self):
# race condition here if this runs at the same as the janus test (creates call)
eventloop.run_until_complete(testIO(self, "list numsessions"))
self.assertEqual(
self._res,
b"Current sessions own: 0\n"
+ b"Current sessions foreign: 0\n"
+ b"Current sessions total: 0\n"
+ b"Current transcoded media: 0\n"
+ b"Current sessions ipv4 only media: 0\n"
+ b"Current sessions ipv6 only media: 0\n"
+ b"Current sessions ip mixed media: 0\n",
)
class TestWSJanus(unittest.TestCase):
@classmethod
def setUpClass(cls):
eventloop.run_until_complete(get_ws(cls, "janus-protocol"))
@classmethod
def tearDownClass(cls):
eventloop.run_until_complete(cls._ws.close())
def testPing(self):
eventloop.run_until_complete(
testIOJson(self, {"janus": "ping", "transaction": "test123"})
)
self.assertEqual(self._res, {"janus": "pong", "transaction": "test123"})
def testPingNoTS(self):
eventloop.run_until_complete(testIOJson(self, {"janus": "ping"}))
self.assertEqual(
self._res,
{
"janus": "error",
"error": {
"code": 456,
"reason": "JSON object does not contain 'transaction' key",
},
},
)
def testInfo(self):
eventloop.run_until_complete(
testIOJson(self, {"janus": "info", "transaction": "foobar"})
)
# ignore version string
self.assertTrue("version_string" in self._res)
del self._res["version_string"]
self.assertEqual(
self._res,
{
"janus": "server_info",
"name": "rtpengine Janus interface",
"plugins": {
"janus.plugin.videoroom": {"name": "rtpengine Janus videoroom"}
},
"transaction": "foobar",
},
)
class TestVideoroom(unittest.TestCase):
@classmethod
def setUpClass(cls):
eventloop.run_until_complete(get_ws(cls, "janus-protocol"))
@classmethod
def tearDownClass(cls):
eventloop.run_until_complete(cls._ws.close())
def startSession(self):
self.maxDiff = None
token = str(uuid.uuid4())
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "add_token",
"token": token,
"admin_secret": "dfgdfgdvgLyATjHPvckg",
},
)
)
self.assertEqual(
self._res,
{"janus": "success", "data": {"plugins": ["janus.plugin.videoroom"]}},
)
# create session
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "create",
"token": token,
"admin_secret": "dfgdfgdvgLyATjHPvckg",
},
)
)
session = self._res["data"]["id"]
self.assertIsInstance(session, int)
self.assertEqual(self._res, {"janus": "success", "data": {"id": session}})
return (token, session)
def startVideoroom(self):
(token, session) = self.startSession()
handle = self.createHandle(token, session)
# create room
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {"request": "create", "publishers": 16},
"handle_id": handle,
"session_id": session,
"token": token,
},
)
)
room = self._res["plugindata"]["data"]["room"]
self.assertIsInstance(room, int)
self.assertNotEqual(room, handle)
self.assertNotEqual(room, session)
self.assertEqual(
self._res,
{
"janus": "success",
"session_id": session,
"sender": handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "created",
"room": room,
"permanent": False,
},
},
},
)
return (token, session, handle, room)
def destroyVideoroom(self, token, session, handle, room):
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {"request": "destroy", "room": room},
"handle_id": handle,
"session_id": session,
"token": token,
},
)
)
self.assertNotEqual(room, handle)
self.assertNotEqual(room, session)
self.assertEqual(
self._res,
{
"janus": "success",
"session_id": session,
"sender": handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "destroyed",
"room": room,
"permanent": False,
},
},
},
)
def createHandle(self, token, session):
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "attach",
"plugin": "janus.plugin.videoroom",
"session_id": session,
"token": token,
"opaque_id": None,
},
)
)
handle = self._res["data"]["id"]
self.assertIsInstance(handle, int)
self.assertNotEqual(handle, session)
self.assertEqual(
self._res,
{"janus": "success", "session_id": session, "data": {"id": handle}},
)
return handle
def createPublisher(self, token, session, room, handle, pubs=[]):
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {"request": "join", "ptype": "publisher", "room": room},
"handle_id": handle,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the joined event
eventloop.run_until_complete(testIJanus(self))
feed = self._res["plugindata"]["data"]["id"]
self.assertIsInstance(feed, int)
self.assertNotEqual(feed, session)
self.assertNotEqual(feed, room)
self.assertNotEqual(feed, handle)
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "joined",
"room": room,
"id": feed,
"publishers": pubs,
},
},
},
)
return feed
def testKeepalive(self):
(token, session) = self.startSession()
eventloop.run_until_complete(
testIOJanus(
self, {"janus": "keepalive", "token": token, "session_id": session}
)
)
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
def testVideoroomWebRTC(self):
(token, session, control_handle, room) = self.startVideoroom()
# timeout test
eventloop.run_until_complete(asyncio.sleep(3))
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "exists",
"room": room,
},
"handle_id": control_handle,
"session_id": session,
"token": token,
},
)
)
self.assertEqual(
self._res,
{
"janus": "success",
"session_id": session,
"sender": control_handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "success",
"room": room,
"exists": True,
},
},
},
)
pub_handle = self.createHandle(token, session)
self.assertNotEqual(pub_handle, control_handle)
feed = self.createPublisher(token, session, room, pub_handle)
self.assertNotEqual(feed, control_handle)
# publish as plain RTP
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "configure",
"room": room,
"feed": feed,
"data": False,
"audio": True,
"video": True,
},
"jsep": {
"type": "offer",
"sdp": (
"v=0\r\n"
"o=x 123 123 IN IP4 203.0.113.3\r\n"
"c=IN IP4 203.0.113.2\r\n"
"s=foobar\r\n"
"t=0 0\r\n"
"m=audio 8000 RTP/AVP 8 0\r\n"
"a=sendonly\r\n"
),
},
"handle_id": pub_handle,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the event notification
eventloop.run_until_complete(testIJanus(self))
sdp = self._res["jsep"]["sdp"]
self.assertIsInstance(sdp, str)
self.assertRegex(
sdp,
re.compile(
"^v=0\r\n"
"o=- \d+ \d+ IN IP4 203.0.113.1\r\n"
"s=rtpengine.*?\r\n"
"t=0 0\r\n"
"m=audio \d+ RTP/AVP 8\r\n"
"c=IN IP4 203.0.113.1\r\n"
"a=rtpmap:8 PCMA/8000\r\n"
"a=recvonly\r\n"
"a=rtcp:\d+\r\n$",
re.DOTALL,
),
)
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": pub_handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"room": room,
"configured": "ok",
"audio_codec": "PCMA",
},
},
"jsep": {"type": "answer", "sdp": sdp},
},
)
sub_handle = self.createHandle(token, session)
self.assertNotEqual(sub_handle, pub_handle)
self.assertNotEqual(sub_handle, control_handle)
# subscriber expects full WebRTC attributes
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "join",
"ptype": "subscriber",
"room": room,
"feed": feed,
},
"handle_id": sub_handle,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the attached event
eventloop.run_until_complete(testIJanus(self))
self.assertEqual(feed, self._res["plugindata"]["data"]["id"])
self.assertNotEqual(feed, control_handle)
self.assertNotEqual(feed, session)
self.assertNotEqual(feed, room)
self.assertNotEqual(feed, pub_handle)
self.assertNotEqual(feed, sub_handle)
sdp = self._res["jsep"]["sdp"]
self.assertIsInstance(sdp, str)
self.assertRegex(
sdp,
re.compile(
"^v=0\r\n"
"o=x 123 123 IN IP4 203.0.113.3\r\n"
"c=IN IP4 203.0.113.1\r\n"
"s=foobar\r\n"
"t=0 0\r\n"
"m=audio \d+ UDP/TLS/RTP/SAVPF 8\r\n"
"a=mid:1\r\n"
"a=rtpmap:8 PCMA/8000\r\n"
"a=sendonly\r\n"
"a=rtcp-mux\r\n"
"a=setup:actpass\r\n"
"a=fingerprint:sha-256 .{95}\r\n"
"a=ice-ufrag:.{8}\r\n"
"a=ice-pwd:.{26}\r\n"
"a=ice-options:trickle\r\n"
"a=candidate:.{16} 1 UDP 2130706431 203.0.113.1 \d+ typ host\r\n"
"a=end-of-candidates\r\n$",
re.DOTALL,
),
)
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": sub_handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "attached",
"room": room,
"id": feed,
},
},
"jsep": {"type": "offer", "sdp": sdp},
},
)
# subscriber #1 answer
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {"request": "start", "room": room, "feed": feed},
"jsep": {
"type": "answer",
"sdp": (
"v=0\r\n"
"o=x 123 123 IN IP4 203.0.113.2\r\n"
"c=IN IP4 0.0.0.0\r\n"
"s=foobar\r\n"
"t=0 0\r\n"
"m=audio 9 RTP/AVP 8\r\n"
"a=mid:audio\r\n"
"a=ice-ufrag:abcd\r\n"
"a=ice-pwd:WD1pLsdgsdfsdWuEBb0vjyZr\r\n"
"a=ice-options:trickle\r\n"
"a=rtcp-mux\r\n"
"a=recvonly\r\n"
),
},
"handle_id": sub_handle,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the attached event
eventloop.run_until_complete(testIJanus(self))
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": sub_handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"started": "ok",
"room": room,
},
},
},
)
self.destroyVideoroom(token, session, control_handle, room)
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "exists",
"room": room,
},
"handle_id": control_handle,
"session_id": session,
"token": token,
},
)
)
self.assertEqual(
self._res,
{
"janus": "success",
"session_id": session,
"sender": control_handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "success",
"room": room,
"exists": False,
},
},
},
)
def testVideoroomSDESDTLS(self):
(token, session, control_handle, room) = self.startVideoroom()
pub_handle = self.createHandle(token, session)
self.assertNotEqual(pub_handle, control_handle)
feed = self.createPublisher(token, session, room, pub_handle)
self.assertNotEqual(feed, control_handle)
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "configure",
"room": room,
"feed": feed,
"data": False,
"audio": True,
"video": True,
},
"jsep": {
"type": "offer",
"sdp": (
"v=0\r\n"
"o=x 123 123 IN IP4 203.0.113.5\r\n"
"c=IN IP4 203.0.113.4\r\n"
"s=foobar\r\n"
"t=0 0\r\n"
"m=audio 30000 RTP/SAVP 8 0 96\r\n"
"a=rtpmap:96 opus/48000\r\n"
"a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:cJOJ7kxQjhFBp2fP6AYjs3vKw7CeBdWZCj0isbJv\r\n"
"a=crypto:2 AES_CM_128_HMAC_SHA1_32 inline:VAzLKvoE3jG9cdH/AZsl/ZqWNXrUzyM4Gw6chrFr\r\n"
"a=crypto:3 AES_256_CM_HMAC_SHA1_80 inline:8AbZePWwsKhLGX3GlXA+yHYPQ3cgraer/9DkFJYCOPZZy3o9wC0NIbIFYZfyHw==\r\n"
"a=crypto:4 AES_256_CM_HMAC_SHA1_32 inline:2GLk3p/csdno4KlGO1TxCVaEt+bifmDlQ5NjnCb5cJYPURiGRSTBEtEq37db8g==\r\n"
"a=fingerprint:sha-256 1A:20:98:16:CA:26:8C:33:62:0B:70:94:73:A0:9B:30:00:1A:EA:26:FC:7D:84:8B:F1:F9:52:2D:A7:92:C5:3D\r\n"
"a=setup:actpass\r\n"
"a=sendonly\r\n"
),
},
"handle_id": pub_handle,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the event notification
eventloop.run_until_complete(testIJanus(self))
sdp = self._res["jsep"]["sdp"]
self.assertIsInstance(sdp, str)
self.assertRegex(
sdp,
re.compile(
"^v=0\r\n"
"o=- \d+ \d+ IN IP4 203.0.113.1\r\n"
"s=rtpengine.*?\r\n"
"t=0 0\r\n"
"m=audio \d+ RTP/SAVP 8\r\n"
"c=IN IP4 203.0.113.1\r\n"
"a=rtpmap:8 PCMA/8000\r\n"
"a=recvonly\r\n"
"a=rtcp:\d+\r\n"
"a=setup:active\r\n"
"a=fingerprint:sha-256 .{95}\r\n$",
re.DOTALL,
),
)
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": pub_handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"room": room,
"configured": "ok",
"audio_codec": "PCMA",
},
},
"jsep": {"type": "answer", "sdp": sdp},
},
)
self.destroyVideoroom(token, session, control_handle, room)
def testVideoroomSDES(self):
(token, session, control_handle, room) = self.startVideoroom()
pub_handle = self.createHandle(token, session)
self.assertNotEqual(pub_handle, control_handle)
feed = self.createPublisher(token, session, room, pub_handle)
self.assertNotEqual(feed, control_handle)
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "configure",
"room": room,
"feed": feed,
"data": False,
"audio": True,
"video": True,
},
"jsep": {
"type": "offer",
"sdp": (
"v=0\r\n"
"o=x 123 123 IN IP4 203.0.113.5\r\n"
"c=IN IP4 203.0.113.4\r\n"
"s=foobar\r\n"
"t=0 0\r\n"
"m=audio 30000 RTP/SAVP 8 0 96\r\n"
"a=rtpmap:96 opus/48000\r\n"
"a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:cJOJ7kxQjhFBp2fP6AYjs3vKw7CeBdWZCj0isbJv\r\n"
"a=crypto:2 AES_CM_128_HMAC_SHA1_32 inline:VAzLKvoE3jG9cdH/AZsl/ZqWNXrUzyM4Gw6chrFr\r\n"
"a=crypto:3 AES_256_CM_HMAC_SHA1_80 inline:8AbZePWwsKhLGX3GlXA+yHYPQ3cgraer/9DkFJYCOPZZy3o9wC0NIbIFYZfyHw==\r\n"
"a=crypto:4 AES_256_CM_HMAC_SHA1_32 inline:2GLk3p/csdno4KlGO1TxCVaEt+bifmDlQ5NjnCb5cJYPURiGRSTBEtEq37db8g==\r\n"
"a=sendonly\r\n"
),
},
"handle_id": pub_handle,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the event notification
eventloop.run_until_complete(testIJanus(self))
sdp = self._res["jsep"]["sdp"]
self.assertIsInstance(sdp, str)
self.assertRegex(
sdp,
re.compile(
"^v=0\r\n"
"o=- \d+ \d+ IN IP4 203.0.113.1\r\n"
"s=rtpengine.*?\r\n"
"t=0 0\r\n"
"m=audio \d+ RTP/SAVP 8\r\n"
"c=IN IP4 203.0.113.1\r\n"
"a=rtpmap:8 PCMA/8000\r\n"
"a=recvonly\r\n"
"a=rtcp:\d+\r\n"
"a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:.{40}\r\n",
re.DOTALL,
),
)
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": pub_handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"room": room,
"configured": "ok",
"audio_codec": "PCMA",
},
},
"jsep": {"type": "answer", "sdp": sdp},
},
)
self.destroyVideoroom(token, session, control_handle, room)
def testVideoroomDTLS(self):
(token, session, control_handle, room) = self.startVideoroom()
pub_handle = self.createHandle(token, session)
self.assertNotEqual(pub_handle, control_handle)
feed = self.createPublisher(token, session, room, pub_handle)
self.assertNotEqual(feed, control_handle)
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "configure",
"room": room,
"feed": feed,
"data": False,
"audio": True,
"video": True,
},
"jsep": {
"type": "offer",
"sdp": (
"v=0\r\n"
"o=x 123 123 IN IP4 203.0.113.5\r\n"
"c=IN IP4 203.0.113.4\r\n"
"s=foobar\r\n"
"t=0 0\r\n"
"m=audio 30000 UDP/TLS/RTP/SAVPF 8 0 96\r\n"
"a=mid:audio\r\n"
"a=rtpmap:96 opus/48000\r\n"
"a=fingerprint:sha-256 1A:20:98:16:CA:26:8C:33:62:0B:70:94:73:A0:9B:30:00:1A:EA:26:FC:7D:84:8B:F1:F9:52:2D:A7:92:C5:3D\r\n"
"a=setup:actpass\r\n"
"a=sendonly\r\n"
),
},
"handle_id": pub_handle,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the event notification
eventloop.run_until_complete(testIJanus(self))
sdp = self._res["jsep"]["sdp"]
self.assertIsInstance(sdp, str)
self.assertRegex(
sdp,
re.compile(
"^v=0\r\n"
"o=- \d+ \d+ IN IP4 203.0.113.1\r\n"
"s=rtpengine.*?\r\n"
"t=0 0\r\n"
"m=audio \d+ UDP/TLS/RTP/SAVPF 8\r\n"
"c=IN IP4 203.0.113.1\r\n"
"a=mid:audio\r\n"
"a=rtpmap:8 PCMA/8000\r\n"
"a=recvonly\r\n"
"a=rtcp:\d+\r\n"
"a=setup:active\r\n"
"a=fingerprint:sha-256 .{95}\r\n$",
re.DOTALL,
),
)
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": pub_handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"room": room,
"configured": "ok",
"audio_codec": "PCMA",
},
},
"jsep": {"type": "answer", "sdp": sdp},
},
)
self.destroyVideoroom(token, session, control_handle, room)
def testVideoroomWebrtcup(self):
(token, session, control_handle, room) = self.startVideoroom()
pub_handle = self.createHandle(token, session)
self.assertNotEqual(pub_handle, control_handle)
feed = self.createPublisher(token, session, room, pub_handle)
self.assertNotEqual(feed, control_handle)
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "configure",
"room": room,
"feed": feed,
"data": False,
"audio": True,
"video": True,
},
"jsep": {
"type": "offer",
"sdp": (
"v=0\r\n"
"o=x 123 123 IN IP4 203.0.113.4\r\n"
"c=IN IP4 203.0.113.4\r\n"
"s=foobar\r\n"
"t=0 0\r\n"
"m=audio 30000 RTP/AVP 8 0 96\r\n"
"a=mid:audio\r\n"
"a=rtpmap:96 opus/48000\r\n"
"a=sendonly\r\n"
),
},
"handle_id": pub_handle,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the event notification
eventloop.run_until_complete(testIJanus(self))
sdp = self._res["jsep"]["sdp"]
self.assertIsInstance(sdp, str)
match_re = re.compile(
"^v=0\r\n"
"o=- \d+ \d+ IN IP4 203.0.113.1\r\n"
"s=rtpengine.*?\r\n"
"t=0 0\r\n"
"m=audio (\d+) RTP/AVP 8\r\n"
"c=IN IP4 203.0.113.1\r\n"
"a=mid:audio\r\n"
"a=rtpmap:8 PCMA/8000\r\n"
"a=recvonly\r\n"
"a=rtcp:\d+\r\n$",
re.DOTALL,
)
self.assertRegex(sdp, match_re)
matches = match_re.search(sdp)
port = int(matches.group(1))
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": pub_handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"room": room,
"configured": "ok",
"audio_codec": "PCMA",
},
},
"jsep": {"type": "answer", "sdp": sdp},
},
)
pub_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
pub_sock.settimeout(1)
pub_sock.bind(("203.0.113.4", 30000))
pub_sock.connect(("203.0.113.1", port))
# send fake RTP to trigger event
m = pub_sock.send(
b"\x80\x08\x12\x34\x43\x32\x12\x45\x65\x45\x34\x23\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
)
# wait for webrtcup event
eventloop.run_until_complete(testIJson(self))
self.assertEqual(
self._res,
{"janus": "webrtcup", "session_id": session, "sender": pub_handle},
)
self.destroyVideoroom(token, session, control_handle, room)
pub_sock.close()
def testVideoroomWebRTCVideo(self):
(token, session, control_handle, room) = self.startVideoroom()
pub_handle = self.createHandle(token, session)
self.assertNotEqual(pub_handle, control_handle)
feed = self.createPublisher(token, session, room, pub_handle)
self.assertNotEqual(feed, control_handle)
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "configure",
"room": room,
"feed": feed,
"data": False,
"audio": True,
"video": True,
},
"jsep": {
"type": "offer",
"sdp": (
"v=0\r\n"
"o=- 3959345330719813235 2 IN IP4 127.0.0.1\r\n"
"s=-\r\n"
"t=0 0\r\n"
"a=group:BUNDLE 0 1\r\n"
"a=extmap-allow-mixed\r\n"
"a=msid-semantic: WMS hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
"m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n"
"c=IN IP4 0.0.0.0\r\n"
"a=rtcp:9 IN IP4 0.0.0.0\r\n"
"a=ice-ufrag:+JrN\r\n"
"a=ice-pwd:TMWORlSHr9fd+0bUNXnlBs5D\r\n"
"a=ice-options:trickle\r\n"
"a=fingerprint:sha-256 FD:56:1A:DB:3E:7B:8E:0B:75:4E:2E:49:1A:91:52:E4:69:9E:66:91:FF:34:A2:50:58:72:C0:8E:C2:87:CA:1F\r\n"
"a=setup:actpass\r\n"
"a=mid:0\r\n"
"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n"
"a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n"
"a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n"
"a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n"
"a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n"
"a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n"
"a=sendonly\r\n"
"a=msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 2de0f1b0-3a39-450e-9804-8305ec87452b\r\n"
"a=rtcp-mux\r\n"
"a=rtpmap:111 opus/48000/2\r\n"
"a=rtcp-fb:111 transport-cc\r\n"
"a=fmtp:111 minptime=10;useinbandfec=1\r\n"
"a=rtpmap:103 ISAC/16000\r\n"
"a=rtpmap:104 ISAC/32000\r\n"
"a=rtpmap:9 G722/8000\r\n"
"a=rtpmap:0 PCMU/8000\r\n"
"a=rtpmap:8 PCMA/8000\r\n"
"a=rtpmap:106 CN/32000\r\n"
"a=rtpmap:105 CN/16000\r\n"
"a=rtpmap:13 CN/8000\r\n"
"a=rtpmap:110 telephone-event/48000\r\n"
"a=rtpmap:112 telephone-event/32000\r\n"
"a=rtpmap:113 telephone-event/16000\r\n"
"a=rtpmap:126 telephone-event/8000\r\n"
"a=ssrc:677770262 cname:NMNDwVd66x2SfiO0\r\n"
"a=ssrc:677770262 msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 2de0f1b0-3a39-450e-9804-8305ec87452b\r\n"
"a=ssrc:677770262 mslabel:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
"a=ssrc:677770262 label:2de0f1b0-3a39-450e-9804-8305ec87452b\r\n"
"m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 35 36 124 119 123\r\n"
"c=IN IP4 0.0.0.0\r\n"
"a=rtcp:9 IN IP4 0.0.0.0\r\n"
"a=ice-ufrag:+JrN\r\n"
"a=ice-pwd:TMWORlSHr9fd+0bUNXnlBs5D\r\n"
"a=ice-options:trickle\r\n"
"a=fingerprint:sha-256 FD:56:1A:DB:3E:7B:8E:0B:75:4E:2E:49:1A:91:52:E4:69:9E:66:91:FF:34:A2:50:58:72:C0:8E:C2:87:CA:1F\r\n"
"a=setup:actpass\r\n"
"a=mid:1\r\n"
"a=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\n"
"a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n"
"a=extmap:13 urn:3gpp:video-orientation\r\n"
"a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n"
"a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\n"
"a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\n"
"a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\n"
"a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\n"
"a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n"
"a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n"
"a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n"
"a=sendonly\r\n"
"a=msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
"a=rtcp-mux\r\n"
"a=rtcp-rsize\r\n"
"a=rtpmap:96 VP8/90000\r\n"
"a=rtcp-fb:96 goog-remb\r\n"
"a=rtcp-fb:96 transport-cc\r\n"
"a=rtcp-fb:96 ccm fir\r\n"
"a=rtcp-fb:96 nack\r\n"
"a=rtcp-fb:96 nack pli\r\n"
"a=rtpmap:97 rtx/90000\r\n"
"a=fmtp:97 apt=96\r\n"
"a=rtpmap:98 VP9/90000\r\n"
"a=rtcp-fb:98 goog-remb\r\n"
"a=rtcp-fb:98 transport-cc\r\n"
"a=rtcp-fb:98 ccm fir\r\n"
"a=rtcp-fb:98 nack\r\n"
"a=rtcp-fb:98 nack pli\r\n"
"a=fmtp:98 profile-id=0\r\n"
"a=rtpmap:99 rtx/90000\r\n"
"a=fmtp:99 apt=98\r\n"
"a=rtpmap:100 VP9/90000\r\n"
"a=rtcp-fb:100 goog-remb\r\n"
"a=rtcp-fb:100 transport-cc\r\n"
"a=rtcp-fb:100 ccm fir\r\n"
"a=rtcp-fb:100 nack\r\n"
"a=rtcp-fb:100 nack pli\r\n"
"a=fmtp:100 profile-id=2\r\n"
"a=rtpmap:101 rtx/90000\r\n"
"a=fmtp:101 apt=100\r\n"
"a=rtpmap:102 H264/90000\r\n"
"a=rtcp-fb:102 goog-remb\r\n"
"a=rtcp-fb:102 transport-cc\r\n"
"a=rtcp-fb:102 ccm fir\r\n"
"a=rtcp-fb:102 nack\r\n"
"a=rtcp-fb:102 nack pli\r\n"
"a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\r\n"
"a=rtpmap:121 rtx/90000\r\n"
"a=fmtp:121 apt=102\r\n"
"a=rtpmap:127 H264/90000\r\n"
"a=rtcp-fb:127 goog-remb\r\n"
"a=rtcp-fb:127 transport-cc\r\n"
"a=rtcp-fb:127 ccm fir\r\n"
"a=rtcp-fb:127 nack\r\n"
"a=rtcp-fb:127 nack pli\r\n"
"a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f\r\n"
"a=rtpmap:120 rtx/90000\r\n"
"a=fmtp:120 apt=127\r\n"
"a=rtpmap:125 H264/90000\r\n"
"a=rtcp-fb:125 goog-remb\r\n"
"a=rtcp-fb:125 transport-cc\r\n"
"a=rtcp-fb:125 ccm fir\r\n"
"a=rtcp-fb:125 nack\r\n"
"a=rtcp-fb:125 nack pli\r\n"
"a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\n"
"a=rtpmap:107 rtx/90000\r\n"
"a=fmtp:107 apt=125\r\n"
"a=rtpmap:108 H264/90000\r\n"
"a=rtcp-fb:108 goog-remb\r\n"
"a=rtcp-fb:108 transport-cc\r\n"
"a=rtcp-fb:108 ccm fir\r\n"
"a=rtcp-fb:108 nack\r\n"
"a=rtcp-fb:108 nack pli\r\n"
"a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f\r\n"
"a=rtpmap:109 rtx/90000\r\n"
"a=fmtp:109 apt=108\r\n"
"a=rtpmap:35 AV1X/90000\r\n"
"a=rtcp-fb:35 goog-remb\r\n"
"a=rtcp-fb:35 transport-cc\r\n"
"a=rtcp-fb:35 ccm fir\r\n"
"a=rtcp-fb:35 nack\r\n"
"a=rtcp-fb:35 nack pli\r\n"
"a=rtpmap:36 rtx/90000\r\n"
"a=fmtp:36 apt=35\r\n"
"a=rtpmap:124 red/90000\r\n"
"a=rtpmap:119 rtx/90000\r\n"
"a=fmtp:119 apt=124\r\n"
"a=rtpmap:123 ulpfec/90000\r\n"
"a=ssrc-group:FID 3005569364 2001490794\r\n"
"a=ssrc:3005569364 cname:NMNDwVd66x2SfiO0\r\n"
"a=ssrc:3005569364 msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
"a=ssrc:3005569364 mslabel:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
"a=ssrc:3005569364 label:6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
"a=ssrc:2001490794 cname:NMNDwVd66x2SfiO0\r\n"
"a=ssrc:2001490794 msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
"a=ssrc:2001490794 mslabel:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
"a=ssrc:2001490794 label:6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
),
},
"handle_id": pub_handle,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the event notification
eventloop.run_until_complete(testIJanus(self))
sdp = self._res["jsep"]["sdp"]
self.assertIsInstance(sdp, str)
self.assertRegex(
sdp,
re.compile(
"^v=0\r\n"
"o=- \d+ \d+ IN IP4 203.0.113.1\r\n"
"s=rtpengine.*?\r\n"
"t=0 0\r\n"
"m=audio \d+ UDP/TLS/RTP/SAVPF 111\r\n"
"c=IN IP4 203.0.113.1\r\n"
"a=mid:0\r\n"
"a=rtpmap:111 opus/48000/2\r\n"
"a=fmtp:111 minptime=10;useinbandfec=1\r\n"
"a=rtcp-fb:111 transport-cc\r\n"
"a=recvonly\r\n"
"a=rtcp:\d+\r\n"
"a=rtcp-mux\r\n"
"a=setup:active\r\n"
"a=fingerprint:sha-256 .{95}\r\n"
"a=ice-ufrag:.{8}\r\n"
"a=ice-pwd:.{26}\r\n"
"a=ice-options:trickle\r\n"
"a=candidate:.{16} 1 UDP 2130706431 203.0.113.1 \d+ typ host\r\n"
"a=end-of-candidates\r\n"
"m=video \d+ UDP/TLS/RTP/SAVPF 96\r\n"
"c=IN IP4 203.0.113.1\r\n"
"a=mid:1\r\n"
"a=rtpmap:96 VP8/90000\r\n"
"a=rtcp-fb:96 goog-remb\r\n"
"a=rtcp-fb:96 transport-cc\r\n"
"a=rtcp-fb:96 ccm fir\r\n"
"a=rtcp-fb:96 nack\r\n"
"a=rtcp-fb:96 nack pli\r\n"
"a=recvonly\r\n"
"a=rtcp:\d+\r\n"
"a=rtcp-mux\r\n"
"a=setup:active\r\n"
"a=fingerprint:sha-256 .{95}\r\n"
"a=ice-ufrag:.{8}\r\n"
"a=ice-pwd:.{26}\r\n"
"a=ice-options:trickle\r\n"
"a=candidate:.{16} 1 UDP 2130706431 203.0.113.1 \d+ typ host\r\n"
"a=end-of-candidates\r\n$",
re.DOTALL,
),
)
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": pub_handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"room": room,
"configured": "ok",
"audio_codec": "opus",
"video_codec": "VP8",
},
},
"jsep": {"type": "answer", "sdp": sdp},
},
)
# subscriber
sub_handle = self.createHandle(token, session)
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "join",
"ptype": "subscriber",
"room": room,
"feed": feed,
},
"handle_id": sub_handle,
"session_id": session,
"token": token,
},
)
)
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
eventloop.run_until_complete(testIJanus(self))
sdp = self._res["jsep"]["sdp"]
self.assertIsInstance(sdp, str)
self.assertRegex(
sdp,
re.compile(
"^v=0\r\n"
"o=- 3959345330719813235 2 IN IP4 127.0.0.1\r\n"
"s=-\r\n"
"t=0 0\r\n"
"a=extmap-allow-mixed\r\n"
"a=msid-semantic: WMS hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
"m=audio \d+ UDP/TLS/RTP/SAVPF 111\r\n"
"c=IN IP4 203.0.113.1\r\n"
"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n"
"a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n"
"a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n"
"a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n"
"a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n"
"a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n"
"a=msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 2de0f1b0-3a39-450e-9804-8305ec87452b\r\n"
"a=ssrc:677770262 cname:NMNDwVd66x2SfiO0\r\n"
"a=ssrc:677770262 msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 2de0f1b0-3a39-450e-9804-8305ec87452b\r\n"
"a=ssrc:677770262 mslabel:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
"a=ssrc:677770262 label:2de0f1b0-3a39-450e-9804-8305ec87452b\r\n"
"a=mid:0\r\n"
"a=rtpmap:111 opus/48000/2\r\n"
"a=fmtp:111 minptime=10;useinbandfec=1\r\n"
"a=rtcp-fb:111 transport-cc\r\n"
"a=sendonly\r\n"
"a=rtcp-mux\r\n"
"a=setup:actpass\r\n"
"a=fingerprint:sha-256 .{95}\r\n"
"a=ice-ufrag:.{8}\r\n"
"a=ice-pwd:.{26}\r\n"
"a=ice-options:trickle\r\n"
"a=candidate:.{16} 1 UDP 2130706431 203.0.113.1 \d+ typ host\r\n"
"a=end-of-candidates\r\n"
"m=video \d+ UDP/TLS/RTP/SAVPF 96\r\n"
"c=IN IP4 203.0.113.1\r\n"
"a=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\n"
"a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n"
"a=extmap:13 urn:3gpp:video-orientation\r\n"
"a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n"
"a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\n"
"a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\n"
"a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\n"
"a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\n"
"a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n"
"a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n"
"a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n"
"a=msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
"a=rtcp-rsize\r\n"
"a=ssrc-group:FID 3005569364 2001490794\r\n"
"a=ssrc:3005569364 cname:NMNDwVd66x2SfiO0\r\n"
"a=ssrc:3005569364 msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
"a=ssrc:3005569364 mslabel:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
"a=ssrc:3005569364 label:6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
"a=ssrc:2001490794 cname:NMNDwVd66x2SfiO0\r\n"
"a=ssrc:2001490794 msid:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC 6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
"a=ssrc:2001490794 mslabel:hJifdaJwqEqHxSG0pVbs1DrLAwiHqz7fKlqC\r\n"
"a=ssrc:2001490794 label:6d6ec7a7-e3d7-4c82-b03c-45e017713abd\r\n"
"a=mid:1\r\n"
"a=rtpmap:96 VP8/90000\r\n"
"a=rtcp-fb:96 goog-remb\r\n"
"a=rtcp-fb:96 transport-cc\r\n"
"a=rtcp-fb:96 ccm fir\r\n"
"a=rtcp-fb:96 nack\r\n"
"a=rtcp-fb:96 nack pli\r\n"
"a=sendonly\r\n"
"a=rtcp-mux\r\n"
"a=setup:actpass\r\n"
"a=fingerprint:sha-256 .{95}\r\n"
"a=ice-ufrag:.{8}\r\n"
"a=ice-pwd:.{26}\r\n"
"a=ice-options:trickle\r\n"
"a=candidate:.{16} 1 UDP 2130706431 203.0.113.1 \d+ typ host\r\n"
"a=end-of-candidates\r\n$",
re.DOTALL,
),
)
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": sub_handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "attached",
"room": room,
"id": feed,
},
},
"jsep": {"type": "offer", "sdp": sdp},
},
)
self.destroyVideoroom(token, session, control_handle, room)
def testVideoroomICE(self):
(token, session, control_handle, room) = self.startVideoroom()
pub_handle = self.createHandle(token, session)
self.assertNotEqual(pub_handle, control_handle)
feed = self.createPublisher(token, session, room, pub_handle)
self.assertNotEqual(feed, control_handle)
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "configure",
"room": room,
"feed": feed,
"data": False,
"audio": True,
"video": True,
},
"jsep": {
"type": "offer",
"sdp": (
"v=0\r\n"
"o=x 123 123 IN IP4 203.0.113.2\r\n"
"c=IN IP4 0.0.0.0\r\n"
"s=foobar\r\n"
"t=0 0\r\n"
"m=audio 9 RTP/AVP 8 0 96\r\n"
"a=mid:audio\r\n"
"a=rtpmap:96 opus/48000\r\n"
"a=ice-ufrag:62lL\r\n"
"a=ice-pwd:WD1pLdamJOWH2WuEBb0vjyZr\r\n"
"a=ice-options:trickle\r\n"
"a=rtcp-mux\r\n"
"a=sendonly\r\n"
),
},
"handle_id": pub_handle,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the event notification
eventloop.run_until_complete(testIJanus(self))
sdp = self._res["jsep"]["sdp"]
self.assertIsInstance(sdp, str)
self.assertRegex(
sdp,
re.compile(
"^v=0\r\n"
"o=- \d+ \d+ IN IP4 203.0.113.1\r\n"
"s=rtpengine.*?\r\n"
"t=0 0\r\n"
"m=audio \d+ RTP/AVP 8\r\n"
"c=IN IP4 203.0.113.1\r\n"
"a=mid:audio\r\n"
"a=rtpmap:8 PCMA/8000\r\n"
"a=recvonly\r\n"
"a=rtcp:\d+\r\n"
"a=rtcp-mux\r\n"
"a=ice-ufrag:.{8}\r\n"
"a=ice-pwd:.{26}\r\n"
"a=ice-options:trickle\r\n"
"a=candidate:.{16} 1 UDP 2130706431 203.0.113.1 \d+ typ host\r\n"
"a=end-of-candidates\r\n$",
re.DOTALL,
),
)
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": pub_handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"room": room,
"configured": "ok",
"audio_codec": "PCMA",
},
},
"jsep": {"type": "answer", "sdp": sdp},
},
)
pub_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
pub_sock.settimeout(1)
pub_sock.bind(("203.0.113.2", 30000))
# trickle update
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "trickle",
"candidate": {
"candidate": "candidate:3279615273 1 udp 2113937151 203.0.113.2 30000 typ host generation 0 ufrag 62lL network-cost 999",
"sdpMid": "audio",
},
"handle_id": pub_handle,
"session_id": session,
"token": token,
},
)
)
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
m = pub_sock.recv(1000)
self.assertRegex(
m,
re.compile(
b"^\x00\x01\x00.\x21\x12\xa4\x42(............)\x80\x22\x00.rtpengine.*?\x00\x06\x00\x0d62lL:(........)\x00\x00\x00\x80\\\x29\x00\x08........\x00\\\x24\x00\x04\x6e\xff\xff\xff\x00\x08\x00\x14....................\x80\\\x28\x00\x04....$",
re.DOTALL,
),
)
sub_handle = self.createHandle(token, session)
self.assertNotEqual(sub_handle, pub_handle)
self.assertNotEqual(sub_handle, control_handle)
# subscriber #1 joins publisher #1
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "join",
"ptype": "subscriber",
"room": room,
"feed": feed,
},
"handle_id": sub_handle,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the attached event
eventloop.run_until_complete(testIJanus(self))
self.assertEqual(feed, self._res["plugindata"]["data"]["id"])
self.assertNotEqual(feed, control_handle)
self.assertNotEqual(feed, session)
self.assertNotEqual(feed, room)
self.assertNotEqual(feed, pub_handle)
self.assertNotEqual(feed, sub_handle)
sdp = self._res["jsep"]["sdp"]
self.assertIsInstance(sdp, str)
self.assertRegex(
sdp,
re.compile(
"^v=0\r\n"
"o=x 123 123 IN IP4 203.0.113.2\r\n"
"c=IN IP4 203.0.113.1\r\n"
"s=foobar\r\n"
"t=0 0\r\n"
"m=audio \d+ UDP/TLS/RTP/SAVPF 8\r\n"
"a=mid:audio\r\n"
"a=rtpmap:8 PCMA/8000\r\n"
"a=sendonly\r\n"
"a=rtcp-mux\r\n"
"a=setup:actpass\r\n"
"a=fingerprint:sha-256 .{95}\r\n"
"a=ice-ufrag:.{8}\r\n"
"a=ice-pwd:.{26}\r\n"
"a=ice-options:trickle\r\n"
"a=candidate:.{16} 1 UDP 2130706431 203.0.113.1 \d+ typ host\r\n"
"a=end-of-candidates\r\n$",
re.DOTALL,
),
)
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": sub_handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "attached",
"room": room,
"id": feed,
},
},
"jsep": {"type": "offer", "sdp": sdp},
},
)
# subscriber #1 answer
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {"request": "start", "room": room, "feed": feed},
"jsep": {
"type": "answer",
"sdp": (
"v=0\r\n"
"o=x 123 123 IN IP4 203.0.113.2\r\n"
"c=IN IP4 0.0.0.0\r\n"
"s=foobar\r\n"
"t=0 0\r\n"
"m=audio 9 RTP/AVP 8\r\n"
"a=mid:audio\r\n"
"a=ice-ufrag:abcd\r\n"
"a=ice-pwd:WD1pLsdgsdfsdWuEBb0vjyZr\r\n"
"a=ice-options:trickle\r\n"
"a=rtcp-mux\r\n"
"a=recvonly\r\n"
),
},
"handle_id": sub_handle,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the attached event
eventloop.run_until_complete(testIJanus(self))
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": sub_handle,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"started": "ok",
"room": room,
},
},
},
)
sub_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sub_sock.settimeout(1)
sub_sock.bind(("203.0.113.2", 30002))
# trickle update
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "trickle",
"candidate": {
"candidate": "candidate:3fgsdfs273 1 udp 2113937151 203.0.113.2 30002 typ host generation 0",
"sdpMid": "audio",
"usernameFragment": "abcd",
},
"handle_id": sub_handle,
"session_id": session,
"token": token,
},
)
)
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
m = sub_sock.recv(1000)
self.assertRegex(
m,
re.compile(
b"^\x00\x01\x00.\x21\x12\xa4\x42(............)\x80\x22\x00.rtpengine.*?\x00\x06\x00\x0dabcd:(........)\x00\x00\x00\x80\\\x29\x00\x08........\x00\\\x24\x00\x04\x6e\xff\xff\xff\x00\x08\x00\x14....................\x80\\\x28\x00\x04....$",
re.DOTALL,
),
)
# TCP trickle test
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "trickle",
"candidate": {
"candidate": "candidate:6 2 TCP 2105393406 2607:fea8:ab00:33::9f4 9 typ host tcptype active",
"sdpMid": "audio",
"usernameFragment": "abcd",
},
"handle_id": sub_handle,
"session_id": session,
"token": token,
},
)
)
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
self.destroyVideoroom(token, session, control_handle, room)
pub_sock.close()
sub_sock.close()
def testVideoroomPubSub(self):
(token, session, control_handle, room) = self.startVideoroom()
# XXX add tests for requests for invalid IDs/handles
handle_p_1 = self.createHandle(token, session)
self.assertNotEqual(handle_p_1, control_handle)
# create feed for publisher #1
feed_1 = self.createPublisher(token, session, room, handle_p_1)
self.assertNotEqual(feed_1, control_handle)
# configure publisher feed #1 w broken SDP
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "configure",
"room": room,
"feed": feed_1,
"data": False,
"audio": True,
"video": True,
},
"jsep": {
"type": "offer",
"sdp": "blah",
},
"handle_id": handle_p_1,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the event notification
eventloop.run_until_complete(testIJanus(self))
self.assertEqual(
self._res,
{
"janus": "error",
"session_id": session,
"sender": handle_p_1,
"error": {"code": 512, "reason": "Failed to parse SDP"},
"plugindata": {"plugin": "janus.plugin.videoroom", "data": {}},
},
)
# configure publisher feed #1
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "configure",
"room": room,
"feed": feed_1,
"data": False,
"audio": True,
"video": True,
},
"jsep": {
"type": "offer",
"sdp": (
"v=0\r\n"
"o=x 123 123 IN IP4 203.0.113.2\r\n"
"c=IN IP4 203.0.113.2\r\n"
"s=foobar\r\n"
"t=0 0\r\n"
"m=audio 6000 RTP/AVP 96 8 0\r\n"
"a=rtpmap:96 opus/48000\r\n"
"a=sendonly\r\n"
),
},
"handle_id": handle_p_1,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the event notification
eventloop.run_until_complete(testIJanus(self))
sdp = self._res["jsep"]["sdp"]
self.assertIsInstance(sdp, str)
# XXX check SDP
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": handle_p_1,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"room": room,
"configured": "ok",
"audio_codec": "opus",
},
},
"jsep": {"type": "answer", "sdp": sdp},
},
)
# attach subscriber handle #1
handle_s_1 = self.createHandle(token, session)
self.assertNotEqual(handle_s_1, control_handle)
# subscriber #1 joins publisher #1
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "join",
"ptype": "subscriber",
"room": room,
"feed": feed_1,
},
"handle_id": handle_s_1,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the attached event
eventloop.run_until_complete(testIJanus(self))
self.assertEqual(feed_1, self._res["plugindata"]["data"]["id"])
self.assertNotEqual(feed_1, control_handle)
self.assertNotEqual(feed_1, session)
self.assertNotEqual(feed_1, room)
self.assertNotEqual(feed_1, handle_p_1)
self.assertNotEqual(feed_1, handle_s_1)
sdp = self._res["jsep"]["sdp"]
self.assertIsInstance(sdp, str)
# XXX check SDP
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": handle_s_1,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "attached",
"room": room,
"id": feed_1,
},
},
"jsep": {"type": "offer", "sdp": sdp},
},
)
# subscriber #1 answer
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {"request": "start", "room": room, "feed": feed_1},
"jsep": {
"type": "answer",
"sdp": (
"v=0\r\n"
"o=x 123 123 IN IP4 203.0.113.2\r\n"
"c=IN IP4 203.0.113.2\r\n"
"s=foobar\r\n"
"t=0 0\r\n"
"m=audio 7000 RTP/AVP 96\r\n"
"a=rtpmap:96 opus/48000\r\n"
"a=recvonly\r\n"
),
},
"handle_id": handle_s_1,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the attached event
eventloop.run_until_complete(testIJanus(self))
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": handle_s_1,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"started": "ok",
"room": room,
},
},
},
)
handle_p_2 = self.createHandle(token, session)
self.assertNotEqual(handle_p_2, control_handle)
feed_2 = self.createPublisher(
token, session, room, handle_p_2, [{"id": feed_1}]
)
# configure publisher feed #2
eventloop.run_until_complete(
testIOJanus(
self,
{
"janus": "message",
"body": {
"request": "configure",
"room": room,
"feed": feed_2,
"data": False,
"audio": True,
"video": True,
},
"jsep": {
"type": "offer",
"sdp": (
"v=0\r\n"
"o=x 123 123 IN IP4 203.0.113.2\r\n"
"c=IN IP4 0.0.0.0\r\n"
"s=foobar\r\n"
"t=0 0\r\n"
"m=audio 9 RTP/AVP 8 0\r\n"
"a=mid:audio\r\n"
"a=rtpmap:96 opus/48000\r\n"
"a=sendonly\r\n"
),
},
"handle_id": handle_p_2,
"session_id": session,
"token": token,
},
)
)
# ack is received first
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
# followed by the notification for publisher #1
eventloop.run_until_complete(testIJson(self))
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": handle_p_1,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"room": room,
"publishers": [{"id": feed_2}],
},
},
},
)
# followed by the "ok" event for publisher #2
eventloop.run_until_complete(testIJanus(self))
sdp = self._res["jsep"]["sdp"]
self.assertIsInstance(sdp, str)
# XXX check SDP
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": handle_p_2,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"room": room,
"configured": "ok",
"audio_codec": "PCMA",
},
},
"jsep": {"type": "answer", "sdp": sdp},
},
)
# detach publisher #1
eventloop.run_until_complete(
testOJanus(
self,
{
"janus": "detach",
"handle_id": handle_p_1,
"session_id": session,
"token": token,
},
)
)
# unpublished event is received first
eventloop.run_until_complete(testIJson(self))
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": handle_p_2,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"room": room,
"unpublished": feed_1,
},
},
},
)
# followed by leaving event is received first
eventloop.run_until_complete(testIJson(self))
self.assertEqual(
self._res,
{
"janus": "event",
"session_id": session,
"sender": handle_p_2,
"plugindata": {
"plugin": "janus.plugin.videoroom",
"data": {
"videoroom": "event",
"room": room,
"leaving": feed_1,
},
},
},
)
# and finally the success
eventloop.run_until_complete(testIJanus(self))
self.assertEqual(
self._res,
{
"janus": "success",
"session_id": session,
"sender": handle_p_1,
},
)
self.destroyVideoroom(token, session, control_handle, room)
if __name__ == "__main__":
eventloop = asyncio.new_event_loop();
so = tempfile.NamedTemporaryFile(mode="wb", delete=False)
se = tempfile.NamedTemporaryFile(mode="wb", delete=False)
os.environ["GLIB_SLICE"] = "debug-blocks"
proc = subprocess.Popen(
[
os.environ.get("RTPE_BIN"),
"--config-file=none",
"-t",
"-1",
"-i",
"203.0.113.1",
"-f",
"-L",
"7",
"-E",
"--listen-http=127.0.0.1:9191",
"--janus-secret=dfgdfgdvgLyATjHPvckg",
"--delete-delay=0",
],
stdout=so,
stderr=se,
)
code = 255
try:
unittest.main()
code = 0
except SystemExit as e:
if e.code == 0:
code = 0
else:
code = e.code
traceback.print_exc()
except:
traceback.print_exc()
proc.terminate()
proc.wait()
so.close()
se.close()
eventloop.close()
if code == 0:
os.unlink(so.name)
os.unlink(se.name)
else:
print("HINT: Stdout and stderr are {} and {}".format(so.name, se.name))
sys.exit(code)