Edited at

WebRTC ハンズオン資料 ScreenCapture & マルチストリーム編


はじめに

この資料は、WebRTC Beginners Tokyo が開催する、WebRTCハンズオン勉強会用の資料です。

※2019.02.06 WebRTC Meetup Tokyo / Osaka 向けに、アップデートしました。

※2019.05.31 Chrome 74 の状況をアップデートしました。



ScreenCapture API


  • 2017年まで ... 各ブラウザ独自にスクリーンキャプチャーを実装

  • 2018年以後 ... 仕様が固まりそれに合わせる動きが進んでいる



仕様


EXAMPLE1

try {

let mediaStream = await navigator.mediaDevices.getDisplayMedia({video:true});
videoElement.srcObject = mediaStream;
} catch (e) {
console.log('Unable to acquire screen capture: ' + e);
}

仕様上は指定できるオプション(まだ効果がないものが多い)


  • width, height, frameRate ... Chrome Canary 74 では有効

  • aspectRatio

  • resizeMode ... "none" / "crop-and-scale"

  • displaySurface ... "monitor" / "window" / "application" / "browser"

  • logicalSurface ... true / false

  • cursor ... "never" / "always" / "motion"



以前のChrome

※詳細は省略


  • 機能拡張を作成し、その中で chrome.desktopCapture.chooseDesktopMedia()を呼び出す

  • 取得した streamID を使って、 navigator.mediaDevices.getUserMedia() で取得する



Chrome 71 (2019.01.27現行)


  • chrome://flags/#enable-experimental-web-platform-features を Enable にすると使える

  • audioは取得できない(エラーになる)

let screen = await navigator.getDisplayMedia( { video: true });



Chrome 72 (近日登場→2019.01.30リリース済)


  • フラグ不要。デフォルトで使えるように

  • 仕様に沿ったAPIになる


    • 仕様上は "monitor", "window", などを指定できるが、それは無視される



  • audioは取得できない(エラーになる)

  • ※2019.01.30 現在、72がリリース済

let screen = await navigator.mediaDevices.getDisplayMedia( { video: true } );

// パラメーターは無視される
let win = await navigator.mediaDevices.getDisplayMedia( { video: { displaySurface: "window" } } );


Chrome 72 のキャプチャ対象選択ダイアログ


Chrome 74 (2019.05.31現在)


  • audioが取得できるように


    • audio単体では取得できない。videoとセット



  • { video: true, audio: true } で getDisplayMedia() を呼び出す

  • 選択ダイアログの左下に 「音声を共有する」のチェックボックスが表示される


    • Windows の場合 ... 全画面 および Chromeタブ の場合

    • それ以外の場合 ... Chromeタブ の場合



  • ユーザーが明示的に「音声を共有する」をチェックした場合は、音声も取れる

※音声を取るにはソースコードのオプションと、ユーザーの操作の2重の両方が必要



Firefox 64/65(現行)


let screen = await navigator.mediaDevices.getUserMedia({
video: {mediaSource: "screen"}
});


  • mediaSrouce には、"window" を指定することもできる



Firefox Nightly 66


  • 仕様に沿った実装がされている


    • 仕様上は "monitor", "window", などを指定できるが、それは無視される



  • audioは取得できない(無視される)

let screen = await navigator.mediaDevices.getDisplayMedia( { video: true } );


Firefox Nightly 66 のキャプチャ対象選択ダイアログ



Safari Technology Preview 73/74


  • 開発オプションで有効にできる


    • Develop - Experimental Features - ScreenCapture



  • 仕様に沿った実装がされている

  • スクリーン全体だけ(ウィンドウは選択できない)

  • audioは取得できない(無視される)

let screen = await navigator.mediaDevices.getDisplayMedia( { video: true } );


Safari TP 73 のキャプチャ許可ダイアログ



スクリーンキャプチャーを試してみる

※2019.01.30追記

GitHub Page で次の3つのパターンが試せる



ビデオチャット+スクリーンキャプチャー

すでにWebRTCでビデオチャットしている最中に、あとからScreenCapureで画面共有をする場合、3通りの方法が考えられる。


  • マルチ Peer

  • ビデオトラック差し替え

  • マルチストリーム



マルチ Peer


  • 新たに PeerConnection を接続し、そちらで画面共有のMediaStreamを送る

  • 利点:最新でないブラウザや、ライブラリでも動作する。異なるブラウザ間でも動作する

  • 欠点:接続まで(少々)時間がかかる。ポートを消費する



ビデオトラック差し替え


  • 通信中の PeerConnection の、ビデオトラックを差し替える


    • RTCRtpSender.replaceTrack() を使う



  • 利点:接続済のPeerConnectionをそのまま利用できる

  • 欠点:カメラ映像と画面共有を同時には使えない(声はOK)



マルチストリーム


  • 通信中の PeerConnection に、ビデオトラックを追加する


    • PeerConnection.addTrack()を使う



  • 利点:接続済のPeerConnectionをそのまま利用できる

  • 欠点:最新のブラウザでしか使えない、今までは異なるブラウザ間では通信できなかった


    • → 各ブラウザが Unified Plan という方式に統一されて、使えるようになる





Unified Plan と Plan B


  • マルチストリーム


    • 複数のビデオトラックやオーディをトラック、さらにはデータチャネルをまとめて通信できる仕組み



  • これまでは、ブラウザごとに異なる方式を利用


    • Firefox ... Unified Plan

    • Chrome, Safari ... Plan B



  • 現在は Unified Plan に統一されるつつある


    • Firefox, Chrome 72〜, Safari TP 67〜(開発オプション)



  • 参考:LT資料:Unified Plan時代のWebRTC(半)手動シグナリング

// Chrome で 明示的にUnified Plan を使う場合

let peer = new RTCPeerConnection({sdpSemantics : "unified-plan"});



SDPの違い: Unified Plan

a=group:BUNDLE sdparta_0 sdparta_1 sdparta_2 sdparta_3 sdparta_4

a=msid-semantic:WMS *

m=application 64895 DTLS/SCTP 5000
a=mid:sdparta_0

m=video 64922 UDP/TLS/RTP/SAVPF 120 121 126 97
a=mid:sdparta_1
a=msid:{968e2268-4336-4d48-xxx} {c40feace-2302-4ca6-yyy}

m=audio 64924 UDP/TLS/RTP/SAVPF 109 101 0 8 9
a=mid:sdparta_2
a=msid:{968e2268-4336-4d48-xxx} {5550b468-49a8-zzz}

m=video 0 UDP/TLS/RTP/SAVPF 120 121 126 97
a=mid:sdparta_3
a=msid:{5f905219-2416-4d96-bbb} {8798f142-402b-419d-ccc}

m=audio 0 UDP/TLS/RTP/SAVPF 109 9 0 8 101
a=mid:sdparta_4
a=msid:{0721cc21-5cb1-473d-cccc} {423518ba-9ca7-41e6-cccc}


  • 各 video, audio 毎に m= の行がある。利用するコーデックなどはこの単位で選択できる


    • → 1つのPeerConnectionで、異なるコーデックのビデオを一緒に送ることができる(仕様上)





SDPの違い: Plan B

a=group:BUNDLE data video audio

m=application 59191 DTLS/SCTP 5000
a=mid:data

m=video 59203 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 … 118 114
a=mid:video
a=ssrc:1934825139 msid:4QD5cPipYYHfxxxx 1bc7a4bb-545c-yyy
a=ssrc:1928081165 msid:5b439d1a-4c68-zzz 190251fa-7934-434c-aaa

m=audio 59207 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
a=mid:audio
a=sendrecv
a=ssrc:3639648018 msid:4QD5cPipYYHfxxx a69cca57-009f-442e-bbb
a=ssrc:3896373756 msid:037f3b64-feee-ccc 7c449c50-8607-ddd


  • m=video, m=audio は1行ずつ。共通のコーデック指定になる



マルチストリームを使ったビデオチャット+画面共有

マルトストリーム(Unified Plan)を用いて


  • カメラ(Video)+マイク(Audio)のビデオチャットに

  • 画面共有(Video)を追加する

Unified Plan が有効になったので、異なるブラウザ間でマルチストリーム可能


  • Chrome 72 --> Firefox 64



デモ

動かし方



  • WebRTCハンズオン 本編のシグナリングサーバーを起動



  • クライアント用のコードを、ローカルにダウンロード(git cloneや、zipでダウンロード)



  • ローカルWebサーバーのフォルダーに配置

  • 2つのブラウザで screen.html を開く


    • Chrome 72 Beta, Chrome Canary 73, Firefox Nightly 66, Safari TP 73 のいずれか



  • 両方のブラウザで、[Start Video]ボタンをクリック

  • 片方のブラウザで、[Connect]ボタンをクリック


    • → P2P通信が確立し、片方の映像がもう一方のブラウザに表示される



  • 片方のブラウザで、[Add Screen]ボタンをクリック


    • スクリーン全体、ウィンドウを選択する

    • → キャプチャーが始まり、もう一方のブラウザに表示される



  • [Remove Screen]ボタンをクリックすると、キャプチャーが終了する



JavaScript API の視点

1つのPeerConnectionで、複数のVideo / Audioの通信が可能



プロトコルの視点



コードの抜粋


画面共有開始側

let screenStream = await navigator.mediaDevices.getDisplayMedia({video: true});

let screenSender = peer.addTrack(screenStream.getVideoTracks()[0], screenStream);

// --- peer.onnegotiationneeded() が発火 --
peer.onnegotiationneeded = async () => {
let offer = await peer.createOffer();
await peer.setLocalDescription(offer);

sendSDP(offer); // 相手に送る
}



受け側

// WebSocketで offer を受け取った場合の処理

async function setOffer(sessionDescription) {
try{
await peerConnection.setRemoteDescription(sessionDescription);
makeAnswer();
} catch(err){
console.error('setRemoteDescription(offer) ERROR: ', err);
}
}

async function makeAnswer() {
try{
let answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
sendSdp(peerConnection.localDescription);
} catch(err){
console.error(err);
}
}




注意: onnegotiationneeded() のタイミングの違い


  • videoTrack, audioTrack を連続して addTrack()した場合


    • Chrome ... 2回発生する

    • Firefox, Safari ... 1回だけ発生する





追記:Unified Plan と Peer.addTranceiver()



Peer.addTransceiver() による片方向通信

Chromeでは、Unified Planと共に RTCPeerConnection.addTransceiver() がサポート


  • これまで


    • 映像、音声、データチャネルのいずれか1つ以上が無いと、有効なOfferが生成できなかった

    • → 片方向配信の場合、必ず配信側がOfferを出す必用があった



  • PeerConnection.addTransceiver() の登場


    • メディアが無くても、RTCRtpTransceiver が作れる

    • RTCRtpTransceiver.direction = 'recvonly' で受信のみが指定可能

    • その状態で、createOffer()できる



  • onnnegotiationneeded()も発火


    • addTransceiver()

    • directrion 変更




コード抜粋

  let videoTransceiver = peer.addTransceiver('video');

videoTransceiver.direction = 'recvonly';

let audioTransceiver = peer.addTransceiver('audio');
audioTransceiver.direction = 'recvonly';

let offer = await peer.createOffer();


デモ



双方向の場合



片方向の場合



まとめ


  • WebRTC は、仕様の実装に向けて進んでいる

  • マルチストリームもUnfied Planで統一



  • マルチストリームも相互接続性が上がり、いよいよ使える


End