🧊

Safari TPのWebRTCでaddTransceiver('audio').setDirection('recvonly')するとMediaStreamがautoplayされない問題

タイトルに収まらんw

SafariのWebRTCは新しめの仕様なので、`createOffer()`にオプションを渡して`recvonly`にする方法が使えない。

pc.createOffer({
  offerToReceiveAudio: true,
  offerToReceiveVideo: true,
})

こういうやつ。

なので、代わりに、

pc.addTransceiver('audio').setDirection('recvonly');
pc.addTransceiver('video').setDirection('recvonly');

// からの
pc.createOffer();

とする必要がある。
だがしかし、こうやって開始したP2Pで相手のストリームを受け取ると、`video`に`autoplay`を指定していても再生が開始されないという謎の挙動を踏みましたというメモです。

しかもSafariどうしでは再現しなくて、片側はChromeFirefoxの時だけっていう・・。

確認バージョン

20170907: 追記

Auto-Play Policy Changes for macOS | WebKit

これのせいでそもそも`autoplay`が効かなくなっただけでは説について。

確かにこれで自動再生できなくなったのは筋が通ってるので、あーこれだったのかーという感じもしつつ、
ただSafari vs Safariのときは問題なかった記憶があるので、すんなり納得できない微妙な感じ。

あとユーザー起点じゃないと再生開始できないなら、以下の元記事で書いてた解決策が使えないことになるけど、試してみたら普通に動いててさらに謎。

確認バージョン

よーわからんなー。

検証コード

webrtc-sandbox/1dir-video at master · leader22/webrtc-sandbox · GitHub

HTML

どっちも同じ。

<video autoplay></video>
<textarea>paste offer sdp</textarea>
<textarea>paste answer sdp</textarea>

ホストは自分のストリームを見るために、ゲストは受け取ったストリームを見るための`video`。
Vanilla ICEなのでそれ用の`textarea`があるだけ。

Chrome / Firefox側(ホスト)

const $video = document.querySelector('video');
const [$offer, $answer] = document.querySelectorAll('textarea');

const pc = new RTCPeerConnection();

navigator.mediaDevices.getUserMedia({ audio: true, video: true })
  .then(stream => {
    $video.srcObject = stream;
    pc.addStream(stream);
  });

pc.onicecandidate = ev => {
  if (ev.candidate === null) $answer.textContent = JSON.stringify(pc.localDescription);
};

$offer.onchange = ev => {
  const sdp = JSON.parse(ev.target.value);

  pc.setRemoteDescription(sdp)
    .then(() => pc.createAnswer())
    .then(sdp => pc.setLocalDescription(sdp))
    .then(() => $answer.textContent = JSON.stringify(pc.localDescription));
};

Safari側(ゲスト)

const $video = document.querySelector('video');
const [$offer, $answer] = document.querySelectorAll('textarea');

const pc = new RTCPeerConnection();

pc.addTransceiver('video').setDirection('recvonly');
pc.addTransceiver('audio').setDirection('recvonly');

pc.createOffer()
  .then(sdp => pc.setLocalDescription(sdp));

pc.onicecandidate = ev => {
  if (ev.candidate === null) $offer.textContent = JSON.stringify(pc.localDescription);
};

pc.ontrack = ev => {
  $video.srcObject = ev.streams[0];
};

$answer.onchange = ev => {
  const sdp = JSON.parse(ev.target.value);

  pc.setRemoteDescription(sdp);
};

解決策

これの問題は再生が開始されないだけで、MediaStreamもちゃんと渡ってるしTrackも2種類来てるしP2Pもつながってる。
なので愚直に`video.play()`してあげればいいだけ。

pc.ontrack = ev => {
  $video.srcObject = ev.streams[0];

  // autoplayを信じずにplay()する!
  $video.paused && $video.play();
};

もう`autoplay`なんて信じない!っていう一幕でしたとさ。

ちなみに

  • ホスト側が`{ audio: false, video: true }`なストリームを流した場合は再生開始される
  • ゲスト側で`video`だけ`recvonly`にした場合も再生開始される

なので、`addTransceiver('audio').setDirection('recvonly')`が諸悪の根源っぽい・・?