question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Janus example] Can't setLocalDescription when request 'subscriber' to Janus server

See original GitHub issue

I try to get media this janus example, but it fail when i set answer to local description.

Log

Sdp send:
v=0
o=- 3768613065 3768613065 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE 0
a=msid-semantic:WMS *
m=video 50342 UDP/TLS/RTP/SAVPF 97 98 99 100 101 102
c=IN IP4 192.168.1.106
a=sendrecv
a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=mid:0
a=msid:96b6d97a-0ada-4f90-9d75-ae3c7cbc14d0 bd6a1f9b-008a-4316-a9de-3f1a32e3269d
a=rtcp:9 IN IP4 0.0.0.0
a=rtcp-mux
a=ssrc-group:FID 2105842630 265384714
a=ssrc:2105842630 cname:{f52d8811-2b40-4e5d-818f-65525f6a05d2}
a=ssrc:265384714 cname:{f52d8811-2b40-4e5d-818f-65525f6a05d2}
a=rtpmap:97 VP8/90000
a=rtcp-fb:97 nack
a=rtcp-fb:97 nack pli
a=rtcp-fb:97 goog-remb
a=rtpmap:98 rtx/90000
a=fmtp:98 apt=97
a=rtpmap:99 H264/90000
a=rtcp-fb:99 nack
a=rtcp-fb:99 nack pli
a=rtcp-fb:99 goog-remb
a=fmtp:99 packetization-mode=1;level-asymmetry-allowed=1;profile-level-id=42001f
a=rtpmap:100 rtx/90000
a=fmtp:100 apt=99
a=rtpmap:101 H264/90000
a=rtcp-fb:101 nack
a=rtcp-fb:101 nack pli
a=rtcp-fb:101 goog-remb
a=fmtp:101 packetization-mode=1;level-asymmetry-allowed=1;profile-level-id=42e01f
a=rtpmap:102 rtx/90000
a=fmtp:102 apt=101
a=candidate:d4bad81fb3c6405654f15a07a2301334 1 udp 2130706431 192.168.1.106 50342 typ host
a=candidate:e14221d1d547f61a5dfb3ba5ecb25c41 1 udp 1694498815 118.69.55.211 50342 typ srflx raddr 192.168.1.106 rport 50342
a=end-of-candidates
a=ice-ufrag:v7e4
a=ice-pwd:hlvgnGDIncSavgWZvpbgGI
a=fingerprint:sha-256 A2:59:BC:78:A1:AC:CA:D8:10:64:BE:39:56:EF:08:CF:A0:40:3F:12:51:9A:E8:FA:AB:BB:F4:A9:18:B2:A4:11
a=setup:actpass

Apply answer:
v=0
o=- 3768613065 3768613065 IN IP4 192.168.1.101
s=VideoRoom 1234
t=0 0
a=group:BUNDLE 0
a=msid-semantic: WMS janus
m=video 9 UDP/TLS/RTP/SAVPF 97
c=IN IP4 192.168.1.101
a=recvonly
a=mid:0
a=rtcp-mux
a=ice-ufrag:TkXI
a=ice-pwd:RGGUPol4QHtXf0mJmLrjCJ
a=ice-options:trickle
a=fingerprint:sha-256 AC:70:3C:28:19:F2:EE:87:46:EE:46:BC:80:8A:56:7B:FE:AF:75:80:FC:9C:D3:11:6D:61:AD:D9:48:5A:8A:4D
a=setup:active
a=rtpmap:97 VP8/90000
a=rtcp-fb:97 ccm fir
a=rtcp-fb:97 nack
a=rtcp-fb:97 nack pli
a=rtcp-fb:97 goog-remb
a=rtcp-fb:97 transport-cc
a=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid
a=candidate:1 1 udp 2013266431 192.168.1.101 39814 typ host
a=end-of-candidates

Subscribe:
v=0
o=- 1559622640473327 1 IN IP4 192.168.1.101
s=VideoRoom 1234
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS janus
m=audio 9 UDP/TLS/RTP/SAVPF 111
c=IN IP4 192.168.1.101
a=sendonly
a=mid:audio
a=rtcp-mux
a=ice-ufrag:XbSE
a=ice-pwd:UQk34IkY61TDmw0rRjLERd
a=ice-options:trickle
a=fingerprint:sha-256 AC:70:3C:28:19:F2:EE:87:46:EE:46:BC:80:8A:56:7B:FE:AF:75:80:FC:9C:D3:11:6D:61:AD:D9:48:5A:8A:4D
a=setup:actpass
a=rtpmap:111 opus/48000/2
a=ssrc:3748566445 cname:janus
a=ssrc:3748566445 msid:janus janusa0
a=ssrc:3748566445 mslabel:janus
a=ssrc:3748566445 label:janusa0
a=candidate:1 1 udp 2013266431 192.168.1.101 58954 typ host
a=end-of-candidates
m=video 9 UDP/TLS/RTP/SAVPF 96
c=IN IP4 192.168.1.101
a=sendonly
a=mid:video
a=rtcp-mux
a=ice-ufrag:XbSE
a=ice-pwd:UQk34IkY61TDmw0rRjLERd
a=ice-options:trickle
a=fingerprint:sha-256 AC:70:3C:28:19:F2:EE:87:46:EE:46:BC:80:8A:56:7B:FE:AF:75:80:FC:9C:D3:11:6D:61:AD:D9:48:5A:8A:4D
a=setup:actpass
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtcp-fb:96 goog-remb
a=ssrc:333201977 cname:janus
a=ssrc:333201977 msid:janus janusv0
a=ssrc:333201977 mslabel:janus
a=ssrc:333201977 label:janusv0
a=candidate:1 1 udp 2013266431 192.168.1.101 58954 typ host
a=end-of-candidates

Create Answer:
v=0
o=- 3768613065 3768613065 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic:WMS *
m=audio 9 UDP/TLS/RTP/SAVPF 111
c=IN IP4 0.0.0.0
a=recvonly
a=mid:audio
a=msid:96b6d97a-0ada-4f90-9d75-ae3c7cbc14d0 b2382091-46c3-4ca4-9de2-4c424a1f32c0
a=rtcp:9 IN IP4 0.0.0.0
a=rtcp-mux
a=ssrc:2457074259 cname:{f52d8811-2b40-4e5d-818f-65525f6a05d2}
a=rtpmap:111 opus/48000/2
a=ice-ufrag:ED1v
a=ice-pwd:ErbuIgtrv8g8vYgREXFjJp
a=fingerprint:sha-256 A2:59:BC:78:A1:AC:CA:D8:10:64:BE:39:56:EF:08:CF:A0:40:3F:12:51:9A:E8:FA:AB:BB:F4:A9:18:B2:A4:11
a=setup:active
m=video 9 UDP/TLS/RTP/SAVPF 96
c=IN IP4 0.0.0.0
a=recvonly
a=mid:video
a=msid:96b6d97a-0ada-4f90-9d75-ae3c7cbc14d0 bd9344e7-c1b2-4c17-941e-c7859853305f
a=rtcp:9 IN IP4 0.0.0.0
a=rtcp-mux
a=ssrc:3592921475 cname:{f52d8811-2b40-4e5d-818f-65525f6a05d2}
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtcp-fb:96 goog-remb
a=ice-ufrag:ED1v
a=ice-pwd:ErbuIgtrv8g8vYgREXFjJp
a=fingerprint:sha-256 A2:59:BC:78:A1:AC:CA:D8:10:64:BE:39:56:EF:08:CF:A0:40:3F:12:51:9A:E8:FA:AB:BB:F4:A9:18:B2:A4:11
a=setup:active

Track audio received
Track video received
Traceback (most recent call last):
  File ".\janus.py", line 264, in <module>
    run(pc=pc, player=player, recorder=recorder, room=args.room, session=session)
  File "C:\Python37\lib\asyncio\base_events.py", line 573, in run_until_complete
    return future.result()
  File ".\janus.py", line 215, in run
    await subscribe(i["id"])
  File ".\janus.py", line 199, in subscribe
    await pc.setLocalDescription(answer)
  File "C:\Users\LAPTOP MSI\Documents\.env37\lib\site-packages\aiortc\rtcpeerconnection.py", line 618, in setLocalDescription
    t._currentDirection = and_direction(t.direction, t._offerDirection)
  File "C:\Users\LAPTOP MSI\Documents\.env37\lib\site-packages\aiortc\rtcpeerconnection.py", line 207, in and_direction
    return sdp.DIRECTIONS[sdp.DIRECTIONS.index(a) & sdp.DIRECTIONS.index(b)]
ValueError: None is not in list
Task was destroyed but it is pending!
task: <Task pending coro=<MediaRecorder.__run_track() running at C:\Users\LAPTOP MSI\Documents\.env37\lib\site-packages\aiortc\contrib\media.py:361> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x000002C189788738>()]>>

Code

I just change run function and add recorder = MediaRecorder(args.record_to) to main

import os
import sys

# Fix not found ffmpeg dll
_ffmpeg = os.path.join(sys.prefix, 'share', 'ffpyplayer', 'ffmpeg', 'bin')
os.environ["PATH"] += os.pathsep + _ffmpeg

import argparse
import asyncio
import logging
import random
import string
import time

import aiohttp

from aiortc import RTCPeerConnection, RTCSessionDescription, VideoStreamTrack
from aiortc.contrib.media import MediaPlayer, MediaRecorder


def transaction_id():
    return "".join(random.choice(string.ascii_letters) for x in range(12))


class JanusPlugin:
    def __init__(self, session, url):
        self._queue = asyncio.Queue()
        self._session = session
        self._url = url

    async def send(self, payload):
        message = {"janus": "message", "transaction": transaction_id()}
        message.update(payload)
        async with self._session._http.post(self._url, json=message) as response:
            data = await response.json()

            if data["janus"] == "success":
                return data
            elif data["janus"] == "error":
                return data
            elif data["janus"] == "ack":
                pass

        response = await self._queue.get()
        self.has_request = False
        assert response["transaction"] == message["transaction"]
        return response


class JanusSession:
    def __init__(self, url):
        self._http = None
        self._poll_task = None
        self._plugins = {}
        self._root_url = url
        self._session_url = None

    async def attach(self, plugin):
        message = {"janus": "attach", "plugin": plugin, "transaction": transaction_id()}
        async with self._http.post(self._session_url, json=message) as response:
            data = await response.json()
            assert data["janus"] == "success"
            plugin_id = data["data"]["id"]
            plugin = JanusPlugin(self, self._session_url + "/" + str(plugin_id))
            self._plugins[plugin_id] = plugin
            return plugin

    async def create(self):
        self._http = aiohttp.ClientSession()
        message = {"janus": "create", "transaction": transaction_id()}
        async with self._http.post(self._root_url, json=message) as response:
            data = await response.json()
            assert data["janus"] == "success"
            session_id = data["data"]["id"]
            self._session_url = self._root_url + "/" + str(session_id)

        self._poll_task = asyncio.ensure_future(self._poll())

    async def destroy(self):
        if self._poll_task:
            self._poll_task.cancel()
            self._poll_task = None

        if self._session_url:
            message = {"janus": "destroy", "transaction": transaction_id()}
            async with self._http.post(self._session_url, json=message) as response:
                data = await response.json()
                assert data["janus"] == "success"
            self._session_url = None

        if self._http:
            await self._http.close()
            self._http = None

    async def _poll(self):
        while True:
            params = {"maxev": 1, "rid": int(time.time() * 1000)}
            async with self._http.get(self._session_url, params=params) as response:
                data = await response.json()
                if data["janus"] == "event":
                    plugin = self._plugins.get(data["sender"], None)
                    if plugin:
                        await plugin._queue.put(data)
                    else:
                        print(data)


async def run(pc, player, recorder, room, session):
    await session.create()

    @pc.on("track")
    async def on_track(track):
        print("Track %s received" % track.kind)
        if track.kind == 'video':
            recorder.addTrack(track)
            await recorder.start()

    # configure media
    media = {
        "audio": False,
        "video": True,
        "videocodec": "vp8"
    }
    # if player and player.audio:
    #     pc.addTrack(player.audio)
    #     media["audio"] = True

    pc.addTrack(player.video)

    plugin = await session.attach("janus.plugin.videoroom")

    response = await plugin.send({
            "body": {
                "request" : "listparticipants",
                'room': room
            },
    })
    publishers = response['plugindata']['data']['participants']

    # join video room
    response = await plugin.send(
        {
            "body": {
                "display": "aiortc",
                "ptype": "publisher",
                "request": "join",
                "room": room,
            }
        }
    )

    # send offer
    await pc.setLocalDescription(await pc.createOffer())
    request = {"request": "configure"}
    request.update(media)
    response = await plugin.send(
        {
            "body": request,
            "jsep": {
                "sdp": pc.localDescription.sdp,
                "trickle": False,
                "type": pc.localDescription.type,
            },
        }
    )
    print('Sdp send:')
    print(pc.localDescription.sdp)

    # apply answer
    answer = RTCSessionDescription(
        sdp=response["jsep"]["sdp"], type=response["jsep"]["type"]
    )
    await pc.setRemoteDescription(answer)
    print('Apply answer:')
    print(response["jsep"]["sdp"])

    async def subscribe(sub_id):
        plugin = await session.attach("janus.plugin.videoroom")
        request = {
            "request" : "join",
            "ptype" : "subscriber",
            "room" : room,
            "feed" : sub_id,
            # "private_id" : ''
        }
        response = await plugin.send(
            {
                "body": request,
            }
        )
        await pc.setRemoteDescription(RTCSessionDescription(
            sdp=response["jsep"]["sdp"], type=response["jsep"]["type"]
        ))
        print('Subscribe:')
        print(response["jsep"]["sdp"])

        answer = await pc.createAnswer()
        print('Create Answer:')
        print(answer.sdp)
        await pc.setLocalDescription(answer)

        response = await plugin.send(
            {
                "body": {"request" : "start"},
                "jsep": {
                    "sdp": pc.localDescription.sdp,
                    "trickle": False,
                    "type": pc.localDescription.type,
                }
            }
        )
        print(response)

    if publishers != []:
        for i in publishers:
            await subscribe(i["id"])

    print('Start call')
    i = 0
    while True:
        await asyncio.sleep(1)

    print('Leave call')


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Janus")
    parser.add_argument("url", help="Janus root URL, e.g. http://localhost:8088/janus")
    parser.add_argument(
        "--room",
        type=int,
        default=1234,
        help="The video room ID to join (default: 1234).",
    ),
    parser.add_argument("--play-from", help="Read the media from a file and sent it."),
    parser.add_argument("--verbose", "-v", action="count")
    args = parser.parse_args()

    if args.verbose:
        logging.basicConfig(level=logging.DEBUG)

    # create signaling and peer connection
    session = JanusSession(args.url)
    pc = RTCPeerConnection()

    # create media source
    if args.play_from:
        player = MediaPlayer(args.play_from)
    else:
        player = None

    recorder = MediaRecorder('test.mkv')

    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(
            run(pc=pc, player=player, recorder=recorder, room=args.room, session=session)
        )
    except KeyboardInterrupt:
        pass
    finally:
        loop.run_until_complete(pc.close())
        loop.run_until_complete(session.destroy())

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:10

github_iconTop GitHub Comments

1reaction
Thong-Trancommented, Mar 3, 2020

you can set debug level is DEBUG to show all log.

logging.basicConfig(level=logging.DEBUG)

aoirtc doesn’t create a same tag to track it (like aoirtc.sub_module). But you can find all of them by https://github.com/aiortc/aiortc/search?q=logging.getLogger&unscoped_q=logging.getLogger

0reactions
Thong-Trancommented, Mar 3, 2020

you are welcome

Read more comments on GitHub >

github_iconTop Results From Across the Web

Janus Demos are not working: WebRtc Connection is stuck in ...
When I tried to add Janus gateway websocket URI in echo test, it returns an error that;. Error connecting to the Janus WebSockets...
Read more >
RTCPeerConnection.setLocalDescription() - Web APIs | MDN
The RTCPeerConnection method setLocalDescription() changes the local description associated with the connection. This description specifies ...
Read more >
Deploying Janus - Janus WebRTC Server
When you're going to deploy Janus (e.g., to try the demos we made available out-of-the-box), ... var server = "http://www.example.com:8088/janus";.
Read more >
webRTC with Janus iOS swift - Stack Overflow
does webRTC handles the playing of audio streams? or we need to handle manually? your code seems to different comparing mine.. so not...
Read more >
Janus: The Server-side WebRTC Jack of All Trades - YouTube
Speaker: Lorenzo Miniero, MeetechoWhile WebRTC was conceived as peer-to-peer, it's actually quite common to have one of the peers in the ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found