最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - WebRTC peer connection does not establish on Safari, does in Chrome, Firefox - Stack Overflow

programmeradmin1浏览0评论

I've put together a quick snippet to test establishing a WebRTC peer connection within the same browser tab context.

const peerConnection1 = new RTCPeerConnection({ iceServers: [ { urls: 'stun:stun.l.google:19302' } ] });
peerConnection1.addEventListener('signalingstatechange', _ => log('1 signaling state ' + peerConnection1.signalingState));
peerConnection1.addEventListener('icegatheringstatechange', _ => log('1 ICE gathering state ' + peerConnection1.iceGatheringState));
peerConnection1.addEventListener('connectionstatechange', _ => log('1 connection state ' + peerConnection1.connectionState));

const peerConnection2 = new RTCPeerConnection({ iceServers: [ { urls: 'stun:stun.l.google:19302' } ] });
peerConnection2.addEventListener('signalingstatechange', _ => log('2 signaling state ' + peerConnection1.signalingState));
peerConnection2.addEventListener('icegatheringstatechange', _ => log('2 ICE gathering state ' + peerConnection1.iceGatheringState));
peerConnection2.addEventListener('connectionstatechange', _ => log('2 connection state ' + peerConnection1.connectionState));

const dataChannel = peerConnection1.createDataChannel(null);

const offer = await peerConnection1.createOffer();
await peerConnection1.setLocalDescription(offer);
await peerConnection2.setRemoteDescription(offer);

const answer = await peerConnection2.createAnswer();
await peerConnection2.setLocalDescription(answer);
await peerConnection1.setRemoteDescription(answer);

peerConnection1.addEventListener('icecandidate', event => {
  log('1 ICE candidate ' + (event.candidate ? event.candidate.candidate : 'null'))
  if (event.candidate !== null) {
    peerConnection2.addIceCandidate(event.candidate);
  }
});

peerConnection2.addEventListener('icecandidate', event => {
  log('2 ICE candidate ' + (event.candidate ? event.candidate.candidate : 'null'))
  if (event.candidate !== null) {
    peerConnection1.addIceCandidate(event.candidate);
  }
});

dataChannel.addEventListener('open', () => {
  dataChannel.send('message from 1 to 2');
});

dataChannel.addEventListener('message', event => {
  log('2: ' + event.data);
});

peerConnection2.addEventListener('datachannel', event => {
  monitor(event.channel, 'dc 2');
  event.channel.addEventListener('open', () => {
    event.channel.send('message from 2 to 1');
  });

  event.channel.addEventListener('message', event => {
    log('1: ' + event.data);
  });
});

This snippet works in Chrome and Firefox (tried both latest versions on Windows), but does not work in Safari, neither on iOS nor on macOS.

The log as seen in working browsers:

1 onnegotiationneeded
1 onsignalingstatechange
1 signaling state have-local-offer
2 onsignalingstatechange
2 signaling state have-local-offer
2 onsignalingstatechange
2 signaling state have-local-offer
1 onsignalingstatechange
1 signaling state stable
1 onicegatheringstatechange
1 ICE gathering state gathering
1 onicecandidate
1 ICE candidate candidate:0 1 UDP 2122252543 ... 59263 typ host
1 onicecandidate
1 ICE candidate candidate:2 1 TCP 2105524479 ... 9 typ host tcptype active
2 onicegatheringstatechange
2 ICE gathering state gathering
2 onicecandidate
2 ICE candidate candidate:0 1 UDP 2122252543 ... 59264 typ host
2 onicecandidate
2 ICE candidate candidate:2 1 TCP 2105524479 ... 9 typ host tcptype active
2 oniceconnectionstatechange
1 oniceconnectionstatechange
1 oniceconnectionstatechange
2 oniceconnectionstatechange
dc 1 onopen
2 ondatachannel
dc 2 onopen
dc 2 onmessage
1: message from 1 to 2
dc 1 onmessage
2: message from 2 to 1
1 onicecandidate
1 ICE candidate candidate:1 1 UDP 1686052863 ... 59263 typ srflx raddr ... rport 59263
1 onicegatheringstatechange
1 ICE gathering state plete
1 onicecandidate
1 ICE candidate null
2 onicecandidate
2 ICE candidate candidate:1 1 UDP 1686052863 ... 59264 typ srflx raddr ... rport 59264
2 onicegatheringstatechange
2 ICE gathering state plete
2 onicecandidate
2 ICE candidate null

The log as seen in non-working browsers:

1 onnegotiationneeded
1 onsignalingstatechange
1 signaling state have-local-offer
1 onicegatheringstatechange
1 ICE gathering state gathering
1 onconnectionstatechange
1 connection state connecting
2 onsignalingstatechange
2 signaling state have-local-offer
2 onsignalingstatechange
2 signaling state have-local-offer
2 onicegatheringstatechange
2 ICE gathering state gathering
2 onconnectionstatechange
2 connection state connecting
1 onsignalingstatechange
1 signaling state stable
1 oniceconnectionstatechange
1 onicecandidate
1 ICE candidate candidate:842163049 1 udp 1677729535 ... 55297 typ srflx raddr 0.0.0.0 rport 0 generation 0 ufrag e+HS network-cost 50
1 onicecandidate
1 ICE candidate null
1 onicegatheringstatechange
1 ICE gathering state plete
2 oniceconnectionstatechange
2 onicecandidate
2 ICE candidate candidate:842163049 1 udp 1677729535 ... 53858 typ srflx raddr 0.0.0.0 rport 0 generation 0 ufrag X+Uv network-cost 50
2 onicecandidate
2 ICE candidate null
2 onicegatheringstatechange
2 ICE gathering state plete

What could be the reason for the difference? It looks like no host candidates at all are collected in Safari. Is this a security measure? Can I turn it off in development to make this code work? How about production? Had this been a full example with ICE and peers on different devices, how could I have made sure candidates were collected in order to establish the peer connection?

I've put together a quick snippet to test establishing a WebRTC peer connection within the same browser tab context.

const peerConnection1 = new RTCPeerConnection({ iceServers: [ { urls: 'stun:stun.l.google.:19302' } ] });
peerConnection1.addEventListener('signalingstatechange', _ => log('1 signaling state ' + peerConnection1.signalingState));
peerConnection1.addEventListener('icegatheringstatechange', _ => log('1 ICE gathering state ' + peerConnection1.iceGatheringState));
peerConnection1.addEventListener('connectionstatechange', _ => log('1 connection state ' + peerConnection1.connectionState));

const peerConnection2 = new RTCPeerConnection({ iceServers: [ { urls: 'stun:stun.l.google.:19302' } ] });
peerConnection2.addEventListener('signalingstatechange', _ => log('2 signaling state ' + peerConnection1.signalingState));
peerConnection2.addEventListener('icegatheringstatechange', _ => log('2 ICE gathering state ' + peerConnection1.iceGatheringState));
peerConnection2.addEventListener('connectionstatechange', _ => log('2 connection state ' + peerConnection1.connectionState));

const dataChannel = peerConnection1.createDataChannel(null);

const offer = await peerConnection1.createOffer();
await peerConnection1.setLocalDescription(offer);
await peerConnection2.setRemoteDescription(offer);

const answer = await peerConnection2.createAnswer();
await peerConnection2.setLocalDescription(answer);
await peerConnection1.setRemoteDescription(answer);

peerConnection1.addEventListener('icecandidate', event => {
  log('1 ICE candidate ' + (event.candidate ? event.candidate.candidate : 'null'))
  if (event.candidate !== null) {
    peerConnection2.addIceCandidate(event.candidate);
  }
});

peerConnection2.addEventListener('icecandidate', event => {
  log('2 ICE candidate ' + (event.candidate ? event.candidate.candidate : 'null'))
  if (event.candidate !== null) {
    peerConnection1.addIceCandidate(event.candidate);
  }
});

dataChannel.addEventListener('open', () => {
  dataChannel.send('message from 1 to 2');
});

dataChannel.addEventListener('message', event => {
  log('2: ' + event.data);
});

peerConnection2.addEventListener('datachannel', event => {
  monitor(event.channel, 'dc 2');
  event.channel.addEventListener('open', () => {
    event.channel.send('message from 2 to 1');
  });

  event.channel.addEventListener('message', event => {
    log('1: ' + event.data);
  });
});

This snippet works in Chrome and Firefox (tried both latest versions on Windows), but does not work in Safari, neither on iOS nor on macOS.

The log as seen in working browsers:

1 onnegotiationneeded
1 onsignalingstatechange
1 signaling state have-local-offer
2 onsignalingstatechange
2 signaling state have-local-offer
2 onsignalingstatechange
2 signaling state have-local-offer
1 onsignalingstatechange
1 signaling state stable
1 onicegatheringstatechange
1 ICE gathering state gathering
1 onicecandidate
1 ICE candidate candidate:0 1 UDP 2122252543 ... 59263 typ host
1 onicecandidate
1 ICE candidate candidate:2 1 TCP 2105524479 ... 9 typ host tcptype active
2 onicegatheringstatechange
2 ICE gathering state gathering
2 onicecandidate
2 ICE candidate candidate:0 1 UDP 2122252543 ... 59264 typ host
2 onicecandidate
2 ICE candidate candidate:2 1 TCP 2105524479 ... 9 typ host tcptype active
2 oniceconnectionstatechange
1 oniceconnectionstatechange
1 oniceconnectionstatechange
2 oniceconnectionstatechange
dc 1 onopen
2 ondatachannel
dc 2 onopen
dc 2 onmessage
1: message from 1 to 2
dc 1 onmessage
2: message from 2 to 1
1 onicecandidate
1 ICE candidate candidate:1 1 UDP 1686052863 ... 59263 typ srflx raddr ... rport 59263
1 onicegatheringstatechange
1 ICE gathering state plete
1 onicecandidate
1 ICE candidate null
2 onicecandidate
2 ICE candidate candidate:1 1 UDP 1686052863 ... 59264 typ srflx raddr ... rport 59264
2 onicegatheringstatechange
2 ICE gathering state plete
2 onicecandidate
2 ICE candidate null

The log as seen in non-working browsers:

1 onnegotiationneeded
1 onsignalingstatechange
1 signaling state have-local-offer
1 onicegatheringstatechange
1 ICE gathering state gathering
1 onconnectionstatechange
1 connection state connecting
2 onsignalingstatechange
2 signaling state have-local-offer
2 onsignalingstatechange
2 signaling state have-local-offer
2 onicegatheringstatechange
2 ICE gathering state gathering
2 onconnectionstatechange
2 connection state connecting
1 onsignalingstatechange
1 signaling state stable
1 oniceconnectionstatechange
1 onicecandidate
1 ICE candidate candidate:842163049 1 udp 1677729535 ... 55297 typ srflx raddr 0.0.0.0 rport 0 generation 0 ufrag e+HS network-cost 50
1 onicecandidate
1 ICE candidate null
1 onicegatheringstatechange
1 ICE gathering state plete
2 oniceconnectionstatechange
2 onicecandidate
2 ICE candidate candidate:842163049 1 udp 1677729535 ... 53858 typ srflx raddr 0.0.0.0 rport 0 generation 0 ufrag X+Uv network-cost 50
2 onicecandidate
2 ICE candidate null
2 onicegatheringstatechange
2 ICE gathering state plete

What could be the reason for the difference? It looks like no host candidates at all are collected in Safari. Is this a security measure? Can I turn it off in development to make this code work? How about production? Had this been a full example with ICE and peers on different devices, how could I have made sure candidates were collected in order to establish the peer connection?

Share Improve this question asked Dec 24, 2018 at 12:25 Tomáš HübelbauerTomáš Hübelbauer 10.8k17 gold badges74 silver badges141 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 5

I've found the source of the problem and a workaround in this WebKit bug report:

https://bugs.webkit/show_bug.cgi?id=189503

The key is to call navigator.mediaDevices.getUserMedia({ video: true }) before trying to establish the peer connection. Safari seems to avoid disclosing the host candidates unless the permissions have been given first. After introducing this line to my example, the connection now succeeds.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论