<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:series="http://organizeseries.com/"
	>

<channel>
	<title>WebRTC &#8211; HTML5Experts.jp</title>
	<atom:link href="/tag/webrtc/feed/" rel="self" type="application/rss+xml" />
	<link>https://html5experts.jp</link>
	<description>日本に、もっとエキスパートを。</description>
	<lastBuildDate>Sat, 07 Jul 2018 03:14:05 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.7.19</generator>
	<item>
		<title>WebRTC開発者にはたまらない！徹頭徹尾、デベロッパーファーストだった「SkyWay Developer Meetup#1」</title>
		<link>/shumpei-shiraishi/24700/</link>
		<pubDate>Mon, 13 Nov 2017 01:00:57 +0000</pubDate>
		<dc:creator><![CDATA[白石 俊平]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[SkyWay]]></category>
		<category><![CDATA[WebRTC]]></category>

		<guid isPermaLink="false">/?p=24700</guid>
		<description><![CDATA[9月29日に開催された、SkyWay初の開発者向けミートアップ「SkyWay Developer Meetup#1」。 2013年12月からトライアルサービスとして提供されてきたWebRTC Platform SkyWa...]]></description>
				<content:encoded><![CDATA[<p>9月29日に開催された、SkyWay初の開発者向けミートアップ「SkyWay Developer Meetup#1」。</p>

<p>2013年12月からトライアルサービスとして提供されてきたWebRTC Platform SkyWayのビジョンとミッション、基本的な使い方からハックな使い方などが紹介された同イベントの模様を紹介する。</p>

<p><img src="/wp-content/uploads/2017/11/42A7958.jpg" alt="" width="640" height="392" class="alignnone size-full wp-image-24774" srcset="/wp-content/uploads/2017/11/42A7958.jpg 640w, /wp-content/uploads/2017/11/42A7958-300x184.jpg 300w, /wp-content/uploads/2017/11/42A7958-207x127.jpg 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<h2>SkyWayのビジョン＆ミッション</h2>

<p>2013年12月から、無償のトライアルサービスとして提供されてきた<a href="https://webrtc.ecl.ntt.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">WebRTC Platform SkyWay</a>（以下、SkyWay）。</p>

<p>一部では「WebRTCでサービス開発を行うならSkyWayが必須」と認識されつつある昨今、ついに2017年9月7日、商用サービスとして新たなスタートを切った。</p>

<p>本記事では、SkyWay初の開発者向けミートアップ「<a href="https://skyway.connpass.com/event/65697/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">SkyWay Developer Meetup#1</a>」の模様をお伝えする。</p>

<p>まず最初に登壇したのは、SkyWayプロダクトマネージャーの水嶋彬貴氏。</p>

<p><img src="/wp-content/uploads/2017/11/42A7924.jpg" alt="" width="640" height="427" class="alignnone size-full wp-image-24770" srcset="/wp-content/uploads/2017/11/42A7924.jpg 640w, /wp-content/uploads/2017/11/42A7924-300x200.jpg 300w, /wp-content/uploads/2017/11/42A7924-207x138.jpg 207w" sizes="(max-width: 640px) 100vw, 640px" /><span style="font-size: 80%;">▲<strong>SkyWayプロダクトマネージャー 水嶋 彬貴氏</strong></span></p>

<p>6,000人以上の開発者、10,000近くのサービス、300近くの企業に既に利用されているSkyWayは、現在なんと1日あたり50万近くのWebRTC接続を捌いているという。</p>

<p>こうした実績は、「WebRTCを使うとこんなにもたくさんのことができる、と開発者の皆さんに証明していただいた」ことの現れであると水嶋氏は強調。</p>

<p><img src="https://codeiq.jp/magazine/wp-content/uploads/2017/10/numbers.jpg" alt="" width="640" height="360" class="alignnone size-full wp-image-54752" /></p>

<p>その上で、新たなスタートを切ったSkyWayのビジョンとミッションを以下のように定義した。</p>

<p><strong>「リアルタイムコミュニケーションを身近な技術に変え、世界中のソフトウェアエンジニアと共に世の中に革新を起こす」</strong></p>

<p><img src="https://codeiq.jp/magazine/wp-content/uploads/2017/10/mission.jpg" alt="" width="640" height="360" class="alignnone size-full wp-image-54753" /></p>

<h2>リアルタイムコミュニケーションを身近な技術に変える</h2>

<p>ミッションの前半「身近な技術に変える」という部分においてはまず、SkyWayの様々なAPIやSDK、日本語を含めたドキュメントが、WebRTCの難しさを中和してエンジニアの負担を減らすことができるとする。</p>

<p>それによりエンジニアは「WebRTCをどう使うか」ではなく、WebRTCを使ったイノベーションやアイデアに集中することができるようになる。</p>

<p><img src="https://codeiq.jp/magazine/wp-content/uploads/2017/10/sdks.jpg" alt="" width="640" height="360" class="alignnone size-full wp-image-54754" /></p>

<p>もうひとつ、身近な技術に変えるというミッションに基づいて提供されているのが、無償の「コミュニティエディション」だ。</p>

<p>無償で利用できる月に500ギガバイトという帯域量は、ボイスチャットであれば5,787日分、ビデオチャットであっても462日分に相当する。</p>

<p><img src="https://codeiq.jp/magazine/wp-content/uploads/2017/10/numbers2.jpg" alt="" width="640" height="360" class="alignnone size-full wp-image-54755" /></p>

<p>これだけの無料枠を手軽に試せるならば、スタートアップや新規事業に採用するのにためらう必要はない。まさに「リアルタイムコミュニケーションを身近にする」を体現した形だといえる。</p>

<h2>──そしてイノベーションを世界に広げる</h2>

<p>しかし、と水嶋氏は言う。SkyWayがいくらWebRTCを身近にしたところで、それだけでは世界中に広がるものではない。</p>

<p>ミッションステートメントの後半部分「世界中のソフトウェアエンジニアと共に世の中に革新を起こす」を達成するには、SkyWayを利用する開発者の助けが必要なのだと。</p>

<p>そこで水嶋氏は「（開発者への）お願い」と題して、以下の4つを挙げていた。</p>

<ul>
<li>横のつながり</li>
<li>イノベーションのアイデア共有</li>
<li>ノウハウの共有や相互支援</li>
<li>事業の成功と後続の支援</li>
</ul>

<p>水嶋氏のプレゼン資料はこちらから参照できる。</p>

<iframe src="//www.slideshare.net/slideshow/embed_code/key/1Sh8n8LrED5AGx" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe>

<div style="margin-bottom:5px"> <strong> <a href="https://html5experts.jp//www.slideshare.net/mizuman1/skyway-vision-mission" title="SkyWay Vision &amp; Mission" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">SkyWay Vision &amp; Mission</a> </strong> from <strong><a href="https://www.slideshare.net/mizuman1" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">Yoshiki Mizushima</a></strong> </div>

<h2>WebRTC開発者にはたまらない！基本からハック、生Q/A大会まで！</h2>

<p>その後は開発者向けにたっぷりと時間が割かれ、SkyWay / WebRTCに関する熱い議論が繰り広げられた。</p>

<p>SkyWayテックリードの岩瀬 義昌氏による「SkyWayを使いこなすために」というセッションは、SkyWayに関する基本から応用、ハックから内部関係者しか知らない情報まで盛りだくさんの内容だった。</p>

<p><img src="/wp-content/uploads/2017/11/42A8039.jpg" alt="" width="640" height="427" class="alignnone size-full wp-image-24772" srcset="/wp-content/uploads/2017/11/42A8039.jpg 640w, /wp-content/uploads/2017/11/42A8039-300x200.jpg 300w, /wp-content/uploads/2017/11/42A8039-207x138.jpg 207w" sizes="(max-width: 640px) 100vw, 640px" /><span style="font-size: 80%;">▲<strong>SkyWayテックリード 岩瀬 義昌氏</strong></span></p>

<p>あまりの情報量に、とてもではないがここでは内容を紹介しきれないため、詳しくは<a href="https://www.slideshare.net/iwashi86/skyway-how-to-use-skyway-webrtc" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">岩瀬氏のスライド</a>をご覧いただきたい。</p>

<iframe src="//www.slideshare.net/slideshow/embed_code/key/xuUXu3NGqjETbc" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe>

<div style="margin-bottom:5px"> <strong> <a href="https://html5experts.jp//www.slideshare.net/iwashi86/skyway-how-to-use-skyway-webrtc" title="SkyWayを使いこなすために - How to use SkyWay (WebRTC)" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">SkyWayを使いこなすために &#8211; How to use SkyWay (WebRTC)</a> </strong> from <strong><a href="https://www.slideshare.net/iwashi86" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">Yoshimasa Iwase</a></strong> </div>

<p>以下に、筆者として気になったポイントをいくつか書き記しておく。</p>

<p>*SkyWayは、現在開発が停止した<a href="http://peerjs.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">PeerJS</a>をベースに開発されていたが、内部はフルスクラッチで書き直しており、継続的なメンテナンスや機能追加を行っている。<strong>「古いのではないか」という心配はあたらない</strong></p>

<p>*SkyWayでは、通常のP2P接続に加え<strong>SFU（Selective Forwarding Unit）もサポートしており、しかも無償から利用できる</strong>。</p>

<p>SFUとは複数のメディアストリームをサーバ側で一本にまとめてクライアントに配信する仕組みであり、多人数のビデオチャットを行う場合などに、P2Pのフルメッシュ型に比べてネットワーク全体の負荷を大きく低減することができる。</p>

<p>SkyWayが提供するRoom APIでは、フルメッシュとSFUを簡単に切り替えることができる。（SFUについては<a href="https://webrtc.ecl.ntt.com/sfu.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">SkyWayのドキュメントにある説明</a>がわかりやすい）</p>

<p>*<strong>APIドキュメントに書かれていない機能もいくつかあり</strong>、統計情報の取得なども行える。気になる方は岩瀬さんのスライドを参照のこと。</p>

<p>*安定性やスケーラビリティについては万全の体制を敷いている。
（後のQ/Aセッションによると、開発チームの人員が5人以上の体制で監視しているほか、運営企業であるNTTコミュニケーションズの監視部隊とも連携しており、24時間365日稼働を保証しているとのこと）</p>

<h2>SkyWay開発者によるライトニングトーク大会</h2>

<p>以降も、ミートアップはまだまだ続く。</p>

<p>ライトニングトーク大会では4人の登壇者がSkyWay開発に関する実際から「やってみた」まで幅広いトピックでプレゼンテーションを行った。</p>

<h3><a href="https://twitter.com/jumboOrNot" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">@jumboOrNot</a><strong>氏による「iOSでのSkyWay開発の勘所とTips」</strong></h3>

<p>レアジョブ英会話というサービスで、SkyWayを利用したiOSアプリを開発した際の生々しい話。</p>

<p>Skypeで行っていたサービスをWebRTC化することで開けた展望、そしてiOSアプリ開発で直面した問題に対してGoogle Firebaseを用いて対処したことを紹介していた。</p>

<p><img src="/wp-content/uploads/2017/11/42A8096.jpg" alt="" width="640" height="427" class="alignnone size-full wp-image-24773" srcset="/wp-content/uploads/2017/11/42A8096.jpg 640w, /wp-content/uploads/2017/11/42A8096-300x200.jpg 300w, /wp-content/uploads/2017/11/42A8096-207x138.jpg 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<h4>▼講演資料はこちら</h4>

<script async class="speakerdeck-embed" data-id="b8286e3ed57649968393a440a8569226" data-ratio="1.33333333333333" src="//speakerdeck.com/assets/embed.js"></script>

<p><br></p>

<h3><a href="https://twitter.com/massie_g" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">@massie_g</a><strong>氏による「SkyWayのビデオチャットを録画しよう。そう、ブラウザでね」</strong></h3>

<p>現在のWebブラウザが備えるAPIのみを用いて、ビデオチャットの録画を行う方法を紹介。</p>

<p><img src="/wp-content/uploads/2017/11/42A8139.jpg" alt="" width="640" height="427" class="alignnone size-full wp-image-24771" srcset="/wp-content/uploads/2017/11/42A8139.jpg 640w, /wp-content/uploads/2017/11/42A8139-300x200.jpg 300w, /wp-content/uploads/2017/11/42A8139-207x138.jpg 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<h4>▼講演資料はこちら</h4>

<iframe src="//www.slideshare.net/slideshow/embed_code/key/1yWoyuzHlI499u" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe>

<div style="margin-bottom:5px"> <strong> <a href="https://html5experts.jp//www.slideshare.net/mganeko/skyway-mix-recording" title="Skywayのビデオチャットを録画しよう。そう、ブラウザでね" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">Skywayのビデオチャットを録画しよう。そう、ブラウザでね</a> </strong> from <strong><a href="https://www.slideshare.net/mganeko" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">mganeko</a></strong> </div>

<p><br></p>

<h3><a href="https://twitter.com/leader22" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">@leader22</a><strong>氏による「SkyWay JS SDKの歩き方」</strong></h3>

<p>SkyWayのJavaScript SDK開発をサポートしたPixelGridの杉浦氏による、JavaScript SDKの読み解きガイド。</p>

<p><img src="/wp-content/uploads/2017/11/42A8161.jpg" alt="" width="640" height="427" class="alignnone size-full wp-image-24777" srcset="/wp-content/uploads/2017/11/42A8161.jpg 640w, /wp-content/uploads/2017/11/42A8161-300x200.jpg 300w, /wp-content/uploads/2017/11/42A8161-207x138.jpg 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<h4>講演資料: <a href="https://leader22.github.io/slides/skyway_dev_meetup-1/" rel="noopener follow external noreferrer" target="_blank" data-wpel-link="external">SkyWay JS SDKの歩き方</a></h4>

<p><br></p>

<h3><a href="https://twitter.com/lz650sss" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">@lz650sss</a><strong>氏による「SkyWay×Cognitive Service」</strong></h3>

<p>SkyWayで受信したビデオチャットの画像をMicrosoft Azureの<a href="https://azure.microsoft.com/ja-jp/services/cognitive-services/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Cognitive Service</a>に送信し、表情を読み取るデモ</p>

<p><img src="/wp-content/uploads/2017/11/42A8205.jpg" alt="" width="640" height="427" class="alignnone size-full wp-image-24778" srcset="/wp-content/uploads/2017/11/42A8205.jpg 640w, /wp-content/uploads/2017/11/42A8205-300x200.jpg 300w, /wp-content/uploads/2017/11/42A8205-207x138.jpg 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<h4>講演資料: <a href="https://qiita.com/sublimer/items/9207b7e6aeaf75717d43" rel="noopener follow external noreferrer" target="_blank" data-wpel-link="external">SkyWay × Cognitive Service</a></h4>

<p><br></p>

<h2>フランクなQ/Aセッション、そして開発者だらけの懇親会。これぞデベロッパーファーストなミートアップだ</h2>

<p>アジェンダの最後はQ/Aセッション。事前アンケートで集めた質問に対して、開発チームが自らフランクに答えるセッションで、非常に和気あいあいとした雰囲気の中盛り上がった。</p>

<p><img src="/wp-content/uploads/2017/11/42A8363.jpg" alt="" width="640" height="418" class="alignnone size-full wp-image-24779" srcset="/wp-content/uploads/2017/11/42A8363.jpg 640w, /wp-content/uploads/2017/11/42A8363-300x196.jpg 300w, /wp-content/uploads/2017/11/42A8363-207x135.jpg 207w" sizes="(max-width: 640px) 100vw, 640px" />
<img src="https://codeiq.jp/magazine/wp-content/uploads/2017/10/qa.jpg" alt="" width="640" height="427" class="alignnone size-full wp-image-54761" /></p>

<p>そして（無料の）懇親会。会場の至る所で、Web(RTC)開発者同士のハイコンテクストな会話が繰り広げられていた。</p>

<p><img src="/wp-content/uploads/2017/11/42A8376.jpg" alt="" width="640" height="401" class="alignnone size-full wp-image-24780" srcset="/wp-content/uploads/2017/11/42A8376.jpg 640w, /wp-content/uploads/2017/11/42A8376-300x188.jpg 300w, /wp-content/uploads/2017/11/42A8376-207x130.jpg 207w" sizes="(max-width: 640px) 100vw, 640px" />
<img src="https://codeiq.jp/magazine/wp-content/uploads/2017/10/social3.jpg" alt="" width="640" height="215" class="alignnone size-full wp-image-54786" /></p>

<p>最後に、イベントを通じて筆者が感じたことを述べておく。</p>

<p>SkyWayはもちろんプラットフォームであるが、このイベントを通じてSkyWayチームが実現したかったことは、<strong>「開発者コミュニティ」というプラットフォームづくり</strong>なのだろうと思う。</p>

<p>SkyWayというプラットフォームを提供してリアルタイムコミュニケーション開発の敷居を下げるだけが彼らの役割ではない。</p>

<p>同時に、開発者基盤としてのコミュニティ構築も図ることで、WebRTCを活用したイノベーションが生まれやすくすることも、また彼らの役割なのだと。</p>

<p>SkyWay開発チームのこうした挑戦が実を結び、日本発のプラットフォームが世界を席巻する日を、筆者は見たい。</p>

<p><span style="font-size: 90%;">※本記事はITエンジニアのためのキャリア応援サイト「<a href="https://codeiq.jp/magazine/" rel="noopener follow external noreferrer" target="_blank" data-wpel-link="external">CodeIQ MAGAZINE</a>」（※2017年11月18日掲載）からの提供記事です。</span></p>
]]></content:encoded>
			</item>
		<item>
		<title>Firefox 51・Chrome 56リリース、Safari 10.1発表、Windows 10 Creator&#8217;s UpdateのEdge、WebKitほか──2017年1月のブラウザ関連ニュース</title>
		<link>/myakura/22272/</link>
		<pubDate>Tue, 07 Feb 2017 03:04:00 +0000</pubDate>
		<dc:creator><![CDATA[矢倉 眞隆]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[Chrome]]></category>
		<category><![CDATA[Edge]]></category>
		<category><![CDATA[Firefox]]></category>
		<category><![CDATA[Safari]]></category>
		<category><![CDATA[WebKit]]></category>
		<category><![CDATA[WebRTC]]></category>
		<category><![CDATA[ブラウザ]]></category>

		<guid isPermaLink="false">/?p=22272</guid>
		<description><![CDATA[連載： WEB標準化動向 (21)2017年1月は新年早々いろいろ動きがありました。Firefox 51とChrome 56のリリース、Safari 10.1発表、Windows 10 Creator’s Updateの...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/webstandards-news/" class="series-156" title="WEB標準化動向" data-wpel-link="internal">WEB標準化動向</a> (21)</div><p>2017年1月は新年早々いろいろ動きがありました。Firefox 51とChrome 56のリリース、Safari 10.1発表、Windows 10 Creator’s UpdateのEdge、WebKitにWebRTCとCredential Management API、Facebookとブラウザベンダー協力の話など、注目ニュースが満載です！</p>

<h2>Firefox 51リリース <span id="firefox51"></span></h2>

<p>1月24日に、Firefox 51がリリースされました。</p>

<ul>
<li><a href="https://blog.mozilla.org/blog/2017/01/24/gets-better-video-gaming-non-secure-web-warning/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Firefox Gets Better Video Gaming and Warns of Non-Secure Websites | The Mozilla Blog</a></li>
<li><a href="https://www.mozilla.jp/firefox/51.0/releasenotes/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Firefox 51.0 リリースノート</a></li>
</ul>

<p><a href="https://hacks.mozilla.org/2017/01/webgl-2-lands-in-firefox/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">WebGL 2のサポート</a>や、<a href="https://blog.mozilla.org/security/2017/01/20/communicating-the-dangers-of-non-secure-http/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">非HTTPSなページ上のパスワードフォーム入力についてのUI改善</a>が目玉ですが、すでに知っているところだと思うので、地味なところをとりあげます。</p>

<ul>
<li><a href="https://developer.mozilla.org/ja/Firefox/Releases/51" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Firefox 51 for developers &#8211; Mozilla | MDN</a></li>
</ul>

<p><a href="https://drafts.csswg.org/css-pseudo/#placeholder-pseudo" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><code>::placeholder</code> 疑似要素</a>と <a href="https://drafts.csswg.org/selectors/#placeholder" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><code>:placeholder-shown</code> 擬似クラス</a>が実装されました。<code>input</code> 要素や<code>textarea</code> 要素の <code>placeholder</code> 属性で指定するプレースホルダ周りのスタイルを調整するためのものです。前者はプレースホルダのテキストを、後者はプレースホルダが表示されているときの要素（<code>input</code> など）を表します。</p>

<p>SVGでは、<a href="https://www.w3.org/TR/2016/CR-SVG2-20160915/linking.html#linkRefAttrs" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><code>xlink</code> 接頭辞なしの <code>href</code> 属性</a>がサポートされました。これまでSVG内で外部リソースを読み込んだりSVG内の要素を参照する際は、XLinkを使っていました。SVGがXMLのフォーマットというところに由来するのですが、名前空間の扱いの難やHTMLとの親和性などをふまえ、SVG 2.0で名前空間なしの属性が導入されました。</p>

<p>この名前空間なしの <code>href</code> 属性ですが、Internet ExplorerがSVGを初実装したIE9から使えます。SVGを実装するにあたって、検討段階だったSVG 2.0で取り入れたい機能の先行実装をさせたという話を以前SVG WGの人に聞いたことがあります。</p>

<p>SVGまわりでは、<code>tabindex</code> 属性や <code>data-</code> 属性に対応しました。使えなかったのに驚く方もいるかもしれませんが、これらはHTMLのグローバル属性だったので、SVGの要素では使えず、<code>dataset</code> プロパティなどのアクセサリーもなかったのでした。これらもSVG 2.0で導入されたものです。</p>

<h2>Chrome 56リリース <span id="chrome56"></span></h2>

<p>1月25日に、Chrome 56がリリースされました。</p>

<ul>
<li><a href="https://developers.google.com/web/updates/2017/01/nic56" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">New In Chrome 56  |  Web  |  Google Developers</a></li>
</ul>

<p>パフォーマンスの観点から、<a href="https://developers.google.com/web/updates/2017/01/scrolling-intervention" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">タッチイベントハンドラの <code>passive</code> オプションがデフォルトで <code>true</code> に</a>なりました。Android版Chromeでは多くの場合、スクロールがスムーズになるとのことです。</p>

<p>CSSでは、<code>position: sticky</code> が復活しました。むかーしむかし実装されていたのですが、パフォーマンスの観点から削除されていたのでした。<code>scroll</code> イベントを監視しなくてもよくなるので、パフォーマンス向上が期待できる機能です。</p>

<p>Web PaymentsのPayment Request APIに追加された　<a href="https://w3c.github.io/browser-payment-api/#canmakepayment-method" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><code>canMakePayment()</code>　メソッド</a>がサポートされました。このメソッド、昨年AppleがWebでのApple Payを発表した祭、<a href="https://lists.w3.org/Archives/Public/public-payments-wg/2016Jun/0013.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Web Paymentsにないものとして上げていたメソッド</a>のひとつです。Web PaymentsのアクティビティにもAppleが関わり、APIの差異を埋めているようなので、今後に期待ですね。</p>

<p>他にも、<a href="https://www.w3.org/TR/css3-background/#the-border-image-repeat" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer"><code>border-image-repeat: space</code></a>がサポートされています。これでようやく実装がそろった感じですね。</p>

<h2>Safari 10.1発表 <span id="safari101-announce"></span></h2>

<p>macOS Sierra 10.12.4のベータ版が開発者向けに公開され、Safariが10.1にアップデートすることがわかりました。昨年春にリリースされたSafari 9.1のように、WebKitがアップグレードされ、新しい機能が導入されます。</p>

<ul>
<li><a href="https://developer.apple.com/library/prerelease/content/releasenotes/General/WhatsNewInSafari/Articles/Safari_10_1.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Safari 10.1</a></li>
</ul>

<p>APIの目玉はCustom Elementsでしょうか。Safari 10でもShadow DOMがサポートされたので、Web Componentsがまたさらに進みます。<br />
ほかにも、Fetch APIやIndexed DB API　2.0, Gamepad APIがサポートされます。Gamepad APIはすこし意外ですね…ブラウザでゲームパッドを使うようなゲームを提供する顧客がいるのでしょうか？</p>

<p>JavaScriptでは、ES2017仕様のAsync Functionsや <code>SharedArrayBuffer</code> に対応します。</p>

<p>HTMLでは、HTML 5のフォームバリデーション機能がついに実装されました。これまでもDOM上では検証していたのですが、invalidな際にフォームの送信をブロックするという挙動を実装していなかったのでした。</p>

<ul>
<li><a href="https://webkit.org/blog/7099/html-interactive-form-validation/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">HTML Interactive Form Validation | WebKit</a></li>
</ul>

<p>CSSではGrid Layoutが有効になります。リリース時期を春とすると、FirefoxやChromeと同時期になります。いっきに使いどころが増えそうです。</p>

<h2>Windows 10 Creator&#8217;s UpdateのEdge <span id="edge-jan2017"></span></h2>

<p>Microsoftのブログが活発に更新され、Windows 10 Creator&#8217;s Updateで提供される各種機能の紹介がされています。<a href="https://html5experts.jp/myakura/21934/" data-wpel-link="internal">先月</a>から引き続きいくつか取り上げます。</p>

<ul>
<li><a href="https://blogs.windows.com/windowsexperience/2017/01/31/microsoft-edge-helps-organize-web/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Microsoft Edge helps you organize your web &#8211; Windows Experience BlogWindows Experience Blog</a></li>
</ul>

<p>このエントリでは、タブのサムネイルや一時退避といったブラウザのUIの機能（Operaみたいですね）や、EPUB、Payment Requset APIのサポートなど、EdgeHTML 15で目玉になる機能をまとめています。</p>

<ul>
<li><a href="https://blogs.windows.com/msedgedev/2017/01/31/introducing-webrtc-microsoft-edge/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Introducing WebRTC 1.0 and interoperable real-time communications in Microsoft Edge &#8211; Microsoft Edge Dev Blog</a></li>
</ul>

<p>WebRTC 1.0は互換性のため実装を始めたもので、あくまでORTCを推奨していくという立場のようです。コーデックもVP8をサポートしましたが、ソフトウェアでコードのため電力消費やCPUの使用はH.264よりも高いとのこと。</p>

<p>他にも、<a href="https://blogs.windows.com/msedgedev/2017/01/10/edge-csp-2/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Content Security Policy Level 2のサポート</a>、<a href="https://blogs.windows.com/windowsexperience/2017/01/27/announcing-windows-10-insider-preview-build-15019-pc/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">フルカラー絵文字のサポート</a>もあるようです。</p>

<h2>WebKitにWebRTCとCredential Management API <span id="webkit-webrtc-credmgmt"></span></h2>

<p>Safari 10.1もうれしいですが、次期メジャーバージョンでも嬉しい機能が入りそうです。</p>

<p>ひとつは、WebRTCです。1月19日に、Appleのエンジニアによって、WebKitにWebRTCのライブラリがチェックインされました。</p>

<ul>
<li><a href="https://trac.webkit.org/changeset/210942" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Changeset 210942 – WebKit — Source/ThirdParty/libwebrtc: [WebRTC] Upload libwebrtc code base ​</a></li>
</ul>

<p>WebRTCについては、<code>getUserMedia()</code> の実装が進んでいましたが、通信周りのAPIは特に進んでいませんでした。EdgeもEdgeHTMLでWebRTC (1.0)を実装しますが、ようやくといったところでしょうか。</p>

<p>コミットログを読むと、Chromeのソースコードをインポートして利用しており、Chromeとの互換性がなかなか期待できそうです。</p>

<p>もうひとつは、Credential Management APIです。1月24日に、AppleのエンジニアがWebKitにCredential Management APIを実装予定という旨のメールをメーリングリストに投稿しています。</p>

<ul>
<li><a href="https://lists.webkit.org/pipermail/webkit-dev/2017-January/028684.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">[webkit-dev] Implement Credential Management Level 1</a></li>
</ul>

<p>Credential Management APIはパスワードやソーシャルログインなど、ユーザーの認証情報を抽象化して扱うAPIで、ログイン周りの煩雑さを解消できます。現在はChromeでのみ使えますが、Safariでも使えるようになるとモバイルでのログイン周りがとても便利になりそうです。</p>

<p>次期iPhoneで顔認証が導入されるといった噂を目にしていまし、信憑性が高まった気もします。</p>

<h2>Facebookとブラウザベンダーの協力 <span id="facebook-browsers-caches"></span></h2>

<p>Facebookの開発者ブログに、ブラウザベンダーと協力してFacebookへのリクエストを省力化したという話が掲載されています。あわせて、Chrome、Mozillaの開発者ブログでも紹介されています。</p>

<ul>
<li><a href="https://code.facebook.com/posts/557147474482256" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">This browser tweak saved 60% of requests to Facebook | Engineering Blog | Facebook Code</a></li>
<li><a href="https://blog.chromium.org/2017/01/reload-reloaded-faster-and-leaner-page_26.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Chromium Blog: Reload, reloaded: faster and leaner page reloads</a></li>
<li><a href="https://hacks.mozilla.org/2017/01/using-immutable-caching-to-speed-up-the-web/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Using Immutable Caching To Speed Up The Web ★ Mozilla Hacks – the Web developer blog</a></li>
</ul>

<p>Facebookは各種アセットを、ハッシュ付きURL＋長大なキャッシュ有効時間というキャッシュ戦略で運用しています。キャッシュの有効時間が長ければ、ブラウザは再読込の際に同じリソースをダウンロードする確率が低くなるので、ネットワークにやさしいです。</p>

<p>しかし、これまでのHTTPキャッシュでは、ダウンロードは防げても、リクエスト自体は起こってしまっていました。パフォーマンスの観点からもうれしくありません。Facebookが自身のアクセスを解析したところ、全体の60％がそういったリクエストだったそうです。更に調べると、他のブラウザが13％〜22％だったのに対し、Chromeだけは63％ととても高い数値を示していたとのこと。</p>

<p>これを解消しようとFacebookのエンジニアがChromeチームに働きかけ、挙動を変更。結果63％だったものが24％まで減少したとのこと。</p>

<p>Firefoxでは、Chromeと同様の挙動変更を行わない代わりに、<code>Cache-Control: immutable</code> という新しいヘッダの値を実装しました。このヘッダについては、Jxckさんが自身のブログにまとめているのでそちらをご覧ください。</p>

<ul>
<li><a href="https://blog.jxck.io/entries/2016-07-12/cache-control-immutable.html" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Cache-Control の Immutable 拡張によるリロード時のキャッシュ最適化 | blog.jxck.io</a></li>
</ul>
]]></content:encoded>
		
		<series:name><![CDATA[WEB標準化動向]]></series:name>
	</item>
		<item>
		<title>そして壁の向こうへ。 NAT/Firewallを越えて通信しよう―WebRTC入門2016</title>
		<link>/mganeko/20618/</link>
		<pubDate>Mon, 12 Dec 2016 00:21:24 +0000</pubDate>
		<dc:creator><![CDATA[がねこまさし]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[WebRTC]]></category>

		<guid isPermaLink="false">/?p=20618</guid>
		<description><![CDATA[連載： WebRTC入門2016 (6)こんにちは！ 2014年に連載した「WebRTCを使ってみよう！」シリーズのアップデート記事も番外編を含めて6回目となりました。2016年の最後として、実際の通信では欠かせないNA...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/webrtc2016/" class="series-380" title="WebRTC入門2016" data-wpel-link="internal">WebRTC入門2016</a> (6)</div><p>こんにちは！ 2014年に連載した<a href="https://html5experts.jp/series/webrtc-beginner/" target="_blank" data-wpel-link="internal">「WebRTCを使ってみよう！」</a>シリーズの<a href="https://html5experts.jp/series/webrtc2016/" target="_blank" data-wpel-link="internal">アップデート記事</a>も番外編を含めて6回目となりました。2016年の最後として、実際の通信では欠かせないNAT越えと、企業ネットワークで使うために必要なFirewallを通過する方法について見ていきましょう。</p>

<h2>NATを越えて</h2>

<h4>NATの役割</h4>

<p>NAT(+IPマスカレード)は企業だけでなく、一般家庭でも使われています。ブロードバンドルーターやWiFiルーターでは、1つのグローバルIPアドレスを、複数のPCやデバイスで共有することができます。このとき、NATには2つの役割があります。</p>

<ul>
<li>インターネットにつながったグローバルなIPアドレスと、家庭内/社内のローカルなネットワークでのIPアドレスの変換</li>
<li>複数のPC/デバイスが同時に通信できるように、ポートマッピングによるポート変換</li>
</ul>

<p>WebRTCでNAT越しに通信すること考えてみましょう。</p>

<h4>ブラウザが知っている情報</h4>

<ul>
<li>ローカルネットワークのIPアドレス</li>
<li>自分が使う（動的に割り振った)UDPポート</li>
</ul>

<h4>ブラウザでは分からない情報</h4>

<ul>
<li>グローバルIPアドレス</li>
<li>NATによってマッピングされた、外部に向けたUDPポート</li>
</ul>

<h4>例えば</h4>

<p>次の架空の例を見てみましょう。二つのPCのブラウザでは、それぞれ自分のローカルIPアドレス、UDPは知ることができます。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2016/12/nat_address.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/12/nat_address-300x181.png" alt="nat_address" width="300" height="181" class="alignnone size-medium wp-image-21744" srcset="/wp-content/uploads/2016/12/nat_address-300x181.png 300w, /wp-content/uploads/2016/12/nat_address.png 640w, /wp-content/uploads/2016/12/nat_address-207x125.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<ul>
<li>左のPCのブラウザが分かること

<ul>
<li>ローカルIPアドレス 192.168.10.11</li>
<li>ローカルUDPポート 4001/UDP</li>
</ul></li>
<li>右のPCのブラウザが分かること

<ul>
<li>ローカルIPアドレス 10.2.100.51</li>
<li>ローカルUDPポート 5001/UDP</li>
</ul></li>
</ul>

<p>しかし、NATの外側から見たグローバルの情報は自分では分かりません。</p>

<ul>
<li>左のPCのブラウザが知らないこと

<ul>
<li>グローバルIPアドレス 178.50.111.222</li>
<li>グローバルUDPポート 51111/UDP</li>
</ul></li>
<li>右のPCのブラウザが知らないこと

<ul>
<li>グローバルIPアドレス 200.100.50.81</li>
<li>グローバルUDPポート 52222/UDP</li>
</ul></li>
</ul>

<p>NATを越えてPeer-to-Peer通信を行うには、シグナリング処理でお互いに自分の知らないグローバルの情報を交換する必要があります。</p>

<h2>STUNが教えてくれること</h2>

<p>自分で知らない情報は、誰かから教えてもらうしかありません。それを可能にするのがSTUN(Session Traversal Utilities for NATs)です。</p>

<p>STUNの仕組みはシンプルです。NATで変換されたIPアドレス/ポートを、外側にいるSTUNサーバーに教えてもらいます。先ほどの例で言えば、このようになります。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2016/12/stun.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/12/stun-300x217.png" alt="stun" width="300" height="217" class="alignnone size-medium wp-image-21745" srcset="/wp-content/uploads/2016/12/stun-300x217.png 300w, /wp-content/uploads/2016/12/stun.png 640w, /wp-content/uploads/2016/12/stun-207x150.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<p>自分のグローバル情報が分かったら、それをシグナリングサーバー経由で相手に渡します。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2016/12/stun_signaling.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/12/stun_signaling-300x215.png" alt="stun_signaling" width="300" height="215" class="alignnone size-medium wp-image-21748" srcset="/wp-content/uploads/2016/12/stun_signaling-300x215.png 300w, /wp-content/uploads/2016/12/stun_signaling.png 640w, /wp-content/uploads/2016/12/stun_signaling-207x148.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<p>STUNサーバーから得られた情報は、シグナリングの過程でICE Candidateとして取得できます。これをシグナリングサーバーなどを経由して相手に送ることになります。（参考： <a href="https://html5experts.jp/mganeko/20013/" target="_blank" data-wpel-link="internal">シグナリングサーバーを動かそう</a>、<a href="https://html5experts.jp/mganeko/20112/" target="_blank" data-wpel-link="internal">シグナリングを拡張して、複数人で通信してみよう </a>）</p>

<p>首尾よくNATとポート変換を潜り抜ければ、Peer-to-Peerで通信を行うことができます。</p>

<h4>STUNサーバーを使ってみよう</h4>

<p>STUNサーバーを自分で動かすこともできますが、幸い公開されているSTUNサーバーがあります。まず、そちらを使ってみましょう。Googleが公開しているサーバーがよく利用されているようです。</p>

<ul>
<li>stun.l.google.com:19302</li>
<li>stun1.l.google.com:19302</li>
<li>stun2.l.google.com:19302</li>
<li>stun3.l.google.com:19302</li>
<li>stun4.l.google.com:19302</li>
</ul>

<p>通常はどれか1つ指定すればOKですが、複数指定することもできます。それでは、実際に<a href="https://html5experts.jp/mganeko/20112/" target="_blank" data-wpel-link="internal">以前のソース</a>の一部を修正してみましょう。</p>

<p></p><pre class="crayon-plain-tag">function prepareNewConnection(id) {
  let pc_config = {"iceServers":[
    {"urls": "stun:stun.l.google.com:19302"},
    {"urls": "stun:stun1.l.google.com:19302"},
    {"urls": "stun:stun2.l.google.com:19302"}
  ]};
  let peer = new RTCPeerConnection(pc_config);
  // ... 省略 ...
}</pre><p></p>

<p>さあ、これで接続を試してください。上手く行けば、例えば自宅と友人の家で通信が可能になっているはずです。</p>

<h4>STUNでNATを越えられないとき</h4>

<p>NATにはグローバルIPアドレスを共有するだけでなく、セキュリティ対策としての役割もあります。内部の端末を隠したり、通信できるポートを制限したり、一種の簡易Firewallとして利用されているケースもあります。その場合はFirewallの場合と同じく、次に説明するTURNを利用する必要があります。</p>

<p>またNATの構造によっては、接続先によって（今回の場合、STUNサーバーとPeer-to-Peerの通信相手）別のポートが割り当てられる Symmetric NAT という物があるようです。この場合もSTUNの仕組みでは通信することができません。やはりTURNの出番ということになります。</p>

<h2>Firewallを越えたい</h2>

<p>一般家庭のようにブロードバンドルーターなどでNATがある環境では、STUNを使えば通信が可能になります。</p>

<p>それに対して一般的な企業では、Firewallにより通信できるポートが制限されるケースが多いようです。STUNを使った場合でもUDPポートは動的に割り振られるままなので、通信をするためにはFirewallにとても大きな穴を空ける必要があります。それではきっとセキュリティ管理者に怒られてしまいます。</p>

<p>こんなケースに対応するのが、TURN(Traversal Using Relays around NAT)の仕組みです。先ほどのSTUNもTURNもどちらもWebRTCのために生まれたのではなく、以前からVoIPやネットワークゲームの世界で使われていたものです。</p>

<h3>TURNの仕組み</h3>

<p>TURNを使った通信では、TURNサーバが実際のストリームデータを受け渡す間に入ります。すべてのパケットをTURNサーバーがリレーすることになり、もはや厳密にはPeer-to-Peer通信ではなくなります。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2016/12/turn.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/12/turn-300x209.png" alt="turn" width="300" height="209" class="alignnone size-medium wp-image-21788" srcset="/wp-content/uploads/2016/12/turn-300x209.png 300w, /wp-content/uploads/2016/12/turn.png 640w, /wp-content/uploads/2016/12/turn-207x144.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<p>ただしこの際、TURNサーバーではパケットをそのままリレーし、内容については一切変更はしません。</p>

<ul>
<li>動画のデコード、エンコードは行わない</li>
<li>データはそのまま受け渡し、両端のPeerで施された暗号は解除しない</li>
</ul>

<p>そのため個人的には、アプリケーション的に見た場合にはPeer-to-Peerを維持していると考えていいと思っています。</p>

<p>TURNサーバーでは暗号化処理や動画のデコード/エンコーディングは行わないので、CPU負荷よりもネットワーク負荷が高くなりやすいです。</p>

<h3>TURNサーバーを用意するには</h3>

<p>それでは、実際にTURNサーバーを動かしてみましょう。2014年の記事では<a href="https://code.google.com/archive/p/rfc5766-turn-server/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">rfc5766-turn-server</a>を使いましたが、今回はその後継である<a href="https://github.com/coturn/coturn" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">coturn</a>を使います。coturnが生まれた背景は、こちらの記事に詳しく書かれています。</p>

<ul>
<li><a href="https://html5experts.jp/iwase/11300/" target="_blank" data-wpel-link="internal">[翻訳] coTURN:マルチテナント型のオープンソースのSTUN/TURNサーバ</a></li>
</ul>

<p>今回は、OSはUbuntu 16.04 LTSを使って導入してみました。 なるべくOSセットアップ直後の素の状態から自分でビルドしてみましょう。</p>

<h4>自分でビルドする手順</h4>

<p>まずは、依存ツールやライブラリを導入します。</p>

<p></p><pre class="crayon-plain-tag">sudo apt-get update
sudo apt-get upgrade</pre><p></p>

<p></p><pre class="crayon-plain-tag">sudo apt-get install gcc
sudo apt-get install sqlite3
sudo apt-get install libssl-dev
sudo apt-get install libevent-dev
sudo apt-get install make</pre><p></p>

<p>次にcoturnのソースを、こちらのサイトからダウンロードします。</p>

<ul>
<li><a href="https://github.com/coturn/coturn/wiki/Downloads" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">https://github.com/coturn/coturn/wiki/Downloads</a></li>
</ul>

<p>私が試したときは4.5.0.4が最新でしたが、2016年12月現在は4.5.0.5が最新のようです。
利用したいバージョンのソースをダウンロードして、解凍してください。</p>

<p></p><pre class="crayon-plain-tag">wget http://turnserver.open-sys.org/downloads/v4.5.0.4/turnserver-4.5.0.4.tar.gz
tar xvfz turnserver-4.5.0.4.tar.gz</pre><p></p>

<p>それではビルドしてみましょう。
</p><pre class="crayon-plain-tag">cd turnserver-4.5.0.4
./configure
make
sudo make install</pre><p></p>

<p>もし途中で依存ライブラリ不足でエラーが出た場合には、追加でインストールしてからビルドし直してみてください。ビルドに成功すると、Ubuntu16の場合は次の場所に関連ファイルがインストールされます。</p>

<ul>
<li>バイナリ →  /usr/local/bin/turnserver</li>
<li>設定ファイル（ひな形） → /usr/local/etc/turnserver.conf.default</li>
</ul>

<p>設定ファイルは適宜 turnserver.conf にコピーして、それを編集して利用してください。</p>

<p>※こちらの手順はcoturn 4.5.0.4 をビルドした際のものです。最新のものでは手順が変更されている可能性がありますので、もし何か見つけたらコメントいただけると嬉しいです。</p>

<h2>coturn を動かすまで</h2>

<p>coturnをTURNサーバーとして動かす際に、通信方法を2種類を使うことがことができます。</p>

<ul>
<li>TURN &#8230; 特定のUDPポートを利用する</li>
<li>TURN over TCP &#8230; 特定のTCPポートを利用する</li>
</ul>

<p>企業で使う場合は、後者を利用したいケースが多いと思います。今回は両方使えるように設定しましょう。</p>

<h3>tunserver.conf の設定</h3>

<p>まず、/usr/local/etc/turnserver.conf.default を /usr/local/etc/turnserver.conf にコピーし、それを編集します。たくさんの設定項目がありますが、TURNをWebRTCで使う際のポイントを見ておきましょう。</p>

<p><strong>
ポートの指定 </strong>
</p><pre class="crayon-plain-tag"># TURN listener port for UDP and TCP (Default: 3478).
# Note: actually, TLS &amp; DTLS sessions can connect to the
# "plain" TCP &amp; UDP port(s), too - if allowed by configuration.
#
listening-port=80</pre><p> 
デフォルトでは3478/UDP を使いますが、それを変更して80/TCPを使うようにします。コメントアウトを外してポート番号80を指定してください。</p>

<p><strong>資格証明方法（クレデンシャル）</strong>
</p><pre class="crayon-plain-tag"># Uncomment to use long-term credential mechanism.
# By default no credentials mechanism is used (any user allowed).
#
lt-cred-mech

# 'Static' user accounts for long term credentials mechanism, only.
# This option cannot be used with TURN REST API.
# 'Static' user accounts are NOT dynamically checked by the turnserver process,
# so that they can NOT be changed while the turnserver is running.
#
#user=username1:key1
#user=username2:key2
# OR:
#user=username1:password1
#user=username2:password2

user=user:password</pre><p> 
WebRTCで使うには、lt-cred-mech を有効にする必要があります。デフォルトではコメントアウトされているので、これを有効にしてください。ユーザアカウントは外部のDBを使うのが適切ですが、今回はシンプルに設定ファイルに直接記入しました。ご利用のシステムに合わせて、外部ファイルや外部DBをご利用ください。</p>

<p>同時にrealmも指定します。自分のサーバーのドメインに合わせて、適宜指定してください。 
</p><pre class="crayon-plain-tag"># The default realm to be used for the users when no explicit
# origin/realm relationship was found in the database, or if the TURN
# server is not using any database (just the commands-line settings
# and the userdb file). Must be used with long-term credentials
# mechanism or with TURN REST API.
#
realm=coturn.yourdomain.com</pre><p></p>

<p><strong>通信プロトコルの指定（変更不要）</strong>
</p><pre class="crayon-plain-tag"># Uncomment if no UDP client listener is desired.
# By default UDP client listener is always started.
#
#no-udp

# Uncomment if no TCP client listener is desired.
# By default TCP client listener is always started.
#
#no-tcp

# Uncomment if no TLS client listener is desired.
# By default TLS client listener is always started.
#
#no-tls

# Uncomment if no DTLS client listener is desired.
# By default DTLS client listener is always started.
#
#no-dtls</pre><p>
プロトコルの指定で no-tcp のコメントアウトを外すとTURN over TCPを利用しなくなります。今回はTCPを使いますので、コメントアウトのままにしておきます。もしUDPを使いたくない場合は no-udp のコメントアウトを外してください。</p>

<p>以前試したときはブラウザでの実装の不十分でtlsやdtlsを利用できなかったのですが、今回試したところデフォルトのまま（有効のまま）で通信できました。もしうまく通信できない場合は、コメントアウトを消してtlsやdtlsを無効にして試してみてください。</p>

<h4>coturnを起動しよう</h4>

<p>それではcoturnを起動しましょう。今回は80ポートを利用しているため、環境によってはroot権限が必要となりますので、sudoを利用してください。</p>

<p></p><pre class="crayon-plain-tag">sudo /usr/local/bin/turnserver -o -v -c /usr/local/etc/turnserver.conf</pre><p></p>

<p>ここで指定しているコマンドライン引数は次の意味です。</p>

<ul>
<li>-o .. デーモンとして起動</li>
<li>-v .. verboseモード。ログを多く出す</li>
<li>-c .. 設定ファイルのパスを指定</li>
</ul>

<h3>クライアントのソースを修正しよう</h3>

<p>それでは準備したcoturnサーバーを使うように、に<a href="https://html5experts.jp/mganeko/20112/" target="_blank" data-wpel-link="internal">クライアント側のソース</a>を修正しましょう。STUNで修正した部分と同じ個所になります。 coturnサーバーが coturn.yourdomain.com で、ポート80で動いていると仮定します。その情報をPeerConnectionに教えてあげましょう。</p>

<p></p><pre class="crayon-plain-tag">function prepareNewConnection(id) {
  let pc_config = {"iceServers":[
    {"urls": "stun:coturn.yourdomain.com:80"},
    {"urls":"turn:coturn.yourdomain.com:80?transport=udp", "username":"user", "credential":"password"},
    {"urls":"turn:coturn.yourdomain.com:80?transport=tcp", "username":"user", "credential":"password"}
  ]};
  let peer = new RTCPeerConnection(pc_config);
  // ... 省略 ...
}</pre><p></p>

<p>また、企業内から外部のシグナリングサーバーを利用するには、そちらも80/TCPや443/TCPを使う必要があるかもしれません。環境に応じてご準備ください。</p>

<p>ここまでできたら、無事Firewallを越えて通信ができるはずです。企業内のブラウザと、外のブラウザ（自宅のPCやアンドロイドのブラウザ）で試してみてください。</p>

<h2>最後に</h2>

<p>WebRTC入門も2016年中になんとか予定していた記事を書き終えることができました。書いている間にもどんどんブラウザがバージョンアップし、新しいAPIが使えるようになっています。最新情報はブラウザのリリースノートやブログをご覧いただくとよいと思います。</p>

<ul>
<li>WebRTC.org <a href="https://webrtc.org/release-notes/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">Chrome Report Notes</a></li>
<li>Mozilla blog <a href="https://blog.mozilla.org/webrtc/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">Advancing WebRTC</a></li>
</ul>

<p>みなさんもWebRTCを活用した素敵なアプリケーションを作ってくださいね。ここまでお付き合いいただき、どうもありがとうございました！</p>
]]></content:encoded>
		
		<series:name><![CDATA[WebRTC入門2016]]></series:name>
	</item>
		<item>
		<title>Firebaseで楽々シグナリング──WebRTC入門2016番外編</title>
		<link>/mganeko/20273/</link>
		<pubDate>Wed, 31 Aug 2016 00:00:42 +0000</pubDate>
		<dc:creator><![CDATA[がねこまさし]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[Firebase]]></category>
		<category><![CDATA[WebRTC]]></category>

		<guid isPermaLink="false">/?p=20273</guid>
		<description><![CDATA[連載： WebRTC入門2016 (5)こんにちは！ 2014年に連載した「WebRTCを使ってみよう！」シリーズのアップデートとしてお送りしているこの連載ですが、今回はもとの連載にはなかった内容を番外編としてお届けしま...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/webrtc2016/" class="series-380" title="WebRTC入門2016" data-wpel-link="internal">WebRTC入門2016</a> (5)</div><p>こんにちは！ 2014年に連載した<a href="https://html5experts.jp/series/webrtc-beginner/" target="_blank" data-wpel-link="internal">「WebRTCを使ってみよう！」</a>シリーズのアップデートとしてお送りしている<a href="https://html5experts.jp/series/webrtc2016/" target="_blank" data-wpel-link="internal">この連載</a>ですが、今回はもとの連載にはなかった内容を番外編としてお届けします。</p>

<h2>httpsのハードル</h2>

<p><a href="https://html5experts.jp/mganeko/20112/" target="_blank" data-wpel-link="internal">前回は複数人、複数会議室</a>で利用できるようにして、実用的なアプリを作る準備ができました。ところが実際に使おうとすると、Chromeのセキュリティポリシーと向き合わなくてはなりません。</p>

<p>「getUserMedia()やService Workerなどの強力なAPIは、セキュアな環境でなくては利用できない」というポリシーは今のWebの状況に合わせたものだと思います。では、その環境をどうやって用意すればよいのでしょうか？</p>

<p>もちろん証明書を取得して、きちんとサーバーを立てるのがまっとうなやり方です。最近はLet&#8217;s Encryptなど無料で証明書を発行するサービスもあります（参考：<a href="https://webrtchacks.com/lets-encrypt/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">&#8220;Let’s Encrypt – how get to free SSL for WebRTC&#8221;</a>）。とはいえ、試験的な利用でそこまで準備するのは大変というのも、正直なところです。そこで今回は、比較的とっつきやすい方法をご紹介します。</p>

<h3>Webサーバー</h3>

<p>HTMLやJavaScriptなどの静的コンテンツを配置できる手段はいくつかあります。エンジニアの皆さんであれば、次の2つをすでに利用されている方々も多いのではないでしょうか？</p>

<ul>
<li>GitHub Pages</li>
<li>Google App Engine</li>
</ul>

<p>どちらも http / https の両方でアクセスできますし、独自ドメインで利用することも可能です。（※利用方法についてはWebに多くの情報がありますので、そちらをご参照ください）</p>

<p>また、シグナリングで利用するFirebaseにも静的コンテンツのホスティング機能があります。</p>

<h3>シグナリングサーバー</h3>

<p>シグナリングにはsocket.ioなど、WebSocketを活用した仕組みが使われる例が多いようです。もちろん他の方法（例えば手動シグナリング）でもいいのですが、サーバーとクライアント（ブラウザ）で双方向に通信するにはWebSocketが適切なのでしょう。</p>

<p>https://～から取得されたHTML/JavaScriptからWebSocketサーバーに接続する場合には、そちらもセキュアでなくてはなりません。(ws://～ではなく、wss://～）。WebSocketを利用したシグナリングサーバーを自分で用意する場合、そちらでも証明書が必要になります。</p>

<p>そこで今回はリアルタイムメッセージングに利用できるBaaSである<a href="https://www.firebase.com/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">Firebase</a>を使って、シグナリングの仕組みを構築したいと思います。</p>

<h2>Firebaseでシグナリングを実現するには</h2>

<p>まずはFirebaseにサインアップし、必要なキーやURLを入手しましょう。</p>

<ul>
<li>参考：<a href="https://html5experts.jp/technohippy/18040/" target="_blank" data-wpel-link="internal">Firebaseで作る簡単リアルタイムウェブアプリケーション </a></li>
</ul>

<p>（※手順については今回は省略させていただきます。あしらかず）</p>

<p>ブラウザでFirebaseの機能を利用するために、必要なライブラリを読み込んでおきます。今回はデータベースを利用するので、必要なjsファイルは次の通りとなります。</p>

<p></p><pre class="crayon-plain-tag">&lt;script src="https://www.gstatic.com/firebasejs/3.1.0/firebase-app.js"&gt;&lt;/script&gt;
 &lt;script src="https://www.gstatic.com/firebasejs/3.1.0/firebase-database.js"&gt;&lt;/script&gt;</pre><p></p>

<p>それから取得しておいたキーとURLを用いてFirebaseに接続します。
</p><pre class="crayon-plain-tag">// Initialize Firebase
  let config = {
    apiKey: "yourAPIKey",  // &lt;-- please set your API key
    databaseURL: "https://yourapp.firebaseio.com/",  // &lt;-- please set your database URL
  };
  firebase.initializeApp(config);
  let database = firebase.database();</pre><p></p>

<h3>シグナリングで送りたいモノ</h3>

<p>前回の記事にもあるように、シグナリングでは2つの通信ケースがあります。</p>

<ul>
<li>ルーム内の他のメンバー全員（接続している他のクライアントすべて）に送る</li>
<li>特定のメンバー（特定のクライアント）だけに送る</li>
</ul>

<p>また後者のためには、特定のメンバーを識別するための何らかのIDが必要となります。</p>

<ul>
<li>クライアント側で、重ならない/重なりにくい ようにIDを決める（UUID、タイムスタンプ、乱数など）</li>
<li>サーバー側でIDを振り出す</li>
</ul>

<p>Firebaseを使うとデータベースに格納されるオブジェクトのすべてにIDが振られるので、今回はそれを利用します。</p>

<h3>データベースの構造</h3>

<p>Firebaseはデータベースによる階層構造をもったデータの永続化と、その追加/変更/削除のイベント通知が行えます。今回は次のようなアプリ/ルーム/メンバーの階層構造としました。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2016/08/firebase_structure_11.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/08/firebase_structure_11-300x218.png" alt="firebase_structure_1" width="300" height="218" class="alignnone size-medium wp-image-20292" srcset="/wp-content/uploads/2016/08/firebase_structure_11-300x218.png 300w, /wp-content/uploads/2016/08/firebase_structure_11.png 640w, /wp-content/uploads/2016/08/firebase_structure_11-207x151.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<p>例えばルーム&#8221;test&#8221;にメンバー&#8221;bbb&#8221;が参加する場合、次の2カ所のイベントを待ち受けています。</p>

<ul>
<li>multi/room_test/&#95;broadcast&#95; の child_added イベント</li>
<li>multi/room_test/&#95;direct&#95;/member_bbb の child_added イベント</li>
</ul>

<p>また、multi/room_test/&#95;join&#95; はメッセージのやりとり以外の用途で使います。</p>

<h3>メンバーのIDの決定</h3>

<p>メンバーを特定してメッセージを送るには、メンバーを識別するIDが必要です。先ほどは仮に&#8221;bbb&#8221;としましたが、実際には衝突を避けるためにFirebase側で振り出されるキーを利用することにしました。</p>

<p></p><pre class="crayon-plain-tag">let databaseRoot = 'myapp/multi/';
    let key = database.ref(databaseRoot + room + '/_join_').push({ joined : 'unknown'}).key
    clientId = 'member_' + key;
    database.ref(databaseRoot + room + '/_join_/' + key).update({ joined : clientId});</pre><p></p>

<p>データベースにパスを指定してpush()すると、子要素が追加されて、そのキーが返っています。その値を使って先ほどの子要素の内容を更新しています。</p>

<p>ここまで終わった時のDatabaseの内容をFirebaseのコンソールで見てみると、次のようになっています。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2016/08/firebase_join.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/08/firebase_join-300x182.png" alt="firebase_join" width="300" height="182" class="alignnone size-medium wp-image-20299" srcset="/wp-content/uploads/2016/08/firebase_join-300x182.png 300w, /wp-content/uploads/2016/08/firebase_join.png 640w, /wp-content/uploads/2016/08/firebase_join-207x125.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<h3>ルーム内へのブロードキャスト</h3>

<p>シグナリングの流れは<a href="https://html5experts.jp/mganeko/20112/" target="_blank" data-wpel-link="internal">前回と同じ</a>です。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2016/07/multi_callme_simple.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/07/multi_callme_simple-300x225.png" alt="multi_callme_simple" width="300" height="225" class="alignnone size-medium wp-image-20140" srcset="/wp-content/uploads/2016/07/multi_callme_simple-300x225.png 300w, /wp-content/uploads/2016/07/multi_callme_simple.png 640w, /wp-content/uploads/2016/07/multi_callme_simple-207x155.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<ul>
<li>新たに通信を開始したい人（member_xxxx）が、通信開始の合図のルーム内にブロードキャスする(“call me”）</li>
</ul>

<p>&#8220;test&#8221;ルームにブロードキャストする場合には、 multi/room_test/&#95;broadcast&#95; の下を使います。</p>

<p></p><pre class="crayon-plain-tag">let roomBroadcastRef = database.ref(databaseRoot + room + '/_broadcast_');

  // 通信開始の合図
  function callMe() {
    emitRoom({type: 'call me'});
  }

  function emitRoom(msg) {
    msg.from = clientId; // メッセージに送信元（自分のID)をセット
    roomBroadcastRef.push(msg);
  }</pre><p></p>

<p>各メンバーはこのブロードキャストのイベントを待ち受けていて、typeに応じて処理を行います。</p>

<p></p><pre class="crayon-plain-tag">roomBroadcastRef.on('child_added', function(data) {
      let message = data.val();
      let fromId = message.from;
      if (fromId === clientId) {
        // ignore self message (自分自身からのメッセージは無視する）
        return;
      }
      
      if (message.type === 'call me') {
        // 接続処理
      }
    });</pre><p></p>

<p>socket.ioでは自分自身からメッセージは飛んで来ませんが、Firebaseでは自分でpush()してもイベントが飛んでくるので、それは無視しています。</p>

<h3>特定のメンバーへのメッセージ</h3>

<p>通信開始の合図である&#8221;call me&#8221;以外は、特定のメンバー宛のメッセージのやり取りになります。例えば member_bbb 宛のメッセージは、この下にpush()します。</p>

<ul>
<li>multi/room_test/<em>direct</em>/member_bbb</li>
</ul>

<p></p><pre class="crayon-plain-tag">function emitTo(id, msg) {
    msg.from = clientId; // メッセージに送信元（自分のID)をセット
    database.ref(databaseRoot + room + '/_direct_/' + id).push(msg);
  }</pre><p></p>

<p>Offer/Answerの交換やICE Candidateのやり取りは、すべてこちらのメンバー宛のメッセージになります。</p>

<h3>Firebaseのルール設定</h3>

<p>Firebaseのデータベースにはアクセスのルール指定があります。デフィルトではread/writeにAuth必要なルールが生成されていますが、今回のサンプルでは特定のパス以下はAuth不要で読み書きできるよう、ルールを追加しました。</p>

<p></p><pre class="crayon-plain-tag">{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null",
    "myapp" : {
      "multi" : {
        ".read": true,
        ".write": true
      }
    }
  }
}</pre><p></p>

<h2>NAT越えの設定 STUNの設定</h2>

<p>せっかくFirebaseでメッセージをやり取りできるので、NATを超えてWebRTCで通信できるようにしましょう。</p>

<p>NATを超えて通信を行うには、ローカルネットワークでのIPアドレスではなく、グローバルIPを伝える必要があります。今回は詳しく説明しませんが、そのための仕組みがSTUNです。GoogleがSTUNサーバーを公開しているので、それを使わせてもらいましょう。</p>

<p></p><pre class="crayon-plain-tag">function prepareNewConnection(id) {
    let pc_config = {"iceServers":[{"urls": "stun:stun.l.google.com:19302"}]}; // for STUN server
    let peer = new RTCPeerConnection(pc_config);
    // ... 省略 ...
  }</pre><p></p>

<p>※ <a href="https://html5experts.jp/mganeko/5554/" target="_blank" data-wpel-link="internal">2014年の記事</a>では&#8221;url&#8221; と指定していましたが、現在の仕様では&#8221;urls&#8221;と指定する必要があります。</p>

<p>このようにSTUNサーバーを指定することで、通信経路の候補(ICE Candidate)に、STUN経由で取得した情報も含まれるようになります。</p>

<h2>動かしてみよう</h2>

<p>今回のサンプルをGitHub Pagesで公開しています。Firebaseもしばらく(〜2016年9月末の予定）使える状態にしておきますので、皆さんもお試しください。</p>

<ul>
<li>GitHub Pages で試す <a href="https://mganeko.github.io/webrtcexpjp/basic2016/multi_firebase.html" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">multi_firebase.html</a> (Chrome/Firefox）</li>
<li>GitHub でソースを見る <a href="https://github.com/mganeko/webrtcexpjp/blob/master/basic2016/multi_firebase.html" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">multi_firebase.html</a> </li>
</ul>

<p>※シグナリングの手段以外は、<a href="https://html5experts.jp/mganeko/20112/" target="_blank" data-wpel-link="internal">前回のソース</a>と同様の処理になっています。</p>

<h3>使い方</h3>

<ul>
<li>URLの後ろに ?<em>room</em> という形で、好きなルーム名を指定してください

<ul>
<li>https://mganeko.github.io/webrtcexpjp/basic2016/multi_firebase.html?<em>お好きなルーム名</em></li>
</ul></li>
<li>ルーム名を指定せずに multi_firebase.html を開くと、ランダムにルーム名を決定します</li>
<li>[Start Video]をクリックし、カメラの映像とマイクの音声を取得します</li>
<li>通信相手にも同じルーム名を指定してブラウザ(Chrome/Firebox)でアクセスしてもらいます

<ul>
<li>?<em>room</em> も含むURLを伝えてください</li>
<li>&#8220;Mail link of this room&#8221; をクリックすると、URLを送るためのメーラーが開きます。宛先を指定して送信してください</li>
</ul></li>
<li>通信相手にも[Start Video]をクリックし、カメラの映像とマイクの音声を取得してもらいます</li>
<li>自分、あるいは相手から[Connect]ボタンを押してください</li>
<li>Firebase経由で情報が交換され、P2P通信が始まります</li>
<li>このサンプルでは、同じルームに同時に4人まで(3人の相手と)通信することができます</li>
</ul>

<h3>トラブルシューティング</h3>

<ul>
<li>異なるPCで通信できない場合

<ul>
<li>→ ルーム名が一致しているか確認していください</li>
<li>ルーム名を指定せずにブラウザでアクセスすると、ランダムにルーム名が決定されます</li>
<li>異なるPCでは、それぞれ異なるルーム名がランダムに決定されます</li>
</ul></li>
<li>一度接続できたが、その後できなくなった場合

<ul>
<li>ルーム内の過去のブロードキャストメッセージが残っている可能性があります</li>
<li>ルーム名を変更して、再度接続してみてください</li>
</ul></li>
<li>（例えば）会社と自宅で通信できない場合

<ul>
<li>→ NATだけでなく、FirewallでUDP通信やポートが制限されている可能性があります</li>
<li>※Firewallを超えての通信については、次回の記事で取り上げる予定です</li>
</ul></li>
</ul>

<h3>Edge同士での通信</h3>

<p>webrtc.orgが提供している<a href="https://github.com/webrtc/adapter" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">adapter.js</a>を使うと、多くのブラウザの差異を吸収することができます。最新の<a href="https://webrtc.github.io/adapter/adapter-latest.js" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">https://webrtc.github.io/adapter/adapter-latest.js</a>を読み込めば、Microsoft EdgeのサポートしてるORTCを、WebRTCのオブジェクトのインターフェイスを通して利用することができます。</p>

<p>残念ながらサポートしているビデオコーデックの制限で、ChromeやFirefoxとのビデオ通信はできませんが、Edge同士であれば今回のFirebaseを使ったシグナリングでビデオ/オーディオのP2P通信を行うことができます。</p>

<ul>
<li>GitHub Pages で試す <a href="https://mganeko.github.io/webrtcexpjp/basic2016/multi_firebase_adapter.html" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">multi_firebase_adapter.html</a> (Chrome/Firefox）</li>
<li>GitHub でソースを見る <a href="https://github.com/mganeko/webrtcexpjp/blob/master/basic2016/multi_firebase_adapter.html" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">multi_firebase_adapter.html</a> </li>
</ul>

<p><a href="https://html5experts.jp/wp-content/uploads/2016/08/edge_firebase.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/08/edge_firebase-300x135.png" alt="edge_firebase" width="300" height="135" class="alignnone size-medium wp-image-20323" srcset="/wp-content/uploads/2016/08/edge_firebase-300x135.png 300w, /wp-content/uploads/2016/08/edge_firebase.png 640w, /wp-content/uploads/2016/08/edge_firebase-207x93.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<h4>注意点</h4>

<ul>
<li>Edgeでは、同一のカメラを複数のウィンドウ/タブで利用することができません。同一PCで通信する場合は、複数のカメラを用意してください</li>
<li>Edgeでは現在のところSTUNはサポートされていない(TURNのみサポート)ため、今回のサンプルでNATを越えた通信はできません</li>
</ul>

<h2>次回は</h2>

<p>今回はFirebaseを使ってシグナリングを実現しました。次回はNAT/Firewallを超えてのWebRTC通信についてお届けする予定です。</p>
]]></content:encoded>
		
		<series:name><![CDATA[WebRTC入門2016]]></series:name>
	</item>
		<item>
		<title>シグナリングを拡張して、複数人で通信してみよう ーWebRTC入門2016</title>
		<link>/mganeko/20112/</link>
		<pubDate>Mon, 01 Aug 2016 00:00:18 +0000</pubDate>
		<dc:creator><![CDATA[がねこまさし]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[WebRTC]]></category>

		<guid isPermaLink="false">/?p=20112</guid>
		<description><![CDATA[連載： WebRTC入門2016 (4)こんにちは！ 2014年に連載した「WebRTCを使ってみよう！」シリーズのアップデート記事も4回目となり、佳境に入りました。前回の1対1の通信をベースに、今回はより実用的なビデオ...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/webrtc2016/" class="series-380" title="WebRTC入門2016" data-wpel-link="internal">WebRTC入門2016</a> (4)</div><p>こんにちは！ 2014年に連載した<a href="https://html5experts.jp/series/webrtc-beginner/" target="_blank" data-wpel-link="internal">「WebRTCを使ってみよう！」シリーズ</a>のアップデート記事も4回目となり、佳境に入りました。<a href="https://html5experts.jp/mganeko/20013/" target="_blank" data-wpel-link="internal">前回の1対1の通信</a>をベースに、今回はより実用的なビデオチャットを目指して複数人で通信可能なように拡張してみましょう。</p>

<h2>複数人、複数会議室を目指して</h2>

<p>前回作ったのは、1つのシグナリングサーバーに対して、同時に1ペアだけが利用できる仕組みでした。これを複数人で、複数会議室で利用できるようにしていきましょう。こちらの図の左のBeforeの状態から、右のAfterの状態を実現します。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2016/07/rtc_11_to_nn.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/07/rtc_11_to_nn-300x176.png" alt="rtc_11_to_nn" width="300" height="176" class="alignnone size-medium wp-image-20119" srcset="/wp-content/uploads/2016/07/rtc_11_to_nn-300x176.png 300w, /wp-content/uploads/2016/07/rtc_11_to_nn.png 640w, /wp-content/uploads/2016/07/rtc_11_to_nn-207x122.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<h2>複数人で通信するためには</h2>

<p>WebRTCはPeer-to-Peer (P2P) で通信する仕組みです。なので複数人と通信するためには、複数の<code>RTCPeerConnection</code>を用意する必要があります。図にするとこんな感じです。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2016/07/PeerConnection_multi.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/07/PeerConnection_multi-300x205.png" alt="PeerConnection_multi" width="300" height="205" class="alignnone size-medium wp-image-20113" srcset="/wp-content/uploads/2016/07/PeerConnection_multi-300x205.png 300w, /wp-content/uploads/2016/07/PeerConnection_multi.png 640w, /wp-content/uploads/2016/07/PeerConnection_multi-207x142.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<p>また、P2P通信を開始するためにSDP(Offer/Answer)を交換する必要がありますが、それぞれの相手ごとに生成、交換しなければなりません。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2016/07/PeerConnection_sdp_multi.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/07/PeerConnection_sdp_multi-300x230.png" alt="PeerConnection_sdp_multi" width="300" height="230" class="alignnone size-medium wp-image-20114" srcset="/wp-content/uploads/2016/07/PeerConnection_sdp_multi-300x230.png 300w, /wp-content/uploads/2016/07/PeerConnection_sdp_multi.png 640w, /wp-content/uploads/2016/07/PeerConnection_sdp_multi-207x159.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<p>P2Pなので当然かもしれませんが、私は最初に複数の相手と通信しようとして混乱してしまいました。この考え方を踏まえていれば、あとは力技になります。</p>

<h2>シグナリングサーバーの対応</h2>

<p>シグナリングサーバーは前回の1対1の時と同じく、Node.jsを使って用意しましょう。複数会議室を実現するのに便利なため、今回はwsモジュールではなく、より高機能のsocket.ioを使います。Node.jsのインストールは終わっていると思うので、コマンドプロンプト/ターミナルから、次のコマンドを実行してください。 ※必要に応じて、sudoなどをご利用ください。</p>

<p></p><pre class="crayon-plain-tag">npm install socket.io</pre><p></p>

<p>2014年にはsocket.ioはv0.9でしが、今回はv1.4.xになっています。</p>

<p>シグナリングサーバーのコードは前回の1対1と同様にメッセージを中継するのが役目ですが、接続してきたクライアントがルーム（会議室）に入室要求を送ってきたら、socket.ioのサーバ側でそのルームに <code>join()</code> してあげます。</p>

<p></p><pre class="crayon-plain-tag">// ---- multi room ----
    socket.on('enter', function(roomname) {
      socket.join(roomname);
      console.log('id=' + socket.id + ' enter room=' + roomname);
      setRoomname(roomname);
    });

    function setRoomname(room) {
      socket.roomname = room;
    }</pre><p></p>

<p>クライアントからのメッセージ送信には、次の2つのパターンがあります。</p>

<ul>
<li>ルーム内の他のメンバー全員（接続している他のクライアントすべて）に送る</li>
<li>特定のメンバー（特定のクライアント）だけに送る</li>
</ul>

<p>シグナリングサーバーでは、送信先が指定されていればその相手だけに、指定されていなければルーム内の全員（送信者以外）にメッセージを送ります。
※その際に、送信元を特定できるID（socket.ioが管理しているID)を追加しています。</p>

<p></p><pre class="crayon-plain-tag">socket.on('message', function(message) {
        message.from = socket.id; // 送信元のIDをメッセージに追加

        // get send target
        var target = message.sendto;
        if (target) { // 特定の相手に送る場合
          socket.to(target).emit('message', message);　
          return;
        }

        // broadcast in room
        emitMessage('message', message);
    });

    // ルーム内の全員に送る場合
    function emitMessage(type, message) {
      // ----- multi room ----
      var roomname = getRoomname();

      if (roomname) {
        // ルーム内に送る
        socket.broadcast.to(roomname).emit(type, message);
      }
      else {
        // ルーム未入室の場合は、全体に送る
        socket.broadcast.emit(type, message);
      }
    }</pre><p></p>

<p>サーバーの全体のソースは次の通りです。これを例えば <code>signaling_room.js</code> というファイル名で保存します。</p>

<p></p><pre class="crayon-plain-tag">"use strict";

var srv = require('http').Server();
var io = require('socket.io')(srv);
var port = 3002;
srv.listen(port);
console.log('signaling server started on port:' + port);

// This callback function is called every time a socket
// tries to connect to the server
io.on('connection', function(socket) {
    // ---- multi room ----
    socket.on('enter', function(roomname) {
      socket.join(roomname);
      console.log('id=' + socket.id + ' enter room=' + roomname);
      setRoomname(roomname);
    });

    function setRoomname(room) {
      socket.roomname = room;
    }

    function getRoomname() {
      var room = socket.roomname;
      return room;
    }

    function emitMessage(type, message) {
      // ----- multi room ----
      var roomname = getRoomname();

      if (roomname) {
        console.log('===== message broadcast to room --&gt;' + roomname);
        socket.broadcast.to(roomname).emit(type, message);
      }
      else {
        console.log('===== message broadcast all');
        socket.broadcast.emit(type, message);
      }
    }

    // When a user send a SDP message
    // broadcast to all users in the room
    socket.on('message', function(message) {
        var date = new Date();
        message.from = socket.id;
        console.log(date + 'id=' + socket.id + ' Received Message: ' + JSON.stringify(message));

        // get send target
        var target = message.sendto;
        if (target) {
          console.log('===== message emit to --&gt;' + target);
          socket.to(target).emit('message', message);
          return;
        }

        // broadcast in room
        emitMessage('message', message);
    });

    // When the user hangs up
    // broadcast bye signal to all users in the room
    socket.on('disconnect', function() {
        // close user connection
        console.log((new Date()) + ' Peer disconnected. id=' + socket.id);

        // --- emit ----
        emitMessage('user disconnected', {id: socket.id});

        // --- leave room --
        var roomname = getRoomname();
        if (roomname) {
          socket.leave(roomname);
        }
    });

});</pre><p></p>

<p>コマンドプロンプト/ターミナルから、 次のように起動してください。（ファイル名は適宜置き換えてくださいね）</p>

<p></p><pre class="crayon-plain-tag">node signaling_room.js</pre><p></p>

<h2>クライアント側の拡張</h2>

<p>次はクライアントとなるブラウザ側の処理を拡張していきます。</p>

<h3>socket.io サーバーへの接続</h3>

<p>今回はシグナリングサーバーを同じPCの3002番ポートで動かしていると想定します。HTMLファイルの先頭で、socket.ioのクライアント用のjsファイルを読み込みます。</p>

<p></p><pre class="crayon-plain-tag">&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
 &lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8"&gt;
 &lt;title&gt;multi party&lt;/title&gt;
 &lt;script src="http://localhost:3002/socket.io/socket.io.js"&gt;&lt;/script&gt;
&lt;/head&gt;
... 省略 ...
&lt;/htm&gt;</pre><p></p>

<p>クライアントではsocket.ioのサーバー (localhost:3002) に接続します。このとき、<code>ws://～</code> ではなく、<code>http://～/</code> となることがWebSocketを直接利用した場合と異なります。</p>

<p></p><pre class="crayon-plain-tag">// ----- use socket.io ---
  let port = 3002;
  let socket = io.connect('http://localhost:/' + port + '/');
  socket.on('connect', function(evt) {
    // 接続したときの処理
  });</pre><p></p>

<p>※実際のシグナリングサーバーの環境に合わせて、URLやポート番号は変更してください。</p>

<h3>ルーム（会議室）への入室</h3>

<p>クライアンではsocket.ioのサーバーに接続したら、希望のルームに入室を依頼します。ルーム名は今回はURLの後ろに　<code>?部屋名</code> という形で指定することにしてみました。（お好きな方法で指定してください）</p>

<p></p><pre class="crayon-plain-tag">let room = getRoomName();
  socket.on('connect', function(evt) {
    console.log('socket.io connected. enter room=' + room );
    socket.emit('enter', room);
  });

  // -- room名を取得 --
  function getRoomName() { // たとえば、 URLに  ?roomname  とする
    let url = document.location.href;
    let args = url.split('?');
    if (args.length &gt; 1) {
      let room = args[1];
      if (room != '') {
        return room;
      }
    }
    return '_testroom';
  }</pre><p></p>

<h3>複数通信の流れ</h3>

<p>1対1の時は相手が1人しかいない前提だったので、ただちにOffer SDP / Answer SDPを送信していました。今回は相手が何人いるか分からない状況からスタートしますし、相手ごとに個々にOffer SDP / Answer SDPを送受信する必要があります。そこで、相手を確認するやりとりを追加しました。図にすると、次のような流れになります。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2016/07/multi_callme_simple.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/07/multi_callme_simple-300x225.png" alt="multi_callme_simple" width="300" height="225" class="alignnone size-medium wp-image-20140" srcset="/wp-content/uploads/2016/07/multi_callme_simple-300x225.png 300w, /wp-content/uploads/2016/07/multi_callme_simple.png 640w, /wp-content/uploads/2016/07/multi_callme_simple-207x155.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<p>もう少し細かく見ると、次のような処理を行っています。</p>

<ul>
<li>新たに通信を開始したい人（ブラウザA）が、通信開始の合図のルーム内にブロードキャストする(&#8220;call me&#8221;）</li>
<li>受け取った人（ブラウザB、ブラウザC）は、Offer SDPを生成して、ブラウザAを宛先に指定して送ります

<ul>
<li><code>RTCPeerConnection</code>を生成</li>
<li><code>RTCPeerConnection.createOffer()</code>でOffer SDPを生成</li>
<li><code>setLocalDescription()</code>で覚える</li>
<li>ブラウザA宛てに送信</li>
</ul></li>
<li>ブラウザAは、Offer SDPを受け取り、Answer SDPを生成してそれぞれの相手に送り返します

<ul>
<li>相手ごとに<code>RTCPeerConnection</code>を生成</li>
<li>それぞれ受け取ったOffer SDPを<code>RTCPeerConnection.setRemoteDescription()</code>で覚える</li>
<li><code>RTCPeerConnection.createAnswer()</code>でAnswer SDPを生成</li>
<li><code>setLocalDescription()</code>で覚える</li>
<li>それぞれの相手にAnswer SDPを返信</li>
</ul></li>
<li>ブラウザB、ブラウザCは、Answer SDPを受け取る

<ul>
<li>受け取ったAnswer SDPを<code>RTCPeerConnection.setRemoteDescription()</code>で覚える</li>
</ul></li>
</ul>

<p><a href="https://html5experts.jp/wp-content/uploads/2016/07/multi_callme.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/07/multi_callme-300x225.png" alt="multi_callme" width="300" height="225" class="alignnone size-medium wp-image-20136" srcset="/wp-content/uploads/2016/07/multi_callme-300x225.png 300w, /wp-content/uploads/2016/07/multi_callme.png 640w, /wp-content/uploads/2016/07/multi_callme-207x156.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<h3>ソースの修正：複数通信の準備</h3>

<p>それでは、ブラウザ側のソースも手を入れていきましょう。まず、複数の<code>RTCPeerConnection</code>を扱えるように用意します。</p>

<p></p><pre class="crayon-plain-tag">// ---- for multi party -----
  let peerConnections = [];
  const MAX_CONNECTION_COUNT = 3;

  // --- RTCPeerConnections ---
  function getConnectionCount() {
    return peerConnections.length;
  }

  function canConnectMore() {
    return (getConnectionCount() &lt; MAX_CONNECTION_COUNT);
  }

  function isConnectedWith(id) {
    if (peerConnections[id])  {
      return true;
    }
    else {
      return false;
    }
  }

  function addConnection(id, peer) {
    peerConnections[id] = peer;
  }

  function getConnection(id) {
    let peer = peerConnections[id];
    return peer;
  }

  function deleteConnection(id) {
    delete peerConnections[id];
  }</pre><p></p>

<p>相手の映像を表示する<code>video</code>タグも、動的に生成して複数管理できるようにします。</p>

<p></p><pre class="crayon-plain-tag">let remoteVideos = [];
  let container = document.getElementById('container');

  // --- video elements ---
  function addRemoteVideoElement(id) {
    let video = createVideoElement('remote_video_' + id);
    remoteVideos[id] = video;
    return video;
  }

  function getRemoteVideoElement(id) {
    let video = remoteVideos[id];
    return video;
  }

  function deleteRemoteVideoElement(id) {
    removeVideoElement('remote_video_' + id);
    delete remoteVideos[id];
  }

  function createVideoElement(elementId) {
    let video = document.createElement('video');
    video.width = '160';
    video.height = '120';
    video.id = elementId;

    video.style.border = 'solid black 1px';
    video.style.margin = '2px';

    container.appendChild(video);

    return video;
  }

  function removeVideoElement(elementId) {
    let video = document.getElementById(elementId);
    container.removeChild(video);
    return video;
  }</pre><p></p>

<p>さらに<code>RTCPeerConnection</code>の接続や、相手からのメディアストリーム、<code>video</code>タグを連動して扱う処理も追加しておきましょう。</p>

<p></p><pre class="crayon-plain-tag">// --- video elements ---
  function attachVideo(id, stream) {
    let video = addRemoteVideoElement(id);
    playVideo(video, stream);
    video.volume = 1.0;
  }

  function detachVideo(id) {
    let video = getRemoteVideoElement(id);
    pauseVideo(video);
    deleteRemoteVideoElement(id);
  }
  
  function isRemoteVideoAttached(id) {
    if (remoteVideos[id]) {
      return true;
    }
    else {
      return false;
    }
  }

  // --- RTCPeerConnections ---
  function stopConnection(id) {
    detachVideo(id);

    if (isConnectedWith(id)) {
      let peer = getConnection(id);
      peer.close();
      deleteConnection(id);
    }
  }

  function stopAllConnection() {
    for (let id in peerConnections) {
      stopConnection(id);
    }
  }</pre><p></p>

<h3>ソースの修正：シグナリングの変更</h3>

<p>WebSocket直接利用から、socket.ioの利用に変わったので、シグナリングも変更します。</p>

<p></p><pre class="crayon-plain-tag">// ----- use socket.io ---
  let port = 3002;
  let socket = io.connect('http://localhost:/' + port + '/');
  let room = getRoomName();
  socket.on('connect', function(evt) {
    socket.emit('enter', room);
  });
  socket.on('message', function(message) {
    let fromId = message.from;

    if (message.type === 'offer') {
      // -- got offer ---
      let offer = new RTCSessionDescription(message);
      setOffer(fromId, offer);
    }
    else if (message.type === 'answer') {
      // --- got answer ---
      let answer = new RTCSessionDescription(message);
      setAnswer(fromId, answer);
    }
    else if (message.type === 'candidate') {
      // --- got ICE candidate ---
      let candidate = new RTCIceCandidate(message.ice);
      addIceCandidate(fromId, candidate);
    }
    else if (message.type === 'call me') {
      if (! isReadyToConnect()) {
        console.log('Not ready to connect, so ignore');
        return;
      }
      else if (! canConnectMore()) {
        console.warn('TOO MANY connections, so ignore');
      }

      if (isConnectedWith(fromId)) {
        // already connnected, so skip
        console.log('already connected, so ignore');
      }
      else {
        // connect new party
        makeOffer(fromId);
      }
    }
    else if (message.type === 'bye') {
      if (isConnectedWith(fromId)) {
        stopConnection(fromId);
      }
    }
  });
  socket.on('user disconnected', function(evt) {
    let id = evt.id;
    if (isConnectedWith(id)) {
      stopConnection(id);
    }
  });

  // --- broadcast message to all members in room
  function emitRoom(msg) {
    socket.emit('message', msg);
  }

  function emitTo(id, msg) {
    msg.sendto = id;
    socket.emit('message', msg);
  }</pre><p></p>

<p>通信開始要求の&#8221;call me&#8221;や、切断要求の&#8221;bye&#8221;の処理を追加しています。また接続準備が整っていない場合（カメラやマイクを取得していない場合）や、すでに接続中の相手からの接続要求は無視するようにしています。</p>

<h3>SDPやICE candidateのハンドリング</h3>

<p>複数の相手とOffer/Answer SDPやICE candidateをやり取りするので、相手を意識した処理に拡張します。といっても違いは対応する<code>RTCPeerConnection</code>のオブジェクトを生成したり取り出したりするところだけで、他は1対1の場合と同様です。すべてを掲載すると長いので全体は<a href="https://github.com/mganeko/webrtcexpjp/blob/master/basic2016/multi.html" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">GitHubを参照</a>してくただくとして、ここでは例を取り上げます。</p>

<h4>Offer SDPの送信</h4>

<p>新たに <code>RTCPeerConnection</code>を生成し、Offer SDPの作成、送信を行います。
</p><pre class="crayon-plain-tag">function makeOffer(id) {
    peerConnection = prepareNewConnection(id);
    addConnection(id, peerConnection);

    peerConnection.createOffer()
    .then(function (sessionDescription) {
      console.log('createOffer() succsess in promise');
      return peerConnection.setLocalDescription(sessionDescription);
    }).then(function() {
      console.log('setLocalDescription() succsess in promise');

      // -- Trickle ICEの場合は、初期SDPを相手に送る -- 
      sendSdp(id, peerConnection.localDescription);

      // -- Vanilla ICEの場合には、まだSDPは送らない --
    }).catch(function(err) {
      console.error(err);
    });
  }

  function sendSdp(id, sessionDescription) {
    let message = { type: sessionDescription.type, sdp: sessionDescription.sdp };
    emitTo(id, message);
  }</pre><p></p>

<h4>Answer SDPを受け取った場合の処理</h4>

<p>対応する<code>RTCPeerConnection</code>を取り出し、Answer SDPを覚えさせます。
</p><pre class="crayon-plain-tag">function setAnswer(id, sessionDescription) {
    let peerConnection = getConnection(id);
    if (! peerConnection) {
      console.error('peerConnection NOT exist!');
      return;
    }

    peerConnection.setRemoteDescription(sessionDescription)
    .then(function() {
      console.log('setRemoteDescription(answer) succsess in promise');
    }).catch(function(err) {
      console.error('setRemoteDescription(answer) ERROR: ', err);
    });
  }</pre><p></p>

<p>ICE candidateのやりとりなども、同様に対応する<code>RTCPeerConnection</code>のオブジェクトを取り出して行います。また、手動シグナリングで使っていたSDPをテキストエリアに表示する部分も取り除きました。</p>

<h3>カメラ、マイクの取得</h3>

<p>前回までは映像だけでしたが、今回はマイクの音声も取得してより実用的にしましょう。ただし1台のPCでやる場合はハウリングしてしまうので、ヘッドフォンを使ってください。</p>

<p></p><pre class="crayon-plain-tag">// start local video
  function startVideo() {
    getDeviceStream({video: true, audio: true})
    .then(function (stream) { // success
      localStream = stream;
      playVideo(localVideo, stream);
    }).catch(function (error) { // error
      console.error('getUserMedia error:', error);
      return;
    });
  }</pre><p></p>

<p><code>getDeviceStream()</code>は、新旧<code>getUserMedia()</code>をラップするために用意した関数です。これに<code>audio:true</code>の指定を渡してマイクも取得しています。</p>

<p>今回マイクの音声も取得するようにしたため、メディアストリームにはビデオとオーディオの2つのトラックが含まれます。このため、相手側の<code>RTCPeerConnection</code>の<code>ontrack()</code>イベントが2回呼び出されますが、2回目は無視するように修正しました。（<code>RTCPeerConnection.ontrack()</code>は現在Firefoxのみがサポートしています）
</p><pre class="crayon-plain-tag">function prepareNewConnection(id) {
    let pc_config = {"iceServers":[]};
    let peer = new RTCPeerConnection(pc_config);

    // --- on get remote stream ---
    if ('ontrack' in peer) {
      peer.ontrack = function(event) {
        let stream = event.streams[0];
        if (isRemoteVideoAttached(id)) {
          console.log('stream already attached, so ignore'); // &lt;--- 同じ相手からの2回目以降のイベントは無視する
        }
        else {
          //playVideo(remoteVideo, stream);
          attachVideo(id, stream);
        }
      };
    }
    else {
      peer.onaddstream = function(event) {
        let stream = event.stream;
        console.log('-- peer.onaddstream() stream.id=' + stream.id);
        //playVideo(remoteVideo, stream);
        attachVideo(id, stream);
      };
    }

    // ... 省略 ....
  }</pre><p></p>

<h3>全体のソース</h3>

<p>主要な部分は以上の通りですが、他にも細かい修正があります。全体のソースはGitHubでご覧ください。</p>

<ul>
<li>クライアント用のソース &#8230;  <a href="https://github.com/mganeko/webrtcexpjp/blob/master/basic2016/multi.html" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">GitHubで見る</a></li>
<li>シグナリングサーバーのソース &#8230; <a href="https://github.com/mganeko/webrtcexpjp/blob/master/basic2016/server/signaling_room.js" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">GitHubで見る</a></li>
</ul>

<h2>接続してみよう</h2>

<p>今回のサンプルでは4人まで同時に通信できるようにしてみました。それぞれブラウザを立ち上げて[Start Video]→[Connect]の順にボタンをクリックし接続してください。このように複数の相手と接続できるはずです。</p>

<p><a href="https://html5experts.jp/wp-content/uploads/2016/07/multi_connected_4.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/07/multi_connected_4-300x144.png" alt="multi_connected_4" width="300" height="144" class="alignnone size-medium wp-image-20158" srcset="/wp-content/uploads/2016/07/multi_connected_4-300x144.png 300w, /wp-content/uploads/2016/07/multi_connected_4.png 640w, /wp-content/uploads/2016/07/multi_connected_4-207x100.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<p>※localhost以外に繋ぐときは、Chromeではカメラ/マイクの取得に失敗します。その場合はFirefoxをご利用ください。</p>

<h2>次回は</h2>

<p>今回のサンプルでは複数人で同時にビデオチャットができるようにしました。が、Chromeではhttp://～で/<code>getUserMedia()</code>が許可されていないため、他のPCと通信するのは厄介です。</p>

<p>Webサーバだけであれば、GitHub Pagesなどでhttps://～を利用することができますが、シグナリングサーバーのWebSocket通信も暗号化する必要がありまり、そちらは自分で証明書を取って対処しなけばなりません。/</p>

<p>そこで次回は番外編として、Firebaseを使った暗号化通信をシグナリングに利用した例をご紹介したいと思います。</p>
]]></content:encoded>
		
		<series:name><![CDATA[WebRTC入門2016]]></series:name>
	</item>
		<item>
		<title>シグナリングサーバーを動かそう ーWebRTC入門2016</title>
		<link>/mganeko/20013/</link>
		<pubDate>Thu, 14 Jul 2016 00:45:36 +0000</pubDate>
		<dc:creator><![CDATA[がねこまさし]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[WebRTC]]></category>
		<category><![CDATA[WebSocket]]></category>

		<guid isPermaLink="false">/?p=20013</guid>
		<description><![CDATA[連載： WebRTC入門2016 (3)こんにちは！ 2014年に連載した「WebRTCを使ってみよう！」シリーズのアップデート記事も3回目となりました。今回は、前回の「手動」で行ったP2P通信の準備を、自動で行えるよう...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/webrtc2016/" class="series-380" title="WebRTC入門2016" data-wpel-link="internal">WebRTC入門2016</a> (3)</div><p>こんにちは！ 2014年に連載した<a href="https://html5experts.jp/series/webrtc-beginner/" target="_blank" data-wpel-link="internal">「WebRTCを使ってみよう！」シリーズ</a>のアップデート記事も3回目となりました。今回は、前回の「手動」で行ったP2P通信の準備を、自動で行えるようにしてみましょう。</p>

<h2>シグナリングサーバーを立てよう</h2>

<p><a href="https://html5experts.jp/mganeko/19814/" target="_blank" data-wpel-link="internal">前回は手動でコピー＆ペーストを行い</a>、WebRTCのP2P通信を始めるために次の情報を交換しました。</p>

<ul>
<li>SDP</li>
<li>ICE candidate</li>
</ul>

<p>今回はこれを仲介するサーバー（シグナリングサーバー）を動かしてみましょう。方法として次の2つをご用意しました。</p>

<ul>
<li>Node.jsを使ったシグナリングサーバー</li>
<li>Chromeアプリ</li>
</ul>

<h3>Node.jsを準備しよう</h3>

<p>まず、WebSocketを使ってシグナリングを行う方法をご紹介します。WebSocketの扱いやすさから、ここではNode.jsを使います。（もちろん他の言語を使っても同様にシグナリングサーバーを作ることができます）<a href="https://nodejs.org/en/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">こちらの公式サイト</a>から、プラットフォームに対応したNode.jsを入手してインストールしてください。今回私は 4.4.7 LTSを使いました。</p>

<p>Node.jsのインストールが完了したら、次はWebSocketサーバー用のモジュールをインストールします。コマンドプロンプト/ターミナルから、 次のコマンドを実行してください。 ※必要に応じて、sudoなどをご利用ください。
</p><pre class="crayon-plain-tag">npm install ws</pre><p> 
※<a href="https://html5experts.jp/mganeko/5349/" target="_blank" data-wpel-link="internal">以前の連載</a>ではsocket.ioを使いましたが、今回はよりプリミティブなwsを使っています。</p>

<h3>シグナリングサーバーを動かそう</h3>

<p>次のコードを好きなファイル名で保存してください。（例えば signaling.js)
</p><pre class="crayon-plain-tag">"use strict";

let WebSocketServer = require('ws').Server;
let port = 3001;
let wsServer = new WebSocketServer({ port: port });
console.log('websocket server start. port=' + port);

wsServer.on('connection', function(ws) {
  console.log('-- websocket connected --');
  ws.on('message', function(message) {
    wsServer.clients.forEach(function each(client) {
      if (isSame(ws, client)) {
        console.log('- skip sender -');
      }
      else {
        client.send(message);
      }
    });
  });
});

function isSame(ws1, ws2) {
  // -- compare object --
  return (ws1 === ws2);     
}</pre><p> 
ポート番号は必要に応じて変更してください。起動するにはコマンドプロンプト/ターミナルから、 次のコマンドを実行します。</p><pre class="crayon-plain-tag">node signaling.js</pre><p>
シグナリングサーバーの動作はシンプルで、クライアントからメッセージを受け取ったら他のクライアントに送信するだけです。</p>

<h3>Chromeアプリを使う場合は</h3>

<p>場合によってはNode.jsをインストールして動かすのは、ハードルが高くて難しいケースもあるかもしれません。そんな人のために、Chromeアプリで「<a href="https://chrome.google.com/webstore/detail/simple-message-server/bihajhgkmpfnmbmdnobjcdhagncbkmmp" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">simple message server</a>」というものを作ってみました。
<a href="https://html5experts.jp/wp-content/uploads/2016/07/simple_message_server_store.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/07/simple_message_server_store-300x183.png" alt="simple_message_server_store" width="300" height="183" class="alignnone size-medium wp-image-20028" srcset="/wp-content/uploads/2016/07/simple_message_server_store-300x183.png 300w, /wp-content/uploads/2016/07/simple_message_server_store.png 640w, /wp-content/uploads/2016/07/simple_message_server_store-207x126.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a><br />
Chromeを利用したアプリとしてインストールし、アプリタブから起動して利用します。デスクトップ用のChromeが動く環境（Windows, MaxOS X, Linux, ChromeOS）で動くはずです。</p>

<p>起動すると、 ws://localhost:3001/ でクライアントからの接続を待ち受けます。※実装があまいので時々不安定になります。その場合は[restart]ボタンを押してリセットし、ブラウザもリロードして接続しなおしてください。</p>

<h2>シグナリング処理を変更しよう</h2>

<p>それでは前回の手動シグナリングのコードを、少しずつ変更していきましょう。まずWebSocketで用意したシグナリングサーバーに接続します。JavaScriptに次の処理を追加してください。（URLは使っているポートに合わせて修正してください）
</p><pre class="crayon-plain-tag">let wsUrl = 'ws://localhost:3001/';
  let ws = new WebSocket(wsUrl);
  ws.onopen = function(evt) {
    console.log('ws open()');
  };
  ws.onerror = function(err) {
    console.error('ws onerror() ERR:', err);
  };</pre><p></p>

<p>次に、WebSocketでメッセージを受け取った場合の処理を追加します。
</p><pre class="crayon-plain-tag">ws.onmessage = function(evt) {
    console.log('ws onmessage() data:', evt.data);
    let message = JSON.parse(evt.data);
    if (message.type === 'offer') {
      // -- got offer ---
      console.log('Received offer ...');
      textToReceiveSdp.value = message.sdp;
      let offer = new RTCSessionDescription(message);
      setOffer(offer);
    }
    else if (message.type === 'answer') {
      // --- got answer ---
      console.log('Received answer ...');
      textToReceiveSdp.value = message.sdp;
      let answer = new RTCSessionDescription(message);
      setAnswer(answer);
    }
  };</pre><p> 
JSONテキストからオブジェクトを復元し、typeに応じて前回用意したsetOffer()/setAnswer()を呼び出し、RTCPeerConnectionに渡しています。</p>

<h3>SDPの送信</h3>

<p>Offer/AnswerのSDPの送信も、WebSocket経由で行います。前回要したsendSdp()を次のように変更します。
</p><pre class="crayon-plain-tag">function sendSdp(sessionDescription) {
    console.log('---sending sdp ---');

    textForSendSdp.value = sessionDescription.sdp;
    /*--- テキストエリアをハイライトするのを止める
    textForSendSdp.focus();
    textForSendSdp.select();
    ----*/

    // --- シグナリングサーバーに送る ---
    let message = JSON.stringify(sessionDescription);
    console.log('sending SDP=' + message);
    ws.send(message);
  }</pre><p> 
SDPをJSONテキストに変換してWebSocketでシグナリングサーバーに送信しています。</p>

<h2>実際に動かしてみよう</h2>

<p>シグナリングサーバーを起動して、ChromeかFirefoxのウィンドウを2つ開いて修正したHTMLを読み込んでください。ChromeとFirefoxの間で通信することもできます。</p>

<p>Webサーバーを立てるのが難しい場合は、GitHub Pages にもサンプルを公開しているので、そちらで試すこともできます。その場合でもシグナリングサーバーは自分で用意する必要があるのでご注意ください。</p>

<ul>
<li>GitHub Pages で試す <a href="http://mganeko.github.io/webrtcexpjp/basic2016/ws_signaling_1to1_vanilla.html" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">ws_signaling_1to1_vanilla.html</a>

<ul>
<li>※http://～/ なのでFirefoxのみ</li>
</ul></li>
<li>GitHub でソースを見る <a href="https://github.com/mganeko/webrtcexpjp/blob/master/basic2016/ws_signaling_1to1_vanilla.html" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">ws_signaling_1to1_vanilla.html</a></li>
</ul>

<h4>(1) カメラの取得</h4>

<p>両方のウィンドウで[Start Video]ボタンをクリックします。カメラのアクセスを許可すると、それぞれリアルタイムの映像が表示されます。<br />
<a href="https://html5experts.jp/wp-content/uploads/2016/07/ws_signaling_startvideo.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/07/ws_signaling_startvideo-300x161.png" alt="ws_signaling_startvideo" width="300" height="161" class="alignnone size-medium wp-image-20032" srcset="/wp-content/uploads/2016/07/ws_signaling_startvideo-300x161.png 300w, /wp-content/uploads/2016/07/ws_signaling_startvideo.png 640w, /wp-content/uploads/2016/07/ws_signaling_startvideo-207x111.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a><br /></p>

<h4>(2) 通信開始</h4>

<p>どちらかのウィンドウで[Connect]ボタンを押します。(3)SDP（ICE candidateを含む）が自動で交換され、(4)ビデオ通信が始まります。<br />
<a href="https://html5experts.jp/wp-content/uploads/2016/07/ws_signaling_connect.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/07/ws_signaling_connect-300x162.png" alt="ws_signaling_connect" width="300" height="162" class="alignnone size-medium wp-image-20033" srcset="/wp-content/uploads/2016/07/ws_signaling_connect-300x162.png 300w, /wp-content/uploads/2016/07/ws_signaling_connect.png 640w, /wp-content/uploads/2016/07/ws_signaling_connect-207x112.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<p>手動シグナリングに比べて操作がずっと簡単になりました。これなら実際に利用できそうですね。</p>

<h2>Trickle ICE を使ってみよう</h2>

<p>コピー＆ペーストを手動で行う必要がなくなったので、ICE candidateを発生するたびに交換するTrickle ICE を使ってみましょう。流れはこのような形になります。<br />
<a href="https://html5experts.jp/wp-content/uploads/2016/06/hand2016_trickle.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/hand2016_trickle-300x224.png" alt="hand2016_trickle" width="300" height="224" class="alignnone size-medium wp-image-19863" srcset="/wp-content/uploads/2016/06/hand2016_trickle-300x224.png 300w, /wp-content/uploads/2016/06/hand2016_trickle.png 640w, /wp-content/uploads/2016/06/hand2016_trickle-207x155.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a><br />
すべてのICE candidateが出そろう前にP2P通信が確立する（ことがある）メリットがあります。（※<a href="https://html5experts.jp/mganeko/5349/" target="_blank" data-wpel-link="internal">2014年の記事</a>では「すべてのICE candidateの交換が終わるとP2P通信が始まる」と書いていましたが、これは誤りです）</p>

<h3>SDPをすぐに送信する</h3>

<p>Offer SDP/Answer SDPを生成したら、すぐに相手に送るように変更します。
</p><pre class="crayon-plain-tag">function makeOffer() {
    peerConnection = prepareNewConnection();
    peerConnection.createOffer()
    .then(function (sessionDescription) {
      console.log('createOffer() succsess in promise');
      return peerConnection.setLocalDescription(sessionDescription);
    }).then(function() {
      console.log('setLocalDescription() succsess in promise');

      // -- Trickle ICE の場合は、初期SDPを相手に送る -- 
      sendSdp(peerConnection.localDescription);　// &lt;--- ここを加える

      // -- Vanilla ICE の場合には、まだSDPは送らない --
    }).catch(function(err) {
      console.error(err);
    });
  }

  function makeAnswer() {
    console.log('sending Answer. Creating remote session description...' );
    if (! peerConnection) {
      console.error('peerConnection NOT exist!');
      return;
    }
    
    peerConnection.createAnswer()
    .then(function (sessionDescription) {
      console.log('createAnswer() succsess in promise');
      return peerConnection.setLocalDescription(sessionDescription);
    }).then(function() {
      console.log('setLocalDescription() succsess in promise');

      // -- Trickle ICE の場合は、初期SDPを相手に送る -- 
      sendSdp(peerConnection.localDescription);　// &lt;--- ここを加える

      // -- Vanilla ICE の場合には、まだSDPは送らない --
    }).catch(function(err) {
      console.error(err);
    });
  }</pre><p></p>

<h3>ICE candidateも、すぐに交換する</h3>

<p>ICE candidateを収集した際も、すぐに送るように変更します。</p>

<p></p><pre class="crayon-plain-tag">function prepareNewConnection() {
    // ... 省略 ...

    // --- on get local ICE candidate
    peer.onicecandidate = function (evt) {
      if (evt.candidate) {
        console.log(evt.candidate);

        // Trickle ICE の場合は、ICE candidateを相手に送る
        sendIceCandidate(evt.candidate); // &lt;--- ここを追加する

        // Vanilla ICE の場合には、何もしない
      } else {
        console.log('empty ice event');

        // Trickle ICE の場合は、何もしない
        
        // Vanilla ICE の場合には、ICE candidateを含んだSDPを相手に送る
        //sendSdp(peer.localDescription); // &lt;-- ここをコメントアウトする
      }
    };

    // ... 省略 ....
  }

  function sendIceCandidate(candidate) {
    console.log('---sending ICE candidate ---');
    let obj = { type: 'candidate', ice: candidate };
    let message = JSON.stringify(obj);
    console.log('sending candidate=' + message);
    ws.send(message);
  }</pre><p></p>

<p>合わせてICE candidateをWebSocket経由で受け取った場合の処理も追加しましょう。相手からICE candidateを受け取ったら、その度にRTCPeerConnection.addIceCandidate()で覚えさせます。
</p><pre class="crayon-plain-tag">ws.onmessage = function(evt) {
    console.log('ws onmessage() data:', evt.data);
    let message = JSON.parse(evt.data);
    if (message.type === 'offer') {
      // -- got offer ---
      console.log('Received offer ...');
      textToReceiveSdp.value = message.sdp;
      let offer = new RTCSessionDescription(message);
      setOffer(offer);
    }
    else if (message.type === 'answer') {
      // --- got answer ---
      console.log('Received answer ...');
      textToReceiveSdp.value = message.sdp;
      let answer = new RTCSessionDescription(message);
      setAnswer(answer);
    }
    else if (message.type === 'candidate') { // &lt;--- ここから追加
      // --- got ICE candidate ---
      console.log('Received ICE candidate ...');
      let candidate = new RTCIceCandidate(message.ice);
      console.log(candidate);
      addIceCandidate(candidate);
    }
  };

  function addIceCandidate(candidate) {
    if (peerConnection) {
      peerConnection.addIceCandidate(candidate);
    }
    else {
      console.error('PeerConnection not exist!');
      return;
    }
  }</pre><p> 
さあ、これで修正は完了です。</p>

<h3>Trickle ICEを実行しよう</h3>

<p>手順はVanilla ICEの場合と同じです。シグナリングサーバーを起動して、ChromeかFirefoxのウィンドウを2つ開いて修正したHTMLを読み込んでください。あとは同様に[Start Video]→[Connect]です。</p>

<p>見た目も特に変わりはありません。もしかしたら人によっては早く繋がるのを実感できるかもしれません。</p>

<p>GitHub Pages/GitHubも用意しています。</p>

<ul>
<li>GitHub Pages で試す <a href="http://mganeko.github.io/webrtcexpjp/basic2016/ws_signaling_1to1_trickle.html" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">ws_signaling_1to1_trickle.html</a>

<ul>
<li>※http://～/ なのでFirefoxのみ。シグナリングサーバーは別途localhost上で起動しておく必要があります</li>
</ul></li>
<li>GitHub でソースを見る <a href="https://github.com/mganeko/webrtcexpjp/blob/master/basic2016/ws_signaling_1to1_trickle.html" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">ws_signaling_1to1_trickle.html</a></li>
</ul>

<h2>2台のPC間の通信</h2>

<p>ここまできたら、せっかくなので2台の別々のPCで通信してみたくなります。同じネットワークに属するPC同士ならば通信できるはずです。例として次のような状況を考えてみましょう。</p>

<ul>
<li>IPアドレスが 192.168.0.2 と、 192.168.0.3 の2台のPCがある</li>
<li>前者(192.168.0.2)のポート:8080でWebサーバー、ポート:3001でNode.jsのシグナリングサーバーが動いている
<br /><a href="https://html5experts.jp/wp-content/uploads/2016/07/2pc_firefox.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/07/2pc_firefox-300x227.png" alt="2pc_firefox" width="300" height="227" class="alignnone size-medium wp-image-20049" srcset="/wp-content/uploads/2016/07/2pc_firefox-300x227.png 300w, /wp-content/uploads/2016/07/2pc_firefox.png 640w, /wp-content/uploads/2016/07/2pc_firefox-207x157.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a><br /></li>
</ul>

<p>Firefoxの場合は、接続するURLを変更すれば問題なく動きます。やっかいなのはChromeの場合です。</p>

<ul>
<li>Chromeでは、カメラやマイクにアクセスするためのgetUserMedia()が、原則としてhttp://～では許可されていない/</li>
<li>http://localhost/～ は例外的な扱いで許可されている
<br /><a href="https://html5experts.jp/wp-content/uploads/2016/07/2pc_chrome.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/07/2pc_chrome-300x227.png" alt="2pc_chrome" width="300" height="227" class="alignnone size-medium wp-image-20048" srcset="/wp-content/uploads/2016/07/2pc_chrome-300x227.png 300w, /wp-content/uploads/2016/07/2pc_chrome.png 640w, /wp-content/uploads/2016/07/2pc_chrome-207x157.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a><br /></li>
</ul>

<p>きちんと対処すると、次のような対策が必要です。ちょっと試すにはハードルが高いですよね。</p>

<ul>
<li>証明書を取得して https://～/ でアクセスするように、Webサーバーに設定</li>
<li>合わせて、シグナリングサーバーも wss://～ の暗号化通信を使うように設定</li>
</ul>

<p>そこで実験的に無理やり動かすには、次のような方法があります。Webサーバーとシグナリングサーバーは同一である必要はなく、また異なるWebサーバーでも構わないことを利用しています。
<br /><a href="https://html5experts.jp/wp-content/uploads/2016/07/2pc_chrome_force.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/07/2pc_chrome_force-300x226.png" alt="2pc_chrome_force" width="300" height="226" class="alignnone size-medium wp-image-20050" srcset="/wp-content/uploads/2016/07/2pc_chrome_force-300x226.png 300w, /wp-content/uploads/2016/07/2pc_chrome_force.png 640w, /wp-content/uploads/2016/07/2pc_chrome_force-207x156.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a><br /></p>

<p>お勧めはしませんが、どうしてもやりたい場合の参考としてどうぞ。</p>

<h2>次回は</h2>

<p>今回はNode.jsとWebSocketを使ったシグナリングを実現しました。残念ながら今回の仕組みでは、1対1の通信しか行うことができません。次回はこれを拡張し、複数人で同時に通信できるようにしたいと思います。</p>

<h2>オマケ：WebRTCの仕様の差分のおさらい</h2>

<p>オマケとして、今回のWebRTC再入門2016シリーズで取り上げているWebRTC関連仕様の変更箇所について、おさらいしおきましょう。(2016年6月現在）</p>

<h3>getUserMeida</h3>

<ul>
<li>navigator.mediaDevices.getUserMedia() が新しく用意された

<ul>
<li>旧APIの navigator.getUserMedia()は Firefoxでは非推奨</li>
</ul></li>
<li>ベンダープレフィックスが取れた</li>
<li>コールバックではなくPromiseベースになった</li>
<li>Firefox, Edge で利用可能。Chromeではフラグ指定が必要</li>
</ul>

<h3>ベンダープレフィックスの除去</h3>

<ul>
<li>Firefoxでは、主要なオブジェクトのベンダープレフィックスが取れた。mozプレフィックス付は非推奨に

<ul>
<li>新：RTCPeerConnection, RTCSessionDescription, RTCIceCandidate</li>
<li>旧：mozRTCPeerConnection, mozRTCSessionDescription, mozRTCIceCandidate　（非推奨）</li>
</ul></li>
<li>ただしChromeでは、一部ベンダープレフィックス付のまま

<ul>
<li>プレフィックス有り： webkitRTCPeerConnection</li>
<li>プレフィックス無し： RTCSessionDescription, RTCIceCandidate</li>
</ul></li>
</ul>

<h3>RTCPeerConnection</h3>

<ul>
<li>主要なメソッドがPromiseベースになった

<ul>
<li>createOffer(), createAnswer()</li>
<li>setLocalDescription(), setRemoteDescription()</li>
</ul></li>
<li>メディアストリーム処理の新しいイベントハンドラontrack()が追加、onaddstream()は非推奨

<ul>
<li>Firefoxではサポート済、Chromeでは未サポート</li>
</ul></li>
</ul>

<p><a href="https://www.w3.org/TR/webrtc/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">仕様</a>は常に更新されていますし、ブラウザの実装状況も異なります。最新の情報もご確認ください。</p>
]]></content:encoded>
		
		<series:name><![CDATA[WebRTC入門2016]]></series:name>
	</item>
		<item>
		<title>手動でWebRTCの通信をつなげよう ーWebRTC入門2016</title>
		<link>/mganeko/19814/</link>
		<pubDate>Fri, 01 Jul 2016 02:13:12 +0000</pubDate>
		<dc:creator><![CDATA[がねこまさし]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[WebRTC]]></category>

		<guid isPermaLink="false">/?p=19814</guid>
		<description><![CDATA[連載： WebRTC入門2016 (2)こんにちは！ がねこまさしです。2014年に連載した「WebRTCを使ってみよう！」シリーズを、2016年6月の最新情報に基づき、内容をアップデートして改めてお届けしています。1回...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/webrtc2016/" class="series-380" title="WebRTC入門2016" data-wpel-link="internal">WebRTC入門2016</a> (2)</div><p>こんにちは！ がねこまさしです。2014年に連載した<a target="_blank" href="/series/webrtc-beginner/" data-wpel-link="internal">「WebRTCを使ってみよう！」シリーズ</a>を、2016年6月の最新情報に基づき、内容をアップデートして改めてお届けしています。<a target="_blank" href="/mganeko/19728/" data-wpel-link="internal">1回目はカメラにアクセス</a>してみました。2回目となる今回は、WebRTCの通信の仕組みを実感するために、「手動」でP2P通信をつなげてみましょう。</p>

<h2>WebRTCの通信はどうなっているの？</h2>

<p>WebRTCでは、映像/音声/アプリケーションデータなどをリアルタイムにブラウザ間で送受信することができます。それをつかさどるのが「<strong>RTCPeerConnection</strong>」です。 RTCPeerConnectionには3つの特徴があります。</p>

<ul>
<li>Peer-to-Peer(P2P)の通信 → ブラウザとブラウザの間で直接通信する</li>
<li>UDP/IPを使用 → TCP/IPのようにパケットの到着は保障しないが、オーバーヘッドが少ない</li>
<li>PeerとPeerの間で暗号化通信を行う → P2P通信の前に鍵の交換を行う</li>
</ul>

<p>多少の情報の欠落があっても許容する替わりに、通信のリアルタイム性を重視しています。UDPのポート番号は動的に割り振られ、49152 ～ 65535の範囲が使われるようです。 
<a href="https://html5experts.jp/wp-content/uploads/2014/02/rtcpeerconnection.png" data-wpel-link="internal"><img src="/wp-content/uploads/2014/02/rtcpeerconnection-300x59.png" alt="rtcpeerconnection" width="300" height="59" class="alignnone size-medium wp-image-5185" srcset="/wp-content/uploads/2014/02/rtcpeerconnection-300x59.png 300w, /wp-content/uploads/2014/02/rtcpeerconnection-207x40.png 207w, /wp-content/uploads/2014/02/rtcpeerconnection.png 640w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<h2>P2P通信を確立するまで</h2>

<p>ブラウザ間でP2P通信を行うには、相手のIPアドレスを知らなくてはなりませんし、動的に割り振られるUDPのポート番号も知る必要があります。また、その通信でやり取りできる内容についても、お互い合意しておく必要があります。そのためP2P通信が確立するまでに、WebRTCではいくつかの情報をやり取りしています。</p>

<h3>Session Description Protocol (SDP)</h3>

<p>各ブラウザが通信した内容を示し、テキストで表現されます。例えば次のような情報を含んでいます。</p>

<ul>
<li>通信するメディアの種類（音声、映像）、メディアの形式（コーデック）、アプリケーションデータ</li>
<li>IPアドレス、ポート番号</li>
<li>暗号化の鍵</li>
<li>セッションの属性（名前、識別子、アクティブな時間など）→ WebRTCでは使っていないようです</li>
</ul>

<p><a href="https://html5experts.jp/wp-content/uploads/2014/02/rtcpeer_ip_port.png" data-wpel-link="internal"><img src="/wp-content/uploads/2014/02/rtcpeer_ip_port-300x102.png" alt="rtcpeer_ip_port" width="300" height="102" class="alignnone size-medium wp-image-5190" srcset="/wp-content/uploads/2014/02/rtcpeer_ip_port-300x102.png 300w, /wp-content/uploads/2014/02/rtcpeer_ip_port-207x70.png 207w, /wp-content/uploads/2014/02/rtcpeer_ip_port.png 640w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<h3>ICE Candidate</h3>

<p>P2P通信を行う際にどのような通信経路が使えるかは、お互いのネットワーク環境に依存します。通信経路を定めるための仕組みが「<strong>Interactive Connectivity Establishment</strong> (ICE)」で、その通信経路の候補が「<strong>ICE Candidate</strong>」になります。WebRTCの通信を始める前に、可能性のある候補がリストアップされます。</p>

<ul>
<li>P2Pによる直接通信</li>
<li>NATを通過するためのSTUNサーバーから取得したポートマッピング → 最終的にはP2Pになる</li>
<li>Firefallを越えるための、TURNによるリレーサーバーを介した中継通信</li>
</ul>

<p>候補が見つかったら順次通信を試み、最初につながった経路が採用されます。</p>

<h2>手動シグナリングを実験してみよう</h2>

<p>このように、P2Pを始めるまでの情報のやり取りを「シグナリング」と言います。実は、WebRTCではシグナリングのプロトコルは規定されていません（自由に選べます）。シグナリングを実現するにはWebSocketを使うなど複数の方法がありますが、今回は最も原始的な方法であるコピー＆ペーストを試してみましょう。</p>

<p>ちょっと長いですが、こちらのHTMLをお好きなWebサーバーに配置してください。</p>

<p></p><pre class="crayon-plain-tag">&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
 &lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8"&gt;
 &lt;title&gt;Hand Signaling&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  Hand Signaling 2016&lt;br /&gt;
  &lt;button type="button" onclick="startVideo();"&gt;Start Video&lt;/button&gt;
  &lt;button type="button" onclick="stopVideo();"&gt;Stop Video&lt;/button&gt;
  &amp;nbsp;
  &lt;button type="button" onclick="connect();"&gt;Connect&lt;/button&gt;
  &lt;button type="button" onclick="hangUp();"&gt;Hang Up&lt;/button&gt; 
  &lt;div&gt;
    &lt;video id="local_video" autoplay style="width: 160px; height: 120px; border: 1px solid black;"&gt;&lt;/video&gt;
    &lt;video id="remote_video" autoplay style="width: 160px; height: 120px; border: 1px solid black;"&gt;&lt;/video&gt;
  &lt;/div&gt;
  &lt;p&gt;SDP to send:&lt;br /&gt;
    &lt;textarea id="text_for_send_sdp" rows="5" cols="60" readonly="readonly"&gt;SDP to send&lt;/textarea&gt;
  &lt;/p&gt;
  &lt;p&gt;SDP to receive:&amp;nbsp;
    &lt;button type="button" onclick="onSdpText();"&gt;Receive remote SDP&lt;/button&gt;&lt;br /&gt;
    &lt;textarea id="text_for_receive_sdp" rows="5" cols="60"&gt;&lt;/textarea&gt;
  &lt;/p&gt;
&lt;/body&gt;
&lt;script type="text/javascript"&gt;
  let localVideo = document.getElementById('local_video');
  let remoteVideo = document.getElementById('remote_video');
  let localStream = null;
  let peerConnection = null;
  let textForSendSdp = document.getElementById('text_for_send_sdp');
  let textToReceiveSdp = document.getElementById('text_for_receive_sdp');

  // --- prefix -----
  navigator.getUserMedia  = navigator.getUserMedia    || navigator.webkitGetUserMedia ||
                            navigator.mozGetUserMedia || navigator.msGetUserMedia;
  RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
  RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription;

  // ---------------------- media handling ----------------------- 
  // start local video
  function startVideo() {
    getDeviceStream({video: true, audio: false})
    .then(function (stream) { // success
      localStream = stream;
      playVideo(localVideo, stream);
    }).catch(function (error) { // error
      console.error('getUserMedia error:', error);
      return;
    });
  }

  // stop local video
  function stopVideo() {
    pauseVideo(localVideo);
    stopLocalStream(localStream);
  }

  function stopLocalStream(stream) {
    let tracks = stream.getTracks();
    if (! tracks) {
      console.warn('NO tracks');
      return;
    }
    
    for (let track of tracks) {
      track.stop();
    }
  }
  
  function getDeviceStream(option) {
    if ('getUserMedia' in navigator.mediaDevices) {
      console.log('navigator.mediaDevices.getUserMadia');
      return navigator.mediaDevices.getUserMedia(option);
    }
    else {
      console.log('wrap navigator.getUserMadia with Promise');
      return new Promise(function(resolve, reject){    
        navigator.getUserMedia(option,
          resolve,
          reject
        );
      });      
    }
  }

  function playVideo(element, stream) {
    if ('srcObject' in element) {
      element.srcObject = stream;
    }
    else {
      element.src = window.URL.createObjectURL(stream);
    }
    element.play();
    element.volume = 0;
  }

  function pauseVideo(element) {
    element.pause();
    if ('srcObject' in element) {
      element.srcObject = null;
    }
    else {
      if (element.src &amp;&amp; (element.src !== '') ) {
        window.URL.revokeObjectURL(element.src);
      }
      element.src = '';
    }
  }

  // ----- hand signaling ----
  function onSdpText() {
    let text = textToReceiveSdp.value;
    if (peerConnection) {
      console.log('Received answer text...');
      let answer = new RTCSessionDescription({
        type : 'answer',
        sdp : text,
      });
      setAnswer(answer);
    }
    else {
      console.log('Received offer text...');
      let offer = new RTCSessionDescription({
        type : 'offer',
        sdp : text,
      });
      setOffer(offer);
    }
    textToReceiveSdp.value ='';
  }
 
  function sendSdp(sessionDescription) {
    console.log('---sending sdp ---');
    textForSendSdp.value = sessionDescription.sdp;
    textForSendSdp.focus();
    textForSendSdp.select();
  }

  // ---------------------- connection handling -----------------------
  function prepareNewConnection() {
    let pc_config = {"iceServers":[]};
    let peer = new RTCPeerConnection(pc_config);

    // --- on get remote stream ---
    if ('ontrack' in peer) {
      peer.ontrack = function(event) {
        console.log('-- peer.ontrack()');
        let stream = event.streams[0];
        playVideo(remoteVideo, stream);
      };
    }
    else {
      peer.onaddstream = function(event) {
        console.log('-- peer.onaddstream()');
        let stream = event.stream;
        playVideo(remoteVideo, stream);
      };
    }

    // --- on get local ICE candidate
    peer.onicecandidate = function (evt) {
      if (evt.candidate) {
        console.log(evt.candidate);

        // Trickle ICE の場合は、ICE candidateを相手に送る
        // Vanilla ICE の場合には、何もしない
      } else {
        console.log('empty ice event');

        // Trickle ICE の場合は、何もしない
        // Vanilla ICE の場合には、ICE candidateを含んだSDPを相手に送る
        sendSdp(peer.localDescription);
      }
    };

    
    // -- add local stream --
    if (localStream) {
      console.log('Adding local stream...');
      peer.addStream(localStream);
    }
    else {
      console.warn('no local stream, but continue.');
    }

    return peer;
  }

  function makeOffer() {
    peerConnection = prepareNewConnection();
    peerConnection.createOffer()
    .then(function (sessionDescription) {
      console.log('createOffer() succsess in promise');
      return peerConnection.setLocalDescription(sessionDescription);
    }).then(function() {
      console.log('setLocalDescription() succsess in promise');

      // -- Trickle ICE の場合は、初期SDPを相手に送る -- 
      // -- Vanilla ICE の場合には、まだSDPは送らない --
      //sendSdp(peerConnection.localDescription);
    }).catch(function(err) {
      console.error(err);
    });
  }

  function setOffer(sessionDescription) {
    if (peerConnection) {
      console.error('peerConnection alreay exist!');
    }
    peerConnection = prepareNewConnection();
    peerConnection.setRemoteDescription(sessionDescription)
    .then(function() {
      console.log('setRemoteDescription(offer) succsess in promise');
      makeAnswer();
    }).catch(function(err) {
      console.error('setRemoteDescription(offer) ERROR: ', err);
    });
  }
  
  function makeAnswer() {
    console.log('sending Answer. Creating remote session description...' );
    if (! peerConnection) {
      console.error('peerConnection NOT exist!');
      return;
    }
    
    peerConnection.createAnswer()
    .then(function (sessionDescription) {
      console.log('createAnswer() succsess in promise');
      return peerConnection.setLocalDescription(sessionDescription);
    }).then(function() {
      console.log('setLocalDescription() succsess in promise');

      // -- Trickle ICE の場合は、初期SDPを相手に送る -- 
      // -- Vanilla ICE の場合には、まだSDPは送らない --
      //sendSdp(peerConnection.localDescription);
    }).catch(function(err) {
      console.error(err);
    });
  }

  function setAnswer(sessionDescription) {
    if (! peerConnection) {
      console.error('peerConnection NOT exist!');
      return;
    }

    peerConnection.setRemoteDescription(sessionDescription)
    .then(function() {
      console.log('setRemoteDescription(answer) succsess in promise');
    }).catch(function(err) {
      console.error('setRemoteDescription(answer) ERROR: ', err);
    });
  }
  
  // start PeerConnection
  function connect() {
    if (! peerConnection) {
      console.log('make Offer');
      makeOffer();
    }
    else {
      console.warn('peer already exist.');
    }
  }

  // close PeerConnection
  function hangUp() {
    if (peerConnection) {
      console.log('Hang up.');
      peerConnection.close();
      peerConnection = null;
      pauseVideo(remoteVideo);
    }
    else {
      console.warn('peer NOT exist.');
    }
  }

&lt;/script&gt;
&lt;/html&gt;</pre><p></p>

<p>GitHub Pagesでも公開していますので、すぐに試すことができます。</p>

<ul>
<li>GitHub Pages で試す <a target="_blank" href="https://mganeko.github.io/webrtcexpjp/basic2016/hand_signaling.html" data-wpel-link="external" rel="follow external noopener noreferrer">hand_signaling.html</a> (Chromeもフラグ設定不要）</li>
<li>GitHub でソースを見る <a target="_blank" href="https://github.com/mganeko/webrtcexpjp/blob/master/basic2016/hand_signaling.html" data-wpel-link="external" rel="follow external noopener noreferrer">hand_signaling.html</a></li>
<li>Safari Technology Preview 対応版を試す <a target="_blank" href="https://mganeko.github.io/webrtcexpjp/basic2016/hand_signaling_track.html" data-wpel-link="external" rel="follow external noopener noreferrer">hand_signaling_track.html</a></li>
<li>Safari Technology Preview 対応版のソース <a target="_blank" href="https://github.com/mganeko/webrtcexpjp/blob/master/basic2016/hand_signaling_track.html" data-wpel-link="external" rel="follow external noopener noreferrer">hand_signaling_track.html</a></li>
</ul>

<p>次に、PCにWebカメラを接続してからFirefox 47 またはChrome 51でアクセスしてみてください。（※Chromeの場合はカメラ映像取得の制限があるので、https://～か/ http://localhost/～のWebサーバーが必要になります）残念ながら今回はEdgeでは利用できません。</p>

<p>通信するために2つページを開く必要があります。同一ウィンドウで複数タブを開くよりも、別のウィンドウで横に並べたほうが作業しやすいです。</p>

<h3>接続手順</h3>

<p>接続手順は<a target="_blank" href="/mganeko/5181/" data-wpel-link="internal">2014年のもの</a>よりも簡略化しました。それでも間違えやすいので慎重に操作してくださいね。</p>

<h4>(1) 映像の取得</h4>

<p>両方のウィンドウで[Start Video]ボタンをクリックします。カメラのアクセスを許可すると、それぞれリアルタイムの映像が表示されます。<br />
<a href="https://html5experts.jp/wp-content/uploads/2016/06/hand2016_1.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/hand2016_1-300x137.png" alt="hand2016_1" width="300" height="137" class="alignnone size-medium wp-image-19841" srcset="/wp-content/uploads/2016/06/hand2016_1-300x137.png 300w, /wp-content/uploads/2016/06/hand2016_1.png 640w, /wp-content/uploads/2016/06/hand2016_1-207x94.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<h4>(2) 通信の開始</h4>

<p>左のウィンドウで[Connect]ボタンをクリックしてください。すると[SDP to send:]のテキストエリアに、ドバドバっとSDPの文字列が表示されます。<br />
<a href="https://html5experts.jp/wp-content/uploads/2016/06/hand2016_2.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/hand2016_2-300x133.png" alt="hand2016_2" width="300" height="133" class="alignnone size-medium wp-image-19842" srcset="/wp-content/uploads/2016/06/hand2016_2-300x133.png 300w, /wp-content/uploads/2016/06/hand2016_2.png 640w, /wp-content/uploads/2016/06/hand2016_2-207x92.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<h4>(3)(4) SDPの送信（左→右）</h4>

<p>(3)左の[SDP to send:]の内容をコピーし、(4)右の[SDP to receive:]の下のテキストエリアにペーストします。<br />
<a href="https://html5experts.jp/wp-content/uploads/2016/06/hand2016_3_4.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/hand2016_3_4-300x140.png" alt="hand2016_3_4" width="300" height="140" class="alignnone size-medium wp-image-19844" srcset="/wp-content/uploads/2016/06/hand2016_3_4-300x140.png 300w, /wp-content/uploads/2016/06/hand2016_3_4.png 640w, /wp-content/uploads/2016/06/hand2016_3_4-207x96.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a><br /></p>

<h4>(5) SDPの受信（右）</h4>

<p>右の[Receive remote SDP]ボタンをクリックすると、今度は右のウィンドウの[SDP to send:]のテキストエリアに、ドバドバっとSDPの文字列が表示されます。<br />
<a href="https://html5experts.jp/wp-content/uploads/2016/06/hand2016_5.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/hand2016_5-300x136.png" alt="hand2016_5" width="300" height="136" class="alignnone size-medium wp-image-19847" srcset="/wp-content/uploads/2016/06/hand2016_5-300x136.png 300w, /wp-content/uploads/2016/06/hand2016_5.png 640w, /wp-content/uploads/2016/06/hand2016_5-207x94.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<h4>(6)(7) SDPの返信（左←右）</h4>

<p>さっきと反対に(6)右の[SDP to send:]の内容をコピーし、(7)左の[SDP to receive:]の下のテキストエリアにペーストします。<br />
<a href="https://html5experts.jp/wp-content/uploads/2016/06/hand2016_6_7.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/hand2016_6_7-300x137.png" alt="hand2016_6_7" width="300" height="137" class="alignnone size-medium wp-image-19848" srcset="/wp-content/uploads/2016/06/hand2016_6_7-300x137.png 300w, /wp-content/uploads/2016/06/hand2016_6_7.png 640w, /wp-content/uploads/2016/06/hand2016_6_7-207x94.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<h4>(8) SDPの受信（左）</h4>

<p>左の[Receive remote SDP]ボタンをクリックします。しばらくすると（～数秒）P2P通信が始まり両方のウィンドウに2つ目の動画が表示されるはずです。<br />
<a href="https://html5experts.jp/wp-content/uploads/2016/06/hand2016_8.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/hand2016_8-300x139.png" alt="hand2016_8" width="300" height="139" class="alignnone size-medium wp-image-19849" srcset="/wp-content/uploads/2016/06/hand2016_8-300x139.png 300w, /wp-content/uploads/2016/06/hand2016_8.png 640w, /wp-content/uploads/2016/06/hand2016_8-207x96.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a><br /></p>

<p>上手くいかなかった場合は、コピー範囲が欠けているか、手順が抜けている可能性があります。深呼吸してから両方のブラウザをリロードし、もう一度試してみてください。</p>

<p>それでも通信できない場合は、実はネットワーク環境の問題の可能性があります。この記事の「トラブルシューティング」の章をご覧ください。（あるいはソースのバグや、説明の不備の可能性もあります。何かお気づきの際にはご指摘ください）</p>

<h2>トラブルシューティング</h2>

<p>これまで何度も手動シグナリングを試して/試していただいて、通信ができないケースがありました。当初は原因が分からなかったのですが、その後に判明したケースを説明します。</p>

<h3>外部ネットワークにつながっていない場合</h3>

<p>PCが全くネットワークに接続されていない状態では、カメラ映像の取得に成功しても通信ができません。これはネットワークに接続されていない状態では、通信経路の情報であるICE Candidateが収集できないためです。<br />
例え同一PC内で通信を行う場合にも、外部に接続できる状態で利用する必要があります。</p>

<p>ハンズオン等で手動シグナリングを試してもらうことがあるのですが、長いことこの制約に気が付かず通信できないで悩んでいました。</p>

<h3>Chrome &#8211; Firefox 間での通信</h3>

<p>WebRTCではChrome &#8211; Chrome間や、Firefox &#8211; Firefox 間のように同一種類のブラウザ同士だけでなく、Chrome &#8211; Firefox間でも通信することができます。もちろん手動シグナリングでも同様です。<br />
ところが実際に1台のPCで Firefox &#8211; Chrome 間で手動シグナリングを行おうとすると、カメラ映像の取得で衝突してしまうケースがあります。この場合は次のどちらかをお試しください</p>

<ul>
<li>(a) 2台のカメラをご用意して、ブラウザごとに違うカメラの映像を取得する</li>
<li>(b) 映像は片方のブラウザのみで取得し、そのブラウザから[Connect]で通信を始める

<ul>
<li>→ ※この場合は片方向の映像通信となります</li>
</ul></li>
</ul>

<h2>裏側で起こっていること</h2>

<p>それでは映像通信に成功したところで、その裏側で起きていることを見てみましょう。</p>

<h3>Vanilla ICE と Trickle ICE</h3>

<p>WebRTCのP2P通信を確立するためのシグナリングでは、次の2種類の情報を交換する必要があると説明しました。</p>

<ul>
<li>SDP</li>
<li>ICE candidate</li>
</ul>

<p>ところが今回の手動シグナリングではSDPしか交換していません。いったいぜんたい、ICE candidateの情報はどうなっているのでしょうか？</p>

<p>実はICE Candidateの情報は、今回交換しているSDPの中に含まれています。実際に私のPCで取得したSDPの一部を掲載します。（※IPアドレスは一部マスクしています）</p>

<p></p><pre class="crayon-plain-tag">m=video 58461 UDP/TLS/RTP/SAVPF 100 101 116 117 96 97 98
c=IN IP4 192.168.xxx.xxx
a=rtcp:58465 IN IP4 192.168.xxx.xxx
a=candidate:2999745851 1 udp 2122260223 192.168.xxx.xxx 58461 typ host generation 0 network-id 4
a=candidate:2747735740 1 udp 2122194687 192.168.xxx.xxx 58462 typ host generation 0 network-id 3
a=candidate:1606961068 1 udp 2122129151 10.2.xxx.xxx 58463 typ host generation 0 network-id 2
a=candidate:1435463253 1 udp 2122063615 192.168.xxx.xxx 58464 typ host generation 0 network-id 1
a=candidate:2999745851 2 udp 2122260222 192.168.xxx.xxx 58465 typ host generation 0 network-id 4
a=candidate:2747735740 2 udp 2122194686 192.168.xxx.xxx 58466 typ host generation 0 network-id 3
a=candidate:1606961068 2 udp 2122129150 10.2.xxx.xxx 58467 typ host generation 0 network-id 2
a=candidate:1435463253 2 udp 2122063614 192.168.xxx.xxx 58468 typ host generation 0 network-id 1
a=candidate:4233069003 1 tcp 1518280447 192.168.xxx.xxx 9 typ host tcptype active generation 0 network-id 4
a=candidate:3980714572 1 tcp 1518214911 192.168.xxx.xxx 9 typ host tcptype active generation 0 network-id 3
a=candidate:290175836 1 tcp 1518149375 10.2.xxx.xxx 9 typ host tcptype active generation 0 network-id 2
a=candidate:453808805 1 tcp 1518083839 192.168.xxx.xxx 9 typ host tcptype active generation 0 network-id 1
a=candidate:4233069003 2 tcp 1518280446 192.168.xxx.xxx 9 typ host tcptype active generation 0 network-id 4
a=candidate:3980714572 2 tcp 1518214910 192.168.xxx.xxx 9 typ host tcptype active generation 0 network-id 3
a=candidate:290175836 2 tcp 1518149374 10.2.xxx.xxx 9 typ host tcptype active generation 0 network-id 2
a=candidate:453808805 2 tcp 1518083838 192.168.xxx.xxx 9 typ host tcptype active generation 0 network-id 1
a=ice-ufrag:xxxxxxxxxxxxx
a=ice-pwd:xxxxxxxxxxxx</pre><p> 
a=candidate: で始まる行がICE candidateになります。（※仮想化ソフトを入れている影響で複数のネットワークが候補になっています）。
SDPを最初に取得したときにはICE candidateの行は含まれず、その後ICE candidateが収集されるにしたがって、SDPの中に追加されます。<br />
今回は全てのICE candidateが出そろった後に、SDPとまとめて交換しています。このような方式を <strong>&#8220;Vanilla ICE&#8221;</strong> と呼びます。<br />
<a href="https://html5experts.jp/wp-content/uploads/2016/06/hand2016_vanilla.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/hand2016_vanilla-300x225.png" alt="hand2016_vanilla" width="300" height="225" class="alignnone size-medium wp-image-19861" srcset="/wp-content/uploads/2016/06/hand2016_vanilla-300x225.png 300w, /wp-content/uploads/2016/06/hand2016_vanilla.png 640w, /wp-content/uploads/2016/06/hand2016_vanilla-207x156.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<p>これに対して、初期のSDPを交換し、その後ICE Candidateを順次交換する方式を <strong>&#8220;Trickle ICE&#8221;</strong> と呼びます。すべてのICE candidateを交換し終わる前にP2P通信が始まることがあるので、Trickle ICEの方が一般的に早く接続が確立します。<br />
<a href="https://html5experts.jp/wp-content/uploads/2016/06/hand2016_trickle.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/hand2016_trickle-300x224.png" alt="hand2016_trickle" width="300" height="224" class="alignnone size-medium wp-image-19863" srcset="/wp-content/uploads/2016/06/hand2016_trickle-300x224.png 300w, /wp-content/uploads/2016/06/hand2016_trickle.png 640w, /wp-content/uploads/2016/06/hand2016_trickle-207x155.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<h3>Offer と Answer</h3>

<p>SDPには通信を始める側(Offer)と、通信を受け入れる側(Answer)があります。必ずOffer → Answerの順番でやりとりする必要があります。</p>

<h2>ソースコードを追いかけてみよう</h2>

<h4>Offer SDPの生成</h4>

<p>それでは、SDP(+ ICE candidate)のやり取りをソースコードで見てみましょう。まずは[Connect]ボタンを押してSDPを生成するところまでです。（ソースコードは抜粋しています）
</p><pre class="crayon-plain-tag">// start PeerConnection
  function connect() {
      makeOffer();
  }

  // Offer SDPを生成する
  function makeOffer() {
    peerConnection = prepareNewConnection(); // RTCPeerConnectionを生成し、必要なメッセージハンドラを設定

    peerConnection.createOffer()
    .then(function (sessionDescription) {
      return peerConnection.setLocalDescription(sessionDescription);
    }).then(function() {
      // -- Trickle ICE の場合は、初期SDPを相手に送る -- 
      // -- Vanilla ICE の場合には、まだSDPは送らない --
      //sendSdp(peerConnection.localDescription);  &lt;-- Vanilla ICEなので、まだ送らない
    }).catch(function(err) {
      console.error(err);
    });
  }</pre><p> 
発信側で[Connect]ボタンをクリックすると、次の処理が行われます。</p>

<ul>
<li>RTCPeerConnection のオブジェクトを生成</li>
<li>RTCPeerConnection.createOffer() で Offer SDPを生成</li>
<li>生成したOffer SDPを、RTCPeerConnection.setLocalDescription()で覚える</li>
<li>Trickle ICEの場合はすぐにSDPを送信するが、今回は送信しない</li>
</ul>

<p>createOffer(), setLocalDescription()は非同期で処理が行われます。従来はコールバックで後続処理を記述していましたが、現在はPromiseを返すので、then()の中に処理を記述します。<br />
※<a target="_blank" href="/mganeko/5181/" data-wpel-link="internal">2014年の記事では</a>setLocalDescription()が非同期であることを意識しおらず、誤った記述になっていました。</p>

<h4>ICE candidateの収集</h4>

<p>次は ICE candidateの収集です。ICE candidateの収集も非同期に行われるため、RTCPeerConnectionのイベントハンドラで行います。
</p><pre class="crayon-plain-tag">function prepareNewConnection() {
    let pc_config = {"iceServers":[]};
    let peer = new RTCPeerConnection(pc_config);

    // --- on get local ICE candidate
    peer.onicecandidate = function (evt) {
      if (evt.candidate) { // ICE candidate が収集された場合
        console.log(evt.candidate);

        // Trickle ICE の場合は、ICE candidateを相手に送る
        // Vanilla ICE の場合には、何もしない
      } else { // ICE candidateの収集が完了した場合
        console.log('empty ice event');

        // Trickle ICE の場合は、何もしない
        // Vanilla ICE の場合には、ICE candidateを含んだSDPを相手に送る
        sendSdp(peer.localDescription);
      }
    };

    // ... 省略 ....

    // 通信対象の映像/音声ストリームを追加する
    if (localStream) {
      console.log('Adding local stream...');
      peer.addStream(localStream);
    }


    return peer;
  }</pre><p> 
今回のコードではprepareNewConnection()の中でRTCPeerConnectionオブジェクトを生成し、各種イベントハンドラを設定しています。ICE candidateのためRTCPeerConnection.onicecandidateにイベントハンドラを記述しています。このイベントは複数回発生します。<br />
全てのICE candidateを収集し終わると空のイベントが渡ってきます。このタイミングで最終的なSDPを相手に送信します。今回の手動シグナリングではsendSdp()の中でテキストエリアに表示しています。</p>

<h4>Offser SDPの受信</h4>

<p>応答側にOffer SDPをペーストして[Receive remote SDP]ボタンをクリックすると、onSdpText() → setOffer() と呼び出されます。
</p><pre class="crayon-plain-tag">function onSdpText() {
    let text = textToReceiveSdp.value;
    if (peerConnection) { // Answerの場合
      // ... 省略 ...
    }
    else { // Offerの場合
      let offer = new RTCSessionDescription({
        type : 'offer',
        sdp : text,
      });
      setOffer(offer);
    }
    textToReceiveSdp.value ='';
  }</pre><p></p>

<p>さらに setOffer()の中では次の処理が行われています。</p>

<ul>
<li>PeerConnectionのオブジェクトを生成</li>
<li>受け取ったOffer SDPを setRemoteDescription()で覚える。Promiseを使った非同期処理を行う</li>
<li>成功したら makeAnswer()の中でAnswer SDPを生成
<pre class="crayon-plain-tag">function setOffer(sessionDescription) {
    peerConnection = prepareNewConnection();
    peerConnection.setRemoteDescription(sessionDescription)
    .then(function() {
      makeAnswer();
    }).catch(function(err) {
      console.error('setRemoteDescription(offer) ERROR: ', err);
    });
  }</pre> </li>
</ul>

<h4>Answer SDPの生成→送信</h4>

<p>makeAnswer()の中ではOfferの時と同様な処理が行われます。
</p><pre class="crayon-plain-tag">function makeAnswer() {   
    peerConnection.createAnswer()
    .then(function (sessionDescription) {
      return peerConnection.setLocalDescription(sessionDescription);
    }).then(function() {
      // -- Trickle ICE の場合は、初期SDPを相手に送る -- 
      // -- Vanilla ICE の場合には、まだSDPは送らない --
      //sendSdp(peerConnection.localDescription);
    }).catch(function(err) {
      console.error(err);
    });
  }</pre><p></p>

<ul>
<li>RTCPeerConnection.createAnswer() で Answer SDPを生成</li>
<li>生成したAnswer SDPを、RTCPeerConnection.setLocalDescription()で覚える。Promiseを使った非同期処理を行う</li>
<li>Trickle ICEの場合はすぐにSDPを送信するが、今回は送信しない</li>
</ul>

<p>この後 RTCPeerConnection.onicecandidate()でICE candidateを収集し、すべて揃ったらsendSdp()でOffer側に送り返します。</p>

<h4>Answer SDPの受信</h4>

<p>発信側にAnser SDPをペーストして[Receive remote SDP]ボタンをクリックすると、onSdpText() → setAnswer() と呼び出されます。
</p><pre class="crayon-plain-tag">function onSdpText() {
    let text = textToReceiveSdp.value;
    if (peerConnection) { // Answerの場合
      let answer = new RTCSessionDescription({
        type : 'answer',
        sdp : text,
      });
      setAnswer(answer);
    }
    else { // Offerの場合
      // ... 省略 ...
    }
    textToReceiveSdp.value ='';
  }

  function setAnswer(sessionDescription) {
    peerConnection.setRemoteDescription(sessionDescription)
    .then(function() {
      console.log('setRemoteDescription(answer) succsess in promise');
    }).catch(function(err) {
      console.error('setRemoteDescription(answer) ERROR: ', err);
    });
  }</pre><p> 
setAnswer()の中ではRTCPeerConnection.setRemoteDescription()で受け取ったSDPを覚えます。</p>

<h4>映像/音声の送受信</h4>

<p>PeerConnectionのオブジェクトを生成した際に、送信する映像/音声ストリームをRTCPeerConnection.addStream()で指定しておきます。
</p><pre class="crayon-plain-tag">function prepareNewConnection() {
    let pc_config = {"iceServers":[]};
    let peer = new RTCPeerConnection(pc_config);

    // ... 省略 ...

    // -- add local stream --
    if (localStream) {
      peer.addStream(localStream);
    }

    return peer;
  }</pre><p></p>

<p>SDPの交換が終わると、P2P通信に相手の映像/音声が含まれていればイベントが発生します。従来はRTCPeerConnection.onaddstream() にハンドラを記述していましたが、新しいイベントが策定されRTCPeerConnection.ontrack() にハンドラを記述するようになっています。Firefoxはontrack()がすでに使えるようになっていて、onaddstream()は非推奨になっています。（Chromeは未対応です）</p>

<p></p><pre class="crayon-plain-tag">function prepareNewConnection() {
    let pc_config = {"iceServers":[]};
    let peer = new RTCPeerConnection(pc_config);

    // --- on get remote stream ---
    if ('ontrack' in peer) {
      peer.ontrack = function(event) {
        let stream = event.streams[0];
        playVideo(remoteVideo, stream);
      };
    }
    else {
      peer.onaddstream = function(event) {
        let stream = event.stream;
        playVideo(remoteVideo, stream);
      };
    }

    // ... 省略 ....
  }</pre><p> 
以上で主要な処理の解説は終わりです。</p>

<h2>次回は</h2>

<p>今回は手動で情報交換を行い、原始的なビデオチャットを動かしてみました。P2P通信が確立するまでの動きを実感していただけたのではないでしょうか？</p>

<p>実際の利用場面では手動シグナリングなんかやってらません。次回はシグナリングサーバーを使って、通信を行ってみたいと思います。</p>
]]></content:encoded>
		
		<series:name><![CDATA[WebRTC入門2016]]></series:name>
	</item>
		<item>
		<title>カメラを使ってみよう ーWebRTC入門2016</title>
		<link>/mganeko/19728/</link>
		<pubDate>Fri, 24 Jun 2016 00:00:28 +0000</pubDate>
		<dc:creator><![CDATA[がねこまさし]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[WebRTC]]></category>
		<category><![CDATA[カメラ]]></category>

		<guid isPermaLink="false">/?p=19728</guid>
		<description><![CDATA[連載： WebRTC入門2016 (1)こんにちは！ がねこまさしです。2014年に連載した「WebRTCを使ってみよう！」シリーズですが、内容がすっかり古くなってしまいました。そこで2016年6月の最新情報に基づき、内...]]></description>
				<content:encoded><![CDATA[<div class="seriesmeta">連載： <a href="https://html5experts.jp/series/webrtc2016/" class="series-380" title="WebRTC入門2016" data-wpel-link="internal">WebRTC入門2016</a> (1)</div><p>こんにちは！ がねこまさしです。2014年に連載した<a href="https://html5experts.jp/series/webrtc-beginner/" target="_blank" data-wpel-link="internal">「WebRTCを使ってみよう！」シリーズ</a>ですが、内容がすっかり古くなってしまいました。そこで2016年6月の最新情報に基づき、内容をアップデートして改めてお届けしたいと思います。</p>

<h2>WebRTCとは？</h2>

<p>WebRTCとは”Web Real-Time Communication”の略で、Webブラウザ上でビデオ/オーディオの通信や、データ通信を行うための規格です。HTML5で新しく策定されたもので、複数の技術の連携で成り立っています。 ちなみに策定には複数の団体が絡んでいています。</p>

<ul>
<li>API → <a href="https://www.w3.org/TR/webrtc/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">W3C</a></li>
<li>ビデオ/オーディオのコーデック → <a href="http://tools.ietf.org/wg/rtcweb/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">IETF</a></li>
</ul>

<p>APIの策定作業はWebRTC 1.0に向けて大詰めに入っています。またより詳細な低レベルのAPIを定義している<a href="http://ortc.org/wp-content/uploads/2016/05/ortc.html" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">ORTC</a>も登場し、将来の統合に向けた動きも始まっています。</p>

<p>コーデックの選定では、ブラウザがサポートしなければならないビデオコーデックに、既にデファクトスタンダードともいえるH.264が加わりました。また、Googleが開発しているオープンソースのビデオコーデック「VP9」をサポートするブラウザも増えています。</p>

<h2>WebRTCで何ができるの？</h2>

<p>WebRTCは厳密に言うとビデオ/オーディオ/データ通信を行うための仕組みですが、他にも関連が深い技術があります。この連載では3つをまとめてWebRTC（とその仲間たち）ということで扱います。</p>

<ul>
<li>カメラ、マイクといったデバイスへのアクセスする &#8230; <a href="https://www.w3.org/TR/mediacapture-streams/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">Media Capture and Streams</a></li>
<li>ビデオ/オーディオ/データ通信を行う &#8230; <a href="https://www.w3.org/TR/webrtc/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">WebRTC 1.0: Real-time Communication Between Browsers</a></li>
<li>ビデオ/オーディオの録画/録音を行う &#8230; <a href="https://www.w3.org/TR/mediastream-recording/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">MediaStream Recording</a></li>
</ul>

<p>この3兄弟に、HTML5の様々な要素を組み合わせて活用することができます。</p>

<ul>
<li>JavaScript（大前提）</li>
<li>videoタグ、audioタグ</li>
<li>CSS3</li>
<li>Canvas</li>
<li>WebGL</li>
<li>Web Audio API</li>
<li>WebSocket</li>
</ul>

<h2>新しいAPIでカメラを使ってみよう</h2>

<p>カメラやマイクと言ったデバイスにアクセスするには、従来は<code>navigator.getUserMedia()</code>というAPIを使いました。2016年6月現在では、これに代わる新しいAPIが登場しています。</p>

<ul>
<li><code>navigator.mediaDevices.getUserMedia()</code> になった</li>
<li>ベンダープレフィックスが取れた</li>
<li>コールバックではなく<code>Promise</code>ベースになった</li>
</ul>

<p>またデスクトップブラウザの場合は次のブラウザで利用することができます。</p>

<ul>
<li>Firefox 47 &#8230; 利用可能</li>
<li>Chrome 51 &#8230; 利用する場合にはフラグ設定が必要

<ul>
<li>chrome://flagsというURLを開く</li>
<li>「試験運用版のウェブ プラットフォームの機能 #enable-experimental-web-platform-features」を有効に</li>
<li>Chromeを再起動</li>
</ul></li>
<li>Edge 25 &#8230; Windows 10 のEdgeでも利用可能</li>
</ul>

<h3>サンプルコード</h3>

<p>それではカメラから映像を取得してみましょう。サンプルコードはこちらです。
</p><pre class="crayon-plain-tag">&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
 &lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8"&gt;
 &lt;title&gt;Camera with mediaDevice&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;button onclick="startVideo()"&gt;Start&lt;/button&gt;
  &lt;br /&gt;
  &lt;video id="local_video" autoplay style="width: 320px; height: 240px; border: 1px solid black;"&gt;&lt;/video&gt;
&lt;/body&gt;
&lt;script type="text/javascript"&gt;
  let localVideo = document.getElementById('local_video');
  let localStream;
  
  // start local video
  function startVideo() {
    navigator.mediaDevices.getUserMedia({video: true, audio: false})
    .then(function (stream) { // success
      localStream = stream;
      localVideo.src = window.URL.createObjectURL(localStream);
    }).catch(function (error) { // error
      console.error('mediaDevice.getUserMedia() error:', error);
      return;
    });
  }
&lt;/script&gt;
&lt;/html&gt;</pre><p></p>

<p>このHTMLファイルに、（Webサーバ経由で）ブラウザからアクセスしてみてください。GitHub Pagesでも公開していますので、すぐに試すことができます。</p>

<ul>
<li>GitHub Pages で試す <a target="_blank" href="https://mganeko.github.io/webrtcexpjp/basic2016/camera_new.html" data-wpel-link="external" rel="follow external noopener noreferrer">camera_new.html</a>（※Chromeの場合は、上記のフラグ設定が必要です）</li>
<li>GitHub でソースを見る <a target="_blank" href="https://github.com/mganeko/webrtcexpjp/blob/master/basic2016/camera_new.html" data-wpel-link="external" rel="follow external noopener noreferrer">camera_new.html</a></li>
</ul>

<p>ブラウザで[Start]ボタンをクリックすると、カメラへのアクセスの許可を求めるダイアログが表示されますので、許可してください。<br />
<strong>Firefoxの場合</strong>
<a href="https://html5experts.jp/wp-content/uploads/2016/06/getusermedia_ff.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/getusermedia_ff-300x144.png" alt="getusermedia_ff" width="300" height="144" class="alignnone size-medium wp-image-19758" srcset="/wp-content/uploads/2016/06/getusermedia_ff-300x144.png 300w, /wp-content/uploads/2016/06/getusermedia_ff-207x100.png 207w, /wp-content/uploads/2016/06/getusermedia_ff.png 322w" sizes="(max-width: 300px) 100vw, 300px" /></a><br /></p>

<p><strong>Chromeの場合</strong>
<a href="https://html5experts.jp/wp-content/uploads/2016/06/getusermedia_chrome.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/getusermedia_chrome-300x112.png" alt="getusermedia_chrome" width="300" height="112" class="alignnone size-medium wp-image-19759" srcset="/wp-content/uploads/2016/06/getusermedia_chrome-300x112.png 300w, /wp-content/uploads/2016/06/getusermedia_chrome-207x77.png 207w, /wp-content/uploads/2016/06/getusermedia_chrome.png 354w" sizes="(max-width: 300px) 100vw, 300px" /></a><br /></p>

<p><strong>Edgeの場合</strong><br />
<a href="https://html5experts.jp/wp-content/uploads/2016/06/getusermedia_edge.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/getusermedia_edge-300x45.png" alt="getusermedia_edge" width="300" height="45" class="alignnone size-medium wp-image-19789" srcset="/wp-content/uploads/2016/06/getusermedia_edge-300x45.png 300w, /wp-content/uploads/2016/06/getusermedia_edge.png 640w, /wp-content/uploads/2016/06/getusermedia_edge-207x31.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a><br />
ウィンドウの下の部分にこのように表示されます。他のブラウザと違うので、ちょっと気が付きにくいかもしれません。</p>

<p>すると、このようにカメラの映像が表示されるはずです。
<a href="https://html5experts.jp/wp-content/uploads/2016/06/camera_ff.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/camera_ff-300x247.png" alt="camera_ff" width="300" height="247" class="alignnone size-medium wp-image-19768" srcset="/wp-content/uploads/2016/06/camera_ff-300x247.png 300w, /wp-content/uploads/2016/06/camera_ff-207x171.png 207w, /wp-content/uploads/2016/06/camera_ff.png 428w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<h3>HTMLファイルの置き場所</h3>

<p>カメラやマイクにアクセスするには、サンプルのHTMLファイルをWebサーバーに配置する必要がありますが、実はブラウザによって条件が異なります。</p>

<ul>
<li>Chromeの場合 &#8230; 原則 https://～/ のみ。例外として http://localhost/～ でも利用可能</li>
<li>Firefoxの場合 &#8230; https://～/ および http://～/ の両方で利用可能。さらに file://～ でも利用可能</li>
<li>Edgeの場合 &#8230; https://～/ および http://～/ の両方で利用可能。さらに file://～ でも利用可能</li>
</ul>

<p>FirefoxやEdgeの場合は、ローカルのHTMLファイルを直接読み込んでも、利用可能です。</p>

<h3>トラブルシューティング</h3>

<p>カメラのアクセスの許可を求められた際に「常に拒否」すると、そのサイトからはカメラの映像を取得することができなくなります。その場合は明示的に再許可してあげる必要があります。</p>

<p><strong>Firefoxの場合</strong><a href="https://html5experts.jp/wp-content/uploads/2016/06/getusermedia_ff_ng.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/getusermedia_ff_ng-300x204.png" alt="getusermedia_ff_ng" width="300" height="204" class="alignnone size-medium wp-image-19761" srcset="/wp-content/uploads/2016/06/getusermedia_ff_ng-300x204.png 300w, /wp-content/uploads/2016/06/getusermedia_ff_ng-207x141.png 207w, /wp-content/uploads/2016/06/getusermedia_ff_ng.png 403w" sizes="(max-width: 300px) 100vw, 300px" /></a>で「許可」または「毎回確認する」を選択</p>

<p><strong>Chromeの場合</strong><a href="https://html5experts.jp/wp-content/uploads/2016/06/getusermedia_chrome_ng.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/getusermedia_chrome_ng-300x151.png" alt="getusermedia_chrome_ng" width="300" height="151" class="alignnone size-medium wp-image-19762" srcset="/wp-content/uploads/2016/06/getusermedia_chrome_ng-300x151.png 300w, /wp-content/uploads/2016/06/getusermedia_chrome_ng-207x104.png 207w, /wp-content/uploads/2016/06/getusermedia_chrome_ng.png 471w" sizes="(max-width: 300px) 100vw, 300px" /></a>「常に許可する」を選択</p>

<p><strong>Edgeの場合</strong><br />
※いまのところ「常に拒否」することはできないようなので、このケースは発生しません。</p>

<h2>新旧 getUserMedia() をラップしてみると</h2>

<p>新しいAPIである navigator.mediaDevices.getUserMedia()は、まだChromeではデフォルトの状態では使えません。これは、引数で渡すオプションの指定方法が、まだ新しい書き方に沿っていないことが原因です。
</p><pre class="crayon-plain-tag">let deviceID = getSelectedIDsomehow(); // デバイスIDを指定する

// Firefoxの場合
let constraints = {
  audio: false,
  video: { 
   deviceId: {exact: deviceID}
  }
};

// Chromeの場合
let constraintsForChrome = {
  audio: false,
  video: {
    optional: [{sourceId: deviceID}]
  }
};</pre><p></p>

<p>そのため、Chromeではしばらく新しいAPIはデフォルトでは使えない状態が続くと思われます。そこで古いAPIをPromise型にラップする例を用意してみました。（オプション指定の違いは吸収できていません）
</p><pre class="crayon-plain-tag">// --- prefix -----
  navigator.getUserMedia  = navigator.getUserMedia    || navigator.webkitGetUserMedia ||
                            navigator.mozGetUserMedia || navigator.msGetUserMedia;

  // ---- 新旧APIをPromiseでラップする ----
  function getDeviceStream(option) {
    if ('getUserMedia' in navigator.mediaDevices) {
      console.log('navigator.mediaDevices.getUserMadia');
      return navigator.mediaDevices.getUserMedia(option);
    }
    else {
      console.log('wrap navigator.getUserMadia with Promise');
      return new Promise(function(resolve, reject){    
        navigator.getUserMedia(option,
          resolve,
          reject
        );
      });      
    }
  }

  // 利用例
  function startVideo() {
    getDeviceStream({video: true, audio: false})
    .then(function (stream) { // success
      localStream = stream;
      localVideo.src = window.URL.createObjectURL(localStream);
    }).catch(function (error) { // error
      console.error('getUserMedia error:', error);
      return;
    });
  }</pre><p> 
これを使ってカメラ映像を取得する例もご用意しました。Chrome 51でもデフォルトのままで利用できます。</p>

<ul>
<li>GitHub Pages で試す <a target="_blank" href="https://mganeko.github.io/webrtcexpjp/basic2016/camera_old_new.html" data-wpel-link="external" rel="follow external noopener noreferrer">camera_old_new.html</a></li>
<li>GitHub でソースを見る <a target="_blank" href="https://github.com/mganeko/webrtcexpjp/blob/master/basic2016/camera_old_new.html" data-wpel-link="external" rel="follow external noopener noreferrer">camera_old_new.html</a></li>
</ul>

<p>様々なWebRTC関連のブラウザの違いを吸収する <code>adapter.js</code> が <a href="https://github.com/webrtc/adapter" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">本家のGitHub</a>で公開されています。より便利に使いたい場合は、<a href="http://webrtc.github.io/adapter/adapter-latest.js" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">そちらを</a>ご利用ください。</p>

<h2>CSS3と組み合わせてみよう</h2>

<p>WebRTCは他の要素と組み合わせて使うことができると書きました。実際にCSS3と組み合わせてみましょう。</p>

<p></p><pre class="crayon-plain-tag">&lt;!-- 通常 --&gt;
  &lt;video id="local_video" autoplay style="width: 320px; height: 240px; border: 1px solid black;"&gt;&lt;/video&gt;

  &lt;!-- 左右反転 --&gt;
  &lt;video id="flip_video" autoplay style="transform: scaleX(-1); width: 320px; height: 240px; border: 1px solid black;"&gt;&lt;/video&gt;

  &lt;!-- 角丸 --&gt;
  &lt;video id="round_video" autoplay style="border-radius: 80px 80px 80px 80px; width: 320px; height: 240px; border: 1px solid black;"&gt;&lt;/video&gt;

  &lt;!-- セピア --&gt;
  &lt;video id="filter_video" autoplay style="filter: sepia(100%); -webkit-filter: sepia(100%); width: 320px; height: 240px; border: 1px solid black;"&gt;&lt;/video&gt;</pre><p> 
ほかにも様々なバリエーションが考えられます。ぜひ自分でもいろいろ試してみてください。</p>

<p>また、CSS3アニメーションを使うこともできます。例えばこんな感じです。
</p><pre class="crayon-plain-tag">&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
 &lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8"&gt;
 &lt;title&gt;Camera with mediaDevice&lt;/title&gt;
 &lt;style type="text/css"&gt;
   #rotate_video
   {
    animation-duration:4s;
    animation-iteration-count:infinite;
    animation-timing-function:linear;
    animation-name:rotate360;     
   }

   #shake_video
   {
    animation-duration:0.5s;
    animation-iteration-count:infinite;
    animation-timing-function:ease-in-out;
    animation-name:shake;     
   }
   
   @keyframes rotate360
   {
    0%{transform:rotate(0deg);}
    100%{transform:rotate(360deg);}
   }

   @keyframes shake
   {
    0%{transform:rotate(-20deg);}
    50%{transform:rotate(20deg);}
    100%{transform:rotate(-20deg);}
   }
  &lt;/style&gt; 
&lt;/head&gt;
&lt;body&gt;
  Camera with mediaDevice.getUserMedia()&lt;br /&gt;
  &lt;button onclick="startVideo()"&gt;Start&lt;/button&gt;
  &lt;br /&gt;
  &lt;!-- 通常 --&gt;
  &lt;video id="local_video" autoplay style="width: 320px; height: 240px; border: 1px solid black;"&gt;&lt;/video&gt;
  &lt;br /&gt;

  &lt;!-- 回転アニメーション --&gt;
  &lt;video id="rotate_video" autoplay style="width: 320px; height: 240px; border: 1px solid black;"&gt;&lt;/video&gt;

  &lt;!-- 振動アニメーション --&gt;
  &lt;video id="shake_video" autoplay style="width: 320px; height: 240px; border: 1px solid black;"&gt;&lt;/video&gt;
  
&lt;/body&gt;
&lt;script type="text/javascript"&gt;
  // --- prefix -----
  navigator.getUserMedia  = navigator.getUserMedia    || navigator.webkitGetUserMedia ||
                            navigator.mozGetUserMedia || navigator.msGetUserMedia;

  let localVideo = document.getElementById('local_video');
  let localStream;
  
  // start local video
  function startVideo() {
    getDeviceStream({video: true, audio: false})
    .then(function (stream) { // success
      localStream = stream;
      localVideo.src = window.URL.createObjectURL(localStream);
	  
      document.getElementById('rotate_video').src = localVideo.src;
      document.getElementById('shake_video').src = localVideo.src;
    }).catch(function (error) { // error
      console.error('mediaDevice.getUserMedia() error:', error);
      return;
    });
  }

  // ---- 新旧APIをPromiseでラップする ----
  function getDeviceStream(option) {
    if ('getUserMedia' in navigator.mediaDevices) {
      console.log('navigator.mediaDevices.getUserMadia');
      return navigator.mediaDevices.getUserMedia(option);
    }
    else {
      console.log('wrap navigator.getUserMadia with Promise');
      return new Promise(function(resolve, reject){    
        navigator.getUserMedia(option,
          resolve,
          reject
        );
      });      
    }
  }
&lt;/script&gt;
&lt;/html&gt;</pre><p> 
<a href="https://html5experts.jp/wp-content/uploads/2016/06/camera_css_animation.png" data-wpel-link="internal"><img src="/wp-content/uploads/2016/06/camera_css_animation-300x123.png" alt="camera_css_animation" width="300" height="123" class="alignnone size-medium wp-image-19778" srcset="/wp-content/uploads/2016/06/camera_css_animation-300x123.png 300w, /wp-content/uploads/2016/06/camera_css_animation.png 640w, /wp-content/uploads/2016/06/camera_css_animation-207x85.png 207w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>

<p>CSS3と組み合わせたサンプルも、GitHub Pagesでも公開していますので、試してみてください。（ラップ関数を使っているので、デフォルトのChrome 51でも利用できます）</p>

<ul>
<li>GitHub Pages で試す <a target="_blank" href="https://mganeko.github.io/webrtcexpjp/basic2016/camera_css_wrap.html" data-wpel-link="external" rel="follow external noopener noreferrer">camera_css_wrap.html</a></li>
<li>GitHub でソースを見る <a target="_blank" href="https://github.com/mganeko/webrtcexpjp/blob/master/basic2016/camera_css_wrap.html" data-wpel-link="external" rel="follow external noopener noreferrer">camera_css_wrap.html</a></li>
</ul>

<h2>次回は</h2>

<p>今回は新しいAPIを使って、カメラの映像を取得してみました。次回はWebRTCによる通信を手動で接続してみます。</p>
]]></content:encoded>
		
		<series:name><![CDATA[WebRTC入門2016]]></series:name>
	</item>
		<item>
		<title>エキスパートたちが語り尽くす、WebRTCの「つらみ」ーWebRTC Conferenceパネルディスカッションレポート</title>
		<link>/mganeko/18386/</link>
		<pubDate>Fri, 18 Mar 2016 00:00:41 +0000</pubDate>
		<dc:creator><![CDATA[がねこまさし]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[システム開発]]></category>
		<category><![CDATA[SkyWay]]></category>
		<category><![CDATA[WebRTC]]></category>

		<guid isPermaLink="false">/?p=18386</guid>
		<description><![CDATA[こんにちは、がねこです。この記事では先日開催された「WebRTC Conference Japan 2016」から、2日目に行われたパネル・ディスカッション「ここがつらいよWebRTC &#8211; WebRTC開発の...]]></description>
				<content:encoded><![CDATA[<p><style>
.post-detail-contents p {
  text-indent: 0;
  clear: left;
}
b.speaker {
  background-size: 48px;
  display: inline-block;
  width: 48px;
  height: 18px;
  float: left;
  padding-right: 8px;
  padding-top: 48px;
  text-align: center;
  margin-bottom: 4px;
  font-weight: normal;
  font-size: small;
}
b.speaker.ganeko {
  background: url('/wp-content/uploads/2016/03/ganeko.jpg') no-repeat;
}
b.speaker.minamoto {
  background: url('/wp-content/uploads/2016/03/minamoto.jpg') no-repeat;
}
b.speaker.ago {
  background: url('/wp-content/uploads/2016/03/ago.jpg') no-repeat;
}
b.speaker.alen {
  background: url('/wp-content/uploads/2016/03/alen.jpg') no-repeat;
}</p>

<p>.post-detail-contents p { text-indent:0; } #contents #post-detail .block-contents img[width="70"] { display: inline-block; float: left; margin-right: 1em; width: 48px; } #contents #post-detail .block-contents p { overflow: hidden; } #contents #post-detail .block-contents p strong { font-weight: bold; } #contents #post-detail .block-contents p > br { display: none; } .post-detail-contents p > span { display: block; min-height: 48px; }</p>

<p></style></p>

<p>こんにちは、がねこです。この記事では先日開催された「<a href="http://webrtcconference.jp/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">WebRTC Conference Japan 2016</a>」から、2日目に行われたパネル・ディスカッション「ここがつらいよWebRTC &#8211; WebRTC開発の落とし穴」の内容をご紹介します。</p>

<p><img src="/wp-content/uploads/2016/03/webrtc-1.jpg" alt="" width="640" height="224" class="aligncenter size-full wp-image-18529" srcset="/wp-content/uploads/2016/03/webrtc-1.jpg 640w, /wp-content/uploads/2016/03/webrtc-1-300x105.jpg 300w, /wp-content/uploads/2016/03/webrtc-1-207x72.jpg 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<h2>パネラー紹介</h2>

<p>パネラーとして、次の3名にご登壇いただきました。モデレータは私が担当させていただきました。</p>

<p><img src="/wp-content/uploads/2016/03/webrtc-5.jpg" alt="" width="100" height="100" class="alignleft size-full wp-image-18535" />源 拓洋さん<br>ソフトバンク株式会社所属。社内、グループ会社間で利用するビデオ会議システムを、WebRTCを使って構築、運用されています。主にAndroidのネイティブアプリを担当されていますが、MCU/SFUといったメディアサーバーにも詳しいです。</p>

<p><img src="/wp-content/uploads/2016/03/webrtc-4.jpg" alt="" width="100" height="100" class="alignleft size-full wp-image-18534" />吾郷 郷さん<br>ChatWork株式会社所属。クラウド型ビジネスコミュニケーションツールを提供されていて、そのサービスの一環としてWebRTCを使ったビデオ会議/音声通話を実現されています。担当はPCブラウザが中心とのことです。</p>

<p><img src="/wp-content/uploads/2016/03/webrtc-3.jpg" alt="" width="100" height="100" class="alignleft size-full wp-image-18533" />飯田 アレン 真人さん<br>NTTコミュニケーションズ株式会社所属。開発者向けにWebRTCを使うためのPaaSであるSkyWayを提供されています。PCブラウザが中心に、サーバー側も担当されているとのことです。</p>

<p><img src="/wp-content/uploads/2016/03/webrtc-2.jpg" alt="" width="100" height="100" class="alignleft size-full wp-image-18532" />我如古正志（モデレータ）<br>インフォコム株式会社所属。WebRTCを使った社内用Webチャットシステムや、イベント配信システムを構築、運用しています。クライアント側からサーバー側まで広く浅く担当しています。</p>

<p><br>
下図のように、WebRTCの使い方や担当領域がそれぞれ異なるパネラーの方に、集まっていただきました。</p>

<p><img src="/wp-content/uploads/2016/03/webrtc_panel_position1.png" alt="webrtc_panel_position" width="640" height="473" class="aligncenter size-full wp-image-18530" srcset="/wp-content/uploads/2016/03/webrtc_panel_position1.png 640w, /wp-content/uploads/2016/03/webrtc_panel_position1-300x222.png 300w, /wp-content/uploads/2016/03/webrtc_panel_position1-207x153.png 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<h2>クライアントサイドでの「つらみ」</h2>

<h3>サポート対象のブラウザとOSについて</h3>

<p>
<b class="speaker ganeko">我如古</b>まずはクライアント側で苦労するポイントと、その対策を聞いてみたいと思います。クライアントサイドではブラウザによって実装が異なるなど、苦労するポイントがあると思いますが、まずどのようなクライアントをサポート対象としているか、教えていただけますか？
</p>

<p>
<b class="speaker minamoto">源</b>
私のところでは社内のツールとしてはChromeがブラウザとして採用されているので、原則的にはChromeのみに対応しています。なのでここに挙げられているFirefox, Safari, IE, Edgeはすべて切り捨てています。
</p>

<p>
<b class="speaker ago">吾郷</b> 
うちではChrome, Firefoxをメインに据えていますが、IE/Safariについてはプラグインというかたちで提供しています。Safariについてはプラグインの対応中という段階ですが。Edgeに関してはまだ手が回っていないですが、今後サポートしていきたいと考えています。
</p>

<p>
<b class="speaker alen">アレン</b> 
SkyWayではChromeとFirefoxがメインになっていますが、IEとSafariもプラグイン対応しています。Edgeはまだ手がついていません。PCアプリに関しては、Electronなどを使って実装することもできます。(iOSやAndroidといった)モバイルアプリもSDKをリリースしています。
</p>

<p>
<b class="speaker ganeko">我如古</b> SkyWay以外では、モバイルアプリは対象とされていないのでしょうか？
</p>

<p>
<b class="speaker minamoto">源</b>
うちでは、iOSアプリケーションについては提供しています。Androidに関しては幸いにしてAndroid版のChromeブラウザがWebRTCに対応しているんですね。なのでレスポンシブデザインみたいなかたちで、Androidの画面解像度に合わせたWebのインターフェイスを提供して対応しています。
</p>

<p>
<b class="speaker ago">吾郷</b> 
うちの場合はPCアプリ、モバイルアプリについてはまだ対応中というかたちです。これも追々対応していきたいというステータスになっています。
</p>

<h3>プラットフォームの差異をどう扱う？</h3>

<p>
<b class="speaker ganeko">我如古</b> ありがとうございます。今の中である程度話に出たかもしれませんが、あるブラウザを切り捨てるかどうかどうか、どんな戦略をとられていますか？　またブラウザの異なるバージョンのサポートにはどのようなポリシーを持たれていますか？
</p>

<p>
<b class="speaker minamoto">源</b>
その点に関しては、先ほど述べた通り、Chromeブラウザ以外は原則的に捨てています。「自分で頑張る」（頑張って差異を吸収する）ということも、もちろん部分部分ではやっています。一律で社内の人は同じブラウザの同じバージョンを使っているはずではあるんですけれども、個別に異なる環境を使ってらっしゃる方もいるので、そのあたりはJavaScriptで差異を吸収しています。Chromeブラウザ間のバージョン差異というのは当然あるので、そこは頑張って吸収しています。
</p>

<p>
<b class="speaker ago">吾郷</b> 
うちの場合だと「自分で頑張る」と「便利ライブラリを使う」の両方対応しているという状況です。IE/Safariのプラグインに関しては <a href="https://github.com/webrtc/adapter" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">adapter.js</a> を使うというのが一般的な手法なのでこれを利用していますが、それでも間に合わないところや、FirefoxやChromeでも特定のバージョンで動きがおかしいところは条件分岐して対応するということも、行っています。
</p>

<p>
<b class="speaker alen">アレン</b> 
SkyWayでは、利用しているライブラリ<a href="http://peerjs.com/" target="_blank" data-wpel-link="external" rel="follow external noopener noreferrer">Peer.js</a>でかなり吸収されているところはあります。対応しきれていないところは、adapter.jsを使ったりして吸収しています。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
Peer.jsも独自で手を入れ、対応しているとお聞きしていますが？
</p>

<p>
<b class="speaker alen">アレン</b> 
たまに結構古いバージョン（のブラウザ）では、例えばonnegotiationneededイベントに対応していないブラウザなどで動作を変えるとか、そういうコードは入っています。
</p>

<p>
<b class="speaker minamoto">源</b>
adaptor.js って、ご存知のかた多いですか？…意外と少ないですね。adaptor.jsというのはwebrtc.orgからオープンソースで公開されているJavaScriptで、Firefox, Chrome, Opera間のWebRTC実装差異を吸収してくれます。ただ先ほど述べられた通り、Trackとか一部の扱いについては残念なところがあるので、その辺に関しては自分で一生懸命吸収しないといけないです。

付加情報としてはIEやSafariのプラグインを提供してくれる会社は、adapter.jsと互換のあるインターフェイスを提供していることが多いです。adapter.jsに対応していれば簡単にプラグインが利用できて、マルチプラットフォーム、クロスプラットフォーム対応にできるという大きな利点があります。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
こちらについては、自分で作るのと、他社のライブラリを使うのと、併せて使うということでしょうか。
</p>

<p>
<b class="speaker ago">吾郷</b> 
うちの場合は他の会社と協力して開発を行っています。でも実際のところ、お金を払えば全部お任せでうまくいく…かというとそうでもなくて、こちらでも検証して「この部分がおかしい」とフィードバックしながらお互いにやっていくしかないという状況です。どういう会社を選定するかということにもよると思いますが、費用が高い会社さんととそうでもない会社さんと、もちろんあります。（後者なら）こちらからもコスト（労力）をかけて一緒に開発していくようなかたちになるんじゃないかと、思っています。
</p>

<p>
<b class="speaker alen">アレン</b> 
SkyWayのIEとSafariプラグインは、他社のものを使っております。そこは実装コストが高いのと、すでに安定して動いているモノがあるので、それを使った方が、SkyWayとして集中すべきところに集中できると思っています。

（JavaScriptの）クライアントライブラリに関してはオープンソースがベースになっていますが、そこに本体で行われた修正のマージとか、SkyWay独自の修正とか、両方入れております。
</p>

<p>
<b class="speaker minamoto">源</b>
たまにありますよね。海外でこのあいだ話題になったのは「<strong>Peer.js（本家）の実装よりもSkyWayの実装の方が早いから、SkyWayの方を改造して使おう</strong>」という発言。それぐらいSkyWayは有名です。すごいですね！（会場笑）
</p>

<h3>デバイスの差異をどう扱う？</h3>

<p>
<b class="speaker ganeko">我如古</b> 
実際同じブラウザ、同じバージョンだとしても、デバイスや機種（スペック）によって動く動かないということがあると思いますが、その辺はサポートはどうされていますか？
</p>

<p>
<b class="speaker ago">吾郷</b> 
うちの場合はB2Cの個人宅で使われることもあるので、すべての機種をサポートするのは非常に難しい、というのが正直なところです。例えばマシンのスペックが低い状態、かつネットワークの調子がおかしい、通信している相手が多い、など重なった場合などもあります。どこからというスペックの基準を設けることもある程度検討はしていますが、正直なとろ結構難しい対応になっています。
</p>

<p>
<b class="speaker alen">アレン</b> 
SkyWayはプラットフォームなので、そこはあまり考えていないです。できるだけ広い機種で使えることは目指していますが、アプリの開発者がこの機種をサポートしたかったら、ビデオをあきらめてオーディオだけにしたり、そういうことをしていただいています。プラットフォームとしてすべてのケースをサポートするのは難しいので、アプリの開発者に任せております。
</p>

<p>
<b class="speaker minamoto">源</b>
うちの場合は社内利用なので、当然のことならが利用される機種に関しても一定の物に限られているので、そういう点に置いてはテストが簡単という部分はあります。しかしながらサポートするデバイスに関しては、iPhone/iOSは機種の数がそもそも少ないのと実装差異がほとんどないので問題ではなかったりします。Androidの方が実装差異というか、機種依存がありますので、そこの部分を一生懸命に吸収することになります。</p>

<p>
企業ユースの場合になりますが、パソコンのマイク/スピーカーはそこまでコミュニケーションに最適化された設計になっていなくて、とりあえず内蔵マイク入れてみました、というのが多いので正直言ってあまりよい動きはしません。音周りについては本来コミュニケーションするために作られた電話機（スマートフォン）というのは凄く有効に使えます。</p>

<p>パソコンに関しては映像はあまり問題が出なんですけど、音に関しては聞こえづらいなと問題がやっぱり出てきます。そういう点に関しては、USBのヘッドセットやUSBの会議用マイクスピーカーを使うということをやるだけで、まったく違うレベルの品質が提供できます。ユーザーサポートとしては会議用マイクスピーカーを一回利用したいという部署に貸し出して試してもらって、良いと思えば買ってもらう、としています。やるとだいたい購入という話になります。それぐらい変わります。</p>

<p><strong>WebRTCの音声品質はもともとものすごく良い</strong>んですね。Opusというコーデックで圧縮されているということで、VoLTEレベルの音声品質は普通に出ているはずです。音声品質が悪いのはだいたいパソコン側の要因です。オートゲインコントロールの暴走もあります。そういった現象も回避できるので、Web会議用のスピーカーを利用するというのを、原則として勧めています。
</p>

<h2>「繋がらない苦労」を共有してみる</h2>

<p>
<b class="speaker ganeko">我如古</b> 
話題が尽きないんですけれども、次の「繋がらない話」に入りたいと思います。（WebRTCを使ったシステムを）実際に動かすと「繋がらない」と言われることがよくあると思います。一言で「繋がらない」と言っても、現象や原因は様々だと思うので、その辺を聞いてみたいと思います。みなさん実際にサービスを提供されていて、「繋がらない」と言われることはよくありますか？ほとんどないですか？
</p>

<p>
<b class="speaker minamoto">源</b>
「繋がらない」と言われることは、「あるかないか」でいうと、もちろんあります。どういう要因で繋がらないかというとなかなか難しいところではありますけどね。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
じゃあ、繋がらない理由として、 よくあるケースを紹介していただけますか？
</p>

<p>
<b class="speaker ago">吾郷</b> 
うちの場合だと、よくある例としてPCのスペックというのもかなり大きくあります。ブラウザの種類の問題というのも、もちろんあるんですが、例えばChromeでつながらないという場合だと、PCのスペックが足りなくて、参加人数が多いと後から来た人が入れないことがあります。ユーザーさんから見て使っていて何が起こっているのかよく分からない状況になってしまうので、それで「繋がらない」という話になってしまいます。</p>

<p>もちろんネットワークの問題とかもあるんですが、最初から全く繋がらないという状態と、途中から入る人が入れないという状態だと、「繋がらない」という問い合わせは後者の方が多いです。
</p>

<p>
<b class="speaker minamoto">源</b>
追加の方が繋がらないと言われることが多いということですか？
</p>

<p>
<b class="speaker ago">吾郷</b> 
実際使っていると、その方が動作に違和感を覚えるという意味です。
</p>

<p>
<b class="speaker minamoto">源</b>
あー、なるほど。ということは、初期のWebRTC接続に関しては普通に抜けてしまう（繋がる）ケースの方が多い？
</p>

<p>
<b class="speaker ago">吾郷</b> 
もちろん、それでつながらないというケースもあるにはあるのですが、その場合だとこちらからもある程度サポートはしやすいというか、切り分けはかなり楽です。そうでない場合、人数が入ってくると後の人が繋がらないという状況になると、どういうふうにサポートしてよいか難しくなるという問題があります。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
プラットフォームだといろいろなケースがあると思いますが？
</p>

<p>
<b class="speaker alen">アレン</b> 
一般的に使われている方からはそんなに問い合わせはないんですけど、大企業でデモしたりする時って、やっぱり企業のネットワークってかなり制限が多いので、そこでつながらないことが非常に多いです。そもそもWebSocketが通らないからシグナリングできないとか、Man-in-the-middleで完全にブロックされているとか…。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
最悪ですね（苦笑）。
</p>

<p>
<b class="speaker alen">アレン</b> 
本当にそういうケースが多くて、ネットワークポリシーをちょっと変えてもらったら繋がるケースも多いです。やっぱり<strong>企業ネットワークはすごく難しい</strong>です。</p>

<p>ただ、社内ネットワークの内側同士だったら意外とよく繋がるんですね。弊社でも（PCのポリシーの制約で）カメラとマイクはつなげられないんですけど、DataChannelでファイル送ったりすることはできるので…（笑）。
</p>

<h3>明るい話をしよう。WebRTCは繋がる！</h3>

<p>
<b class="speaker minamoto">源</b>
一応「明るい展望の話をしよう」という話になっていたはずなので、ちょっと話の流れ変えていいですか(笑)。繋がらない話ばかりだとみんな「WebRTCはつらい」と思っちゃうでしょうし。
</p>

<p>ざっくり言うと大多数の人は繋がります。「繋がらない」と我々がしゃべっているのは、「繋がらない」ケースについて取り上げているだけ、だいたいの人は繋がります。どれぐらい繋がるかというと、</p>

<ul>
 <li>家庭-家庭間ではほぼ間違いなく繋がる</li>
 <li>家庭-携帯端末間もほぼ間違いなくつながる</li>
 <li>企業-家庭間もほぼ間違いなくつながる</li>
 <li>繋がらないのは、企業-企業間</li>
</ul>

<p>と、こんな感じでまとめられると思います。</p>

<p>
企業-企業間に関しては利用頻度が高い場合は、そこのネットワーク管理者に相談して、自分たちのサーバーだけ何とか通してくれという交渉ができると思っているので、原則的には繋がるものだと考えていただいてよいです。「繋がらないというのは、あくまでもレアケース」、ということは持ち帰っていただけると幸いです。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
企業の場合はガードされているけど、理由があれば開けてもらえる、というところがあると思います。
</p>

<p>
<b class="speaker ago">吾郷</b> 
ここでどういうふうに開けるかという問題があって、サーバーを分散しているときにそれのIPアドレスを教えてくれと言われて、つらいという話があったりもします。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
動的にIPアドレスがふられる場合ですね。
</p>

<p>
<b class="speaker ago">吾郷</b> 
はい。DNSで名前振ってるので、それを指定して開けるのが難しい場合もあって（IPアドレス指定が必要なとき）、そういう時は、結構うーん、というような状況になることがあります。
</p>

<p>
<b class="speaker minamoto">源</b>
動的な場合は難しいですね…。でもいろいろやりようはあると思っていて、例えば繋がるようにするのであれば、対象となる企業が決まっていれば、その企業向けにTURNサーバーを（固定で）立ててあげるというのも十分良いと思います。そのTURNサーバー向けの通信は全て通してくださいね、というお願いの仕方はあると思っています。
</p>

<p>
<b class="speaker ago">吾郷</b> 
うちの場合はそこまで各企業さん向けに大きくカスタマイズして提供することは難しい事情もあります。なので、どういうふうにサービスを提供したいか、だれに対してどのようなかたちでサービスを提供したいか、によってここの解決方法は選択肢があるのかな、と。</p>

<p>逆に言えばそこの部分をかなり考えないと、あとあとすごく大変になってしまうという事態はありえると思ってます。
</p>

<p>
<b class="speaker minamoto">源</b>
ちなみに弊社におけるつながらない問題に関しては、通信環境の問題で繋がらないというのは、ほぼほぼ無いです。というのは、そういう風にして繋がるように（設定や設置を）工夫しているというのはあるんですが、原則的に繋がらないというのはレアケースになります。
</p>

<p>ユーザーから繋がらないという問い合わせが来るもので一番件数が多いのは「カメラとマイク」の問題です。「カメラとマイク、(PCに)つながってませんでした～」とかも結構あるのですが、最多は最初に getUserMedia() でブラウザがユーザに許可を求めて来た際に、「今回は私は映像使わないからとか、見ているだけだから映像音声いらない」ということで一回拒否をしちゃうんですね。</p>

<h3>ユーザへの権限許可の求めかた</h3>

<p><strong>一回拒否しちゃうと、二回目そのページを開いたときも、ブラウザはその設定を読み込んじゃう</strong>んですよ。そうすると二回目以降その人が映像音声使いたくても、ブラウザとしてはポリシーに拒否が設定されているので、ユーザとしては「繋がらない」というかたちで結果が出てきます。</p>

<p>
そういうところは（世の中的にも）結構な問題になっていて、FirefoxとChromeではその警告の出方が違うんですね。なので（ユーザへの）案内の仕方とかマニュアルの作り方とかも変わってくるんですね。海外のサービスを見ていると結構おもしろくて、getUserMedia()の許可を取ってねというのは、画面いっぱいにすごくドーンと出るんですよ。あーいうのってすごく重要なんだなーと見ています。
</p>

<p>
<b class="speaker alen">アレン</b> 
サンプルアプリでChromeとFirefoxに対応して「ここをちゃんとクリックしてね」と出しても、だれも読んでくれないです（笑）。だから全画面でやらないとスルーされてしまうので、そこは注意しなければいけないです。
</p>

<p>
<b class="speaker minamoto">源</b>
辛いですねー。昨日のセッションで発表されていた Appear.in とかは、なのでちょっと見に行って参考にさせてもらうと、あー、やっぱりこうやらないと問い合わせくるよねー（と思います）。</p>

<p>利用者の多いサービスは問い合わせ件数を下げることに一生懸命になりますから、やっぱりUIとかもそういう工夫していると思います。なので「なんでこうなっているのだろう？」という疑問から入るといろいろ解決していくのかなと。
</p>

<p>
<b class="speaker ago">吾郷</b> 
うちの場合いくつかの言語に対応しているので、その部分の多言語対応が必要だったりとかします。単純にメッセージを変えるだけではなく、画面キャプチャーして「この部分のこのアイコンをクリックしてね」というのがその言語ごとに必要になったりして、それかけるブラウザ数になって、「ああああ…」ていうかたちになったりも、しています
</p>

<p>
<b class="speaker minamoto">源</b>
やっぱり標準化してほしいですよねー。
</p>

<p><br></p>

<h3>それでもやっぱり繋がらなかった時の対処法</h3>

<p>
<b class="speaker ganeko">我如古</b> 
じゃあ、いままでですごいレアな、「こんな原因でつながらなかったのか」という例があれば紹介していただけますか？
</p>

<p>
<b class="speaker minamoto">源</b>
インターネットだけ頑張って抜けるようにしている環境だと、シグナリングサーバー（チャットサーバー）までは来ているのに、その後のWebRTCは繋がらないという特殊な環境の人は、一定数あります。レアケースだとは思います。
</p>

<p>
<b class="speaker ago">吾郷</b> 
OSのファイアーウォールの設定だとか、ネットワーク上は通信はできるのに、そのPCの設定や中のファイアーウォールの設定で繋がりにくかった、それも全く繋がらないわけではない、という状況になったことはあります。
</p>

<p>
<b class="speaker alen">アレン</b> 
これは繋がらないわけではないのですがちょっと面白い話で、社内のハッカソンをやった時に、家庭用ルーターを使ってみんなアプリを作っていました。何時間か経ってアプリが動き始めたら、フルメッシュで10&#215;10の通信が家庭用ルータを通って、融けました（笑）。それ以降通信できなくなってしまいました。家庭用ルーターでむっちゃ大量通信する場合は注意してください。
</p>

<p>
<b class="speaker minamoto">源</b>
あります、あります。それはあります。社内利用の場合にはある特定のフロアでみんなが利用して、そのフロアの無線LANルーターが死亡して、解像度が下がって画質がすごく悪くなったんだけど…（と言われる）。それは皆が使うと帯域狭くなって画質が動的に下げられてしまうので、悪くなるのは当たり前なんですけど、そういうことは起こりますね。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
いろいろ原因があると思いますが、何か切り分けとか情報収集とかする仕組みって用意されていますか？　あるいはユーザーが自分で切り分けられるツールとか、そういう工夫があれば教えてください。
</p>

<p>
<b class="speaker minamoto">源</b>
そういう点に関してはtest.webrtc.orgというサイトがあって、そこで一通りのテストは通せるので、それの社内版を作って（ユーザーに）テストしてもらうということを、我々の場合にはやっています。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
自分でやってもらうということですね？
</p>

<p>
<b class="speaker minamoto">源</b>
ユーザーにやってもらう形になります。もちろん社内利用なのでブラウザから出てくるの情報であったり、接続用の交換情報であったりはログとして取ってあるので、繋がらないという連絡を受けた時はその人のログを追いかけて、その人の接続系の情報を見てそこから判断することも、実際にやっています。
</p>

<p>
<b class="speaker alen">アレン</b> 
こちらも似た感じで、シンプルなアプリでまず試してもらってます。あと Peer.js というライブラリを使っているので、開発者にログを見てもらって、setRemoteDescriptio()でコケたとかいうのが分かるので、それで見てもらいます。それでも解決しなかったら、ログを収集して、結構シンプルなところで落ちていることが多いので、例えばAPIキーが間違っているとか、そういう手段を使っています。
</p>

<p>
<b class="speaker ago">吾郷</b> 
うちの場合は一緒に開発してくださっているベンダーさんといろいろやり取りしながら、その上で状態が分かるエクステンションを使ったりすることはあるのですが、一般ユーザーの方が情報を得られるというかたちでは現状できていないです。
</p>

<p>
<b class="speaker minamoto">源</b>
この（資料のページ）の下二つは良いですね。「現地に見に行く」と、「超能力で透視する」（笑）。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
社内だと（見に行くは）ありますよね？
</p>

<p>
<b class="speaker minamoto">源</b>
そういうこともやりたいのですけど、なかなか難しいんですよね。現地見に行こうとすると片道4時間かかるからこの日飛ぶよねーとかもあるので、現地見に行くというのはなかなか厳しいなーと思います。でもそれって、情報欲しいじゃないですか？どうやって収集するのかと考えるんですけど、そこのネットワーク構成教えてくださいと言っても、ネットワーク構成した人がすでにいなかったりするので、そうすると全然わからないんですよね。なので現地見に行くっていうのはかなり難しいなと思います。
<br />
WebRTC界隈の人で集まると、<strong>だいたいの人が超能力で透視している</strong>ので、ちょっとそれはどうなんだろうなーと思ったりします（笑）。
</p>

<h2>サーバサイドもつらい？</h2>

<p>
<b class="speaker ganeko">我如古</b> 
残り時間も少なくなってきたので、最後サーバーサイドの話をしたいと思います。WebRTCでブラウザ同士がP2P通信するというので最初やろうとすると私も躓いたのですが、なんだサーバーが要るじゃないの、全然嘘じゃんと思うんですよね。
</p>

<p>いろんな環境で使おうとすると(TURNなど)サーバーの種類が増えて、さらに大規模に使おうとするとサーバーこそがちゃんと作らないと繋がらないということになりますよね。その辺を聞いてみたいと思います。
</p>

<p>シグナリングはどうされていますか？　既存の互換の仕組みを使うケースもあるし、独自の仕組みを作るケースもあるし、あるいはサービスを使うケースもあると思うんですけど？
</p>

<p>
<b class="speaker ago">吾郷</b> 
うちの場合は自社ではなくて、一緒に開発しているところの方を使っています。どこがお勧めか、と書かれているのですが、正直なところケースバイケースで、簡単に言えば金額が高いところは丸っと収まる回答になると思うのですが、なかなかそういう（高い金額を出す）のも難しかったりすると、選択は難しいです。</p>

<p>実際、シグナリングサーバーの安定性だとか、通信の繋がる/繋がらないの問題だとかに関して、どのくらいノウハウを持っていてそれが製品に反映されていくのかというのが、外から見ているとほとんど分からないというのがあるので、ここはかなり難しい問題だなと思ってます。
</p>

<p>
<b class="speaker alen">アレン</b> 
自社で持っています。おすすめするならSkyWayです（笑）。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
これで違うのが出てきたらびっくりしますね（笑）。
</p>

<p>
<b class="speaker minamoto">源</b>
SkyWayさん、すごく検証されていますもんね。えっと、うちも自社で持っています。ちょっと前まで違う物を使っていたのですが、最近(node.jsの)Socket.ioという流行りのものに変えてみようかなーと、一人で画策しています。なので、実装的にはWebSocketになります。</p>

<p>ただ、WebSocket意外と抜けないんです。WebSocket抜けないのにWebRTC抜けるというネットワークが平然と存在しているのが結構断崖でして、抜けるという点においては、XHRとかSSE(Server Sent Events)とかは意外と普通に抜けるので、接続系だけでもそっちにした方がよいのかなと思いつつも、クライアントの負荷が増大するので、難しいなと思っている今日この頃ですね。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
ありがとうございます。シグナリングが終わってもSTUN/TURNといったNAT/Firewallを超えるためのサーバーを使うと思いますが、こちらも自分で運用するか、他社のを使うか。自分で運用にするにしても（地理的に）地球上のどこに置くか、というのがあると思うのですが、その辺はどんなか考えを持たれていますか？
</p>

<p>
<b class="speaker ago">吾郷</b> 
うちの場合はシグナリングサーバーと同じように外部で提供してもらっているサーバーを使用しています。地理的なところで言うとUSのAWSの中なんですが、ただ使ってて実際に国内to国内でTURNを使った環境っていうので通信をすることがあるのですが、その辺に関して直接繋げるときと比べてすごく遅延が発生しているという感覚はそこまで大きくないですね。</p>

<p>今のところここに関しては、そんなに問題になってないです。もちろんこちらに、日本に置いてほしいという要望は上げてはいるのですが、そこに関しては現状そこまで大きな問題とは認識していないです。
</p>

<p>
<b class="speaker alen">アレン</b> 
TURNサーバーも自社で提供しております。これは弊社のCloudnというクラウドサービスに乗っているので日本にあります。
主にお客様が日本のかたが多いので、あまりそれで問題はないです。ただ今後世界中に展開したいという話があります。
</p>

<p>
<b class="speaker minamoto">源</b>
採用しているアーキテクチャの都合上TURNサーバーというのはほとんど使わない仕様にもなっているんですけれども、TURNサーバー自体は通信会社ですので、自社の基盤の上に持っています。当然日本にあります。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
そうすると次の質問はあんまり問題にならないのでしょうかね？ TURNサーバーお金かかりますよねということを聞こうと思ったのですが、基盤を持っている会社さんはあまりその辺は気にならないというところがあるのでしょうか？
</p>

<p>
<b class="speaker minamoto">源</b>
気にならないというとこももちろんあるんですけれども、そもそもTURNを使うというのはそれなりにレアケースなので、普通に提供していたとしてもそれほど気にならないです。というのは、1サーバーTURNで立っているくらいの認識ですね。
</p>

<p>
<b class="speaker alen">アレン</b> 
SkyWayでもかなり接続数がありますけども、今のところTURN 1台で足りておりますので、あんまりそこは気にしておりません。
</p>

<p>
<b class="speaker ago">吾郷</b> 
うちの場合もお願いしているので具体的にどういう風にスケール対策をしているかというところまで、こちらから大きく踏み込んで何か調査だとか依頼だとかしていることではないのですが、ただWebRTCを使うときのコストに関して言うとおそらくTURNサーバーを使うかどうかというところで非常に大きく変わるのかなと思っていまして、ここはやっぱりどんぐらいお金を払うかというところにかなり依存します。</p>

<p>最初に使うだけで結構金額がかかるっていう会社さんと、TURNサーバーを使うかどうかで金額が変わる会社さんと、2種類あって、TURNサーバーを使うかどうかはその辺でかなり変わってくるのですが。</p>

<p>この質問のスケーリングというところだけに関して言うと、外部のベンダーさんに依頼している場合はそこまで問題にならない。うちの場合もTURNサーバーが遅いだとか、止まるっていう事態に関してはそこまで問題にはなっていないので。
</p>

<p>
<b class="speaker minamoto">源</b>
平均1日1万セッションのSkyWayさんでもTURNが1台でよいというのは、何か元気が湧いてきますね（笑）。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
ほとんどTURNを使わずに済むということですね。
</p>

<p>
<b class="speaker minamoto">源</b>
でかいコスト要因だと思うんですけどもね。
</p>

<p><br></p>

<h2>会場からのQ&amp;A</h2>

<p>
<b class="speaker ganeko">我如古</b> 
この辺りで来場のみなさんの質問を受けつけたいと思います。何か質問とかある方は、手を挙げてください。
</p>

<p>
<strong>会場から1</strong> 
貴重な話ありがとうございます。つながらない問題にちょっと近いところなんですけれども、弊社で社内で利用するためにシグナリングサーバー立ててやろうとしているんですけれども、DataChannel使ったファイル転送みたいなことをやろうとしていて、ファイルのサイズがそんなに大きくない間は比較的安定稼働するんですけれども、ファイルが100MB, 1GB、10GBというふうになってくると、ネットワークの帯域の問題は置いておいたとしても、ブラウザが応答しなくなるとか、受け側が突然何にも言わなくなるみたいな事象が出てしまっていて、何をどうしたらよいのか分からないという感じになってしまっているのですが、もしそのあたり何かお知恵があれば教えていただきたいなと思っております。
</p>

<p>
<b class="speaker alen">アレン</b> 
弊社で使っているものは結構細かく、数百キロバイトぐらいに分けて送っております。たしか、そこを直接書いていないので正確には覚えていませんが、たしかバッファーが足りないとかの問題で上手くいかないので、細かく分けて送る方法の方が良いともいます。
</p>

<p>
<strong>会場から1</strong> 
いまPeer.jsに手を入れて使っているんですけども、SkyWayさんのライブラリだとそのあたりがもう少し安定するという感じなんですね？
</p>

<p>
<b class="speaker alen">アレン</b> 
そこはPeer.jsの実装と同じなので、アプリ側で分けて送信するという仕組みが必要になります。
</p>

<p>
<strong>会場から1</strong> 
ありがとうございます
</p>

<p><b class="speaker ganeko">我如古</b> 
他にいかがでしょうか？
</p></p>

<p>
<strong>会場から2</strong> 
SkyWayに関して質問です。SkyWayを使ってアプリを作ろうとしているんですが、SkyWayは同時接続に関してどれぐらいのテストをされているのですか？
</p>

<p>
<b class="speaker alen">アレン</b> 
（同時接続に関しては）テストはそれほどしておりません。ただ今のところ同時接続数が多すぎて落ちたことはないと思います。仕様上、WebSocketの接続は、同時10万セッション可能としております。ただそこまで上がったことがないので、なんとも。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
今の（質問）はシグナリングサーバーの接続数のことでしょうか？　それともブラウザ側の接続数のことでしょうか？
</p>

<p>
<strong>会場から2</strong> 
シグナリングする時にクライアント側から一気に同時接続かかった場合、サーバーがもつのかどうか？　という話なんですが
</p>

<p>
<b class="speaker alen">アレン</b> 
今のところ問題になるような接続数はないですが。
</p>

<p>
<strong>会場から2</strong> 
（今後）もしかしたらあるかもしれない、ということですよね。分かりました。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
何か最後言い残したこととか、他の人に聞いてみたいこととかあれば、一人ずつどうぞ。
</p>

<p>
<b class="speaker minamoto">源</b>
言い残したところという訳でもないのですが、原則的には繋がるテクノロジーなので、特に問題はないとは思っています。
先ほどの同時接続の話にしても、サーバー側が受け付けられる上限よりも、ブラウザー側が同時に通信できる上限の方が先に到達してしまうので、ボトルネック要素というのは常に変わっていくのかな、と思っています。
</p>

<p>
<b class="speaker ago">吾郷</b> 
実際ちゃんとしたビデオ会議システムと言いますか、すごく大手の方が作られているものと同じものを作ろうとすると、WebRTCというのは全然まだまだ発展途上で辛いという問題が多いのですが、ただそうでない方法、クラウド上で簡単に通話だとか会話だとか、もしくはP2Pのデータ送信ができるというところで捉えると、もっとかなり可能性の大きいものなんじゃないかと、思っています。

それであれば今でも十分使えるし、ユースケースもこれからももっともっと広がっていく、広げていくところに、価値があるんじゃないかと私は考えています。
</p>

<p>
<b class="speaker alen">アレン</b> 
じゃあ二人に質問があるんですけれど、WebRTCって本当に毎月何かが変わっているんですけど、変更にどうやって追い付いていくかを、ちょっとお聞きしたいんですけれど。結構いろいろ参加しているのに、結構落としているところがあるので、そこだけ。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
じゃあ、一言ずつどうでしょう？
</p>

<p>
<b class="speaker minamoto">源</b>
どうやって追い付くかという点においては、テストするしかないです。これは新しいChromeブラウザが出ると一通りの機能についてはちゃんとテストをしています。
</p>

<p>
<b class="speaker ago">吾郷</b> 
うちの場合には基本的には外部の会社さんにお願いしているというのと、弊社でも実際これを使いながら仕事をしているので、その上で見つけていく、自分たちでドッグフーディングしていく、ということしかないかなと思います。
</p>

<p>
<b class="speaker minamoto">源</b>
一言だけ追加で。http://webrtcjp.info/ というページがあるんですね。これは確かSkyWayの方々やっておられると思うんですけど、そこにslackがあります。このslack、結構今回登壇している方だとかが入っています。なので質問するとすぐ答えてくれます。WebRTC開発やろうという方は、とりあえずつまったらそのSlackに投稿してみてください。だれか答えてくれるはずです。しかも、かなり高い精度で答えてきます。
</p>

<p>
<b class="speaker ganeko">我如古</b> 
では、これで終わりたいと思います。どうもありがとうございました。
</p>
]]></content:encoded>
			</item>
		<item>
		<title>夏野剛・及川卓也・白石俊平が語る「WebRTCが切り拓く2020年のIoT」～リアルタイムコミュニケーションがもたらす破壊的イノベーション～</title>
		<link>/miyuki-baba/18411/</link>
		<pubDate>Thu, 10 Mar 2016 00:00:47 +0000</pubDate>
		<dc:creator><![CDATA[馬場 美由紀]]></dc:creator>
				<category><![CDATA[最新動向]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[W3C]]></category>
		<category><![CDATA[WebRTC]]></category>

		<guid isPermaLink="false">/?p=18411</guid>
		<description><![CDATA[2月16日・17日と2日間にわたって「WebRTC Conference 2016」が開催されました。「2020年」と「IoT」という2つのキーワードを軸に、リアルタイムコミュニケーションがもたらす破壊的イノベーションに...]]></description>
				<content:encoded><![CDATA[<p>2月16日・17日と2日間にわたって「WebRTC Conference 2016」が開催されました。「2020年」と「IoT」という2つのキーワードを軸に、リアルタイムコミュニケーションがもたらす破壊的イノベーションについて語られた「WebRTCが切り拓く2020年のIoT」。<br>HTML5 Experts.jp編集長・白石俊平氏をモデレーターに、夏野剛氏・及川卓也氏を特別ゲストとして迎えての特別セッション。今回は講演内容をほぼ再現版としてお届けいたします。</p>

<p><img src="/wp-content/uploads/2016/03/DSC00720.jpg" alt="" width="640" height="456" class="aligncenter size-full wp-image-18422" srcset="/wp-content/uploads/2016/03/DSC00720.jpg 640w, /wp-content/uploads/2016/03/DSC00720-300x214.jpg 300w, /wp-content/uploads/2016/03/DSC00720-207x147.jpg 207w" sizes="(max-width: 640px) 100vw, 640px" /></p>

<h2>夏野剛氏、及川卓也氏、白石俊平氏が縦横無尽に語り合う</h2>

<p><strong>白石</strong>：まずは、登壇者のご紹介から。慶應義塾大学 政策・メディア研究科 特別招聘教授の夏野剛さんです。あまりご存じない方も多いかもしれませんけど、夏野さんはW3Cのボート（幹事）をずっとやっていらっしゃったんですよね。4年間ぐらいでしたっけ。</p>

<p><strong>夏野</strong>：はい。2期、4年やってました。大変でした（笑）。</p>

<p><strong>白石</strong>：本日はそうした標準化団体に深くコミットしていらした方として、WebRTCについて語っていただきたいと思います。</p>

<p><strong>夏野</strong>：ちなみに、W3Cの設立メンバーが慶應大学なんですよね。ファウンディングメンバー。で、村井先生に「ボードやれ」って言われて、立候補したという。</p>

<p><strong>白石</strong>：次に、Increments株式会社、プロダクトマネージャーの及川卓也さんです。この、WebRTCに関する対談については、前職のGoogleで、Google Chromeの開発に携わっていらっしゃったということで、実装面などについてもお話いただきたいと思います。</p>

<p><strong>及川</strong>：Incrementsは「<a href="https://qiita.com/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">Qiita</a>」というプログラムの情報共有サービスを提供しています。もう辞めちゃったんですけど、Chromeのエンジニアリングマネージャーも、結構大変でございました。楽しかったですけど（笑）。</p>

<p><img src="/wp-content/uploads/2016/03/natsuno2.jpg" alt="" width="100" height="134" class="alignleft size-full wp-image-18432" /><strong>慶應義塾大学 政策・メディア研究科 特別招聘教授　夏野剛氏</strong><br><span style="font-size: 80%;">早稲田大学政治経済学部卒、東京ガス入社。ペンシルバニア大学経営大学院（ウォートンスクール）卒。ベンチャー企業副社長を経て、1997年NTTドコモへ。99年に「iモード」、その後も多くのサービスを立ち上げた。2008年にドコモ退社。現在は慶應大学の特別招聘教授のほか、カドカワ、トランスコスモス、セガサミーホールディングス、ぴあ、グリー、DLE、U-NEXTなどの取締役を兼任。09年から13年までHTMLの標準化機関であるW3C(World Wide Web Consortium)のアドバイザリーボードメンバーを務める。</span></p>

<p><img src="/wp-content/uploads/2016/03/oikawa2.jpg" alt="" width="100" height="117" class="alignleft size-full wp-image-18437" /><strong>Increments株式会社 プロダクトマネージャー　及川卓也氏</strong><br><span style="font-size: 90%;">早稲田大学理工学部卒業後、外資系コンピューター企業の研究開発、マイクロソフト株式会社（当時）の日本語版・韓国語版Windowsの開発統括を経て、グーグルでプロダクトマネージャとエンジニアリングマネージャを務める。2015年11月よりIncrementsにてプロダクトマネージャとして従事。</span></p>

<p><img src="/wp-content/uploads/2016/03/shiraishi2.jpg" alt="" width="100" height="123" class="alignleft size-full wp-image-18440" /><strong>株式会社オープンウェブ・テクノロジー代表取締役CEO　白石俊平氏</strong><br><span style="font-size: 85%;">エンジニア、コピーライター。日本最大のWeb技術者コミュニティhtml5jを5年間リードした後スタートアップを創業、テクノロジー分野に特化したキュレーションサービス「<a href="http://techfeed.io/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">TechFeed</a>」を2015年12月にリリース。日本の開発者の役に立つべく、プロダクトマネージャー兼CEOとして奔走中。「HTML5&amp;API入門」（日経BP）など、著作執筆多数。Web技術者向けメディアHTML5 Experts.jp編集長も務める。</span></p>

<h2>WebRTCをどう見るか？</h2>

<p><strong>白石</strong>：まず最初のテーマは「WebRTCをどう見るか」です。本日のセッションテーマは未来感のあるものが多いので、ビジョン的なお話をお聞きしたいなと思っています。</p>

<p><strong>及川</strong>：基本ポジティブですね。少し前だと、音声やビデオのカンファレンスをやろうとすると、Flashを使わなきゃいけない時代もあった。あれって結構面倒くさいんですよね。音声やビデオで軽く会議をやろうとした瞬間に、プラグイン入っていないから、インストールしてくださいって表示されたりして。そのためのセットアップで、少なくとも1分とか2分経ってしまったり、下手するとブラウザをリロードしなくちゃいけなかったり。</p>

<p>普通のテキストチャットと同じぐらいカジュアルなコミュニケーションができるようになったのは、WebRTCのおかげです。今後はどんなブラウザでも使えるようになっていく、という意味で非常にポジティブかなと。一方でっていうことが、ちょっとあるんですけれども、その前に夏野さんのご意見も聞きたいと思います。</p>

<p><img src="/wp-content/uploads/2016/03/oikawa3.jpg" alt="" width="600" height="424" class="aligncenter size-full wp-image-18459" srcset="/wp-content/uploads/2016/03/oikawa3.jpg 640w, /wp-content/uploads/2016/03/oikawa3-300x212.jpg 300w, /wp-content/uploads/2016/03/oikawa3-207x146.jpg 207w" sizes="(max-width: 600px) 100vw, 600px" /><br></p>

<p><strong>夏野</strong>：今及川さんが言ったみたいにパソコンをベースにしてWebで何でもできちゃうと、ソフトをダウンロードすることはほとんど要らなくなって、せいぜいプラグインくらい。そのプラグインすらもHTML5になってくると、ほとんど要らなくなるっていう世界になってきた。</p>

<p>そんな理想型に進んでるのに、スマホが出てきたことによって、状況が変わってきた。スマホが出てきてから5年ぐらい経ちますが、その間にWebの勢いって1回萎えているんですよ。理由は簡単。ネイティブアプリで遊ぶほうが忙しすぎて、Webなんか立ちあげている場合じゃないから。</p>

<p>ネイティブアプリにする理由の一つは、グラフィックスの性能。もう一つはこれ、まさにリアルタイムコミュニケーションなんですよね。このWebRTCをきちんとスマホまで含めて、マルチデバイスで広がっていくと、かなり開発者の負荷も減らせるし、サービスの性能も向上することにつながると思っています。いち早くモバイルも巻き込んだ中で、WebRTCがどんどん広がっていくことを切望しますね。</p>

<p><img src="/wp-content/uploads/2016/03/natsuno4.jpg" alt="" width="600" height="398" class="aligncenter size-full wp-image-18461" srcset="/wp-content/uploads/2016/03/natsuno4.jpg 640w, /wp-content/uploads/2016/03/natsuno4-300x199.jpg 300w, /wp-content/uploads/2016/03/natsuno4-207x137.jpg 207w" sizes="(max-width: 600px) 100vw, 600px" /></p>

<h2>「相互運用性」という課題</h2>

<p><strong>白石</strong>：なるほど。たしかにWebRTCはマルチプラットフォームや、マルチデバイスといったところでは、結構強みがあると思います。及川さんの言いかけたお話を。</p>

<p><strong>及川</strong>：そういう意味で言うと、プラットフォーム、相互運用のところのレイヤーが1つ上がったかたちになっていくと思うんですよ。どのブラウザでも使えるということは、すごい貴重な技術であるのと同時に、もしかしたらロックインされるだとか、相互運用性までを考えないといけない。</p>

<p>例えば、WebRTCを使ってビデオカンファレンスができるかっていう話になったときに、実装依存の部分がたくさんあるわけですよね。おそらく制御のところだったり、ビデオコーデックなんかは、現状すべてのブラウザに当てはまるところはないんじゃないかな。もちろん、相手に合わせて変えるっていうのは実現可能だと思うんですね。GoogleもWebRTCを使ったそういったサービスを出しています。</p>

<p>一方、Facebookのチャットからビデオメッセージやビデオチャットをやりたいといっても、ハングアウトにはつながらないし、（別サービスとは）つながる世界が想像できないわけですね。これって、どっかで見た、過去で見た、未来なんですよ。どういうことかと言うと、シンプルなテキストチャットのときもそういう議論はたくさんあったんですね。</p>

<p>Yahoo!メッセンジャー、AOLメッセンジャーと、MSNのメッセンジャーと、Google Talkをつなげたいよね、という流れになり、ちゃんと仕様を考えましょうということになった。IETF（Internet Engineering Task Force）で、XMPPだとかSimpleが候補に上がりました。陣営二つに分かれちゃったわけですけれども。</p>

<p>Googleはその部分、なぜか知らないけど、すごい積極的にやっていいます。Google Talkは、XMPP完全準拠でJabberというかたちにして、ほかのJabberのサーバーにもつなげられたのに、結局はGoogleもシャットダウンしちゃった。相互運用ができる状況じゃないと。</p>

<p>なぜならば、技術以前にソーシャルグラフというか、ユーザーのデータベースをわざわざ相手に開放したくないわけですよね。なので、この部分まで含めてWebRTCに期待するとしたら、それはちょっと厳しいところもあるし、どこまでの部分をコモディティ化していくか、という課題があるかなと。</p>

<p>本当は全部相互運用ができたほうがいいのだけれども、そこにいくまでには、過去の歴史を見ても、なかなか厳しい現実問題としてある。さっき「でも」というところでちょっと言いたかったことです。</p>

<p><img src="/wp-content/uploads/2016/03/shiraishi3.jpg" alt="" width="600" height="399" class="aligncenter size-full wp-image-18465" srcset="/wp-content/uploads/2016/03/shiraishi3.jpg 640w, /wp-content/uploads/2016/03/shiraishi3-300x200.jpg 300w, /wp-content/uploads/2016/03/shiraishi3-207x138.jpg 207w" sizes="(max-width: 600px) 100vw, 600px" /></p>

<h2>GoogleがWebRTCを立ち上げた背景とは</h2>

<p><strong>白石</strong>：なるほど。ちなみにWebRTCって、もともとの出自はGoogleさんがオープンソースで<a href="https://webrtc.org/" data-wpel-link="external" target="_blank" rel="follow external noopener noreferrer">webrtc.org</a>を立ち上げたところからかなと思うんです。それ、間違いないですか？</p>

<p><strong>夏野</strong>：たぶん、それが最初だったと思いますよ。</p>

<p><strong>白石</strong>：どういうビジョンを持って、このプロジェクトを始められたのでしょうか。</p>

<p><strong>及川</strong>：理由は非常に単純で、Googleも要はすべてWebでやりたいわけですよ。</p>

<p><strong>白石</strong>：検索とか広告とか、そういったところにつなげるためでしょうか。</p>

<p><strong>及川</strong>：それは、ビジネスモデル的には、当然あるとは思います。だけど、Googleって恵まれた会社で、8割以上のエンジニアは、お金のことを全く考えなくていいんですよ。</p>

<p>で、「あなたたちの役割というは、インターネットが健全に発展するためのものです」と。そのネイティブで持っているアプリケーションが使える機能をそのままWebでも使えるようにするために、HTML5を含め、いろんなことをやっていたんですね。</p>

<p>最初は、Canvasだってもともと使えなくって。Canvasが使えないと、この2次元対応で1点から1点までの線を引くことすらできなかったわけですよね。そこから始まり、ちゃんと3Dができるようになり、全部やっていったあとに、自然にビデオカンファレンスだとか、リアルタイムコミュニケーションだとか、そういったものができて当たり前という時代になったんだと思うんです。</p>

<p><strong>白石</strong>：なるほど。</p>

<p><strong>夏野</strong>：僕も標準化の現場にいたのでわかる。面白いのは、Googleってライトサイドのプレイヤーなんです。</p>

<p><strong>白石</strong>：ダークサイドの逆ですね。</p>

<p><strong>夏野</strong>：ダークサイドもいるんだよね（笑）。つまり、ネットの中立性とか、ブラウザがみんなに使われるとか、相互運用がどうのとか、そういうこととは、逆のことを考えている会社もいろいろあって、面白いですよ。</p>

<p><strong>白石</strong>：そういうダークサイドな会社が、W3Cにいるところが面白いですね。</p>

<p><strong>夏野</strong>：それ以上聞かないで（笑）。最近はいなくなりましたけど、10年前は大変でした。</p>

<h2>Webですべてを完結する世界は作れるか</h2>

<p><strong>白石</strong>：僕、さっき及川さんのお話を聞いて、及川さんが昔講演でおっしゃっていたことを思い出しました。デスクトップアプリケーションで起きた最後のイノベーションはSkypeで、そのあとはずっとWebで起きているみたいな話を5年前にしてたんですよね。そのSkypeが今ようやくWebの補完をしているんだなって。覚えていらっしゃいますか、そんなことを言っていたのを。</p>

<p><strong>及川</strong>：そうですね。もう少し補足で説明すると、Webというのはもう多くのユーザーが使っていて、プラットフォームになっています。それをあえてリマインドするために、横軸に時系列で、1990年、2000年、2005年で振り返ってみたんです。</p>

<p>昔はデスクトップ系のちゃんとインストールして使うようなアプリケーションがたくさんあった。それが、2005、2006年からを境に、TwitterやGoogleMap、YouTubeなどのサービスが出てきた。この違いは何かというと、ここから先は「Webですべて完結するようになっているんですよ」、という話は、僕の持ちネタですね(笑)。</p>

<p><img src="/wp-content/uploads/2016/03/oikawa6.jpg" alt="" width="600" height="374" class="aligncenter size-full wp-image-18478" /></p>

<p><strong>夏野</strong>：それと同時期にスマホが流行ってきた段階で、Skypeはアプリを出すのが、結構遅れたんですね。結局、FacebookやTwitterはアプリを出して、よりモバイルのほうにユーザーが寄っていった。ここがまた微妙なところで、PCのほうが進んでいるのに、スマホが出てきて逆転されている。これからの大きな課題なんだろうなと思ってます。もうモバイルの世界は、どんどんネイティブの世界にいってるんですから。</p>

<p><strong>白石</strong>：また（ネイティブ→Webという）歴史は繰り返すのか、というとちょっと疑問ですね。スマホもWeb中心になっていくのかというと、今のところそうとも思えない。</p>

<p><strong>及川</strong>：モバイルのネイティブアプリだけじゃ解決できないものもあります。まず、ユーザーにインストールしてもらえない。インストしたとしても使えない、という壁が二つあるんですね。</p>

<p>例えば、よくあるグルメの人気アプリ。この近くに何か美味しいお店がないかなと考えたときに、ユーザーがぱっと思い浮かぶのは日本を代表するサービスのサイトなんですね。トップ、2番目、3番目、4番目…までインストしてもらうのは難しい。自分のサービスがグルメ業界の4番目だったとしたら、まずはインストしてもらうことが第一の壁です。</p>

<p>さらにその4番目をインストしてもらったとしても、お店を探したときに、1番目、2番目、3番目で解決しちゃったなら、4番目なんて使わないわけですね。するとインストしても使わないわけです。実際には、検索したほうが便利なことがたくさんありますね。検索して、上から本当にいいものって選んでもらったり、もしくは検索したときにそのサービスが気に入ったら、そこからインストしてもらう導線を張ればいいと。</p>

<p>なので、Webとモバイルはハイブリッドにやったほうがいいんじゃないかと。deep linkingだったりだとか、app indexingだったり。</p>

<p>あとは機能面ですね。Geolocationはモバイルのほうが優れているし、昔はブラウザで正確にとることができなかった。だからGoogleみたいなブラウザベンダーが追い付こうと頑張った。でもそうこうしているうちに、モバイルのほうもパフォーマンスだとかUXだとか、機能面でも追いつこうとしているところだと思います。</p>

<p><strong>白石</strong>：なるほど。WebRTCも当然ながらも、デスクトップアプリやモバイルアプリしかできなかったリアルタイムコミュニケーションを、Webに載せていく流れになるわけですね。</p>

<p><img src="/wp-content/uploads/2016/03/DSC00638.jpg" alt="" width="600" height="393" class="aligncenter size-full wp-image-18467" srcset="/wp-content/uploads/2016/03/DSC00638.jpg 640w, /wp-content/uploads/2016/03/DSC00638-300x196.jpg 300w, /wp-content/uploads/2016/03/DSC00638-207x136.jpg 207w" sizes="(max-width: 600px) 100vw, 600px" /></p>

<h2>IoT・ロボティクスとWebRTCの未来は、民主化するイノベーションになるか</h2>

<p><strong>白石</strong>：次のテーマは、「IoTやロボティクスとWebRTCの未来」です。</p>

<p><strong>夏野</strong>：IoTやロボティクスというと、Webとあまり関係なさそうな感じもするんですけど、実は密接に関係している。結局このセンサーというのは、それ自身がコミュニケーション、特にリアルタイムコミュニケーションすることが前提で作られているからです。</p>

<p>じゃあ、すべてのデバイスごとに全部クライアントを作るのか、みたいな話になってくるわけですけど。受け側はWebでいいわけですね。実際、受け側はRTCでやる。実は、RTCの相手先としては、もうこのセンサーやロボティクスとか、いわゆるIoT系は、見逃しちゃいけない。それどころか、それをメインに考えてもいいんじゃないかと。</p>

<p><strong>及川</strong>：去年、慶應大学SFCのORF（オープンリサーチフォーラム）で、村井純先生がギター弾きたくなったという理由で開催されたイベントがあったんです。いきなり「及川さん、ギター弾かないか？」というメールがきて、「はい」って言っちゃったら、ニコ生で全国中継されるっていう（笑）。そのとき東京ビッグサイトか何かでやっていた楽器フェアとニコファーレをつないで何かやろうとなったときに、WebRTCを使ったんです。そこで面白かったのが、Web MIDIやWeb Audioを活用したORFを使ったこと。Web MIDIは電子楽器をつなぐための規格で、楽器をWebのシーケンサーから鳴らせるものなんですけど、ほかの用途にも使えちゃうんですね。</p>

<p>要は、MIDIでデバイスをコントロールするように、MIDI信号を理解してデバイスが動くようにすることができるわけです。今までの技術の歴史を見たときにも、本来と違う使われ方をして、いろんな技術革新が起きていることっていうのはたくさんあります。民主化するイノベーションというのがあって、本来のベンダーが想定したものとは違ういろんなかたちに、クリエイターやユーザーが発展させていくことがある。WebRTCというか、Web MIDIというのもその一例であり、そういった想定外のケースが出てきても面白いんじゃないかなと思いますね。</p>

<h2>リアルタイムの強みをWebRTCはどう切り拓くか</h2>

<p><strong>夏野</strong>：今の話で感化されて、思い出したことがあります。インターネットが出てきて、動画のアーカイブができるようになったことは、最大のメリットだと、みんな思っているんですよね。つまり、テレビと違って縛られないから、いつでも自分のタイミングで、好きなときに動画を見たり、情報を入手できる。</p>

<p>これがインターネットの最大のメリットだったはずなのに、さっきの村井先生のコンサートや演奏みたいなものは、アーカイブで見ようとする人があんまりいないんですよね。リアルタイムで見るほうが価値があるという、不思議なことが起こっていまして。ニコニコはライブ配信したものを、後からタイムシフトというかたちで見たり、アーカイブにして見たりすることができるんですが、ほとんどの動画でリアルタイムで配信するほうが視聴者多いんですよ。</p>

<p>YouTubeは世界的にダウンロード型で成功している話はありますが、感動のレベルっていうのは、やっぱりリアルタイムのほうが大きいんです。これ、ちょっとジレンマというか、ネットのジレンマなんですけどね。いわゆるネットのよさをある意味放棄しているわけですよね。</p>

<p>ニコニコが何でそうなるかというと、コメントが書けるからなんです。リアルタイムで行ったものに書き込むのと、アーカイブで見ているものに書き込むのでは、盛り上がりが違うんですね。今、この瞬間に、何万人が、俺のコメント見ている、こういいうのを書いたよ、という満足感が違うみたいななんですよ。だから、YouTubeとニコニコというのは、全然違うものとして、完全に両立していて、ユーザーは使い分けちゃうわけなんですけども。そういう世界がWebRTCでも繰り広げられていくんじゃないかって思います。</p>

<p><strong>白石</strong>：面白いですね。Web系の標準化技術は、コモディティ化みたいなインパクトが、割と強調されている。今まですごいお金かかったものが安くできますよ、誰でもできますよみたいな価値観ではなく、リアルタイムならではの価値観というか、リアリティみたいなところの面白さや価値はあるのかもなと、ちょっと思いましたね。</p>

<p><img src="/wp-content/uploads/2016/03/DSC00680.jpg" alt="" width="600" height="347" class="aligncenter size-full wp-image-18476" srcset="/wp-content/uploads/2016/03/DSC00680.jpg 640w, /wp-content/uploads/2016/03/DSC00680-300x173.jpg 300w, /wp-content/uploads/2016/03/DSC00680-207x120.jpg 207w" sizes="(max-width: 600px) 100vw, 600px" /></p>

<p><strong>夏野</strong>：一方で注意しなきゃいけないのは、アメリカはやたらとみんな、パソコンの前に座っているんですよ。自分のオフィスでも、プライベートでも。だから、このRTC系のアプリケーションがいっぱい出てくる。SNSもそうだけどね。それがゆえに、Facebookはモバイル対応遅れたっていう話もあるんですけど。一方で、日本はどうかというと、エンジニアは結構PCの前に座っているけど、それ以外の人はもう基本スマホじゃないですか。だからこそ日本が率先して、モバイルとWebを覆うようなRTCのアプリケーションとか、仕様を決めていくのが重要なんじゃないかと思うんです。</p>

<p><strong>白石</strong>：そういう違いもあって、日本人がよくTwitter使うのかもしれないですね。テキストベースで軽いから、リアルタイムで接することができるし。</p>

<p><strong>及川</strong>：やっぱり、Webってリアルタイムじゃなかったんですよね、ずっと。Webのもともとのオリジンが情報じゃないですか。いわゆるHTMLやCSS、Googleドキュメントとか、情報の多くはめちゃくちゃドキュメントなわけですよ。リアルタイムで複数人数がコミュニケーションできるようになってきたとはいえ、どこか静的なものであったり、もしくは動的だといっても限度があったところだったんですよね。だから、リアルタイムでやりとりできるWebRTCが入ってきて、今までのWebの要素技術とどう組み合わせるかということを、考えてはじめているんじゃないかな。</p>

<p>WebRTCのほうに期待したいのは、今までのWebのコア要素の何かをちゃんと生かせるようなこと。Webのいいところは、マッシュアップだなと。WebRTCをいかにもっとWeb化するかというところに、方向性、将来の発展を考えていくのも面白いんじゃないかなと思います。</p>

<h2>IT企業の経営者は『攻殻機動隊』を読むべき！？</h2>

<p><strong>白石</strong>：なるほど、たしかにマッシュアップはありですね。ところで話は変わりますが、遠隔地でロボットを操作したり、触覚とかも再現するみたいな面白い映画があるそうですね。</p>

<p><strong>夏野</strong>：『サロゲート』という最近のアメリカ映画で、ロボットが人間の社会生活のすべてを代行する近未来を描いているんです。ロボットが生活しているから、何かあってもオペレーターである人間は安全という話なんですが、こういう考え方ってすでに『攻殻機動隊』で描かれているんです。</p>

<p>1989年の漫画なんですけど、現在SFで語られているほとんどすべての要素は、全部1989年の日本のコミック漫画に入っているというのは、すごいですよね。だから、僕は、すべてのIT企業の経営者は『攻殻機動隊』を読めと言っています。残念ながら読んでいる人はほとんどいないですね。</p>

<p><img src="/wp-content/uploads/2016/03/DSC00690.jpg" alt="" width="600" height="407" class="aligncenter size-full wp-image-18469" srcset="/wp-content/uploads/2016/03/DSC00690.jpg 640w, /wp-content/uploads/2016/03/DSC00690-300x203.jpg 300w, /wp-content/uploads/2016/03/DSC00690-207x140.jpg 207w" sizes="(max-width: 600px) 100vw, 600px" /></p>

<p><strong>及川</strong>：医療なんて『da Vinci』でしたっけ。医療ロボットがあるじゃないですか。WebRTCで遠隔医療というかたちを提供できるならば、非常に役に立つだろうのもあるんじゃないかなと。</p>

<h2>コミュニケーション・SNS と、(Web)RTCの未来</h2>

<p><strong>白石</strong>：次のテーマは、「コミュニケーションSNSとWebRTCの未来」です。今のコミュニケーションって、基本的には非同期で、ほぼリアルタイムじゃないものがメインだったと思います。でもTwitter等もどんどんリアルタイム化していって、さっきのニコニコの話も出ていましたが、リアルタイムのコミュニケーションの分野にも進化を掘り起こすのではないかなというお話です。</p>

<p><strong>夏野</strong>：SNSの使い方はリアルタイム型に変わってきましたね。Facebookを見ていると、もう誰がオンライン中かわかってしまう。その人からすぐに返答が返ってこない時点で、それが一つのメッセージになっちゃいますよね。</p>

<p><strong>白石</strong>：たしかに。</p>

<p><strong>夏野</strong>：奥さんや上司からの連絡に、オンライン状態にあるのに返事がないと、非常に面倒くさいことになるよね（笑）。でも時代の流れとして、やっぱり同期するほうに向かっている。だから、LINEの既読というのはウザがられながらも、貴重な機能として、みんな受け入れられているんだと思う。</p>

<p>ウザがられても受け入れられるのは、すべてのアプリケーションがそうなってく可能性であって、面白いですよね。昔、携帯電話が出てきたときに、ほとんどのサラリーマンが嫌だと言ったんですけど、みんな受け入れました。だから、もう受け入れた上で何ができるかを考える方が生産的ですね。</p>

<p><img src="/wp-content/uploads/2016/03/natsuno5.jpg" alt="" width="600" height="403" class="aligncenter size-full wp-image-18483" srcset="/wp-content/uploads/2016/03/natsuno5.jpg 640w, /wp-content/uploads/2016/03/natsuno5-300x202.jpg 300w, /wp-content/uploads/2016/03/natsuno5-207x139.jpg 207w" sizes="(max-width: 600px) 100vw, 600px" /></p>

<p><strong>及川</strong>：ツールとして本来目的にしていることと、どう使われるかかにずれが出てくることがありますよね。本来、同期型リアルタイムであるチャットを今言ったみたいに、既読だとかを一切抜きにして言うと、それをリアルタイムとか同期型に使わないことも増えてきていると。</p>

<p>その象徴が、エンジニアの方はよく使っていると思われるSlack。Slackは基本チャットなんだけど、普通のチャットと言われるには、非常に違和感を感じます。もちろんな同期型のコミュニケーションのフロー型と言われ、ストック型じゃないとは言われているんだけど、一方でストック的にも使えたりとか、非同期的なコミュニケーションもしっかりできる。ある時間をすぎると勝手にスリープモードに入るようになったりする機能が入ったりしていて、コミュ障のエンジニアに優しいようなところがあったりする。</p>

<p>だから、RTCもリアルタイムということと、もう一つの非同期と言う言葉が、必ずしも一緒じゃないんじゃないかなと思うんですね。</p>

<h2>ビデオ会議はWebRTCでどう変わるか</h2>

<p><strong>白石</strong>：ちなみに、お二人は海外の方とテレカン（teleconference）やビデオ会議を結構されてたと思うんですが、そういった観点でWebRTCは、どうなんでしょうか。</p>

<p><strong>及川</strong>：電話とビデオが入ったものとの違いは、すごく大きいんですよ。やはり、その五感が伝わったほうがコミュニケーションしやすい。映りたくないからビデオをOFFにしますって、やった途端にその人のプレゼンスは、その会議で下がっちゃうんですよね。顔がちょっと映るというだけでも、バリューはかなりのものがあると思います。</p>

<p>ただし、こういったインターネットのコミュニケーションは技術が発達したとしても、現地に行かなければだめなことが、山ほどあって。常にマイクとカメラがあるわけではないという話もあるですが、要は時差ですね。その人と同じ時間帯でどのぐらい働けるか、空気を共有できるかというのは、絶対にインターネットとかIT技術では解決できない。なので、RTC的なものがどんなに発達したとしても、フェイストゥフェイスでの重要性というのは、まあ、なくならないのかな。いいのか、悪いのか、ちょっとわからないですけど。</p>

<p><strong>白石</strong>：フェイストゥフェイスはビデオ会議よりも、同じ部屋にいたほうが伝わるものが多いというのはありますね。顔も見えているし、表情も全部見えているのに、何か伝わらないみたいな。そういう感覚はありませんか。</p>

<p><strong>夏野</strong>：それは技術的な問題もありますが、面白い話があるんです。Haloというテレビ電話会議のシステムをHPが販売していたんですよ。実は、これを開発したのが映画会社。離れた場所での会議でもリアルな感覚を伝えるために、スクリーンに映る顔のサイズが、本当に座っているのと完全に同じように映るような仕組にした。ここで見ると、向こう側に本当に人がいる感じががある。</p>

<p>イスとか机も、全部そろっていて4カ所までつなげる。4カ所つなぐと、円テーブルに座っている感じにちゃんと映るようになっていて、上のモニターは資料とかを映すんですね。この3面で誰が誰を見ているのが、完全にわかります。これ、飲み会できますよ、マジで。でもこの仕組が異常に高いので、WebRTCが必要なんです。</p>

<p><img src="/wp-content/uploads/2016/03/DSC00704.jpg" alt="" width="600" height="399" class="aligncenter size-full wp-image-18472" srcset="/wp-content/uploads/2016/03/DSC00704.jpg 640w, /wp-content/uploads/2016/03/DSC00704-300x200.jpg 300w, /wp-content/uploads/2016/03/DSC00704-207x138.jpg 207w" sizes="(max-width: 600px) 100vw, 600px" /></p>

<p><strong>白石</strong>：人間はサイズとか、目線とか、そういう情報からもいろいろ得ているんですね。WebRTCは、それを再現できると。</p>

<p><strong>夏野</strong>：これはすごいよくできていますよ。</p>

<p><strong>及川</strong>：でも、逆もあるかなと思うんですね。リアルには会わないということ。いくつか軸があるんですけど、一つはリモートワーク。例えば、GitHubという会社は全員リモートなんですね。だから、どこが主従かというのはないし、リモートを前提にしたコミュニケーションが成り立つ。</p>

<p>もう一つは仮想現実というか、さっきの「アバター」的な話でいうと、リアルに会ったほうが、面倒くさいことってたくさんあるわけですよ。だから、自分の化身がネット上にいて、その人たち同士で話をしますと。その結果、ちゃんと自分が、リアルの世界に持って帰って、何かやりますみたいな、そういったコミュニケーションもあるんじゃないかなと思うんですね。</p>

<p><strong>夏野</strong>：さっきの映画『サロゲート』とか『攻殻機動隊』がそうかな。その世界ではものすごい美人なんだけど、男かもしれない。実は80歳かもしれない。そのほうが見た目に惑わされず、意見とか人間の本質的なメッセージをちゃんと受け取れるということですよね。</p>

<p><img src="/wp-content/uploads/2016/03/DSC00691.jpg" alt="" width="600" height="414" class="aligncenter size-full wp-image-18470" srcset="/wp-content/uploads/2016/03/DSC00691.jpg 640w, /wp-content/uploads/2016/03/DSC00691-300x207.jpg 300w, /wp-content/uploads/2016/03/DSC00691-207x143.jpg 207w" sizes="(max-width: 600px) 100vw, 600px" /></p>

<p><strong>夏野</strong>：冒頭のリアルタイムコミュニケーションの話ですが、実はW3Cでの重要な会議って、全部IRCを使っているんですね。で、発言の要旨を打つ人がいるんですよ。国際的な会議なので、ネイティブなみに英語ができないけど、技術的にはすごい優秀だったりする人もいる。ちゃんと文字化してくれると、話についていけなかったときも、議事録を見て、ちょっと遅れてついてきてくれる。これ、リアルタイムコミュニケーションのすごくいい事例だなと思っています。GoogleがやっているようなWebで自動翻訳されるようになったら、相当、国際会議は楽になるなと。あれ、いいカルチャーですね。</p>

<p><strong>白石</strong>：Google I/Oでもやってましたね。僕も参加したとき、それに感銘を受けました。</p>

<p><strong>及川</strong>：アメリカはそういったキャプチャー的なものが進んでいて、ハンディキャップがある方に、平等な機会を与えるという考えにもつながっているんです。私もTPACとかでW3Cの会議に行ったことあるんですけど、すごい助かりますね。いろんな人がしゃべっていると、追いつかないとこがあるんですが、ログ見ればちゃんと確認できる。</p>

<p>そのときに、もし話すのがちょっと厳しかったら、そこのIRC上で「さっきのところ、実はこうだったよ。今確認したよ」って書いとくだけで、きちっと皆さん読んでくれるし。ログにも残るし、いろんな国から、いろんな言語がネイティブの人が入るときには、非常にいい仕組じゃないかなと思いますね。</p>

<p><strong>夏野</strong>：だからこういうのは、日本こそ、僕は頑張ってほしいなと思っていて。日本人が、一番損しているわけじゃないですか、コミュニケーションってさ。これを技術で解決しようというのを、あんまり困っていない、アメリカの会社がやるより、日本の会社がもっと真剣にやったほうがいいんじゃないかと思うんですけど。</p>

<p><strong>白石</strong>：ぜひ、やっていただきたいですね！</p>

<h2>仮想現実／拡張現実と、WebRTCの未来</h2>

<p><strong>白石</strong>：次は仮想現実、拡張現実みたいなお話をお聞きしたいんですけど。</p>

<p><strong>夏野</strong>：さっきの話って、拡張現実に近いですよ。拡張現実って、その上にアニメキャラを出して何か言わせることだけじゃなくて、リアルに会話しているところに、それが全部テキスト化されるなんていったら、それは拡張現実じゃないですか。</p>

<p>だから、あんまりSFチックなファンタジックな方向に考えないで、もうちょっとリアリスティックに考えると、拡張現実はもっと現実を拡張していくわけなので、もっとあっていいと思うね。ARが何に使えるかというのは、もう誰もまともなソリューション出せないままきてしまったから。でも、よく考えると、ARを使うとここに字幕が出る。これは十分面白いよね。</p>

<p><strong>白石</strong>：たしかに役に立ちますね。</p>

<p><strong>及川：</strong>さっきのSlackは単純なチャットシステムであるのと同時に、いろんなかたちでbotが使えるわけですよね。そのbotに話しかけることによって、継続的インテグレーションして始めるのも面白い。私たちの会社だと、ランチ行きたいって言ったら、ランチのおすすめ教えてくれたりだとか、その時間になると「今日の掃除当番、誰がほうきです、掃除してください」って言ってくれる。</p>

<p>テキストベースでもいろんなことができるし、今、夏野さんがおっしゃったみたいに、ARやVRbotみたいなものが、WebRTCのクラウドとして、いろんな振る舞いしていったりすることが、もう拡張現実の世界になってくるんだと思うんですよね。</p>

<p><img src="/wp-content/uploads/2016/03/oikawa4.jpg" alt="" width="600" height="393" class="aligncenter size-full wp-image-18480" srcset="/wp-content/uploads/2016/03/oikawa4.jpg 640w, /wp-content/uploads/2016/03/oikawa4-300x196.jpg 300w, /wp-content/uploads/2016/03/oikawa4-207x136.jpg 207w" sizes="(max-width: 600px) 100vw, 600px" /></p>

<p><strong>白石</strong>：なるほど。</p>

<p><strong>及川</strong>：実はbotだらけでもいいんじゃないかなと思うことがたくさんあって。人間に話すと面倒くさいことなどは、とっても頭のいいbot、もしくは、人工無脳的にちょっとばかなbotとコミュニケーションするほうが、いろいろ円滑に進めるところがある気がします。テキストだけでなく、RTCの世界でも出てきたりするといいんじゃないかなと思いますね。</p>

<h2>次のデバイスとして期待しているものは？</h2>

<p><strong>白石</strong>：会場からの質問も受け付けたいと思います。</p>

<p><strong>Q:次のデバイスとして期待しているものってありますか？</strong></p>

<p><strong>夏野</strong>：どれぐらいのレンジで考えるかですが、僕はこの30年以内に全部デバイスはなくなると思っています。だから、完全に電脳。電脳の中でWebを浮かべれば、Webが浮かぶし、情報は全部で取れるし、送ろうと思えばすぐ送れることになる。ウェアラブルとかVRモノも出ているけど、期待するのはノーデバイス。あとは、『攻殻機動隊』を読んでください。</p>

<p><strong>及川</strong>：逆張りで言わせていただくと、紙ですね（笑）。要は、やっぱりアナログって強いんですよ。だから、ペンでもって自分で何かを書いてとかっていうのもあるし、目にも優しいですので。今の紙、そのものじゃないかもしれないけれども、こういった紙をメタファーにしたようなデバイスは、インターネットにおいて、ずっと取り残されている。確か、ペーパーレスというキーワードとともに、そのIT化、コンピュータ化の中で、過去のデバイスにされているんだけれども、実は再発明されるべきものじゃないかな。紙みたいなデバイスにRTCが入ってきたら、何かできるのかと期待します。</p>

<p><img src="/wp-content/uploads/2016/03/DSC00637.jpg" alt="" width="600" height="354" class="aligncenter size-full wp-image-18474" srcset="/wp-content/uploads/2016/03/DSC00637.jpg 640w, /wp-content/uploads/2016/03/DSC00637-300x177.jpg 300w, /wp-content/uploads/2016/03/DSC00637-207x122.jpg 207w" sizes="(max-width: 600px) 100vw, 600px" /></p>

<h2>WebRTCに期待したいことは？</h2>

<p><strong>白石</strong>：最後にWebRTCに絡めた何か一言づつお願いします。</p>

<p><strong>夏野</strong>：さんざん出してきましたが『攻殻機動隊』です。つまりね、日本のエンジニアはものすごい短視眼的になりがちなんですよ。だけどやっぱり20年先、10年先に何がありそうか、あってほしいか。ありそうかを予測するのは面倒くさいけれど、あってほしいかと言う文脈から戻ってWebRTCを考えることが、そのデバイスをどう考えるとかという発想前にしてほしいんですよね。そうしないと、何か突き抜けられないし、つまらないね。あとは『攻殻機動隊』を読んでください。</p>

<p><strong>及川</strong>：僕は最近、botがすごい好きなんですね。だから、RTCでbot、しかも今誰かがサポートしてくれるんじゃなくて、僕の代わりに全部やってくれると。だから、朝早くビデオ会議とかだと面倒くさいので、もう僕の振る舞いを覚えてくれたら勝手に動いてくれるような、そういった世界観が来るといいなと思います。</p>

<p><strong>白石</strong>：皆さん、楽しんでいただけましたでしょうか。楽しんでいただけた方は、大きな拍手で終わりたいと思います。どうもありがとうございました。（盛大な拍手！）</p>

<p><img src="/wp-content/uploads/2016/03/IMG_5134.jpg" alt="" width="600" height="369" class="aligncenter size-full wp-image-18485" srcset="/wp-content/uploads/2016/03/IMG_5134.jpg 600w, /wp-content/uploads/2016/03/IMG_5134-300x185.jpg 300w, /wp-content/uploads/2016/03/IMG_5134-207x127.jpg 207w" sizes="(max-width: 600px) 100vw, 600px" /></p>
]]></content:encoded>
			</item>
	</channel>
</rss>
