🧊

Firefox 68から空文字列のcandidateが発行されるように

なります。

  • つまりどういうことで
  • どういう場合に困るか
  • そもそもどういうことか

みたいなのをまとめておきます。

空文字列のcandidate

WebRTCのRTCPeerConnectionで、通信に使う経路の候補が見つかったときの話です。

const pc = new RTCPeerConnection();

pc.addEventListener('icecandidate', ev => {
  // コレ
  ev.candidate;
});

この`ev.candidate`に候補情報が入ってて、基本的にはコレをシグナリングする。
受け取った側は、`addIceCandidate()`でそれをセットする。

で、現状のFirefox 67や、Chrome/Safariなどなどは、`icecandidate`イベントで2パターンの`candidate`を発行してました。

つまり`ev.candidate`が、

  • `RTCIceCandidate`のインスタンスで、各種プロパティが詰まったもの
  • `null`

の2パターンだった。

そこに今回、

  • `RTCIceCandidate`のインスタンスで、`candidate`プロパティが空文字列のもの

という3パターン目が追加されるというわけ。

{
  candidate: "", // 👀
  sdpMLineIndex: 0,
  sdpMid: "0",
  usernameFragment: "26d84f30"
}

こういうやつ。

どういう場合に困るか

今までは`null`か`RTCIceCandidate`かどっちかだったので、こういうコードを書いてたはず。

const pc = new RTCPeerConnection();

pc.addEventListener('icecandidate', ev => {
  // null でないならシグナリングして addIceCandidate() する
  if (ev.candidate !== null) {
    signaling.send('candidate', ev.candidate);
  }
});

こうしてた理由は、そもそも不要な上に`addIceCandidate(null)`がどのブラウザでもエラーになってたから。

で、今回の問題は、今回追加されるやつがこの条件をくぐり抜けてくる + Firefox 68以外では動かない。

978582 - addIceCandidate({candidate: "", sdpMLineIndex: 0 })) throws an error - chromium - Monorail - https://bugs.chromium.org/p/chromium/issues/detail?id=978582

これがChrome側に寄せられたバグ報告で、こっちはFirefox側のお気持ち。

1562281 - Interop: Chrome/Safari rejects the RTCIceCandidate (>=68) with the empty candidate string. - https://bugzilla.mozilla.org/show_bug.cgi?id=1562281

Safari

199316 – addIceCandidate({candidate: "", sdpMLineIndex: 0 })) throws an error - https://bugs.webkit.org/show_bug.cgi?id=199316

どうすればいいか

端的には、`null`と一緒で無視すればいい。

実際の動作には影響ないし。

const pc = new RTCPeerConnection();

pc.addEventListener('icecandidate', ev => {
  if (ev.candidate && ev.candidate.candidate !== '') {
    signaling.send('candidate', ev.candidate);
  }
});

// or 受け取り側で addIceCandidate() 前にチェックするでも

nullとの違い

ここからおさらいコーナー。

`null`と空文字列の違いって何?なぜ今こんな修正を?という人向け。
答えはSpecに。

WebRTC 1.0: Real-time Communication Between Browsers - https://www.w3.org/TR/webrtc/#dom-rtcicecandidate-candidate

ざっくり訳しておく。

The icecandidate event is used for three different types of indications:

icecandidateイベントには、3パターンの意味がある。

A candidate has been gathered. The candidate member of the event will be populated normally. It should be signaled to the remote peer and passed into addIceCandidate.

通常通り`candidate`が収集されたとき。 リモートにシグナリングされて、`addIceCandidate()`される。

An RTCIceTransport has finished gathering a generation of candidates, and is providing an end-of-candidates indication as defined by Section 8.2 of [TRICKLE-ICE]. This is indicated by candidate.candidate being set to an empty string. The candidate object should be signaled to the remote peer and passed into addIceCandidate like a typical ICE candidate, in order to provide the end-of-candidates indication to the remote peer.

その`RTCIceTransport`が候補収集を終えて、TrickleICEの仕様にあるように`end-of-candidates`の意を表明するとき。
このとき、`candidate.candidate`が空文字列になり、シグナリングされて`addIceCandidate()される。`

All RTCIceTransports have finished gathering candidates, and the RTCPeerConnection's RTCIceGatheringState has transitioned to "complete". This is indicated by the candidate member of the event being set to null. This only exists for backwards compatibility, and this event does not need to be signaled to the remote peer. It's equivalent to an "icegatheringstatechange" event with the "complete" state.

すべての`RTCIceTransport`で候補収集が終わって、`RTCPeerConnection`の`iceGatheringState`が`complete`になるとき。
このとき、`null`が発行される。
これはシグナリングする必要はなく、`icegatheringstatechange`イベントがあるなら不要ではあるが、後方互換性のために残ってる。

空文字列のパターンで、`sdpMid`とか`sdpMLineIndex`がちゃんとついてるのはそういうワケでしたとさ。

まぁ昨今のトレンドとしては、基本的にすべてを1つにBUNDLEするし、`end-of-candidates`なんて待ってないんやけど・・。

おまけ

蛇足ですが、Firefox 68なら `addIceCandidate(null)` もできるようになります!