Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webrtc initial negotiation succeeds but renegotiation fails #1079

Closed
pushkarprasad007 opened this issue Mar 27, 2024 · 0 comments
Closed

Webrtc initial negotiation succeeds but renegotiation fails #1079

pushkarprasad007 opened this issue Mar 27, 2024 · 0 comments

Comments

@pushkarprasad007
Copy link

I have implemented audio transfer over webrtc using aiortc on python backend (Heavily inspired from server folder in examples). The flow is like so:

  1. (On click of start button) Browser sends audio stream (from PC microphone) to server
  2. Server sends audio to browser (a.mp3)

pc.addTrack(MediaPlayer(os.path.join(ROOT, "a.mp3")).audio)

  1. Browser plays the audio sent by server (This works fine)
  2. On click of the (Stop) button on browser, the renegotiation happens, and on server side, we stop the sending track (which sends a.mp3) and add a new track (which should send b.mp3)

pc.addTrack(MediaPlayer(os.path.join(ROOT, "b.mp3")).audio)

All works fine until step 4, at which point I get the following error on server:

INFO:aioice.ice:Connection(0) Check CandidatePair(('2401:4900:1c82:3426:145b:a320:f63f:fbe8', 51868) -> ('2401:4900:1c82:3426:651e:a3ff:e97f:fa6', 56518)) State.FROZEN -> State.FAILED
INFO:aioice.ice:Connection(0) Check CandidatePair(('2401:4900:1c82:3426:651e:a3ff:e97f:fa6', 53645) -> ('2401:4900:1c82:3426:651e:a3ff:e97f:fa6', 56518)) State.FROZEN -> State.FAILED
INFO:aioice.ice:Connection(0) Check CandidatePair(('192.168.1.2', 55237) -> ('192.168.1.2', 64442)) State.FROZEN -> State.FAILED

I'm doing re-negotiation as seen in client.js code below:

negotiate(1)

which sends offer & returns answer. Interesting thing is the first time negotiation happens, things work fine, and then 4th step onwards, the ICE failing state starts coming, and as a result I cannot hear audio b.mp3 being played on browser.

server.py

import argparse
import asyncio
import json
import logging
import os
import ssl
import uuid
import socketio
from aiohttp import web
from aiortc import MediaStreamTrack, RTCPeerConnection, RTCSessionDescription, RTCRtpSender
from aiortc.contrib.media import MediaBlackhole, MediaPlayer, MediaRecorder, MediaRelay
from aiortc.mediastreams import MediaStreamError
from av import AudioFrame
import asyncio
import fractions
import time
from av.frame import Frame
from aiortc.mediastreams import VideoStreamTrack
from aiortc.rtcrtpreceiver import RemoteStreamTrack


ROOT = os.path.dirname(__file__)
logger = logging.getLogger("pc")
pcs = set()
relay = MediaRelay()


app = web.Application()
# Create a Socket.IO server instance
sio = socketio.AsyncServer(async_mode='aiohttp')
sio.attach(app)

async def removeAndAddTrack(pc: RTCPeerConnection):
    print("Came inside removeAndAddTrack")
    sender:RTCRtpSender = pc.getSenders()[0]
    await sender.stop()
    print("Stopped the sender")
    pc.addTrack(MediaPlayer(os.path.join(ROOT, "sample-9s.wav")).audio)
    print("Added a new track")

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 javascript(request):
    content = open(os.path.join(ROOT, "client.js"), "r").read()
    return web.Response(content_type="application/javascript", text=content)


@sio.event
async def renegotiate(sid, offer):
    session = await sio.get_session(sid)
    pc:RTCPeerConnection = session["peer"]

    sender = next((sender for sender in pc.getSenders()), None)
    print("RENEGOTIATE ----------")
    if sender:
        print("Came inside removeAndAddTrack")
        sender:RTCRtpSender = pc.getSenders()[0]
        await sender.stop()
        print("Stopped the sender")
        pc.addTrack(MediaPlayer(os.path.join(ROOT, "b.mp3")).audio)

    # Set local & remote session
    offersdp = RTCSessionDescription(sdp=offer, type="offer")
    await pc.setRemoteDescription(offersdp)
    print("OFFER : ------------")
    print(offersdp)
    
    answer = await pc.createAnswer()
    await pc.setLocalDescription(answer)
    print("ANSWER : ------------")
    print(answer)
    await sio.emit("answer", answer.sdp) 

@sio.event
async def offer(sid, offer_sdp):
    offer = RTCSessionDescription(sdp=offer_sdp, type='offer')

    pc = RTCPeerConnection()
    await sio.save_session(sid, {"peer" : pc})

    pc_id = "PeerConnection(%s)" % uuid.uuid4()
    pcs.add(pc)

    # Audio Recorder for incoming audio track
    audio_rec = None

    # Video Recorder for incoming video track
    video_rec = None

    # All the incoming audio tracks
    incoming_audio_tracks = [] 

    def log_info(msg, *args):
        logger.info(pc_id + " " + msg, *args)

    #if args.record_to:
    if True: 
        # Record the incoming track stream to wav file locally
        audio_rec = MediaRecorder(os.path.join(ROOT, "recording-step-1.mp3"))
    else:
        audio_rec = MediaBlackhole()

    # Initialise mediarecorder
    video_rec = MediaRecorder(
        os.path.join(ROOT, "video-recording.mp4"))
                

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

    @pc.on("track")
    def on_track(track):
        log_info("Track %s received", track.kind)

        if track.kind == "audio":
            incoming_audio_tracks.append(track)

            # Start to send silence empty audio in outgoing track stream
            pc.addTrack(MediaPlayer(os.path.join(ROOT, "a.mp3")).audio)
            
            # Start recording incoming microphone audio stream into recording wav file
            audio_rec.addTrack(track)


        @track.on("ended")
        async def on_ended():
            log_info("Track %s ended", track.kind)
            if audio_rec:
                await audio_rec.stop()
            if video_rec:
                await video_rec.stop()

    # handle offer
    await pc.setRemoteDescription(offer)
    print("Added Remote Desc")
    
    if audio_rec:
        await audio_rec.start()
    if video_rec:
        await video_rec.start()

    # send answer
    answer = await pc.createAnswer()
    await pc.setLocalDescription(answer)
    print("Adding Local Desc")
    print(answer)

    await sio.emit("answer", pc.localDescription.sdp) 
 
async def on_shutdown(app):
    # close peer connections
    coros = [pc.close() for pc in pcs]
    await asyncio.gather(*coros)
    pcs.clear()


# Socket.IO event handler for the 'connect' event
@sio.on('connect')
async def connect(sid, environ):
    print('Connected:', sid)

# Socket.IO event handler for the 'disconnect' event
@sio.on('disconnect')
async def disconnect(sid):
    print('Disconnected:', sid)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="WebRTC audio / video / data-channels demo"
    )
    parser.add_argument("--cert-file", help="SSL certificate file (for HTTPS)")
    parser.add_argument("--key-file", help="SSL key file (for HTTPS)")
    parser.add_argument(
        "--host", default="0.0.0.0", help="Host for HTTP server (default: 0.0.0.0)"
    )
    parser.add_argument(
        "--port", type=int, default=8080, help="Port for HTTP server (default: 8080)"
    )
    parser.add_argument("--record-to", help="Write received media to a file.")
    parser.add_argument("--verbose", "-v", action="count")
    args = parser.parse_args()

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

    if args.cert_file:
        ssl_context = ssl.SSLContext()
        ssl_context.load_cert_chain(args.cert_file, args.key_file)
    else:
        ssl_context = None

    app.on_shutdown.append(on_shutdown)
    app.router.add_get("/", index)
    app.router.add_get("/client.js", javascript)
    #app.router.add_post("/offer", offer)
    
    web.run_app(
        app, access_log=None, host=args.host, port=args.port, ssl_context=ssl_context
    )

client.js

// get DOM elements
var dataChannelLog = document.getElementById('data-channel'),
    iceConnectionLog = document.getElementById('ice-connection-state'),
    iceGatheringLog = document.getElementById('ice-gathering-state'),
    signalingLog = document.getElementById('signaling-state');

// Initialize Socket.IO connection
var socket = io();

// peer connection
var pc = null;

// data channel
var dc = null, dcInterval = null;

function createPeerConnection() {
    var config = {
        sdpSemantics: 'unified-plan'
    };

    if (document.getElementById('use-stun').checked) {
        config.iceServers = [{ urls: ['stun:stun.l.google.com:19302'] }];
    }

    pc = new RTCPeerConnection(config);

    // register some listeners to help debugging
    pc.addEventListener('icegatheringstatechange', () => {
        iceGatheringLog.textContent += ' -> ' + pc.iceGatheringState;
    }, false);
    iceGatheringLog.textContent = pc.iceGatheringState;

    pc.addEventListener('iceconnectionstatechange', () => {
        iceConnectionLog.textContent += ' -> ' + pc.iceConnectionState;
    }, false);
    iceConnectionLog.textContent = pc.iceConnectionState;

    pc.addEventListener('signalingstatechange', () => {
        signalingLog.textContent += ' -> ' + pc.signalingState;
    }, false);
    signalingLog.textContent = pc.signalingState;

    
    // connect audio / video
    pc.addEventListener('track', (evt) => {
        console.log("Came in event listener for track event");
        if (evt.track.kind == 'audio') {
            document.getElementById('audio').srcObject = evt.streams[0];
            console.log("Added remote audio stream");
        }
    });

    return pc;
}

function enumerateInputDevices() {
    const populateSelect = (select, devices) => {
        let counter = 1;
        devices.forEach((device) => {
            const option = document.createElement('option');
            option.value = device.deviceId;
            option.text = device.label || ('Device #' + counter);
            select.appendChild(option);
            counter += 1;
        });
    };

    navigator.mediaDevices.enumerateDevices().then((devices) => {
        populateSelect(
            document.getElementById('audio-input'),
            devices.filter((device) => device.kind == 'audioinput')
        );
        populateSelect(
            document.getElementById('video-input'),
            devices.filter((device) => device.kind == 'videoinput')
        );
    }).catch((e) => {
        alert(e);
    });
}

function negotiate(again = 0) {
    return pc.createOffer().then((offer) => {
        return pc.setLocalDescription(offer);
    }).then(() => {
        // wait for ICE gathering to complete
        return new Promise((resolve) => {
            if (pc.iceGatheringState === 'complete') {
                resolve();
            } else {
                function checkState() {
                    if (pc.iceGatheringState === 'complete') {
                        pc.removeEventListener('icegatheringstatechange', checkState);
                        resolve();
                    }
                }
                pc.addEventListener('icegatheringstatechange', checkState);
            }
        });
    }).then(() => {
        var offer = pc.localDescription;
        var codec;

        codec = document.getElementById('audio-codec').value;
        if (codec !== 'default') {
            offer.sdp = sdpFilterCodec('audio', codec, offer.sdp);
        }

        codec = document.getElementById('video-codec').value;
        if (codec !== 'default') {
            offer.sdp = sdpFilterCodec('video', codec, offer.sdp);
        }

        document.getElementById('offer-sdp').textContent = offer.sdp;

        if (again ==  0) {
            socket.emit("offer", offer.sdp);
        } else {
            socket.emit("renegotiate", offer.sdp)
        }
    }).catch((e) => {
        alert(e);
    });
}

socket.on('answer', function (sdp_answer) {
    console.log("----- Answer ------")
    console.log(sdp_answer)
    document.getElementById('answer-sdp').textContent = sdp_answer;
    pc.setRemoteDescription({type:"answer", sdp: sdp_answer});
});

// Handle connection event
socket.on('connect', () => {
    console.log('Connected to server');
});

// Handle disconnection event
socket.on('disconnect', () => {
    console.log('Disconnected from server');
});

function start() {
    document.getElementById('start').style.display = 'none';

    pc = createPeerConnection();

    pc.onnegotiationneeded = e => {
        console.log("Inside on negotiation needed");
        if (pc.signalingState != "stable") return;
    }

    var time_start = null;

    const current_stamp = () => {
        if (time_start === null) {
            time_start = new Date().getTime();
            return 0;
        } else {
            return new Date().getTime() - time_start;
        }
    };

    if (document.getElementById('use-datachannel').checked) {
        var parameters = JSON.parse(document.getElementById('datachannel-parameters').value);

        dc = pc.createDataChannel('chat', parameters);
        dc.addEventListener('close', () => {
            clearInterval(dcInterval);
            dataChannelLog.textContent += '- close\n';
        });
        dc.addEventListener('open', () => {
            dataChannelLog.textContent += '- open\n';
            dcInterval = setInterval(() => {
                var message = 'ping ' + current_stamp();
                dataChannelLog.textContent += '> ' + message + '\n';
                dc.send(message);
            }, 1000);
        });
        dc.addEventListener('message', (evt) => {
            dataChannelLog.textContent += '< ' + evt.data + '\n';

            if (evt.data.substring(0, 4) === 'pong') {
                var elapsed_ms = current_stamp() - parseInt(evt.data.substring(5), 10);
                dataChannelLog.textContent += ' RTT ' + elapsed_ms + ' ms\n';
            }

            if (evt.data.substring(0, 10) === 'trackended') {
                // This is received from python when the audio wav file streamed
                // is completed
                 		
            }

            if (evt.data.substring(0, 11) === 'renegotiate') {
                const offer = evt.data.substring(11);

                pc.createAnswer()
                    .then((answer) => {
                        return pc.setRemoteDescription(new RTCSessionDescription({ type: 'offer', sdp: offer }))
                            .then(() => {
                                // Set local description first
                                return pc.setLocalDescription(answer);
                            })
                            .then(() => {
                                const message = 'answer' + pc.localDescription.sdp;
                                dc.send(message);
                            });
                    })
                    .catch((error) => {
                        console.log(offer);
                        console.error('Failed to renegotiate:', error);
                    });
            }
        });
    }

    // Build media constraints.

    const constraints = {
        audio: false,
        video: false
    };

    if (document.getElementById('use-audio').checked) {
        const audioConstraints = {};

        const device = document.getElementById('audio-input').value;
        if (device) {
            audioConstraints.deviceId = { exact: device };
        }

        constraints.audio = Object.keys(audioConstraints).length ? audioConstraints : true;
    }

    if (document.getElementById('use-video').checked) {
        const videoConstraints = {};

        const device = document.getElementById('video-input').value;
        if (device) {
            videoConstraints.deviceId = { exact: device };
        }

        const resolution = document.getElementById('video-resolution').value;
        if (resolution) {
            const dimensions = resolution.split('x');
            videoConstraints.width = parseInt(dimensions[0], 0);
            videoConstraints.height = parseInt(dimensions[1], 0);
        }

        // Specify frame rate here
        videoConstraints.frameRate = { ideal: 30, max: 30 };

        constraints.video = Object.keys(videoConstraints).length ? videoConstraints : true;
    }

    // Acquire media and start negociation.

    if (constraints.audio || constraints.video) {
        if (constraints.video) {
            document.getElementById('media').style.display = 'block';
        }
        navigator.mediaDevices.getUserMedia(constraints).then((stream) => {

            document.getElementById('video').srcObject = stream;

            stream.getTracks().forEach((track) => {
                pc.addTrack(track, stream);
            });
            return negotiate();
        }, (err) => {
            alert('Could not acquire media: ' + err);
        });
    } else {
        negotiate();
    }

    document.getElementById('stop').style.display = 'inline-block';
}

function restart() {
    document.getElementById('restart').style.display = 'none';
}

function stop() {
    document.getElementById('stop').style.display = 'none';
    negotiate(1)
    document.getElementById('restart').style.display = 'inline-block';
}

function sdpFilterCodec(kind, codec, realSdp) {
    var allowed = []
    var rtxRegex = new RegExp('a=fmtp:(\\d+) apt=(\\d+)\r$');
    var codecRegex = new RegExp('a=rtpmap:([0-9]+) ' + escapeRegExp(codec))
    var videoRegex = new RegExp('(m=' + kind + ' .*?)( ([0-9]+))*\\s*$')

    var lines = realSdp.split('\n');

    var isKind = false;
    for (var i = 0; i < lines.length; i++) {
        if (lines[i].startsWith('m=' + kind + ' ')) {
            isKind = true;
        } else if (lines[i].startsWith('m=')) {
            isKind = false;
        }

        if (isKind) {
            var match = lines[i].match(codecRegex);
            if (match) {
                allowed.push(parseInt(match[1]));
            }

            match = lines[i].match(rtxRegex);
            if (match && allowed.includes(parseInt(match[2]))) {
                allowed.push(parseInt(match[1]));
            }
        }
    }

    var skipRegex = 'a=(fmtp|rtcp-fb|rtpmap):([0-9]+)';
    var sdp = '';

    isKind = false;
    for (var i = 0; i < lines.length; i++) {
        if (lines[i].startsWith('m=' + kind + ' ')) {
            isKind = true;
        } else if (lines[i].startsWith('m=')) {
            isKind = false;
        }

        if (isKind) {
            var skipMatch = lines[i].match(skipRegex);
            if (skipMatch && !allowed.includes(parseInt(skipMatch[2]))) {
                continue;
            } else if (lines[i].match(videoRegex)) {
                sdp += lines[i].replace(videoRegex, '$1 ' + allowed.join(' ')) + '\n';
            } else {
                sdp += lines[i] + '\n';
            }
        } else {
            sdp += lines[i] + '\n';
        }
    }

    return sdp;
}

function escapeRegExp(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}

enumerateInputDevices();

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>WebRTC demo</title>
    <style>
    button {
        padding: 8px 16px;
    }

    pre {
        overflow-x: hidden;
        overflow-y: auto;
    }

    video {
        width: 50%; /* Adjust as needed */
        height: auto; /* Maintain aspect ratio */
        border: 1px solid black; /* Optional: Add border for clarity */
    }

    .option {
        margin-bottom: 8px;
    }

    #media {
        max-width: 1280px;
    }
    </style>
</head>
<body>

<h2>Options</h2>
<div class="option">
    <input id="use-datachannel" checked="checked" type="checkbox"/>
    <label for="use-datachannel">Use datachannel</label>
    <select id="datachannel-parameters">
        <option value='{"ordered": true}'>Ordered, reliable</option>
        <option value='{"ordered": false, "maxRetransmits": 0}'>Unordered, no retransmissions</option>
        <option value='{"ordered": false, "maxPacketLifetime": 500}'>Unordered, 500ms lifetime</option>
    </select>
</div>
<div class="option">
    <input id="use-audio" checked="checked" type="checkbox"/>
    <label for="use-audio">Use audio</label>
    <select id="audio-input">
        <option value="" selected>Default device</option>
    </select>
    <select id="audio-codec">
        <option value="default" selected>Default codecs</option>
        <option value="opus/48000/2">Opus</option>
        <option value="PCMU/8000">PCMU</option>
        <option value="PCMA/8000">PCMA</option>
    </select>
</div>
<div class="option">
    <input id="use-video" type="checkbox"/>
    <label for="use-video">Use video</label>
    <select id="video-input">
        <option value="" selected>Default device</option>
    </select>
    <select id="video-resolution">
        <option value="" selected>Default resolution</option>
        <option value="320x240">320x240</option>
        <option value="640x480">640x480</option>
        <option value="960x540">960x540</option>
        <option value="1280x720">1280x720</option>
    </select>
    <select id="video-transform">
        <option value="none" selected>No transform</option>
        <option value="edges">Edge detection</option>
        <option value="cartoon">Cartoon effect</option>
        <option value="rotate">Rotate</option>
    </select>
    <select id="video-codec">
        <option value="default" selected>Default codecs</option>
        <option value="VP8/90000">VP8</option>
        <option value="H264/90000">H264</option>
    </select>
</div>
<div class="option">
    <input id="use-stun" type="checkbox"/>
    <label for="use-stun">Use STUN server</label>
</div>

<button id="start" onclick="start()">Start</button>
<button id="stop" style="display: none" onclick="stop()">Stop</button>
<button id="restart" style="display: none" onclick="restart()">Restart</button>

<h2>State</h2>
<p>
    ICE gathering state: <span id="ice-gathering-state"></span>
</p>
<p>
    ICE connection state: <span id="ice-connection-state"></span>
</p>
<p>
    Signaling state: <span id="signaling-state"></span>
</p>

<div id="media" style="display: none">
    <h2>Media</h2>

    <audio id="audio" autoplay="true"></audio>
    <video id="video" autoplay="true" playsinline muted="true"></video>
</div>

<h2>Data channel</h2>
<pre id="data-channel" style="height: 200px;"></pre>

<h2>SDP</h2>

<h3>Offer</h3>
<pre id="offer-sdp"></pre>

<h3>Answer</h3>
<pre id="answer-sdp"></pre>

<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.1.2/socket.io.js"></script>
<script src="client.js"></script>

</body>
</html>

On step 4, the SDP for offer and answer look like so:

Offer:

RTCSessionDescription(sdp='v=0\r\no=- 2037773936412914642 3 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS 7fba2d1a-ddfe-4036-9f71-cc3ec7797a92\r\nm=audio 59128 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126\r\nc=IN IP4 192.168.1.2\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=candidate:1477703846 1 udp 2122194687 192.168.1.2 59128 typ host generation 0 network-id 1 network-cost 10\r\na=candidate:3267981133 1 udp 2122262783 2401:4900:1c82:3426:651e:a3ff:e97f:fa6 63481 typ host generation 0 network-id 2 network-cost 10\r\na=candidate:2797153330 1 tcp 1518214911 192.168.1.2 9 typ host tcptype active generation 0 network-id 1 network-cost 10\r\na=candidate:1013169113 1 tcp 1518283007 2401:4900:1c82:3426:651e:a3ff:e97f:fa6 9 typ host tcptype active generation 0 network-id 2 network-cost 10\r\na=ice-ufrag:7D++\r\na=ice-pwd:K1CYl02n8bdoY+3rYQWQAbQP\r\na=ice-options:trickle\r\na=fingerprint:sha-256 67:52:FA:9C:AF:E4:5F:DA:43:90:2C:58:D7:AC:9D:22:74:85:97:4B:CB:E0:39:92:75:29:F6:44:D7:06:E6:B0\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:7fba2d1a-ddfe-4036-9f71-cc3ec7797a92 b5473dbd-45ed-456a-844a-141f33ff638a\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:1865823750 cname:/gC4MSzf3xqbh01/\r\na=ssrc:1865823750 msid:7fba2d1a-ddfe-4036-9f71-cc3ec7797a92 b5473dbd-45ed-456a-844a-141f33ff638a\r\nm=application 64442 UDP/DTLS/SCTP webrtc-datachannel\r\nc=IN IP4 192.168.1.2\r\na=candidate:1477703846 1 udp 2122194687 192.168.1.2 64442 typ host generation 0 network-id 1 network-cost 10\r\na=candidate:3267981133 1 udp 2122262783 2401:4900:1c82:3426:651e:a3ff:e97f:fa6 56518 typ host generation 0 network-id 2 network-cost 10\r\na=candidate:2797153330 1 tcp 1518214911 192.168.1.2 9 typ host tcptype active generation 0 network-id 1 network-cost 10\r\na=candidate:1013169113 1 tcp 1518283007 2401:4900:1c82:3426:651e:a3ff:e97f:fa6 9 typ host tcptype active generation 0 network-id 2 network-cost 10\r\na=ice-ufrag:7D++\r\na=ice-pwd:K1CYl02n8bdoY+3rYQWQAbQP\r\na=ice-options:trickle\r\na=fingerprint:sha-256 67:52:FA:9C:AF:E4:5F:DA:43:90:2C:58:D7:AC:9D:22:74:85:97:4B:CB:E0:39:92:75:29:F6:44:D7:06:E6:B0\r\na=setup:actpass\r\na=mid:1\r\na=sctp-port:5000\r\na=max-message-size:262144\r\n', type='offer')

SDP Answer:

RTCSessionDescription(sdp='v=0\r\no=- 3920503560 3920503560 IN IP4 0.0.0.0\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0 1\r\na=msid-semantic:WMS *\r\nm=audio 55237 UDP/TLS/RTP/SAVPF 111 0 8\r\nc=IN IP4 192.168.1.2\r\na=sendrecv\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=mid:0\r\na=msid:53b037b2-c242-49d8-a477-b044c0d89981 a22e91ea-c9c3-418b-a7c2-2fcaed4c90ed\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=rtcp-mux\r\na=ssrc:1959971329 cname:a149fe7a-97b6-410c-bcc9-a381eba85248\r\na=rtpmap:111 opus/48000/2\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=candidate:e2aea8ef0c61df57b654b7125d14184e 1 udp 2130706431 192.168.1.2 55237 typ host\r\na=candidate:30f6c4893f1e5a71c6114ba5b5f606cd 1 udp 2130706431 2401:4900:1c82:3426:145b:a320:f63f:fbe8 51868 typ host\r\na=candidate:eb8af3b161ddfeb4382c967aa1376a76 1 udp 2130706431 2401:4900:1c82:3426:651e:a3ff:e97f:fa6 53645 typ host\r\na=candidate:7b29ffc97bc5f5ca4db0cbe5623d884d 1 udp 1694498815 106.202.169.164 55237 typ srflx raddr 192.168.1.2 rport 55237\r\na=end-of-candidates\r\na=ice-ufrag:tQZc\r\na=ice-pwd:xjDxSq6CUo6viByQPg17zZ\r\na=fingerprint:sha-256 B5:17:70:2B:73:E2:A2:99:DC:53:DB:27:33:BA:AA:6B:BB:99:D5:40:D4:C0:F0:3F:17:21:8B:61:D0:25:CE:43\r\na=setup:active\r\nm=application 55237 UDP/DTLS/SCTP webrtc-datachannel\r\nc=IN IP4 192.168.1.2\r\na=mid:1\r\na=sctp-port:5000\r\na=max-message-size:65536\r\na=candidate:e2aea8ef0c61df57b654b7125d14184e 1 udp 2130706431 192.168.1.2 55237 typ host\r\na=candidate:30f6c4893f1e5a71c6114ba5b5f606cd 1 udp 2130706431 2401:4900:1c82:3426:145b:a320:f63f:fbe8 51868 typ host\r\na=candidate:eb8af3b161ddfeb4382c967aa1376a76 1 udp 2130706431 2401:4900:1c82:3426:651e:a3ff:e97f:fa6 53645 typ host\r\na=candidate:7b29ffc97bc5f5ca4db0cbe5623d884d 1 udp 1694498815 106.202.169.164 55237 typ srflx raddr 192.168.1.2 rport 55237\r\na=end-of-candidates\r\na=ice-ufrag:tQZc\r\na=ice-pwd:xjDxSq6CUo6viByQPg17zZ\r\na=fingerprint:sha-256 B5:17:70:2B:73:E2:A2:99:DC:53:DB:27:33:BA:AA:6B:BB:99:D5:40:D4:C0:F0:3F:17:21:8B:61:D0:25:CE:43\r\na=setup:active\r\n', type='answer'
@aiortc aiortc locked and limited conversation to collaborators May 21, 2024
@jlaine jlaine converted this issue into discussion #1096 May 21, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant