はじめに
この資料は、WebRTC Beginners Tokyo が開催する、WebRTCハンズオン勉強会用の資料です。
※2019.02.06 WebRTC Meetup Tokyo / Osaka 向けに、アップデートしました。
※2019.05.31 Chrome 74 の状況をアップデートしました。
ScreenCapture API
- 2017年まで ... 各ブラウザ独自にスクリーンキャプチャーを実装
- 2018年以後 ... 仕様が固まりそれに合わせる動きが進んでいる
仕様
- Screen Capture ... https://w3c.github.io/mediacapture-screen-share/
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つのパターンが試せる
- https://mganeko.github.io/webrtc_screen_multistream/capture_test.html
- [getUserMedia]ボタン ... 現行Firefox 64用
- [navigator.getDisplayMedia]ボタン ... Chrome 71+フラグ設定
- [mediaDevices.getDisplayMedia]ボタン ... Chrome 72, Safari TP74+オプション
ビデオチャット+スクリーンキャプチャー
すでに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ハンズオン 本編のシグナリングサーバーを起動
- コード:GitHubのbranch_step3 webrtc-handson-2016/server/signaling.js
- クライアント用のコードを、ローカルにダウンロード(git cloneや、zipでダウンロード)
- コード: GitHub mganeko/webrtc_screen_multistream
- ローカル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で統一
- 参考:Safari 12.1 で VP8 と Unified Plan が入る
- "Safari 12.1 からSafari はWebRTC 対応でブラウザはなく推奨ブラウザです、覚えておいてください。"
- 参考:Safari 12.1 で VP8 と Unified Plan が入る
- マルチストリームも相互接続性が上がり、いよいよ使える