3

We are trying to connect a browser (client) with the aiortc library (server, which sends a single video stream). Currently, the connection is successfully established (on signalingstatechange is stable). However, the media connection is never established, as the ICE connection fails. The two hosts are on the same LAN and direct connectivity has been verified. Used STUN server is stun.l.google.com:19302.

The logs on sever are as follows:

DEBUG:asyncio:Using selector: EpollSelector

<RTCSessionDescription is received>

DEBUG:ice:Connection(0) protocol(0) connection_made(<_SelectorDatagramTransport fd=11 read=idle write=<idle, bufsize=0>>)
DEBUG:ice:Connection(0) protocol(0) > ('66.102.1.127', 19302) Message(message_method=Method.BINDING, message_class=Class.REQUEST, transaction_id=b'\xb1\x1f\xdek(\x9c\x10\xe8\x86\xd1Wn')
DEBUG:ice:Connection(0) protocol(0) < ('66.102.1.127', 19302) Message(message_method=Method.BINDING, message_class=Class.RESPONSE, transaction_id=b'\xb1\x1f\xdek(\x9c\x10\xe8\x86\xd1Wn')

<RTCSessionDescription is sent>

<candidate is received>

Raw messages:
SDP offer:
v=0
o=- 7142951019689507792 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE video
a=msid-semantic: WMS
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 123 127 122 125 107 108 109 124
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:A5nH
a=ice-pwd:P0LIjOkkOl/wTtup5n1JA1mC
a=ice-options:trickle
a=fingerprint:sha-256 E3:8E:87:44:E8:D3:A7:02:C0:70:EA:D8:7F:84:F6:80:17:42:33:07:D8:C3:5D:44:FF:DC:25:4C:68:7E:09:19
a=setup:actpass
a=mid:video
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4 urn:3gpp:video-orientation
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:10 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07
a=recvonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 H264/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:102 H264/90000
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
a=rtpmap:123 rtx/90000
a=fmtp:123 apt=102
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:122 rtx/90000
a=fmtp:122 apt=127
a=rtpmap:125 H264/90000
a=rtcp-fb:125 goog-remb
a=rtcp-fb:125 transport-cc
a=rtcp-fb:125 ccm fir
a=rtcp-fb:125 nack
a=rtcp-fb:125 nack pli
a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=125
a=rtpmap:108 red/90000
a=rtpmap:109 rtx/90000
a=fmtp:109 apt=108
a=rtpmap:124 ulpfec/90000

SDP answer:
v=0
o=- 3760099490 3760099490 IN IP4 0.0.0.0
s=-
t=0 0
a=group:BUNDLE video
a=msid-semantic:WMS *
m=video 36695 UDP/TLS/RTP/SAVPF 96 97 100 101 127 122
c=IN IP4 147.175.160.240
a=sendonly
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=mid:video
a=msid:25c4a009-f81c-4fe7-b811-a39c525da778 6ffba6fa-08fb-401f-9555-8df85942bd71
a=rtcp:9 IN IP4 0.0.0.0
a=rtcp-mux
a=ssrc-group:FID 461163217 873864728
a=ssrc:461163217 cname:{af4834d3-8db4-41cc-a9cd-6565cb8d82bd}
a=ssrc:873864728 cname:{af4834d3-8db4-41cc-a9cd-6565cb8d82bd}
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtcp-fb:96 goog-remb
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:100 H264/90000
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=rtcp-fb:100 goog-remb
a=fmtp:100 packetization-mode=1;level-asymmetry-allowed=1;profile-level-id=42001f
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:127 H264/90000
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=rtcp-fb:127 goog-remb
a=fmtp:127 packetization-mode=1;level-asymmetry-allowed=1;profile-level-id=42e01f
a=rtpmap:122 rtx/90000
a=fmtp:122 apt=127
a=candidate:a2cdfa74ef4f12d00d150bfa588cf805 1 udp 2130706431 147.175.160.240 36695 typ host
a=candidate:237395b2276e331724a06e8dbed92f44 1 udp 1694498815 147.175.160.240 36695 typ srflx raddr 147.175.160.240 rport 36695
a=end-of-candidates
a=ice-ufrag:IHJz
a=ice-pwd:RNwQMxbAm2EjNIBxklFZKd
a=fingerprint:sha-256 9B:BB:E0:63:DE:5D:BC:1F:CC:A2:FA:FA:0B:29:EB:F1:46:24:D6:A4:E7:9C:B3:C1:50:9D:A3:FB:83:EC:74:2D
a=setup:active

{"candidate":"candidate:1963203886 1 udp 2113937151 147.175.160.205 36583 typ host generation 0 ufrag A5nH network-cost 999","sdpMid":"video","sdpMLineIndex":0,"type":"candidate"}

Server code:

class VideoImageTrack(VideoStreamTrack):
    def __init__(self):
        super().__init__()

    async def recv(self):
        pts, time_base = await self.next_timestamp()
        # create frame
        return frame

async def run(pc, recorder, signaling, session_id):
    def add_tracks():
        pc.addTrack(VideoImageTrack())

    # consume signaling
    while True:
        obj = signaling.receive()
        if isinstance(obj, RTCSessionDescription):
            await pc.setRemoteDescription(obj)
            await recorder.start()
            if obj.type == 'offer':
                # send answer
                add_tracks()
                await pc.setLocalDescription(await pc.createAnswer())
                signaling.send(pc.localDescription)
        elif isinstance(obj, RTCIceCandidate):
            pc.addIceCandidate(obj)
        else:
            print('Exiting')
            break

def livestream(session_id,config):
    # create signaling and peer connection
    signaling = Signaling()
    pc = RTCPeerConnection()
    # create media sink
    recorder = MediaBlackhole()
    # run event loop
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    try:
        loop.run_until_complete(run(
            pc=pc,
            recorder=recorder,
            signaling=signaling,
            session_id=session_id))
    except KeyboardInterrupt:
        pass
    finally:
        loop.run_until_complete(recorder.stop())
        loop.run_until_complete(pc.close())

Frontend code (important parts):

var configuration = { iceServers: [
      {urls: 'stun:stun.l.google.com:19302'}]
};
this.pc = new RTCPeerConnection(configuration);


const offerOptions = {
      offerToReceiveVideo: 1,
      offerToReceiveAudio: 0
};

let offer = this.pc.createOffer(offerOptions);
offer.then(async value => {
  await this.pc.setLocalDescription(value);
  this.connection.send(JSON.stringify(value))
});


this.connection.onmessage = async event => {
let data = JSON.parse(event.data);
if (data) {
    if (data.type === 'offer') {
      await this.pc.setRemoteDescription(data);
      await this.pc.setLocalDescription(await this.pc.createAnswer());
      this.connection.send({ 'desc': this.pc.localDescription }.toString());
    } else if (data.type === 'answer') {
      await this.pc.setRemoteDescription(data);
    }
  }
};

this.pc.onnegotiationneeded = async () => {
  await this.pc.setLocalDescription(await this.pc.createOffer());
  this.connection.send({ 'desc': this.pc.localDescription }.toString());
};

this.pc.onicecandidate = (event) => {
    if (event.candidate !== null) {
      let data = JSON.parse(JSON.stringify(event.candidate));
      data['type'] = 'candidate';
      this.connection.send(JSON.stringify(data));
    }
};

1 Answers1

0

There were several problems with our implementation:

  1. removed async calls from server side - then some aiortc coroutines were not run properly
  2. missing handling of 'candidate' type message at front-end
  3. wrong format of message { 'desc': this.pc.localDescription }.toString() - should be JSON.stringify({desc: this.pc.localDescription })

We managed to fix all problems, therefore I close this question.