Aiortc python server connected to ract-native-webrtc

Hi!

I am trying to create an aiortc-based server to send a video stream to a react-native-webrtc client.

Here is my simple server:

import asyncio
import os
import json
from aiohttp import web
from aiortc import MediaStreamTrack, RTCPeerConnection, RTCSessionDescription
from av import VideoFrame
import cv2
import glob

ROOT = os.path.dirname(__file__)
pcs = set()

image_files = sorted(glob.glob("./sample-video-frames/*.jpg"))  # Change to the path to your images
current_image_index = 0

def get_next_image():
    global current_image_index
    img = cv2.imread(image_files[current_image_index])
    current_image_index = (current_image_index + 1) % len(image_files)
    return img

class ImageVideoTrack(MediaStreamTrack):
    kind = "video"

    def __init__(self):
        super().__init__()

    async def recv(self):
        img = get_next_image()

        new_frame = VideoFrame.from_ndarray(img, format="bgr24")
        new_frame.pts = 0
        new_frame.time_base = 0

        await asyncio.sleep(1/30)  # Set the desired frame rate (e.g., 30 FPS)
        return new_frame

async def index(request):
    content = open(os.path.join(ROOT, "index.html"), "r").read()
    return web.Response(content_type="text/html", text=content)

async def offer(request):
    params = await request.json()
    offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])

    pc = RTCPeerConnection()
    pcs.add(pc)

    @pc.on("connectionstatechange")
    async def on_connectionstatechange():
        print("Connection state change:", pc.connectionState)
        if pc.connectionState == "failed":
            await pc.close()
            pcs.discard(pc)

    @pc.on("track")
    def on_track(track):
        print("Track received:", track.kind)
        if track.kind == "video":
            local_video = ImageVideoTrack()
            pc.addTrack(local_video)

            async def send_frames():
                while pc.connectionState == "connected":
                    local_video.on_frame()
                    await asyncio.sleep(0.01)  # Set the desired frame rate (e.g., 100 FPS)

            asyncio.ensure_future(send_frames())

    print("Setting remote description")
    await pc.setRemoteDescription(offer)
    print("Creating answer")
    answer = await pc.createAnswer()

    # Modify the SDP to change "a=inactive" to "a=sendonly"
    sdp_lines = answer.sdp.split('\n')
    modified_sdp_lines = [line if "a=inactive" not in line else "a=sendonly" for line in sdp_lines]
    modified_sdp = '\n'.join(modified_sdp_lines)

    # Set the local description with the modified SDP
    print("Setting local description")
    await pc.setLocalDescription(RTCSessionDescription(sdp=modified_sdp, type=answer.type))

    print("Answer created and local description set")
    return web.Response(
        content_type="application/json",
        text=json.dumps(
            {"sdp": pc.localDescription.sdp, "type": pc.localDescription.type}
        ),
    )



async def on_shutdown(app):
    print("Shutting down")
    coros = [pc.close() for pc in pcs]
    await asyncio.gather(*coros)
    pcs.clear()

if __name__ == "__main__":
    app = web.Application()
    app.on_shutdown.append(on_shutdown)
    app.router.add_get("/", index)
    app.router.add_post("/offer", offer)
    web.run_app(app, access_log=None, host="192.168.122.1", port=8080)

And here the client:

import React, { useEffect, useState } from 'react';
import { View } from 'react-native';
import {
  RTCPeerConnection,
  RTCSessionDescription,
  mediaDevices,
} from 'react-native-webrtc';
import InCallManager from 'react-native-incall-manager';
import { RTCView } from 'react-native-webrtc';

const App = () => {
  const [remoteStream, setRemoteStream] = useState(null);

  useEffect(() => {
    async function startStreaming() {
      const pc = new RTCPeerConnection({
        iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
      });

      pc.ontrack = (event) => {
        console.log('Received remote stream:', event.streams[0]);
        setRemoteStream(event.streams[0]);
      };

      pc.onicecandidate = async (event) => {
        if (event.candidate) {
          console.log('Sending ICE candidate:', event.candidate.substr(0, 50));
          const response = await fetch('http://192.168.122.1:8080/candidate', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({
              candidate: event.candidate,
            }),
          });
        } else {
          console.log('All ICE candidates sent');
        }
      };

      const offer = await pc.createOffer({ offerToReceiveVideo: true });
      console.log('Offer created:', offer.sdp.substr(0, 50));
      await pc.setLocalDescription(offer);
      console.log('Local description set:', pc.localDescription.sdp.substr(0, 50));

      const response = await fetch('http://192.168.122.1:8080/offer', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          sdp: pc.localDescription.sdp,
          type: pc.localDescription.type,
        }),
      });

      const responseData = await response.json();
      const answer = new RTCSessionDescription(responseData);
      console.log('Answer received:', answer.sdp.substr(0, 50));
      await pc.setRemoteDescription(answer);
      console.log('Remote description set:', pc.remoteDescription.sdp.substr(0, 50));

      InCallManager.start({ media: 'video' });
    }

    startStreaming();

    return () => {
      InCallManager.stop();
    };
  }, []);

  return (
    <View style={{ flex: 1, backgroundColor: 'black' }}>
      {remoteStream && (
        <RTCView
          objectFit="cover"
          style={{ flex: 1 }}
          streamURL={remoteStream.toURL()}
        />
      )}
    </View>
  );
};

export default App;

Peer connection is successfully established as per logs:

 LOG  Running "WebrtcImageApp" with {"rootTag":11}
 LOG  rn-webrtc:pc:DEBUG 0 ctor +0ms
 LOG  rn-webrtc:pc:DEBUG 0 createOffer +1ms
 LOG  rn-webrtc:pc:DEBUG 0 createOffer OK +14ms
 LOG  Offer created: v=0
o=- 8514285042449230946 2 IN IP4 127.0.0.1
s
 LOG  rn-webrtc:pc:DEBUG 0 setLocalDescription +2ms
 LOG  rn-webrtc:pc:DEBUG 0 setLocalDescription OK +17ms
 LOG  Local description set: v=0
o=- 8514285042449230946 2 IN IP4 127.0.0.1
s
 LOG  Answer received: v=0
o=- 3890536821 3890536821 IN IP4 0.0.0.0
s=-
 LOG  rn-webrtc:pc:DEBUG 0 setRemoteDescription +62ms
 LOG  rn-webrtc:pc:DEBUG 0 ontrack +15ms
 LOG  All ICE candidates sent

However, I keep having the following error:

 WARN  Possible Unhandled Promise Rejection (id: 3):
TypeError: Cannot read property 'receiver' of undefined
TypeError: Cannot read property 'receiver' of undefined

It’s likely related to the ontrack event, which is triggered when a remote stream is added to the peer connection. However, I cannot understand how to handle it.

Does anyone have experience with aiortc and react-native-webrtc connection?

the same issue is happening to me

Are you running the absolute latest version of the module? v111.0.0
Also can you provide logs from logcat as it should include the error stack trace.