mirror of https://github.com/sipwise/rtpengine.git
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.
3496 lines
124 KiB
3496 lines
124 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
|
|
|
|
from websockets import connect
|
|
|
|
|
|
eventloop = None
|
|
|
|
|
|
async def make_ws(cls, proto):
|
|
from platform import python_version
|
|
from websockets import __version__
|
|
|
|
if sys.version_info >= (3, 10) and float(__version__) <= 9.1:
|
|
python_v = python_version()
|
|
msg = "python3-websocket {} unsupported in {}".format(__version__, python_v)
|
|
raise unittest.SkipTest(msg)
|
|
for _ in range(1, 300):
|
|
try:
|
|
conn = await connect("ws://127.0.0.1:9191/", subprotocols=[proto])
|
|
return conn
|
|
except FileNotFoundError:
|
|
await asyncio.sleep(0.1)
|
|
|
|
|
|
async def get_ws(cls, proto, num=1):
|
|
cls._ws = []
|
|
for _ in range(num):
|
|
conn = await make_ws(cls, proto)
|
|
cls._ws.append(conn)
|
|
|
|
|
|
async def get_more_ws(cls, proto, num=1):
|
|
for _ in range(num):
|
|
conn = await make_ws(cls, proto)
|
|
cls._ws.append(conn)
|
|
|
|
|
|
async def close_ws(cls):
|
|
for conn in cls._ws:
|
|
await conn.close()
|
|
cls._ws.clear()
|
|
|
|
|
|
async def testIO(self, msg, conn_num=0):
|
|
await self._ws[conn_num].send(msg)
|
|
self._res = await asyncio.wait_for(self._ws[conn_num].recv(), timeout=10)
|
|
|
|
|
|
async def testIOJson(self, msg, conn_num=0):
|
|
await self._ws[conn_num].send(json.dumps(msg))
|
|
self._res = await asyncio.wait_for(self._ws[conn_num].recv(), timeout=10)
|
|
self._res = json.loads(self._res)
|
|
|
|
|
|
async def testIJson(self, conn_num=0):
|
|
self._res = await asyncio.wait_for(self._ws[conn_num].recv(), timeout=10)
|
|
self._res = json.loads(self._res)
|
|
|
|
|
|
async def testIJanus(self, conn_num=0):
|
|
self._res = await asyncio.wait_for(self._ws[conn_num].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, conn_num=0):
|
|
trans = str(uuid.uuid4())
|
|
msg["transaction"] = trans
|
|
self._trans = trans
|
|
await self._ws[conn_num].send(json.dumps(msg))
|
|
await testIJanus(self, conn_num)
|
|
|
|
|
|
async def testOJanus(self, msg, conn_num=0):
|
|
trans = str(uuid.uuid4())
|
|
msg["transaction"] = trans
|
|
self._trans = trans
|
|
await self._ws[conn_num].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(close_ws(cls))
|
|
|
|
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(close_ws(cls))
|
|
|
|
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 TestNGPlain(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
eventloop.run_until_complete(get_ws(cls, "ng-plain.rtpengine.com"))
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
eventloop.run_until_complete(close_ws(cls))
|
|
|
|
def testPing(self):
|
|
eventloop.run_until_complete(testIO(self, "d7:command4:pinge"))
|
|
self.assertEqual(
|
|
self._res,
|
|
b"d6:result4:ponge",
|
|
)
|
|
|
|
|
|
class TestNGPlainJSON(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
eventloop.run_until_complete(get_ws(cls, "ng-plain.rtpengine.com"))
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
eventloop.run_until_complete(close_ws(cls))
|
|
|
|
def testPing(self):
|
|
eventloop.run_until_complete(testIOJson(self, {"command": "ping"}))
|
|
self.assertEqual(
|
|
self._res,
|
|
{"result": "pong"},
|
|
)
|
|
|
|
|
|
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(close_ws(cls))
|
|
|
|
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):
|
|
cls.maxDiff = None
|
|
cls._ws = []
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
eventloop.run_until_complete(close_ws(cls))
|
|
|
|
def startSession(self, conn_num=0):
|
|
# make sure we have a matching connection
|
|
if conn_num >= len(self._ws):
|
|
eventloop.run_until_complete(
|
|
get_more_ws(self, "janus-protocol", conn_num - len(self._ws) + 1)
|
|
)
|
|
|
|
token = str(uuid.uuid4())
|
|
|
|
eventloop.run_until_complete(
|
|
testIOJanus(
|
|
self,
|
|
{
|
|
"janus": "add_token",
|
|
"token": token,
|
|
"admin_secret": "dfgdfgdvgLyATjHPvckg",
|
|
},
|
|
conn_num,
|
|
)
|
|
)
|
|
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",
|
|
},
|
|
conn_num,
|
|
)
|
|
)
|
|
session = self._res["data"]["id"]
|
|
self.assertIsInstance(session, int)
|
|
self.assertEqual(self._res, {"janus": "success", "data": {"id": session}})
|
|
|
|
return (token, session)
|
|
|
|
def startVideoroom(self):
|
|
# start fresh
|
|
self.closeConns()
|
|
|
|
(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 destroySession(self, token, session):
|
|
eventloop.run_until_complete(
|
|
testIOJanus(
|
|
self,
|
|
{
|
|
"janus": "destroy",
|
|
"session_id": session,
|
|
"token": token,
|
|
},
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
self._res,
|
|
{
|
|
"janus": "success",
|
|
"session_id": session,
|
|
},
|
|
)
|
|
|
|
def closeConns(self):
|
|
eventloop.run_until_complete(close_ws(self))
|
|
|
|
def createHandle(self, token, session, conn_num=0):
|
|
eventloop.run_until_complete(
|
|
testIOJanus(
|
|
self,
|
|
{
|
|
"janus": "attach",
|
|
"plugin": "janus.plugin.videoroom",
|
|
"session_id": session,
|
|
"token": token,
|
|
"opaque_id": None,
|
|
},
|
|
conn_num,
|
|
)
|
|
)
|
|
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=[], conn_num=0, feed_id=0
|
|
):
|
|
body = {"request": "join", "ptype": "publisher", "room": room}
|
|
|
|
if feed_id:
|
|
body["id"] = feed_id
|
|
|
|
eventloop.run_until_complete(
|
|
testIOJanus(
|
|
self,
|
|
{
|
|
"janus": "message",
|
|
"body": body,
|
|
"handle_id": handle,
|
|
"session_id": session,
|
|
"token": token,
|
|
},
|
|
conn_num,
|
|
)
|
|
)
|
|
# ack is received first
|
|
self.assertEqual(self._res, {"janus": "ack", "session_id": session})
|
|
# followed by the joined event
|
|
eventloop.run_until_complete(testIJanus(self, conn_num))
|
|
feed = self._res["plugindata"]["data"]["id"]
|
|
self.assertIsInstance(feed, int)
|
|
self.assertNotEqual(feed, session)
|
|
self.assertNotEqual(feed, room)
|
|
self.assertNotEqual(feed, handle)
|
|
if feed_id:
|
|
self.assertEqual(feed_id, feed)
|
|
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",
|
|
"streams": [
|
|
{
|
|
"codec": "PCMA",
|
|
"mid": None,
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"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=- \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: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=tls-id:[0-9a-f]{32}\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,
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
self.destroySession(token, session)
|
|
|
|
def testVideoroomWebRTCAlt(self):
|
|
# alternative usage: publisher == controller, no extra feed_id, no room specified
|
|
|
|
(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 = 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",
|
|
"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",
|
|
"streams": [
|
|
{
|
|
"codec": "PCMA",
|
|
"mid": None,
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"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,
|
|
"streams": [
|
|
{"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(len(self._res["plugindata"]["data"]["streams"]), 1)
|
|
self.assertEqual(feed, self._res["plugindata"]["data"]["streams"][0]["feed_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=- \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: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=tls-id:[0-9a-f]{32}\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,
|
|
"streams": [
|
|
{
|
|
"mindex": 0,
|
|
"feed_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,
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
self.destroySession(token, session)
|
|
|
|
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"
|
|
"a=tls-id:[0-9a-f]{32}\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",
|
|
"streams": [
|
|
{
|
|
"codec": "PCMA",
|
|
"mid": None,
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"jsep": {"type": "answer", "sdp": sdp},
|
|
},
|
|
)
|
|
|
|
self.destroyVideoroom(token, session, control_handle, room)
|
|
self.destroySession(token, session)
|
|
|
|
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",
|
|
"streams": [
|
|
{
|
|
"codec": "PCMA",
|
|
"mid": None,
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"jsep": {"type": "answer", "sdp": sdp},
|
|
},
|
|
)
|
|
|
|
self.destroyVideoroom(token, session, control_handle, room)
|
|
self.destroySession(token, session)
|
|
|
|
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"
|
|
"a=tls-id:[0-9a-f]{32}\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",
|
|
"streams": [
|
|
{
|
|
"codec": "PCMA",
|
|
"mid": "audio",
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"jsep": {"type": "answer", "sdp": sdp},
|
|
},
|
|
)
|
|
|
|
self.destroyVideoroom(token, session, control_handle, room)
|
|
self.destroySession(token, session)
|
|
|
|
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",
|
|
"streams": [
|
|
{
|
|
"codec": "PCMA",
|
|
"mid": "audio",
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"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},
|
|
)
|
|
|
|
# wait for media event
|
|
eventloop.run_until_complete(testIJson(self))
|
|
self.assertEqual(
|
|
self._res,
|
|
{
|
|
"janus": "media",
|
|
"session_id": session,
|
|
"sender": pub_handle,
|
|
"type": "audio",
|
|
"mid": "audio",
|
|
"receiving": True,
|
|
},
|
|
)
|
|
|
|
self.destroyVideoroom(token, session, control_handle, room)
|
|
self.destroySession(token, session)
|
|
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"
|
|
"a=ice-options:trickle\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=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=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 useinbandfec=1; minptime=10\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=tls-id:[0-9a-f]{32}\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=tls-id:[0-9a-f]{32}\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",
|
|
"streams": [
|
|
{
|
|
"codec": "opus",
|
|
"mid": "0",
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
{
|
|
"codec": "VP8",
|
|
"mid": "1",
|
|
"mindex": 1,
|
|
"type": "video",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"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=- \d+ \d+ IN IP4 203.0.113.1\r\n"
|
|
"s=rtpengine.*?\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=mid:0\r\n"
|
|
"a=rtpmap:111 opus/48000/2\r\n"
|
|
"a=fmtp:111 useinbandfec=1; minptime=10\r\n"
|
|
"a=rtcp-fb:111 transport-cc\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=sendonly\r\n"
|
|
"a=rtcp-mux\r\n"
|
|
"a=setup:actpass\r\n"
|
|
"a=fingerprint:sha-256 .{95}\r\n"
|
|
"a=tls-id:[0-9a-f]{32}\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=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=sendonly\r\n"
|
|
"a=rtcp-mux\r\n"
|
|
"a=setup:actpass\r\n"
|
|
"a=fingerprint:sha-256 .{95}\r\n"
|
|
"a=tls-id:[0-9a-f]{32}\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)
|
|
self.destroySession(token, session)
|
|
|
|
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, feed_id=123)
|
|
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",
|
|
"streams": [
|
|
{
|
|
"codec": "PCMA",
|
|
"mid": "audio",
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"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=- \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=sendonly\r\n"
|
|
"a=rtcp-mux\r\n"
|
|
"a=setup:actpass\r\n"
|
|
"a=fingerprint:sha-256 .{95}\r\n"
|
|
"a=tls-id:[0-9a-f]{32}\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\\\x2a\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)
|
|
self.destroySession(token, session)
|
|
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",
|
|
"streams": [
|
|
{
|
|
"codec": "opus",
|
|
"mid": None,
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"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,
|
|
"audio_codec": "opus",
|
|
"streams": [
|
|
{
|
|
"codec": "opus",
|
|
"mid": None,
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
)
|
|
|
|
# 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,
|
|
"audio_codec": "PCMA",
|
|
"streams": [
|
|
{
|
|
"codec": "PCMA",
|
|
"mid": "audio",
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
}
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
# 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",
|
|
"streams": [
|
|
{
|
|
"codec": "PCMA",
|
|
"mid": "audio",
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"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)
|
|
self.destroySession(token, session)
|
|
|
|
def testVideoroomMultiConn(self):
|
|
(token, session_1, control_handle, room) = self.startVideoroom()
|
|
|
|
# publisher #1 with its own connection and session
|
|
(token, session_2) = self.startSession(1)
|
|
self.assertNotEqual(session_1, session_2)
|
|
|
|
pub_handle_1 = self.createHandle(token, session_2, 1)
|
|
self.assertNotEqual(pub_handle_1, control_handle)
|
|
|
|
# create feed for publisher #1
|
|
feed_1 = self.createPublisher(token, session_2, room, pub_handle_1, [], 1)
|
|
|
|
pub_sock_1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
pub_sock_1.settimeout(0.1)
|
|
pub_sock_1.bind(("203.0.113.6", 31000))
|
|
|
|
# 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": False,
|
|
},
|
|
"jsep": {
|
|
"type": "offer",
|
|
"sdp": (
|
|
"v=0\r\n"
|
|
"o=x 123 123 IN IP4 203.0.113.6\r\n"
|
|
"c=IN IP4 203.0.113.6\r\n"
|
|
"s=foobar\r\n"
|
|
"t=0 0\r\n"
|
|
"m=audio 31000 RTP/AVP 96 8 0\r\n"
|
|
"a=rtpmap:96 opus/48000/2\r\n"
|
|
"a=sendonly\r\n"
|
|
"a=mid:a\r\n"
|
|
),
|
|
},
|
|
"handle_id": pub_handle_1,
|
|
"session_id": session_2,
|
|
"token": token,
|
|
},
|
|
1,
|
|
)
|
|
)
|
|
# ack is received first
|
|
self.assertEqual(self._res, {"janus": "ack", "session_id": session_2})
|
|
# followed by the event notification
|
|
eventloop.run_until_complete(testIJanus(self, 1))
|
|
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 96\r\n"
|
|
"c=IN IP4 203.0.113.1\r\n"
|
|
"a=mid:a\r\n"
|
|
"a=rtpmap:96 opus/48000/2\r\n"
|
|
"a=recvonly\r\n"
|
|
"a=rtcp:\d+\r\n$",
|
|
re.DOTALL,
|
|
)
|
|
self.assertRegex(sdp, match_re)
|
|
matches = match_re.search(sdp)
|
|
pub_port_1 = int(matches.group(1))
|
|
|
|
self.assertEqual(
|
|
self._res,
|
|
{
|
|
"janus": "event",
|
|
"session_id": session_2,
|
|
"sender": pub_handle_1,
|
|
"plugindata": {
|
|
"plugin": "janus.plugin.videoroom",
|
|
"data": {
|
|
"videoroom": "event",
|
|
"room": room,
|
|
"configured": "ok",
|
|
"audio_codec": "opus",
|
|
"streams": [
|
|
{
|
|
"codec": "opus",
|
|
"mid": "a",
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"jsep": {"type": "answer", "sdp": sdp},
|
|
},
|
|
)
|
|
|
|
pub_sock_1.connect(("203.0.113.1", pub_port_1))
|
|
|
|
# publisher #2 with its own connection and session
|
|
(token, session_3) = self.startSession(2)
|
|
self.assertNotEqual(session_1, session_3)
|
|
self.assertNotEqual(session_2, session_3)
|
|
|
|
pub_handle_2 = self.createHandle(token, session_3, 2)
|
|
self.assertNotEqual(pub_handle_2, pub_handle_1)
|
|
self.assertNotEqual(pub_handle_2, control_handle)
|
|
|
|
# create feed for publisher #2
|
|
feed_2 = self.createPublisher(
|
|
token,
|
|
session_3,
|
|
room,
|
|
pub_handle_2,
|
|
[
|
|
{
|
|
"id": feed_1,
|
|
"audio_codec": "opus",
|
|
"streams": [
|
|
{
|
|
"codec": "opus",
|
|
"mid": "a",
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
2,
|
|
)
|
|
|
|
pub_sock_2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
pub_sock_2.settimeout(0.1)
|
|
pub_sock_2.bind(("203.0.113.6", 32000))
|
|
|
|
# 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": False,
|
|
},
|
|
"jsep": {
|
|
"type": "offer",
|
|
"sdp": (
|
|
"v=0\r\n"
|
|
"o=x 123 123 IN IP4 203.0.113.6\r\n"
|
|
"c=IN IP4 203.0.113.6\r\n"
|
|
"s=foobar\r\n"
|
|
"t=0 0\r\n"
|
|
"m=audio 32000 RTP/AVP 96 8 0\r\n"
|
|
"a=rtpmap:96 opus/48000/2\r\n"
|
|
"a=sendonly\r\n"
|
|
"a=mid:a\r\n"
|
|
),
|
|
},
|
|
"handle_id": pub_handle_2,
|
|
"session_id": session_3,
|
|
"token": token,
|
|
},
|
|
2,
|
|
)
|
|
)
|
|
# ack is received first
|
|
self.assertEqual(self._res, {"janus": "ack", "session_id": session_3})
|
|
# followed by the event notification
|
|
eventloop.run_until_complete(testIJanus(self, 2))
|
|
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 96\r\n"
|
|
"c=IN IP4 203.0.113.1\r\n"
|
|
"a=mid:a\r\n"
|
|
"a=rtpmap:96 opus/48000/2\r\n"
|
|
"a=recvonly\r\n"
|
|
"a=rtcp:\d+\r\n$",
|
|
re.DOTALL,
|
|
)
|
|
self.assertRegex(sdp, match_re)
|
|
matches = match_re.search(sdp)
|
|
pub_port_2 = int(matches.group(1))
|
|
|
|
self.assertEqual(
|
|
self._res,
|
|
{
|
|
"janus": "event",
|
|
"session_id": session_3,
|
|
"sender": pub_handle_2,
|
|
"plugindata": {
|
|
"plugin": "janus.plugin.videoroom",
|
|
"data": {
|
|
"videoroom": "event",
|
|
"room": room,
|
|
"configured": "ok",
|
|
"audio_codec": "opus",
|
|
"streams": [
|
|
{
|
|
"codec": "opus",
|
|
"mid": "a",
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"jsep": {"type": "answer", "sdp": sdp},
|
|
},
|
|
)
|
|
|
|
pub_sock_2.connect(("203.0.113.1", pub_port_2))
|
|
|
|
# publisher #1 receives notification
|
|
eventloop.run_until_complete(testIJson(self, 1))
|
|
self.assertEqual(
|
|
self._res,
|
|
{
|
|
"janus": "event",
|
|
"plugindata": {
|
|
"data": {
|
|
"publishers": [
|
|
{
|
|
"audio_codec": "opus",
|
|
"id": feed_2,
|
|
"streams": [
|
|
{
|
|
"codec": "opus",
|
|
"mid": "a",
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
}
|
|
],
|
|
}
|
|
],
|
|
"room": room,
|
|
"videoroom": "event",
|
|
},
|
|
"plugin": "janus.plugin.videoroom",
|
|
},
|
|
"sender": pub_handle_1,
|
|
"session_id": session_2,
|
|
},
|
|
)
|
|
|
|
# unpublish
|
|
eventloop.run_until_complete(
|
|
testIOJanus(
|
|
self,
|
|
{
|
|
"janus": "message",
|
|
"body": {
|
|
"request": "unpublish",
|
|
},
|
|
"handle_id": pub_handle_1,
|
|
"session_id": session_2,
|
|
"token": token,
|
|
},
|
|
1,
|
|
)
|
|
)
|
|
# ack is received first
|
|
self.assertEqual(self._res, {"janus": "ack", "session_id": session_2})
|
|
# followed by the event notification
|
|
eventloop.run_until_complete(testIJanus(self, 1))
|
|
|
|
self.assertEqual(
|
|
self._res,
|
|
{
|
|
"janus": "event",
|
|
"session_id": session_2,
|
|
"sender": pub_handle_1,
|
|
"plugindata": {
|
|
"plugin": "janus.plugin.videoroom",
|
|
"data": {
|
|
"videoroom": "event",
|
|
"room": room,
|
|
"unpublished": "ok",
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
# followed by event in the other session
|
|
eventloop.run_until_complete(testIJson(self, 2))
|
|
|
|
self.assertEqual(
|
|
self._res,
|
|
{
|
|
"janus": "event",
|
|
"session_id": session_3,
|
|
"sender": pub_handle_2,
|
|
"plugindata": {
|
|
"plugin": "janus.plugin.videoroom",
|
|
"data": {
|
|
"videoroom": "event",
|
|
"room": room,
|
|
"unpublished": feed_1,
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
# destroy session #2
|
|
self.destroySession(token, session_2)
|
|
# success is received first
|
|
self.assertEqual(self._res, {"janus": "success", "session_id": session_2})
|
|
|
|
# followed by events in the other session
|
|
eventloop.run_until_complete(testIJson(self, 2))
|
|
|
|
self.assertEqual(
|
|
self._res,
|
|
{
|
|
"janus": "event",
|
|
"session_id": session_3,
|
|
"sender": pub_handle_2,
|
|
"plugindata": {
|
|
"plugin": "janus.plugin.videoroom",
|
|
"data": {
|
|
"videoroom": "event",
|
|
"room": room,
|
|
"unpublished": feed_1,
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
eventloop.run_until_complete(testIJson(self, 2))
|
|
|
|
self.assertEqual(
|
|
self._res,
|
|
{
|
|
"janus": "event",
|
|
"session_id": session_3,
|
|
"sender": pub_handle_2,
|
|
"plugindata": {
|
|
"plugin": "janus.plugin.videoroom",
|
|
"data": {
|
|
"videoroom": "event",
|
|
"room": room,
|
|
"leaving": feed_1,
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
pub_sock_1.close()
|
|
pub_sock_2.close()
|
|
self.destroyVideoroom(token, session_1, control_handle, room)
|
|
self.destroySession(token, session_1)
|
|
self.destroySession(token, session_3)
|
|
|
|
def testVideoroomMute(self):
|
|
(token, session, control_handle, room) = self.startVideoroom()
|
|
|
|
pub_handle_1 = self.createHandle(token, session)
|
|
self.assertNotEqual(pub_handle_1, control_handle)
|
|
|
|
# create feed for publisher #1
|
|
feed_1 = self.createPublisher(token, session, room, pub_handle_1)
|
|
self.assertNotEqual(feed_1, control_handle)
|
|
|
|
pub_sock_audio = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
pub_sock_audio.settimeout(0.1)
|
|
pub_sock_audio.bind(("203.0.113.2", 31000))
|
|
|
|
pub_sock_video = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
pub_sock_video.settimeout(0.1)
|
|
pub_sock_video.bind(("203.0.113.2", 31100))
|
|
|
|
# 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 31000 RTP/AVP 96 8 0\r\n"
|
|
"a=rtpmap:96 opus/48000/2\r\n"
|
|
"a=sendonly\r\n"
|
|
"a=mid:a\r\n"
|
|
"m=video 31100 RTP/AVP 97\r\n"
|
|
"a=rtpmap:97 VP9/90000\r\n"
|
|
"a=sendonly\r\n"
|
|
"a=mid:v\r\n"
|
|
),
|
|
},
|
|
"handle_id": pub_handle_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)
|
|
|
|
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 96\r\n"
|
|
"c=IN IP4 203.0.113.1\r\n"
|
|
"a=mid:a\r\n"
|
|
"a=rtpmap:96 opus/48000/2\r\n"
|
|
"a=recvonly\r\n"
|
|
"a=rtcp:\d+\r\n"
|
|
"m=video (\d+) RTP/AVP 97\r\n"
|
|
"c=IN IP4 203.0.113.1\r\n"
|
|
"a=mid:v\r\n"
|
|
"a=rtpmap:97 VP9/90000\r\n"
|
|
"a=recvonly\r\n"
|
|
"a=rtcp:\d+\r\n$",
|
|
re.DOTALL,
|
|
)
|
|
self.assertRegex(sdp, match_re)
|
|
matches = match_re.search(sdp)
|
|
pub_port_audio = int(matches.group(1))
|
|
pub_port_video = int(matches.group(2))
|
|
|
|
self.assertEqual(
|
|
self._res,
|
|
{
|
|
"janus": "event",
|
|
"session_id": session,
|
|
"sender": pub_handle_1,
|
|
"plugindata": {
|
|
"plugin": "janus.plugin.videoroom",
|
|
"data": {
|
|
"videoroom": "event",
|
|
"room": room,
|
|
"configured": "ok",
|
|
"audio_codec": "opus",
|
|
"video_codec": "VP9",
|
|
"streams": [
|
|
{
|
|
"codec": "opus",
|
|
"mid": "a",
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
{
|
|
"codec": "VP9",
|
|
"mid": "v",
|
|
"mindex": 1,
|
|
"type": "video",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
"jsep": {"type": "answer", "sdp": sdp},
|
|
},
|
|
)
|
|
|
|
pub_sock_audio.connect(("203.0.113.1", pub_port_audio))
|
|
pub_sock_video.connect(("203.0.113.1", pub_port_video))
|
|
|
|
# send fake RTP to trigger event
|
|
m = pub_sock_audio.send(
|
|
b"\x80\x60\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_1},
|
|
)
|
|
|
|
# wait for audio media
|
|
eventloop.run_until_complete(testIJson(self))
|
|
self.assertEqual(
|
|
self._res,
|
|
{
|
|
"janus": "media",
|
|
"session_id": session,
|
|
"sender": pub_handle_1,
|
|
"type": "audio",
|
|
"mid": "a",
|
|
"receiving": True,
|
|
},
|
|
)
|
|
|
|
# repeat for video
|
|
m = pub_sock_video.send(
|
|
b"\x80\x61\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"
|
|
)
|
|
|
|
eventloop.run_until_complete(testIJson(self))
|
|
self.assertEqual(
|
|
self._res,
|
|
{
|
|
"janus": "media",
|
|
"session_id": session,
|
|
"sender": pub_handle_1,
|
|
"type": "video",
|
|
"mid": "v",
|
|
"receiving": True,
|
|
},
|
|
)
|
|
|
|
sub_sock_audio = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
sub_sock_audio.settimeout(0.1)
|
|
sub_sock_audio.bind(("203.0.113.3", 31010))
|
|
|
|
sub_sock_video = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
sub_sock_video.settimeout(0.1)
|
|
sub_sock_video.bind(("203.0.113.3", 31110))
|
|
|
|
sub_handle_1 = self.createHandle(token, session)
|
|
self.assertNotEqual(sub_handle_1, pub_handle_1)
|
|
self.assertNotEqual(sub_handle_1, control_handle)
|
|
|
|
# subscriber #1 joins publisher #1
|
|
eventloop.run_until_complete(
|
|
testIOJanus(
|
|
self,
|
|
{
|
|
"janus": "message",
|
|
"body": {
|
|
"request": "join",
|
|
"ptype": "subscriber",
|
|
"plain": True,
|
|
"room": room,
|
|
"feed": feed_1,
|
|
},
|
|
"handle_id": sub_handle_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, pub_handle_1)
|
|
self.assertNotEqual(feed_1, sub_handle_1)
|
|
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 96\r\n"
|
|
"c=IN IP4 203.0.113.1\r\n"
|
|
"a=mid:a\r\n"
|
|
"a=rtpmap:96 opus/48000/2\r\n"
|
|
"a=sendonly\r\n"
|
|
"a=rtcp:\d+\r\n"
|
|
"m=video (\d+) RTP/AVP 97\r\n"
|
|
"c=IN IP4 203.0.113.1\r\n"
|
|
"a=mid:v\r\n"
|
|
"a=rtpmap:97 VP9/90000\r\n"
|
|
"a=sendonly\r\n"
|
|
"a=rtcp:\d+\r\n$",
|
|
re.DOTALL,
|
|
)
|
|
self.assertRegex(sdp, match_re)
|
|
matches = match_re.search(sdp)
|
|
sub_port_audio = int(matches.group(1))
|
|
sub_port_video = int(matches.group(2))
|
|
|
|
self.assertEqual(
|
|
self._res,
|
|
{
|
|
"janus": "event",
|
|
"session_id": session,
|
|
"sender": sub_handle_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.3\r\n"
|
|
"s=foobar\r\n"
|
|
"t=0 0\r\n"
|
|
"m=audio 31010 RTP/AVP 96\r\n"
|
|
"a=rtpmap:96 opus/48000/2\r\n"
|
|
"a=mid:a\r\n"
|
|
"a=recvonly\r\n"
|
|
"m=video 31110 RTP/AVP 97\r\n"
|
|
"a=rtpmap:97 VP9/90000\r\n"
|
|
"a=mid:v\r\n"
|
|
"a=recvonly\r\n"
|
|
),
|
|
},
|
|
"handle_id": sub_handle_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": sub_handle_1,
|
|
"plugindata": {
|
|
"plugin": "janus.plugin.videoroom",
|
|
"data": {
|
|
"videoroom": "event",
|
|
"started": "ok",
|
|
"room": room,
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
sub_sock_audio.connect(("203.0.113.1", sub_port_audio))
|
|
sub_sock_video.connect(("203.0.113.1", sub_port_video))
|
|
|
|
# check forwarding
|
|
m = pub_sock_audio.send(
|
|
b"\x80\x60\x12\x34\x43\x32\x12\x45\x65\x45\x34\x23\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
)
|
|
m = pub_sock_video.send(
|
|
b"\x80\x61\x12\x34\x43\x32\x12\x45\x65\x45\x34\x23\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
)
|
|
|
|
m = sub_sock_audio.recv(1000)
|
|
self.assertEqual(
|
|
m,
|
|
b"\x80\x60\x12\x34\x43\x32\x12\x45\x65\x45\x34\x23\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
|
)
|
|
|
|
m = sub_sock_video.recv(1000)
|
|
self.assertEqual(
|
|
m,
|
|
b"\x80\x61\x12\x34\x43\x32\x12\x45\x65\x45\x34\x23\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
|
)
|
|
|
|
# mute audio
|
|
eventloop.run_until_complete(
|
|
testIOJanus(
|
|
self,
|
|
{
|
|
"janus": "message",
|
|
"body": {
|
|
"request": "configure",
|
|
"room": room,
|
|
"feed": feed_1,
|
|
"data": False,
|
|
"audio": False,
|
|
"video": True,
|
|
},
|
|
"handle_id": pub_handle_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": "event",
|
|
"session_id": session,
|
|
"sender": pub_handle_1,
|
|
"plugindata": {
|
|
"plugin": "janus.plugin.videoroom",
|
|
"data": {
|
|
"videoroom": "event",
|
|
"room": room,
|
|
"configured": "ok",
|
|
"video_codec": "VP9",
|
|
"streams": [
|
|
{
|
|
"disabled": True,
|
|
"mid": "a",
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
{
|
|
"codec": "VP9",
|
|
"mid": "v",
|
|
"mindex": 1,
|
|
"type": "video",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
# check forwarding
|
|
m = pub_sock_audio.send(
|
|
b"\x80\x60\x12\x34\x43\x32\x12\x45\x65\x45\x34\x23\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
)
|
|
m = pub_sock_video.send(
|
|
b"\x80\x61\x12\x34\x43\x32\x12\x45\x65\x45\x34\x23\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
)
|
|
|
|
m = None
|
|
try:
|
|
m = sub_sock_audio.recv(1000)
|
|
except (TimeoutError, socket.timeout):
|
|
pass
|
|
self.assertIsNone(m)
|
|
|
|
m = sub_sock_video.recv(1000)
|
|
self.assertEqual(
|
|
m,
|
|
b"\x80\x61\x12\x34\x43\x32\x12\x45\x65\x45\x34\x23\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
|
)
|
|
|
|
# unmute audio
|
|
eventloop.run_until_complete(
|
|
testIOJanus(
|
|
self,
|
|
{
|
|
"janus": "message",
|
|
"body": {
|
|
"request": "configure",
|
|
"room": room,
|
|
"feed": feed_1,
|
|
"data": False,
|
|
"audio": True,
|
|
"video": True,
|
|
},
|
|
"handle_id": pub_handle_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": "event",
|
|
"session_id": session,
|
|
"sender": pub_handle_1,
|
|
"plugindata": {
|
|
"plugin": "janus.plugin.videoroom",
|
|
"data": {
|
|
"videoroom": "event",
|
|
"room": room,
|
|
"configured": "ok",
|
|
"audio_codec": "opus",
|
|
"video_codec": "VP9",
|
|
"streams": [
|
|
{
|
|
"codec": "opus",
|
|
"mid": "a",
|
|
"mindex": 0,
|
|
"type": "audio",
|
|
},
|
|
{
|
|
"codec": "VP9",
|
|
"mid": "v",
|
|
"mindex": 1,
|
|
"type": "video",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
# check forwarding
|
|
m = pub_sock_audio.send(
|
|
b"\x80\x60\x12\x34\x43\x32\x12\x45\x65\x45\x34\x23\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
)
|
|
m = pub_sock_video.send(
|
|
b"\x80\x61\x12\x34\x43\x32\x12\x45\x65\x45\x34\x23\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
)
|
|
|
|
m = sub_sock_audio.recv(1000)
|
|
self.assertEqual(
|
|
m,
|
|
b"\x80\x60\x12\x34\x43\x32\x12\x45\x65\x45\x34\x23\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
|
)
|
|
|
|
m = sub_sock_video.recv(1000)
|
|
self.assertEqual(
|
|
m,
|
|
b"\x80\x61\x12\x34\x43\x32\x12\x45\x65\x45\x34\x23\x00\x00\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
|
)
|
|
|
|
self.destroyVideoroom(token, session, control_handle, room)
|
|
self.destroySession(token, session)
|
|
pub_sock_audio.close()
|
|
pub_sock_video.close()
|
|
sub_sock_audio.close()
|
|
sub_sock_video.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
eventloop = asyncio.new_event_loop()
|
|
|
|
so = None
|
|
se = None
|
|
proc = None
|
|
|
|
if not os.environ.get("RTPE_TEST_NO_LAUNCH"):
|
|
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()
|
|
|
|
if proc:
|
|
proc.terminate()
|
|
proc.wait()
|
|
|
|
so.close()
|
|
se.close()
|
|
|
|
eventloop.close()
|
|
|
|
if proc:
|
|
if code == 0 and not os.environ.get("RETAIN_LOGS"):
|
|
os.unlink(so.name)
|
|
os.unlink(se.name)
|
|
else:
|
|
print("HINT: Stdout and stderr are {} and {}".format(so.name, se.name))
|
|
sys.exit(code)
|