LoginSignup
62
50

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-01-26
1 / 32

はじめに

この資料は、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 のキャプチャ対象選択ダイアログ

chrome72_capture.png

Chrome 74 (2019.05.31現在)

  • audioが取得できるように
    • audio単体では取得できない。videoとセット
  • { video: true, audio: true } で getDisplayMedia() を呼び出す
  • 選択ダイアログの左下に 「音声を共有する」のチェックボックスが表示される
    • Windows の場合 ... 全画面 および Chromeタブ の場合
    • それ以外の場合 ... Chromeタブ の場合
  • ユーザーが明示的に「音声を共有する」をチェックした場合は、音声も取れる

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

screen_capture_with_audio.png


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 のキャプチャ対象選択ダイアログ

firefox66_capture.png


Safari Technology Preview 73/74

  • 開発オプションで有効にできる
    • Develop - Experimental Features - ScreenCapture
  • 仕様に沿った実装がされている
  • スクリーン全体だけ(ウィンドウは選択できない)
  • audioは取得できない(無視される)
let screen = await navigator.mediaDevices.getDisplayMedia( { video: true } );

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

safari_tp73_capture.png


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

※2019.01.30追記

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


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

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

  • マルチ Peer
  • ビデオトラック差し替え
  • マルチストリーム

マルチ Peer

  • 新たに PeerConnection を接続し、そちらで画面共有のMediaStreamを送る
  • 利点:最新でないブラウザや、ライブラリでも動作する。異なるブラウザ間でも動作する
  • 欠点:接続まで(少々)時間がかかる。ポートを消費する

multi_peer2.png


ビデオトラック差し替え

  • 通信中の PeerConnection の、ビデオトラックを差し替える
    • RTCRtpSender.replaceTrack() を使う
  • 利点:接続済のPeerConnectionをそのまま利用できる
  • 欠点:カメラ映像と画面共有を同時には使えない(声はOK)

replace_track2.png


マルチストリーム

  • 通信中の PeerConnection に、ビデオトラックを追加する
    • PeerConnection.addTrack()を使う
  • 利点:接続済のPeerConnectionをそのまま利用できる
  • 欠点:最新のブラウザでしか使えない、今までは異なるブラウザ間では通信できなかった
    • → 各ブラウザが Unified Plan という方式に統一されて、使えるようになる

multistream.png


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の通信が可能

multistream_js.png


プロトコルの視点

multistream_port.png


コードの抜粋

画面共有開始側
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();

デモ


双方向の場合

sendrcev.png


片方向の場合

recvonly.png


まとめ

  • WebRTC は、仕様の実装に向けて進んでいる
  • マルチストリームもUnfied Planで統一
  • マルチストリームも相互接続性が上がり、いよいよ使える

End

62
50
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62
50