How to setup webrtc connection between django server and browser using aiortc?

647 Views Asked by At

I want to setup webrtc connection between iot device with django server, that located behind NAT, and browser. I'm trying to use aiortc.

When I'm using standalone aiortc example, that contains aiohttp server, it works, but when I'm trying to estabilish connection with my django app, I'm getting WebRTC: ICE failed, add a TURN server and see about:webrtc for more details (in Firefox), but standard "webcamera" example works ok without TURN (only with STUN). I testing it locally, so it couldn't be problem of network. Here is my code:

import os
from aiortc.contrib.media import MediaPlayer, MediaRelay
from aiortc import RTCPeerConnection, RTCSessionDescription
from aiortc.contrib.media import MediaPlayer, MediaRelay
import asyncio
import atexit

ROOT = os.path.dirname(__file__)


relay = None
webcam = None
pcs = set()


def create_media_streams():
    global relay, webcam

    options = {"framerate": "30", "video_size": "640x480"}
    if relay is None:
        webcam = MediaPlayer("/dev/video0", format="v4l2", options=options)
        relay = MediaRelay()
    return None, relay.subscribe(webcam.video)


async def create_peer_connection(sdp, con_type):
    global pcs
    offer = RTCSessionDescription(sdp=sdp, type=con_type)

    pc = RTCPeerConnection()
    pcs.add(pc)

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

    # open media source
    audio, video = create_media_streams()

    if video:
        pc.addTrack(video)

    await pc.setRemoteDescription(offer)

    answer = await pc.createAnswer()
    await pc.setLocalDescription(answer)

    return (pc.localDescription.sdp, pc.localDescription.type)


async def on_shutdown():
    # close peer connections
    coros = [pc.close() for pc in pcs]
    await asyncio.gather(*coros)
    pcs.clear()
    
atexit.register(on_shutdown)

and

class RequestStream(APIView):
    authentication_classes = [SessionAuthentication,
                              BasicAuthentication, LocalhostAuth]

    @async_to_sync
    async def post(self, request, *args, **kwargs):
        sdp, con_type = await create_peer_connection(request.data['sdp'], request.data['type'])

        return Response(
            status=200,
            data={"sdp": sdp, "type": con_type},
        )

and frontend:

var pc = null;

function getCookie(name) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(';').shift();
}

function start() {
    var config = {
        sdpSemantics: 'unified-plan',
        iceServers: [{ urls: ['stun:stun.l.google.com:19302'] }]
    };

    pc = new RTCPeerConnection(config);

    // connect audio / video
    pc.addEventListener('track', function (evt) {
        document.getElementById('video').srcObject = evt.streams[0];
    });

    document.getElementById('start').style.display = 'none';
    pc.addTransceiver('video', { direction: 'recvonly' });
    pc.createOffer().then(function (offer) {
        return pc.setLocalDescription(offer);
    }).then(function () {
        // wait for ICE gathering to complete
        return new Promise(function (resolve) {
            if (pc.iceGatheringState === 'complete') {
                resolve();
            } else {
                function checkState() {
                    if (pc.iceGatheringState === 'complete') {
                        pc.removeEventListener('icegatheringstatechange', checkState);
                        resolve();
                    }
                }
                pc.addEventListener('icegatheringstatechange', checkState);
            }
        });
    }).then(function () {
        var offer = pc.localDescription;
        return fetch('/interpreter/api/request_stream', {
            body: JSON.stringify({
                sdp: offer.sdp,
                type: offer.type,
            }),
            headers: {
                'X-CSRFToken': getCookie('csrftoken'),
                'Content-Type': 'application/json'
            },
            method: 'POST'
        });
    }).then(function (response) {
        return response.json();
    }).then(function (answer) {
        return pc.setRemoteDescription(answer);
    }).catch(function (e) {
        alert(e);
    });
    let intervalId = setInterval(() => { if (pc.iceConnectionState == "connected") { document.getElementById('loading').style.display = 'none'; clearInterval(intervalId); } }, 50);
}

Thank you

0

There are 0 best solutions below