Skip to content
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

Send SDP answer without signalling server #1615

Closed
3 of 4 tasks
AlexeyBoiko opened this issue Aug 13, 2023 · 5 comments
Closed
3 of 4 tasks

Send SDP answer without signalling server #1615

AlexeyBoiko opened this issue Aug 13, 2023 · 5 comments

Comments

@AlexeyBoiko
Copy link

AlexeyBoiko commented Aug 13, 2023

Please read first!

Please use discuss-webrtc for general technical discussions and questions.
If you have found an issue/bug with the native libwebrtc SDK or a browser's behaviour around WebRTC please create an issue in the relevant bug tracker. You can find more information on how to submit a bug and do so in the right place here

  • I understand that issues created here are only relevant to the samples in this repo - not browser or SDK bugs
  • I have provided steps to reproduce
  • I have provided browser name and version
  • I have provided a link to the sample here or a modified version thereof

Note: If the checkboxes above are not checked (which you do after the issue is posted), the issue will be closed.

Browser affected

Version 115.0.5790.171 (Official Build) (64-bit)

Description

Is it possible to send SDP answer over RTCPeerConnection without signaling server?
Exmaple 2 shows what I am asking for.

Steps to reproduce

Example 1. WebRTC communication, when SDP answer send over signaling server.

LOCAL                 Singnaling    REMOTE
peer                  service       peer
  |                      |             |
create SDP offer         |             |
send SDP offer---------->|------------>|
  |                      |     setRemoteSdp(SDP offer)
  |                      |     create SDP answer
  |<---------------------|<----send SDP answer <======
  |                      |             |
setRemoteSdp(SDP answer) |             |
create ICE candidate 1   |             |
send ICE candidate 1---->|------------>|
  |                      |     addIceCandidate(ICE 1)
create ICE candidate 2   |             |
send ICE candidate 2---->|------------>|
  |                            addIceCandidate(ICE 2)
  |                                    |
  |                            select best ICE
  |<---------------------------send REMOTE ICE over WebRTC
  |                                    |
  |<-------Peer2Peer over WebRTC------>|

It is mock signaling server:

const signaling = {
    /** @param {string} senderId,  @param {RTCSessionDescription} sdp */
    sdpSend: (senderId, sdp) => {
        console.log(`${senderId} SDP send`);
        document.dispatchEvent(new CustomEvent('sdp', {detail: {senderId, sdp} }))
    },

    /** @param {string} clientId,  @param {(sdp:RTCSessionDescription)=>void} callBack */
    sdpOnGet: (clientId, callBack) =>
        document.addEventListener('sdp', evt => {
            if (evt.detail.senderId !== clientId) {
                console.log(`${clientId} SDP onGet`);
                callBack(evt.detail.sdp)
            }
        }),

    /** @param {string} senderId,  @param {RTCIceCandidate} candidate */
    iceCandidateSend: (senderId, candidate) => {
        console.log(`${senderId} iceCandidate send`);
        document.dispatchEvent(new CustomEvent('ice', {detail: {senderId, candidate} }))
    },

    /** @param {string} clientId,  @param {(candidate:RTCIceCandidate)=>void} callBack */
    iceCandidateOnGet: (clientId, callBack) =>
        document.addEventListener('ice', evt => {
            if (evt.detail.senderId !== clientId) {
                console.log(`${clientId} iceCandidate onGet`);
                callBack(evt.detail.candidate);
            }
        })
};

Here is working code

//
// remote peer
{
    const remoteClientId = 'REMOTE';

    const remoteCon = new RTCPeerConnection();
    remoteCon.ondatachannel = evt => {
        console.log('REMOTE ondatachannel');
        const channel = evt.channel;
        channel.onopen = (event) => {
            console.log('REMOTE channel onopen');

            const msg = 'Hi from remote';
            console.log(`REMOTE message send: ${msg}`);
            channel.send(msg);
        };
        channel.onmessage = (event) => {
            console.log('REMOTE onmessage:', event.data);
        };
    };

    signaling.iceCandidateOnGet(remoteClientId, async candidate => 
        await remoteCon.addIceCandidate(candidate));

    signaling.sdpOnGet(remoteClientId, async sdpOffer => {
        await remoteCon.setRemoteDescription(sdpOffer);
        await remoteCon.setLocalDescription();
        signaling.sdpSend(remoteClientId, remoteCon.localDescription);
    });
}

//
// local peer
{
    const localClientId = 'LOCAL';

    const localCon = new RTCPeerConnection();
    const localChannel = localCon.createDataChannel('chat');
    localChannel.onopen = evt => {
        console.log('LOCAL channel onopen');

        const msg = 'Hi from local';
        console.log(`LOCAL message send: ${msg}`);
        localChannel.send(msg);
    };
    localChannel.onmessage = evt =>
        console.log('LOCAL onmessage:', evt.data);  

    signaling.sdpOnGet(localClientId, async sdpRemote => {
        await localCon.setRemoteDescription(sdpRemote);

        localCon.onicecandidate = evt => {
            if (evt.candidate) {
                signaling.iceCandidateSend(localClientId, evt.candidate);
            }
        }
    });

    // SDP
    // creates and sets SDP
    await localCon.setLocalDescription();
    signaling.sdpSend(localClientId, localCon.localDescription);
}

Console ouput:

LOCAL SDP send
REMOTE SDP onGet
REMOTE SDP send
LOCAL SDP onGet
LOCAL iceCandidate send
REMOTE iceCandidate onGet
LOCAL channel onopen
LOCAL message send: Hi from local
REMOTE ondatachannel
REMOTE channel onopen
REMOTE message send: Hi from remote
REMOTE onmessage: Hi from local
LOCAL onmessage: Hi from remote

Example 2. WebRTC communication, when SDP answer send over WebRTC
What I am asking for.

As I understand:
LOCAL peer can create SDP, collect all ICE Candidates, and send all at once to REMOTE peer over Signaling Server. REMOTE peer know SDP offer and ICE Candidates -> can send SDP answer over WebRTC.

LOCAL                 Singnaling    REMOTE
peer                  service       peer
  |                      |             |
create SDP offer         |             |
create ICE candidate 1   |             |
create ICE candidate 2   |             |
  |                      |             |
send SDP, ICE1 , ICE 2-->|------------>|
  |                            setRemoteSdp(SDP offer)
  |                            create SDP answer
  |                            addIceCandidate(ICE 1)
  |                            addIceCandidate(ICE 2)
  |                            select best ICE
  |<---------------------------send SDP answer, ICEs over WebRTC
  |                                    |
  |<-------Peer2Peer over WebRTC------>|

singnaling service:

const signaling = {
    /** @param {string} senderId,  @param {RTCSessionDescription} sdp, @param {RTCIceCandidate} iceCandidates */
    sdpAndIceChannelsSend: (senderId, sdp, iceCandidates) => {
        console.log(`${senderId} SDP and ICECandidates send`);
        document.dispatchEvent(new CustomEvent('sdpAndIce', {detail: { senderId, sdp, iceCandidates } }));
    },

    /** @param {string} clientId,  @param {(sdp:RTCSessionDescription, iceCandidates:RTCIceCandidate[])=>void} callBack */
    sdpAndIceChannelsOnGet: (clientId, callBack) =>
        document.addEventListener('sdpAndIce', evt => {
            if (evt.detail.senderId !== clientId) {
                console.log(`${clientId} SDP and iceCandidates onGet`);
                callBack(evt.detail.sdp, evt.detail.iceCandidates);
            }
        }),
};

Not working code

//
// remote peer
{
    const remoteClientId = 'REMOTE';

    const remoteCon = new RTCPeerConnection();
    remoteCon.ondatachannel = evt => {
        console.log('REMOTE ondatachannel');
        const channel = evt.channel;
        channel.onopen = (event) => {
            console.log('REMOTE channel onopen');

            // send message to LOCAL
            const msg = 'Hi from remote';
            console.log(`REMOTE message send: ${msg}`);
            channel.send(msg);
        };
        channel.onmessage = (event) => {
            console.log('REMOTE onmessage:', event.data);
        };
    };

    signaling.sdpAndIceChannelsOnGet(remoteClientId, async (sdpOffer, iceCandidates) => {
        await remoteCon.setRemoteDescription(sdpOffer);
        await remoteCon.setLocalDescription();

        for(const iceCandidate of iceCandidates)
            await remoteCon.addIceCandidate(iceCandidate);
    });
}

//
// local peer
{
    const localClientId = 'LOCAL';

    const localCon = new RTCPeerConnection();
    const localChannel = localCon.createDataChannel('chat');
    localChannel.onopen = evt => {
        console.log('LOCAL channel onopen');

        // send message to REMOTE
        const msg = 'Hi from local';
        console.log(`LOCAL message send: ${msg}`);
        localChannel.send(msg);
    };
    localChannel.onmessage = evt =>
        console.log('LOCAL onmessage:', evt.data);  

    // SDP
    // creates and sets SDP
    await localCon.setLocalDescription();

    const iceCandidates = [];
    localCon.onicecandidate = evt => {
        if (evt.candidate) {
            iceCandidates.push(evt.candidate);
        } else {
            // all ice candidates getted
            // send SDP and iceCandidates to REMOTE
            signaling.sdpAndIceChannelsSend(localClientId,
                localCon.localDescription, iceCandidates);
        }
    }
}

Console ouput:

LOCAL SDP and ICECandidates send
REMOTE SDP and iceCandidates onGet

Expected results

Example of WebRTC connection setup without sendind SDP aswer over signaling server.

Actual results

Can't setup WebRTC connection without sendind SDP aswer over signaling server.

@alvestrand
Copy link
Contributor

As I understand:

LOCAL peer can create SDP, collect all ICE Candidates, and send all at once to REMOTE peer over Signaling Server. REMOTE peer know SDP offer and ICE Candidates -> can send SDP answer over WebRTC.

No, it doesn't work that way.
The REMOTE peer knows the SDP offer and the ICE candidates, so it can send ICE candidates to the LOCAL peer.
But those ICE candidates do not contain the SDP - in particular, they can't send the FINGERPRINT attribute which is critical to establishing the DTLS connection.

People have speculated about the possibility of including the necessary information in the ICE handshake, but so far, nobody (as far as I know) has published a working example of this approach, or undertaken the necessary work of standardization that would be needed to make it part of the official WebRTC spec.

@AlexeyBoiko
Copy link
Author

@alvestrand thank you. Could please clatify:

Imagin text chat app. Users can connect to chat by link.
To send messages to all users every user must have Peer2Peer WebRTC connection with all users. It is many-to-many.
When new user connect to chat, all current users must receive the new user's SDP. The only way current users can get new user's SDP is by using Signling Server.

Thus: every user must have alive socket connection with Signling Server. Socket connection must be alive all time user in chat.
If all users have full time alive socket connection - what's the benefit of WebRTC ?

@alvestrand
Copy link
Contributor

alvestrand commented Aug 14, 2023 via email

@fippo
Copy link
Collaborator

fippo commented Aug 14, 2023

The answer is simple: for that use-case WebRTC offers little benefit. Those come when you add requirements like file transfer and voice/video chat which the signaling/chat server can't handle for capacity reasons.

@AlexeyBoiko
Copy link
Author

AlexeyBoiko commented Aug 14, 2023

@alvestrand @fippo thank you. I know my questions is not an issue report. But it is the only place where I can get answers.

Code below exchange only SDP between peers. It does not send ICE candidates. And its working,

LOCAL                      Singnaling    REMOTE
peer                       server       peer
  |                          |             |
create SDP offer             |             |
  |                          |             |
create ICE candidate 1       |             |
create ICE candidate 2       |             |
(just subscribe              |             |
onicecandidate,              |             |
wait for last ICE            |             |
don't send ICEs)             |             |
  |                          |             |
send SDP only without ICEs-->|------------>|
  |                          |     setRemoteSdp(SDP offer)
  |                          |     create SDP answer
  |<-------------------------|<----send SDP answer without ICEs
setRemoteSdp(SDP answer)                   |
  |                                        |
  |<---------Peer2Peer over WebRTC-------->|

Signaling server mock

const signaling = {
	/** @param {string} senderId,  @param {RTCSessionDescription} sdp */
	sdpAndIceChannelsSend: (senderId, sdp) => {
		console.log(`${senderId} SDP send`);
		document.dispatchEvent(new CustomEvent('sdp', {detail: { senderId, sdp } }));
	},

	/** @param {string} clientId,  @param {(sdp:RTCSessionDescription)=>void} callBack */
	sdpAndIceChannelsOnGet: (clientId, callBack) =>
		document.addEventListener('sdp', evt => {
			if (evt.detail.senderId !== clientId) {
				console.log(`${clientId} SDP onGet`);
				callBack(evt.detail.sdp);
			}
		}),
};

code:

//
// remote peer
{
	const remoteClientId = 'REMOTE';

	const remoteCon = new RTCPeerConnection();
	remoteCon.ondatachannel = evt => {
		console.log('REMOTE ondatachannel');
		const channel = evt.channel;
		channel.onopen = (event) => {
			console.log('REMOTE channel onopen');

			// send message to LOCAL
			const msg = 'Hi from remote';
			console.log(`REMOTE message send: ${msg}`);
			channel.send(msg);
		};
		channel.onmessage = (event) => {
			console.log('REMOTE onmessage:', event.data);
		};
	};

	signaling.sdpAndIceChannelsOnGet(remoteClientId, async (sdpOffer, iceCandidates) => {
		await remoteCon.setRemoteDescription(sdpOffer);
		await remoteCon.setLocalDescription();

		signaling.sdpAndIceChannelsSend(remoteClientId, remoteCon.localDescription);
	});
}

//
// local peer
{
	const localClientId = 'LOCAL';

	const localCon = new RTCPeerConnection();
	const localChannel = localCon.createDataChannel('chat');
	localChannel.onopen = evt => {
		console.log('LOCAL channel onopen');

		// send message to REMOTE
		const msg = 'Hi from local';
		console.log(`LOCAL message send: ${msg}`);
		localChannel.send(msg);
	};
	localChannel.onmessage = evt =>
		console.log('LOCAL onmessage:', evt.data);	

	// SDP
	// creates and sets SDP
	await localCon.setLocalDescription();

	signaling.sdpAndIceChannelsOnGet(localClientId, async sdpAnswer =>
		await localCon.setRemoteDescription(sdpAnswer));

	localCon.onicecandidate = evt => {
		if (!evt.candidate) {
			// all ice candidates getted			
			signaling.sdpAndIceChannelsSend(localClientId, localCon.localDescription);
		}
	};
}

Console ouput:

LOCAL SDP send
REMOTE SDP onGet
REMOTE SDP send
LOCAL SDP onGet
LOCAL channel onopen
LOCAL message send: Hi from local
REMOTE ondatachannel
REMOTE channel onopen
REMOTE message send: Hi from remote
REMOTE onmessage: Hi from local
LOCAL onmessage: Hi from remote

Why this code work if ICEs don't exchange?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants