Safari TP 34では新しい片方向WebRTCの手順が使えるようになったので、試してみた


まえがき

WebRTC APIのアップデートの概要は、@yusuke84さんの記事「WebRTC Update 2016 Summer」が参考になります。

本題

WebRTCのブラウザ実装としては最後発となるSafariですが、Safari Technology Preview(以下、Safari TP) 34のリリースノートを読むと、

  • Added support for receive-only SDP offers through addTransceiver

と書かれています。意外なことに、ChromeやFirefox、Edgeを出し抜いて、です。

RTCRtpTransceiver

現在のWebRTCのAPI仕様では、ストリームの入出力をより自由に扱えるよう、

  • ストリームの入力とコーデックのパラメータ設定等を扱うRTCRtpSender
  • 受信ストリームを扱うRTCRtpReceiver

が設けられています。Safari TP 34では、これら2つをまとめて送受信を管理するRTCRtpTransceiverを備えています。

Before RTCRtpTransceiver

現在のChromeやFirefox等では、片方向に音声・映像を送受信する場合は、RTCPeerConnectionにマイクやカメラのMediaStreamTrackaddTrackで追加して、createOffercreateAnswerofferToReceiveVideoofferToReceiveAnswerといったオプションを使う必要がありました。例えば、Offerer側がビデオを送信するのみで受信しないようにしたい場合は、次のような感じのコードになります。

const pc = new RTCPeerConnection({iceServers: [/* 中略 */]});
navigator.mediaDevices.getUserMedia({video: true}).then(stream => {
  pc.addTrack(stream.getVideoTracks()[0]);
  return pc.createOffer({offerToReceiveVideo: false});
}).then(sdp => {
  return pc.setLocalDescription(sdp);
}).then(() => {
  // ここで、Answerer側にsdpをwebサーバ経由で送信
});

この方法の場合、全オーディオトラック、全ビデオトラックまとめて受信の有無を設定することになります。なお、現在のWebRTCの仕様では、これらのオプションはLegacy Interface Extensionsという扱いになっています。

After RTCRtpTransceiver

RTCRtpTransceiverを使うと、片方向通信の扱い方が次のように変わります。まず、RTCRtpTransceiverは次のような手順で作成します。

RTCRtpTransceiverの作り方(その1)
navigator.mediaDevices.getUserMedia({video: true}).then(stream => {
  const pc = new RTCPeerConnection({iceServers: [/* 中略 */]});
  const transceiver = pc.addTransceiver(stream.getVideoTracks()[0]);
})
RTCRtpTransceiverの作り方(その2)
const pc = new RTCPeerConnection({iceServers: [/* 中略 */]});
const transceiver = pc.addTransceiver('video'); // 引数は'video'もしくは'audio'

作成されたRTCRtpTransceiverには、senderreceiverの2つの属性が設定され、送信(入力)・受信それぞれのMediaStreamTrackをコントロールできるようになっています。

ここで、次のようにすることで、トラック単位で、送信のみ、受信のみを設定することが可能です。

送信のみの例
const transceiver = pc.addTransceiver(stream.getVideoTracks()[0]);
transceiver.receiver.track.enabled = false;
transceiver.setDirection('sendonly');
受信のみの例
const transceiver = pc.addTransceiver('video');
transceiver.setDirection('recvonly');

この後で、pc.createOffer()pc.createAnswer()を実行した時に生成されるSDPに、受信する内容が反映されます。次のSDPの例では、確かにa=recvonlyとなっているのが確認できます。

v=0
o=- 4056205042783684755 3 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE video
a=msid-semantic: WMS
m=video 9 UDP/TLS/RTP/SAVPF 96 98 99 97 100
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:x4Xi
a=ice-pwd:pqIcVaMdsF/jXsud1MpqZRKz
a=fingerprint:sha-256 B8:0E:77:89:95:23:2B:0D:A9:75:60:8A:CA:A7:21:CA:3B:F9:11:0F:CB:C8:ED:A5:B5:46:B0:71:16:BF:F8:58
a=setup:actpass
a=mid:video
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4 urn:3gpp:video-orientation
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=recvonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 red/90000
a=rtpmap:98 ulpfec/90000
a=rtpmap:99 H264/90000
a=rtcp-fb:99 ccm fir
a=rtcp-fb:99 nack
a=rtcp-fb:99 nack pli
a=rtcp-fb:99 goog-remb
a=rtcp-fb:99 transport-cc
a=fmtp:99 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:100 rtx/90000
a=fmtp:100 apt=99

ちなみに、sendonlyで全く受信しないRTCPeerConnectionでSDPを生成すると、次のような結果になります。全く受信を行わないので、当然ながら受信に関するIPアドレスやポートの情報が全く追加されていないのが確認できます。

v=0
o=- 3443265369116888093 2 IN IP4 127.0.0.1
s=-
t=0 0
a=msid-semantic: WMS