video conference

hi guys
how to developing video conferencing functionality i can use template and drf
but i cant pay for panels like agora
is there a way to do this?

Using Django itself to manage the audio video streams? No.

However Django w/Channels works well as the signaling channel for WebRTC communications. This, plus something like coturn running as your STUN and TURN servers can form the basis for a small video conference tool.

Thank you but i dont have that time there is a package for it?

Not that I’m aware of.

1 Like

hello again
i create a project and use youtube content but its not working and video not displayed
can you help me on that?

What are you trying to do, embed the reference to the YouTube video in your site?

no i just say i use youtube videos tutorial to learn how to create a video conference and i failed because i could not deploye and there is some problem
i figure out i have to use sdp and i can create sdp like this json but i cant display it idk how.

{'peer': 'hamid', 'action': 'new-offer', 'message': {'sdp': {'type': 'offer', 'sdp': 'v=0\r\no=- 6738368951181114854 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=g
roup:BUNDLE 0 1 2\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS 99c3aab8-42c2-4db3-96b4-8c642fcc19fb\r\nm=audio 52797 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 
126\r\nc=IN IP4 100.127.255.249\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=candidate:1345736326 1 udp 2122260223 100.127.255.249 52797 typ host generation 0 network-id 1
 network-cost 50\r\na=candidate:2974370906 1 udp 2122194687 192.168.10.206 52798 typ host generation 0 network-id 2\r\na=candidate:782789515 1 tcp 1518280447 
100.127.255.249 9 typ host tcptype active generation 0 network-id 1 network-cost 50\r\na=candidate:3487000919 1 tcp 1518214911 192.168.10.206 9 typ host tcpty
pe active generation 0 network-id 2\r\na=ice-ufrag:uDfJ\r\na=ice-pwd:rQ7NXFr8tzykBFlehciSLE7e\r\na=ice-options:trickle\r\na=fingerprint:sha-256 5E:06:E4:46:16
:B4:21:7B:65:CE:62:02:13:63:C9:5D:7A:BC:A8:40:64:51:0A:2F:A5:FF:DA:89:90:1F:F4:8D\r\na=setup:actpass\r\na=mid:0\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-
audio-level\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc
-extensions-01\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=sendrecv\r\na=msid:99c3aab8-42c2-4db3-96b4-8c642fcc19fb 7f186a5c-d9b9-44a2-99a2-02154225
0cff\r\na=rtcp-mux\r\na=rtpmap:111 opus/48000/2\r\na=rtcp-fb:111 transport-cc\r\na=fmtp:111 minptime=10;useinbandfec=1\r\na=rtpmap:63 red/48000/2\r\na=fmtp:63
 111/111\r\na=rtpmap:9 G722/8000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:13 CN/8000\r\na=rtpmap:110 telephone-event/48000\r\na=rtpmap:126 
telephone-event/8000\r\na=ssrc:502078794 cname:RgkNMvoReCjVxThJ\r\na=ssrc:502078794 msid:99c3aab8-42c2-4db3-96b4-8c642fcc19fb 7f186a5c-d9b9-44a2-99a2-02154225
0cff\r\nm=video 52799 UDP/TLS/RTP/SAVPF 96 97 102 103 104 105 106 107 108 109 127 125 39 40 45 46 98 99 100 101 112 113 116 117 118\r\nc=IN IP4 100.127.255.24
9\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=candidate:1345736326 1 udp 2122260223 100.127.255.249 52799 typ host generation 0 network-id 1 network-cost 50\r\na=candidat
e:2974370906 1 udp 2122194687 192.168.10.206 52800 typ host generation 0 network-id 2\r\na=candidate:782789515 1 tcp 1518280447 100.127.255.249 9 typ host tcp
type active generation 0 network-id 1 network-cost 50\r\na=candidate:3487000919 1 tcp 1518214911 192.168.10.206 9 typ host tcptype active generation 0 network
-id 2\r\na=ice-ufrag:uDfJ\r\na=ice-pwd:rQ7NXFr8tzykBFlehciSLE7e\r\na=ice-options:trickle\r\na=fingerprint:sha-256 5E:06:E4:46:16:B4:21:7B:65:CE:62:02:13:63:C9
:5D:7A:BC:A8:40:64:51:0A:2F:A5:FF:DA:89:90:1F:F4:8D\r\na=setup:actpass\r\na=mid:1\r\na=extmap:14 urn:ietf:params:rtp-hdrext:toffset\r\na=extmap:2 http://www.w
ebrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:13 urn:3gpp:video-orientation\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wid
e-cc-extensions-01\r\na=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\na=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-c
ontent-type\r\na=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\na=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\n
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\na=extmap:11 urn:ietf:params:rtp-hdrext:sdes:rep
aired-rtp-stream-id\r\na=sendrecv\r\na=msid:99c3aab8-42c2-4db3-96b4-8c642fcc19fb b2510489-83c9-4ae1-86e1-44a962d33777\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpma
p:96 VP8/90000\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtpmap:97 rtx
/90000\r\na=fmtp:97 apt=96\r\na=rtpmap:102 H264/90000\r\na=rtcp-fb:102 goog-remb\r\na=rtcp-fb:102 transport-cc\r\na=rtcp-fb:102 ccm fir\r\na=rtcp-fb:102 nack\
r\na=rtcp-fb:102 nack pli\r\na=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\r\na=rtpmap:103 rtx/90000\r\na=fmtp:103 apt=102
\r\na=rtpmap:104 H264/90000\r\na=rtcp-fb:104 goog-remb\r\na=rtcp-fb:104 transport-cc\r\na=rtcp-fb:104 ccm fir\r\na=rtcp-fb:104 nack\r\na=rtcp-fb:104 nack pli\
r\na=fmtp:104 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f\r\na=rtpmap:105 rtx/90000\r\na=fmtp:105 apt=104\r\na=rtpmap:106 H264/9000
0\r\na=rtcp-fb:106 goog-remb\r\na=rtcp-fb:106 transport-cc\r\na=rtcp-fb:106 ccm fir\r\na=rtcp-fb:106 nack\r\na=rtcp-fb:106 nack pli\r\na=fmtp:106 level-asymme
try-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\na=rtpmap:107 rtx/90000\r\na=fmtp:107 apt=106\r\na=rtpmap:108 H264/90000\r\na=rtcp-fb:108 goog-re
mb\r\na=rtcp-fb:108 transport-cc\r\na=rtcp-fb:108 ccm fir\r\na=rtcp-fb:108 nack\r\na=rtcp-fb:108 nack pli\r\na=fmtp:108 level-asymmetry-allowed=1;packetizatio
n-mode=0;profile-level-id=42e01f\r\na=rtpmap:109 rtx/90000\r\na=fmtp:109 apt=108\r\na=rtpmap:127 H264/90000\r\na=rtcp-fb:127 goog-remb\r\na=rtcp-fb:127 transp
ort-cc\r\na=rtcp-fb:127 ccm fir\r\na=rtcp-fb:127 nack\r\na=rtcp-fb:127 nack pli\r\na=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=
4d001f\r\na=rtpmap:125 rtx/90000\r\na=fmtp:125 apt=127\r\na=rtpmap:39 H264/90000\r\na=rtcp-fb:39 goog-remb\r\na=rtcp-fb:39 transport-cc\r\na=rtcp-fb:39 ccm fi
r\r\na=rtcp-fb:39 nack\r\na=rtcp-fb:39 nack pli\r\na=fmtp:39 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f\r\na=rtpmap:40 rtx/90000\r
\na=fmtp:40 apt=39\r\na=rtpmap:45 AV1/90000\r\na=rtcp-fb:45 goog-remb\r\na=rtcp-fb:45 transport-cc\r\na=rtcp-fb:45 ccm fir\r\na=rtcp-fb:45 nack\r\na=rtcp-fb:4
5 nack pli\r\na=fmtp:45 level-idx=5;profile=0;tier=0\r\na=rtpmap:46 rtx/90000\r\na=fmtp:46 apt=45\r\na=rtpmap:98 VP9/90000\r\na=rtcp-fb:98 goog-remb\r\na=rtcp
-fb:98 transport-cc\r\na=rtcp-fb:98 ccm fir\r\na=rtcp-fb:98 nack\r\na=rtcp-fb:98 nack pli\r\na=fmtp:98 profile-id=0\r\na=rtpmap:99 rtx/90000\r\na=fmtp:99 apt=
98\r\na=rtpmap:100 VP9/90000\r\na=rtcp-fb:100 goog-remb\r\na=rtcp-fb:100 transport-cc\r\na=rtcp-fb:100 ccm fir\r\na=rtcp-fb:100 nack\r\na=rtcp-fb:100 nack pli
\r\na=fmtp:100 profile-id=2\r\na=rtpmap:101 rtx/90000\r\na=fmtp:101 apt=100\r\na=rtpmap:112 H264/90000\r\na=rtcp-fb:112 goog-remb\r\na=rtcp-fb:112 transport-c
c\r\na=rtcp-fb:112 ccm fir\r\na=rtcp-fb:112 nack\r\na=rtcp-fb:112 nack pli\r\na=fmtp:112 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001
f\r\na=rtpmap:113 rtx/90000\r\na=fmtp:113 apt=112\r\na=rtpmap:116 red/90000\r\na=rtpmap:117 rtx/90000\r\na=fmtp:117 apt=116\r\na=rtpmap:118 ulpfec/90000\r\na=
ssrc-group:FID 574624911 2964818264\r\na=ssrc:574624911 cname:RgkNMvoReCjVxThJ\r\na=ssrc:574624911 msid:99c3aab8-42c2-4db3-96b4-8c642fcc19fb b2510489-83c9-4ae
1-86e1-44a962d33777\r\na=ssrc:2964818264 cname:RgkNMvoReCjVxThJ\r\na=ssrc:2964818264 msid:99c3aab8-42c2-4db3-96b4-8c642fcc19fb b2510489-83c9-4ae1-86e1-44a962d
33777\r\nm=application 52801 UDP/DTLS/SCTP webrtc-datachannel\r\nc=IN IP4 100.127.255.249\r\na=candidate:1345736326 1 udp 2122260223 100.127.255.249 52801 typ
 host generation 0 network-id 1 network-cost 50\r\na=candidate:2974370906 1 udp 2122194687 192.168.10.206 52802 typ host generation 0 network-id 2\r\na=candid
ate:782789515 1 tcp 1518280447 100.127.255.249 9 typ host tcptype active generation 0 network-id 1 network-cost 50\r\na=candidate:3487000919 1 tcp 1518214911 
192.168.10.206 9 typ host tcptype active generation 0 network-id 2\r\na=ice-ufrag:uDfJ\r\na=ice-pwd:rQ7NXFr8tzykBFlehciSLE7e\r\na=ice-options:trickle\r\na=fin
gerprint:sha-256 5E:06:E4:46:16:B4:21:7B:65:CE:62:02:13:63:C9:5D:7A:BC:A8:40:64:51:0A:2F:A5:FF:DA:89:90:1F:F4:8D\r\na=setup:actpass\r\na=mid:2\r\na=sctp-port:5000\r\na=max-message-size:262144\r\n'}, 'receiver_channel_name': 'specific..inmemory!FQMfrhSgXHup'}}

There is so much more to this than just that.

WebRTC is a point-to-point protocol. All of the exchanges of the SDP data is done from one browser to the other. Django isn’t involved with that - at least not directly. The SDP is just data that the two browsers exchange with each other to define how they’re going to establish a connection.

The only functionality performed by Django (with Channels) would be to serve as the signaling component, allowing the two endpoints to locate each other.

yes i know that and i did somthing like this
this is my consumers

import json
from channels.generic.websocket import AsyncWebsocketConsumer


class VideoChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_group_name = 'test'

        await self.channel_layer.group_add(
            self.room_group_name, self.channel_name
        )
        await self.accept()

    async def disconnect(self, close_code):
        await self.channel_layer.group_discard(
            self.room_group_name, self.channel_name
        )

        print('disconnected')

    async def receive(self, text_data=None, **kwargs):
        json_data = json.loads(text_data)
        message = json_data['message']
        action = json_data['action']

        if action == 'new-offer' or action == 'new-answer':
            receiver_channel_name = json_data["message"]['receiver_channel_name']

            json_data["message"]['receiver_channel_name'] = self.channel_name

            await self.channel_layer.send(
                receiver_channel_name,
                {
                    'type': 'send.sdp',
                    'json_data': json_data
                }
            )

            return

        json_data['message']["receiver_channel_name"] = self.channel_name

        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'send.sdp',
                'json_data': json_data
            }
        )

    async def send_sdp(self, event):
        json_data = event['json_data']
        await self.send(text_data=json.dumps(json_data))

and this is my javascript

let mapPeers = {};

let usernameInput = document.querySelector("#username");
let btnJoin = document.querySelector("#btn-join");

let username;
let webSocket;

function webSocketOnMessage(event) {
    let parsedData = JSON.parse(event.data);

    let peerUsername = parsedData["peer"];
    let action = parsedData["action"];

    if (username === peerUsername) {
        return;
    }

    let receiver_channel_name = parsedData["message"]["receiver_channel_name"];

    if (action === 'new-peer') {
        createOfferer(peerUsername, receiver_channel_name);

        return;
    }

    if (action === 'new-offer') {
        let offer = parsedData["message"]["sdp"];

        createAnswerer(offer, peerUsername, receiver_channel_name);

        return;
    }

    if (action === 'new-answer') {
        let answer = parsedData["message"]["sdp"];

        let peer = mapPeers[peerUsername]

        peer.setRemoteDescription(answer);

        return;
    }
}

btnJoin.addEventListener('click', () => {
    username = usernameInput.value;

    // console.log('username: ', username);
    if (username === '') {
        return;
    }

    usernameInput.value = '';
    usernameInput.disabled = true;
    usernameInput.style.visibility = 'hidden';

    btnJoin.disabled = true;
    btnJoin.style.visibility = 'hidden';

    let labelUsername = document.querySelector("#label-username");
    labelUsername.innerHTML = username;

    let loc = window.location;
    let wsStart = 'ws://';

    if (location.protocol === 'https:') {
        wsStart = 'wss://'
    }

    let endPoint = wsStart + loc.host + loc.pathname
    // console.log('endPoint: ', endPoint)

    webSocket = new WebSocket(endPoint);

    webSocket.addEventListener('open', (e) => {
        // console.log("connection opened");

        sendSignal('new-peer', {});
    });
    webSocket.addEventListener('message', webSocketOnMessage);
    webSocket.addEventListener('close', (e) => {
        // console.log("connection close");
    });
    webSocket.addEventListener('error', (e) => {
        // console.log("Error!");
    });
    cwebSocket = new WebSocket('ws://127.0.0.1:8000/chat/');

});

let localStream = new MediaStream();

const constraints = {
    'video': true, 'audio': true
};

const localVideo = document.querySelector('#local-video');

const btnToggleAudio = document.querySelector('#btn-toggle-audio');
const btnToggleVideo = document.querySelector('#btn-toggle-video')

let userMedia = navigator.mediaDevices.getUserMedia(constraints)
    .then(stream => {
        localStream = stream;
        localVideo.srcObject = localStream;
        localVideo.muted = true;

        let audioTracks = stream.getAudioTracks();
        let videoTracks = stream.getAudioTracks();

        audioTracks[0].enabled = true;
        videoTracks[0].enabled = true;

        btnToggleAudio.addEventListener('click', () => {
            audioTracks[0].enabled = !audioTracks[0].enabled;

            if (audioTracks[0].enabled) {
                btnToggleAudio.innerHTML = 'Audio Mute';
                return;
            }

            btnToggleAudio.innerHTML = 'Audio Unmute';
        });

        btnToggleVideo.addEventListener('click', () => {
            videoTracks[0].enabled = !videoTracks[0].enabled;

            if (videoTracks[0].enabled) {
                btnToggleVideo.innerHTML = 'Video Off';
                return;
            }

            btnToggleVideo.innerHTML = 'Video On';
        });
    })
    .catch(error => {
        // console.log('error: ', error)
    })

function sendSignal(action, message) {
    let jsonStr = JSON.stringify({
        'peer': username, 'action': action, 'message': message,
    });

    webSocket.send(jsonStr)
}

function createOfferer(peerUsername, receiver_channel_name) {
    let peer = new RTCPeerConnection(null);
    console.log(peer)

    addLocalTracks(peer);

    let dc = peer.createDataChannel('channel');
    dc.addEventListener('open', () => {
        // console.log('connection open dc');
    });
    dc.addEventListener('message', dcOnMessage)

    let remoteVideo = createVideo(peerUsername);
    setOnTrack(peer, remoteVideo)

    mapPeers[peerUsername] = [peer, dc];

    peer.addEventListener('iceconnectionstatechange', () => {
        let iceConnectionState = peer.iceConnectionState;

        if (iceConnectionState === 'failed' || iceConnectionState === 'disconnected' || iceConnectionState === 'closed') {
            delete mapPeers[peerUsername];

            if (iceConnectionState !== "closed") {
                peer.close();
            }

            removeVideo(remoteVideo);
        }
    });

    peer.addEventListener('icecandidate', (event) => {
        if (event.candidate) {
            // console.log('new ice candidate: ', JSON.stringify(peer.localDescription))

            return;
        }

        sendSignal('new-offer', {
            'sdp': peer.localDescription, 'receiver_channel_name': receiver_channel_name
        });
    });

    peer.createOffer()
        .then(o => peer.setLocalDescription(o))
        .then(() => {
            // console.log('local connection set successfully')
        })


}


function createAnswerer(offer, peerUsername, receiver_channel_name) {
    let peer = new RTCPeerConnection(null);

    addLocalTracks(peer);

    let remoteVideo = createVideo(peerUsername);
    setOnTrack(peer, remoteVideo)

    peer.addEventListener('datachannel', e => {
        peer.dc = e.channel;

        peer.dc.addEventListener('open', () => {
            // console.log('connection open dc');
        });
        peer.dc.addEventListener('message', dcOnMessage)

        mapPeers[peerUsername] = [peer, peer.dc];

    });


    peer.addEventListener('iceconnectionstatechange', () => {
        let iceConnectionState = peer.iceConnectionState;

        if (iceConnectionState === 'failed' || iceConnectionState === 'disconnected' || iceConnectionState === 'closed') {
            delete mapPeers[peerUsername];

            if (iceConnectionState !== "closed") {
                peer.close();
            }

            removeVideo(remoteVideo);
        }
    });

    peer.addEventListener('icecandidate', (event) => {
        if (event.candidate) {
            // console.log('new ice candidate: ', JSON.stringify(peer.localDescription))

            return;
        }

        sendSignal('new-answer', {
            'sdp': peer.localDescription, 'receiver_channel_name': receiver_channel_name
        });
    });

    peer.setRemoteDescription(offer)
        .then(() => {
            // console.log('remote description set successfully for %s.', peerUsername)

            return peer.createAnswer();
        })
        .then(a => {
            console.log('answer created');

            peer.setLocalDescription(a)
        })
}

function addLocalTracks(peer) {
    localStream.getTracks().forEach(track => {
        peer.addTrack(track, localStream)
    })
}

let messageList = document.querySelector('#message-list')

function dcOnMessage(event) {
    let message = event.data;

    let li = document.createElement('li')
    li.appendChild(document.createTextNode(message));
    messageList.appendChild(li)
}

function createVideo(peerUsername) {
    let videoContainer = document.querySelector('#video-container');
    let remoteVideo = document.createElement('video');
    console.log(remoteVideo)
    remoteVideo.id = peerUsername + '-video'
    remoteVideo.autoplay = true;
    remoteVideo.playsInline = true;

    let videoWrapper = document.createElement('div');
    videoWrapper.appendChild(remoteVideo);

    return remoteVideo;
}

function setOnTrack(peer, remoteVideo) {
    let remoteStream = new MediaStream();
    remoteVideo.srcObject = remoteStream;
    console.log(remoteStream)

    peer.addEventListener('track', async (event) => {
        remoteStream.addTrack(event.track, remoteStream);
    });
}

function removeVideo(video) {
    let videoWrapper = video.parentNode;

    videoWrapper.parentNode.removeChild(videoWrapper);
}

i know it may be useless but this is my html file

<!DOCTYPE html>{% load static %}
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>video conference</title>
    <link rel="stylesheet" href={% static "main/css/style.css" %}>
</head>
<body>
<h3 id="label-username">USERNAME</h3>
<div>
    <input id="username">
    <button id="btn-join">Join Room</button>
</div>

<div class="main-grid-container">
    <div id="video-container">
        <div>
            <video id="local-video" style="float: left;" autoplay playsinline></video>
        </div>
        <button id="btn-toggle-audio">Audio Mute</button>
        <button id="btn-toggle-video">Video Off</button>
    </div>

    <div id="chat">
        <pre
        <h3>Chat</h3>
        <div id="messages">
            <ul id="message-list"></ul>
        </div>

        <div>
            <input id="msg">
            <button id="btn-send-msg">Send Message</button>
        </div>
        <button id="button-share-screen"></button>
    </div>
</div>
<script src={% static "main/js/video-call-script.js" %}></script>
</body>
</html>

can you help me to debug this?
everything is ok and sdp create but wont displayed

Honestly? Not likely.

It took me months to get something working, it’s still having some problems, and my approach is very different from yours. It would likely end up requiring far more time for me to follow what you’re doing than what I could appropriately allocate.

If this is something critical for you, I suggest you hire the appropriate expertise.

i dont think its need that time. everything is fine just i cant display sdp can you suggest me an documentation or something else to how to display and show sdp i search but i did not find somthing useful.

My experience has been similar. I think the biggest problem has been the evolution of the WebRTC APIs. A lot of code and blogs published pre-2019 are obsolete - they might have worked back then, but would need to be adapted for the current stanards.

The best resource I’ve found is Programming WebRTC: Build Real-Time Streaming Applications for the Web by Karl Stolley. (The book is still pre-release, if you buy it now it would be the beta version, but it’s absolutely the best that I’ve seen.)

1 Like

i live in iran i cant buy that but anyways thank you to spent time.