WebOS Goodies

WebOS の未来を模索する、ゲームプログラマあがりの Web 開発者のブログ。

WebOS Goodies へようこそ! WebOS はインターネットの未来形。あらゆる Web サイトが繋がり、共有し、協力して創り上げる、ひとつの巨大な情報システムです。そこでは、あらゆる情報がネットワーク上に蓄積され、我々はいつでも、どこからでも、多彩なデバイスを使ってそれらにアクセスできます。 WebOS Goodies は、さまざまな情報提供やツール開発を通して、そんな世界の実現に少しでも貢献するべく活動していきます。
Subscribe       

Hangout 上でアプリケーションを構築できる Google Hangouts API の使い方(v1.1 対応版)

ご存じの方も多いかと思いますが、先日 DevFestX Japan 2012 Summer というイベントが開催されました。 Google I/O 2012 に参加した API Expert の方々がその報告をするという内容だったのですが、日本全国 7 箇所の会場(+ US 一箇所w)を Hangout で繋ぎ、さらに Hangout On Air を使ってリアルタイムで中継するという、画期的な試みを行いました。結果は大成功で、それぞれの会場での講演が見事に全会場に中継され、質疑応答もバッチリでした。各地域での盛り上がりが生で感じられて、とても得がたい経験だったと思います。

このように強力なコミュニケーションツールである Hangout ですが、実はそれだけではありません。 Google Hangouts API という API セットが公開されており、 Hangout と連携し、それを拡張するアプリケーションが開発可能になっています。例えば映像の上に任意の画像をオーバーレイ表示したり、音声・映像のミュートや音量などといった制御を自動で行うことが可能です。

実はこの API の公開直後に使用方法を紹介したのですが、ほどなくして API の仕様が大幅に変更されてしまい、役に立たなくなっていました。その後さらに多くの機能が追加され、開発した Hangout アプリを一般公開することも可能になったのですが、なかなか時間が取れずに追従できていませんでした。そこで、今回一念発起して内容を改定し、最新版の機能を網羅して再掲載することにしました。グループビデオチャットという究極のリアルタイム Web の世界を、ぜひ味わってみてください!

Hangouts API で Hello World

まずは、「Hello World」と表示するだけの Hangout アプリを例にして Hangouts API による開発方法をご紹介します。 Hangout アプリにはメイン画面で動作する「Main application」とサイドバーで動作する「Extension」の 2 種類があるのですが、ここでは Main application を前提に解説しています。ただ、両者のプログラミングモデルはほぼ同じなので、ほとんどの知識は Extension にも流用できるはずです。

Hangouts API を有効にする

Hangouts API を利用するには、 APIs Console でプロジェクトを作成し、 Hangouts API を有効にしなければいけません。 APIs Console は Google の各種 API の管理をするためのページで、 OAuth 2.0 のクライアントキーの発行や API トラフィックの確認・フィルタリングなどが行えます。 Hangouts API では、 API 自体の有効化とガジェット XML の登録、そしてアプリケーションの公開のために APIs Console を利用します。

以下のリンクから、 APIs Console を開いてください。

https://code.google.com/apis/console

もし APIs Console を使うのが初めてであれば、以下の画面が表示されると思います。「Create project…」という青いボタンをクリックして、最初のプロジェクトを作成してください。もし過去に APIs Console を使ったことがあれば、既存のプロジェクトが使い回せるので、新たに作る必要はありません。

プロジェクトを作成したら、以下の画面が表示されるはずです。左のメニューから「Services」を選択し、「Google+ Hangouts API」の項目を ON に変更してください。

利用規約が表示されるので、「I agree to these terms.」にチェックして、「Accept」をクリックしてください。

元の画面に戻ります。「Google+ Hangouts API」が ON になり、左のメニューに「Hangouts」という項目が増えているのがわかると思います。

これで Hangouts API が有効になりました。

ガジェット XML を作成

前述のとおり、 Hangouts API は Google ガジェット(OpenSocial ガジェット)をベースにしています。したがって、アプリケーションのメインとなるソースコードはガジェット XML になります。 Hello World のガジェット XML はこんな感じになるでしょう。

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="Hello World">
    <Require feature="rpc"/>
    <Require feature="views"/>
  </ModulePrefs>
  <Content type="html">
    <![CDATA[
<!DOCTYPE html>

<script src="//hangoutsapi.talkgadget.google.com/hangouts/_/api/hangout.js?v=1.1"></script>

<h1>Hello World!</h1>

    ]]>
  </Content>
</Module>

Google ガジェットの開発方法については Google のドキュメントなどを参照してください。基本的には、上記の Content タグ内に BODY タグの内容を書いておけば、それがそのまま表示されると考えておけば良いでしょう。 Content タグ内には、 hangout.js を読み込む SCRIPT タグを必ず入れるようにしてください。

ガジェット XML ができたら、それを公開 Web サーバー上にアップロードします。面倒なら、以下の URL で同じ内容を公開しているので、こちらを利用していただいても OK です。

https://webos-goodies.googlecode.com/svn/trunk/blo...

アプリケーションを起動する

作成した Hangout アプリは、まず「Developer Sandbox」という環境で実行し、デバッグなどを行います。これは APIs Console から起動する特殊な Hangout で、プロジェクトのチームメンバしかアクセスできないほか、アプリケーションの再読み込みや共有ステートのリセットなどの開発者向け機能が実装されています。

Developer Sandbox を使用するには、先ほど作成したガジェット XML の URL を、 APIs Console に登録しなければなりません。 APIs Console の左のメニューで「Hangouts」を選択し、「Application URL」にガジェット XML の URL を、「Title」にアプリケーションのタイトルを入力してください。

入力したら、ページの一番下に移動し、「Save」ボタンで保存します。

正しく保存されると「Enter a hangout」というリンク表示されます。これをクリックすると Hangout を開始し、その上で作成した Hangout アプリが起動します。以下は Hello World アプリケーションを起動したところです。

このように、メインのビデオ画面の上に被さるようにアプリケーションが表示されます。また上部にある 3 つのボタンにより、他のアプリケーションを読み込んだり、アプリケーションをリロードしたり、後述の共有ステートの内容をリセットするといったことが可能です。

Developer Sandbox で起動した Hangout に他のユーザーを招待するには、 Google+ のチャットなどで URL を直接伝えてください。あらかじめ APIs Console でチームメンバに加えておくのも忘れずに(「Team」タブで登録できます)。

アプリケーションの公開

自分の Hangout アプリが完成したら、一般ユーザーが利用できるように公開できます。 APIs Console のチームメンバ以外のユーザーでも利用できるようになり、アプリケーションを利用した Hangout を開始できる「Hangout ボタン」を自分のサイトなどに配置できます。以下でその手順をご紹介します。

Chrome Web Store での開発者登録

Hangout アプリを公開するには、 Chrome Web Store での開発者登録が必要です。将来的に Chrome Web Store の仕組みを使った課金とか考えてるんですかね・・・だとしたら嬉しいですね。

Chrome Extension などを作ったことがあれば既に登録済みでしょうが、そうでない方は以下のページで登録してください。

https://chrome.google.com/webstore/developer/dashb...

Google アカウントで登録した後、下の方に表示されるリンクから登録手数料 (US$5.00) を支払って、アカウントをアクティベートしてください。

読み込む JavaScript ファイルを変更する

先ほどガジェット XML で読み込んだ hangout.js ファイルは Developer Sandbox 用のものです。したがって、公開の際には公開用のファイルに差し替えなければなりません。具体的には、 script タグを以下のように書き換えます。

<script src="//talkgadget.google.com/hangouts/_/api/hangout.js?v=1.1"></script>

OAuth 2.0 の Client ID を生成する

Hangout アプリを最初に動作させる際には OAuth 2.0 の認可フローが走ります。そのため、あらかじめ APIs Console で Client ID を生成しておく必要があります。生成方法は以下のとおり。

  1. APIs Console の左サイドバーで「API Access」を選択。
  2. 「Create an OAuth 2.0 client ID...」という大きな青いボタンをクリック。
  3. Product name にアプリケーション名を入力。さらに Product logo にロゴ画像の URL を入れておけば、認可画面に表示されます。終わったら「Next」をクリック。
  4. Application type は Web application 、 Your site or hostname は localhost にでもしておけばよいでしょう。もし自分のドメインを持っていれば、それを入力しても OK です。
  5. 最後に「Create client ID」をクリックすれば、 Client ID が生成されます。

Hangout アプリの場合、認可画面への遷移や認可後のアクセストークンの取得などはすべて Hangout 側が処理してくれるので、開発者はなにもする必要はありません。上記で登録したリダイレクト用の URL も(Hangout アプリで使う限りにおいては)使われることはないので、実際に用意する必要はありません。

APIs Console の残りのフィールドを埋める

Developer Sandbox を利用する際にガジェット XML の URL などを入力した、 APIs Console の Hangout のフォームですが、未入力の項目が多数あるかと思います。公開時には、それらの項目をなるべく埋めておきましょう。とくに Terms of Service, Privacy Policy, Support Contact Information の各 URL の記入は必須です。参考までに、各項目の意味を簡単に説明しておきます。

Application URL
既にご存知のとおり、ガジェット XML の URL を入力します。
Application Type
アプリケーションをメイン画面を専有して表示するか (Main application) 、サイドバーで表示するか (Extension) を選択します。
Additional OAuth Scopes
チェックを入れるとテキストボックスが表示されるので、そこに Google API の scope を入れておけば、 Google APIs Client Library for JavaScript でアクセスできます。サンプルコードが hangout-experiments で公開されているので、参考にすると良いでしょう。
Locale
以下の Title, Application description, Icons のロケールを指定するドロップダウンです。ちょっとわかりにくいですが、例えばこのドロップダウンで Japanese を選択して下の 3 つの項目を入力すると、日本のユーザーに対してそれらのデータが表示されます。続けて Korean のロケールを選択すると下の 3 つの項目がクリアされ、韓国のユーザー向けの内容が入力できます。このようにして、複数のロケールのタイトル等が指定できるようになっています。入力のないロケールのユーザーに対しては、 Default ロケールの内容が表示されます。
Title
アプリケーションの選択画面などに表示されるアプリケーション名です。
Application description
同じく、アプリケーションの説明文です。
Icons
16x16, 16x16(モノクロ), 32x32, 220x140 のそれぞれのロゴ画像の URL を指定します。省略した場合はデフォルトのロゴが表示されます。
Terms of Service
利用規約を掲載した Web ページの URL を指定します。 OAuth 2.0 の認可画面の下にリンクが表示されます。
Privacy Policy
プライバシーポリシーを掲載した Web ページの URL を指定します。同じく OAuth 2.0 の認可画面の下にリンクが表示されます。
Support Contact Information
サポートページの URL を指定します。・・・が、どこに表示されているのかわかりません ^^;

以上の項目を入力し、最後に一番下の「Make your application available to all users」にチェックを入れて「Save」をクリックすれば、アプリケーションが誰でも使える状態になります。

Hangout ボタンを Web ページに掲載する

公開したアプリケーションを利用した Hangout を開始するためには、以下の URL を開けば OK です。「プロジェクトID」は、 APIs Console の URL の「#project:」以降にある数値を指定してください。

https://plus.google.com/hangouts/_?gid=<プロジェクトID>

このリンクを普通に Web ページに掲載しても良いのですが、公式の「Hangout ボタン」が用意されているので、それを利用するとわかりやすいです。 Hangout ボタンを表示するには、以下の HTML コードを Web ページに挿入します。

<a href="https://plus.google.com/hangouts/_?gid=<プロジェクトID>" style="text-decoration:none;">
  <img src="https://ssl.gstatic.com/s2/oz/images/stars/hangout/1/gplus-hangout-<サイズ>-normal.png"
    alt="Start a Hangout"
    style="border:0;<スタイル>"/>
</a>

サイズ、スタイルの部分にはそれぞれ以下の文字列を指定できます。

サイズスタイル
15x79width:79px;height:15px;
20x86width:86px;height:20px;
24x100width:100px;height:24px;
60x230width:230px;height:60px;

例として、以降で解説する各機能のサンプルコードを含めた Hangout アプリのボタンを掲載しておきます。参考にしてください。

Start a Hangout

なお、ユーザーが一度 Hangout ボタン経由で Hangout アプリを使えば、通常の Hangout でも上部のツールバーや「アプリケーションを追加」画面の「最近」タブに登録されるので、そこから直接起動できます。

API リファレンス & コード例

Hangouts API では、通常の Google ガジェットの機能に加えて Hangout と連携するための独自 API が利用できます。参加者の情報取得はもちろん、ビデオ表示のカスタマイズやリアルタイムのデータ共有など、興味深い機能が満載です。以降では、それらの API について簡単なリファレンスとコード例をご紹介していきます。これを読んでいただけば、 Hangouts API の使い方がほぼ理解できるはずです。

なお、記事に掲載しているコード例は JavaScript の部分のみで、対応する HTML は省略しています。それらを含めた完全なガジェットは以下の場所で公開していますので、ご利用ください。

https://webos-goodies.googlecode.com/svn/trunk/blo...

また、 JavaScript コードでは jQuery を利用していますので、あらかじめご了承ください。

API の初期化 (gapi.hangout)

多くの Hangouts API の機能を呼び出すためには、非同期で行われる API の初期化が完了するのを待たなければなりません。したがって、アプリケーションの初期化は以下のように 'onApiReady'' イベントで行います。

gapi.hangout.onApiReady.add(function(e) {
  // アプリケーションの初期化コード
});

Hangout では、参加者が随時出入りするなど、状況が刻々と変化するため、初期化以外でもほとんどの処理はイベントによって行われます。イベントハンドラには引数としてイベントオブジェクト(以下、変数名 e で表します)がひとつ渡され、そこにイベントに付随する情報が格納されます。上記の onApiReady イベントならば、 e.isApiReady に API が利用可能かどうかを示す boolean 値(常に true でしょうが・・・)が格納されています。

API 初期化関連のメソッドとしては、他に以下のものがあります。

isApiReady()
Hangout API が初期化されていたら true を返す。
onApiReady.add(callback)
Hangouts API が利用可能になったときに呼び出されるコールバック関数を追加する。もし API が初期化済みであれば、コールバックは次回のイベント・ディスパッチで呼び出される。
onApiReady.remove(callback)
指定されたコールバック関数を削除する。

基本機能 (gapi.hangout)

現在の Hangout の ID 、参加者の ID 、ロケール、アプリケーションといった Hangout の基本的な情報を取得するための API です。

getHangoutUrl()
Hangout の URL を返します。
getHangoutId()
Hangout の期間中ユニークな ID を返す。他の Hangout との識別に使えますが、再利用される可能性があるので注意してください。
getLocale()
この Hangout の参加者のロケールを返す。例 : 'en'
getStartData()
URL の gd パラメータで指定された文字列を返す。詳細はこちらを参照。
getParticipantId()
ローカル参加者の ID を返す。それぞれの参加者は Hangout に参加するたびに新しい Hangout ID を割り振られる。
hideApp()
アプリケーションを非表示にし、メインウィンドウにビデオを表示する。非表示の間もアプリケーションは動作し続ける。
isApiReady()
API が初期化済みなら true を返す。
isAppVisible()
アプリケーションが表示中なら true を返す。
isPublic()
公開 Hangout なら true を返す。
onAppVisible.add(callback)
アプリケーションの表示状態が変更された際のコールバック関数を追加する。
onAppVisible.remove(callback)
アプリケーションの表示状態が変更された際のコールバック関数を削除する。
onPublicChanged.add(callback)
Hangout が公開された際のコールバック関数を追加する。
onPublicChanged.remove(callback)
Hangout が公開された際のコールバック関数を削除する。

これらの API の使用例を以下に示します。 Hangout の URL や参加者の ID 等を表示し、ボタンでアプリケーションを隠すこともできるようにしています。

gapi.hangout.onApiReady.add(function(e) {
  $('#hangout-url').text(gapi.hangout.getHangoutUrl());
  $('#hangout-id').text(gapi.hangout.getHangoutId());
  $('#locale').text(gapi.hangout.getLocale());
  $('#startdata').text(gapi.hangout.getStartData());
  $('#participant-id').text(gapi.hangout.getParticipantId());
  $('#hide-app').click(function() { gapi.hangout.hideApp(); });
});

参加者情報の取得 (gapi.hangout)

Hangouts API には、 Hangout に参加しているユーザーの情報を取得する機能があります。取得できるユーザー情報 (Participant) は Hangouts API の People のサブセットになっていて、以下のフィールドが含まれます。ただし、常に有効なフィールドは idhasAppEnabled のみで、他のフィールドは該当する参加者がアプリケーションを実行していないと取得できません。

フィールド名データ型説明
idstring参加者を識別する id 。 Google+ ID とは異なります。
displayIndexnumber画面下部の参加者リストでの表示順
hasAppEnabledbooleanもし参加者が現在のアプリケーションを実行していれば true。
hasMicrophonebooleanマイクを接続していれば true。
hasCamerabooleanカメラを接続していれば true。
person.idstring参加者の Google+ ID 。プロフィールページの URL に含まれる ID と同じ。
person.displayNamestring表示に適した参加者名。
person.image.urlstring参加者の画像の URL 。

参加者情報を取得・追跡するためのメソッドとして、以下のものが用意されています。

getParticipants()
ハングアウト参加者を Participant の配列で返す。参加者のリストは現在のサーバーの状態を反映しているので、ローカル参加者が返り値の配列の中に含まれるまでには、若干のタイムラグがある。
getParticipantById(participantId)
participantId に対応する参加者を Participant オブジェクトで返す。
onParticipantsAdded.add(callback)
参加者が追加された際に呼び出されるコールバック関数を追加する。 e.addedParticipants で追加された参加者の配列にアクセスできる。
onParticipantsAdded.remove(callback)
参加者が追加された際に呼び出されるコールバック関数を削除する。
onParticipantsChanged.add(callback)
参加者に変更があったときに呼び出されるコールバック関数を追加する。 e.participants で現在の参加者の配列にアクセスできる。
onParticipantsChanged.remove(callback)
参加者に変更があったときに呼び出されるコールバック関数を削除する。
onParticipantsRemoved.add(callback)
参加者が Hangout を抜けた際に呼び出されるコールバック関数を追加する。 e.removedParticipants で抜けた参加者の配列にアクセスできる。
onParticipantsRemoved.remove(callback)
参加者が Hangout を抜けた際に呼び出されるコールバック関数を削除する。

これらの API を利用して、参加者のリストを表示する例を以下に示します。アプリケーション起動時の参加者リストを表示するだけでなく、 onParticipantsChanged のイベントで随時更新されるようにしています。

function updateParticipants(participants) {
  var parentEl = $('#participants').empty();
  $.each(participants, function() {
    var name = this.id;
    if(this.person && this.person.displayName)
      name = this.person.displayName + ' (' + this.person.id + ')';
    $('<li/>').text(name).appendTo(parentEl);
  });
}

gapi.hangout.onApiReady.add(function(e) {
  updateParticipants(gapi.hangout.getParticipants());
  gapi.hangout.onParticipantsChanged.add(function(e) {
    updateParticipants(e.participants);
  });
});

アプリケーションのインストール情報 (gapi.hangout)

前述のとおり、 Hangouts API では参加者が該当アプリケーションを実行しているかどうかによって取得できる情報が制限されます。したがって、どの参加者がアプリケーションをインストールしているかは重要な情報です。そこで、この情報をリアルタイムに追跡できる機能が用意されています。

getEnabledParticipants()
アプリケーション実行中の参加者を Participant の配列で返す。
onEnabledParticipantsChanged.add(callback)
アプリケーション実行中の参加者リストに変更があった際に呼び出されるコールバック関数を追加する。 e.enabledParticipants でアプリケーション実行中の参加者の配列にアクセスできる。
onEnabledParticipantsChanged.remove(callback)
アプリケーション実行中の参加者リストに変更があった際に呼び出されるコールバック関数を削除する。
onParticipantsDisabled.add(callback)
参加者がアプリケーションの実行を中断した際に呼び出されるコールバック関数を追加する。 e.disabledParticipants でアプリケーションの実行を中断した参加者の配列にアクセスできる。
onParticipantsDisabled.remove(callback)
参加者がアプリケーションの実行を中断した際に呼び出されるコールバック関数を削除する。
onParticipantsEnabled.add(callback)
参加者がアプリケーションの実行を開始した際に呼び出されるコールバック関数を追加する。 e.enabledParticipants でアプリケーションの実行を開始した参加者の配列にアクセスできる。
onParticipantsEnabled.remove(callback)
参加者がアプリケーションの実行を開始した際に呼び出されるコールバック関数を削除する。

以下はアプリケーションをインストールした参加者のみを表示する例です。

function updateAppParticipants(participants) {
  var parentEl = $('#app-participants').empty();
  $.each(participants, function() {
    if(this.hasAppEnabled) {
      var name = (this.person && this.person.displayName) || this.id;
      $('<li/>').text(name).appendTo(parentEl);
    }
  });
}

gapi.hangout.onApiReady.add(function(e) {
  updateAppParticipants(gapi.hangout.getEnabledParticipants());
  gapi.hangout.onEnabledParticipantsChanged.add(function(e) {
    updateAppParticipants(e.enabledParticipants);
  });
});

AV機器の情報の取得 (gapi.hangout.av)

gapi.hangout.av 名前空間には、 Hangout で使用する AV 機器(カメラ、マイク、スピーカー)の状態取得・制御を行うメソッドが実装されています。さまざまな機能がありますが、まずは機器が利用可能かどうかを調査する API からご紹介します。

hasCamera()
カメラが接続されていて利用可能ならば true を、そうでなければ false を返す。
hasMicrophone()
マイクが接続されていて利用可能ならば true を、そうでなければ false を返す。
hasSpeakers()
スピーカーが接続されていて、利用可能ならば true を、そうでなければ false を返す。
onHasCamera.add(callback)
カメラをアクティベート・デアクティベートしたときに呼ばれるコールバック関数を追加する。「アクティベート」はカメラが接続されていて利用可能なことを意味する(ミュート状態は関係ない)。 e.hasCamera にカメラのアクティベート状態が boolean 値で渡される。
onHasCamera.remove(callback)
カメラをアクティベート・デアクティベートしたときに呼ばれるコールバック関数を削除する。
onHasMicrophone.add(callback)
マイクをアクティベート・デアクティベートときに呼ばれるコールバック関数を追加する。「アクティベート」はマイクが接続されていて利用可能なことを意味する(ミュート状態は関係ない)。 e.hasMicrophone にマイクのアクティベート状態が boolean 値で渡される。
onHasMicrophone.remove(callback)
マイクをアクティベート・デアクティベートときに呼ばれるコールバック関数を削除する。
onHasSpeakers.add(callback)
スピーカーをアクティベート・デアクティベートしたときに呼ばれるコールバック関数を追加する。「アクティベート」はスピーカーが接続されていて利用可能なことを意味する(ミュート状態は関係ない)。 e.hasSpeakers にスピーカーのアクティベート状態が boolean 値で渡される。
onHasSpeakers.remove(callback)
スピーカーをアクティベート・デアクティベートしたときに呼ばれるコールバック関数を削除する。

あまり使う機会は多くないかと思いますが、特定の機器が必須の処理を行うときには、これらの API であらかじめ接続をチェックしておいたほうが良いかもしれません。以下は各機器の接続状態を表示するコードの例です。

function updateAVStatus() {
  $('#camera-status').text(gapi.hangout.av.hasCamera() ? 'あり' : 'なし');
  $('#microphone-status').text(gapi.hangout.av.hasMicrophone() ? 'あり' : 'なし');
  $('#speakers-status').text(gapi.hangout.av.hasSpeakers() ? 'あり' : 'なし');
};

gapi.hangout.onApiReady.add(function(e) {
  updateAVStatus();
  gapi.hangout.av.onHasCamera.add(updateAVStatus);
  gapi.hangout.av.onHasMicrophone.add(updateAVStatus);
  gapi.hangout.av.onHasSpeakers.add(updateAVStatus);
});

AV 機器のミュート (gapi.hangout.av)

各機器のミュートを制御することも可能です。映像や音声の送信を一時的に停止できます。

clearCameraMute()
カメラのミュート状態を最後に手動で設定された状態に戻す
clearMicrophoneMute()
マイクのミュート状態を最後に手動で設定された状態に戻す
getCameraMute()
カメラが映像を送信していれば true を、そうでなければ false を返す。
getMicrophoneMute()
マイクがミュートされていたら true を、ミュートされていなければ false を返す。
requestParticipantMicrophoneMute(participantId)
participantId で指定された参加者にオーディオをミュートするように促す。
setCameraMute(muted)
映像の送信を開始・中断する。 muted に true を指定すれば映像送信を中断、 false なら再開。
setMicrophoneMute(muted)
マイクのミュート状態を設定する。 muted に true を指定するとミュート、 false ならミュート解除。
onCameraMute.add(callback)
映像の送信を開始・中断したときに呼ばれるコールバック関数を追加する。 e.isCameraMute に映像の送信状態が boolean 値で渡される。
onCameraMute.remove(callback)
映像の送信を開始・中断したときに呼ばれるコールバック関数を削除する。
onMicrophoneMute.add(callback)
マイクのミュート状態が変化したときに呼ばれるコールバック関数を追加する。 e.isMicrophoneMute にミュート状態が boolean 値で渡される。
onMicrophoneMute.remove(callback)
マイクのミュート状態が変化したときに呼ばれるコールバック関数を削除する。

以下は、ワンクリックで映像と音声の送信を停止するエマージェンシーボタン(笑)の実装例です。エマージェンシーボタンをクリックすると、映像がブランクになり、音声もミュートされます。もう一度クリックすれば元に戻ります。

var muted = false;
gapi.hangout.onApiReady.add(function(e) {
  $('#emergency').click(function(e) {
    muted = !muted;
    gapi.hangout.av.setMicrophoneMute(muted);
    gapi.hangout.av.setCameraMute(muted);
  });
});

AV 機器のその他の制御 (gapi.hangout.av)

それぞれの参加者のマイクへの入力レベルを取得・監視したり、映像の代わりに表示するアバターを指定することも可能です。とくにマイクレベルは固定の値ではなくリアルタイムに変化する入力レベルを監視できるので、面白い応用ができそうです。

clearAvater(participantId)
participantId で指定された参加者の映像を復帰させる。
getParticipantAudioLevel(participantId)
participantId で指定した参加者のボリュームを取得する。返り値は 2 要素の配列で、最初の要素に左チャンネル、次の要素に右チャンネルのボリューム値が 0 〜 10 で格納されている。
getAvater(participantId)
participantId で指定した参加者のアバター画像(画面下のビデオフィードに重ねる画像)の URL を返す。もし参加者にアバター画像が指定されていなければ undefined を返す。
getParticipantVolume(participantId)
participantId で指定した参加者のマイクへの入力レベルを 0 から 5 までの数値で返す。
getVolumes()
すべての参加者の入力レベルを取得する。返り値はキーに参加者の ID 、値に参加者のボリュームを格納したオブジェクト。
isParticipantAudible(participantId)
participantId で指定された参加者の音声がミュートされていれば true を返す。
isParticipantVisible(participantId)
participantId で指定した参加者の映像がローカル参加者に見えているなら true を返す。
setAvater(participantId, imageUrl)
participantId で指定した参加者の映像を imageUrl で指定したアバター画像に差し替える。
setParticipantAudible(participantId, audible)
participantId で指定された参加者のミュート状態を変更する。 audible が true ならミュート、 false ならミュート解除。ローカルでの音声出力にのみ影響し、他の参加者が聞く音声には影響しない。
setParticipantAudioLevel(participantId, audioLevel)
participantId で指定された参加者のボリュームを設定する。 audioLevel は数値もしくは 2 要素の配列 [leftVol, rightVol] 。
setParticipantVisible(participantId, visible)
participantId で指定した参加者の可視・不可視を設定する。 visible に true を指定すれば可視に、 false なら不可視になる。
onVolumesChanged.add(callback)
参加者のマイク入力レベルが変化したときに呼ばれるコールバック関数を追加する。 e.volumes にすべての参加者の新しいボリュームを格納したオブジェクトが渡される。
onVolumesChanged.remove(callback)
参加者のマイク入力レベルが変化したときに呼ばれるコールバック関数を削除する。

以下は、各参加者のマイクへの入力レベルを表示する例です。それぞれの参加者のマイクに入力されている信号レベルがリアルタイムで表示されます。

var volumeElementMap = {};
function updateVolume(volumeInfo) {
  for(id in (volumeInfo || {})) {
    var span = volumeElementMap[id];
    if(span) {
      span.text(volumeInfo[id]);
    }
  }
}

function updateVolumeParticipants(participants) {
  volumeElementMap = {};
  var parentEl = $('#volume').empty();
  $.each(participants, function() {
    var span = $('<span />');
    var name = ((this.person && this.person.displayName) || this.id) + ' : ';
    $('<li/>').text(name).append(span).appendTo(parentEl);
    volumeElementMap[this.id] = span;
  });
}

gapi.hangout.onApiReady.add(function(e) {
  updateVolumeParticipants(gapi.hangout.getParticipants());
  updateVolume(gapi.hangout.av.getVolumes());
  gapi.hangout.onParticipantsChanged.add(function(e) {
    updateVolumeParticipants(e.participants);
  });
  gapi.hangout.av.onVolumesChanged.add(function(e) {
    updateVolume(e.volumes)
  });
});

画像のオーバーレイ (gapi.hangout.av.effects)

標準の Hangout アプリのひとつに「Google Effects」というのがあるのをご存知でしょうか。顔に帽子や王冠などの画像を重ねて表示してくれる楽しいアプリですが、これも Hangouts API のgapi.hangout.av.effects 名前空間にあるメソッドで簡単に実現できます。この機能を利用するには、以下の 3 つの手順を踏みます。

  1. gapi.hangout.av.effects.createImageResource() で ImageResource オブジェクトを生成
  2. ImageResource オブジェクトのメソッドで Overlay もしくは FaceTrackingOverlay オブジェクトを生成
  3. setVisible() メソッドで表示開始

gapi.hangout.av.effects.createImageResource() は表示する画像の URL を引数にとり、それを保持する ImageResource オブジェクトを単純に返します。ImageResource オブジェクトには以下のメソッドがあります。

createFaceTrackingOverlay(params)
フェイストラッキングで顔に画像を重ねるための FaceTrackingOverlay オブジェクトを返す。 params には trackingFeature, offset, rotateWithFace, rotation, scale, scaleWithFace のフィールドが指定可能。それぞれの意味は FaceTrackingOverlay の関連メソッド参照。
createOverlay(params)
画像をビデオに重ねるための Overlay オブジェクトを返す。 params には position, rotation, scale.magnitude, scale.reference のフィールドが指定可能。それぞれの意味は Overlay の関連メソッド参照。
getUrl()
画像ファイルの URL を返す。
showFaceTrackingOverlay(params)
createFaceTrakingOverlay() と同じだが、同時に表示を開始する。
showOverlay(params)
createOverlay() と同じだが、同時に表示を開始する。

フェイストラッキングで顔のパーツに合わせて画像を表示するには、 createFaceTrackingOverlay() もしくは showFaceTrackingOverlay() を呼んで FaceTrackingOverlay オブジェクトを生成します。両者の違いは、呼び出しと同時に表示を開始するかどうかだけです。返り値の FaceTrackingOverlay オブジェクトには、以下のメソッドがあります。

getImageResource()
FaceTrackingOverlay オブジェクトを生成した ImageResource オブジェクトを返す。
getOffset()
x と y のフィールドを持つオブジェクトで、画像の位置オフセットを返す。
getRotateWithFace()
画像を顔の角度に合わせて回転させるかどうかを返す。
getRotation()
画像の回転角をラジアンで返す。
getScale()
画像の倍率を返す。
getScaleWithFace()
画像を顔のサイズに合わせてスケーリングするかどうかを返す。
getTrackingFeature()
顔のどのパーツに画像を重ねるかを、後述の FaceTrackingFeature のメンバで返す。
isVisible()
表示中かどうかを返す。
setOffset(value, y)
画像の位置オフセットを指定する。 value が数値の場合はそれを X オフセット、 y を Y オフセットとして扱う。 value がオブジェクトの場合は、その x, y フィールドをそれぞれ X オフセット、 Y オフセットとして扱い、 y は無視する(省略可能)。
setRotateWithFace(shouldRotate)
shouldRotate に true を指定すると、画像を顔の角度に合わせて回転する。 false なら回転しない。
setRotation(rotation)
画像の回転量をラジアンで指定する。
setScale(scale)
画像の拡大率を指定する。
setScaleWithFace(shouldScale)
shouldScale に true を指定すると、顔のサイズに合わせて画像をスケーリングする。 false ならスケーリングしない。
setTrackingFeature(feature)
顔のどのパーツに画像を重ねるかを、後述の FaceTrackingFeature のメンバで返す。
setVisible(visible)
visible に true を指定すると、表示を開始する。 false なら表示を終了する。

これらのメソッドを使うことで、オーバーレイ画像の表示・非表示や、位置、サイズなどを制御できます。逆に、そうした制御が必要なければ、 showFaceTrackingOverlay() を呼び出すだけで返り値は捨ててしまってもかまわないと思います。

createFaceTrackingOverlay() / showFaceTrackingOverlay() の params.trackingFeature パラメータや、 setTrackingFeature() メソッドには、以下の定数が指定できます。かなり細かく制御できるのがわかりますね。

  • gapi.hangout.av.effects.FaceTrakingFeature.LEFT_EYE
  • gapi.hangout.av.effects.FaceTrakingFeature.LEFT_EYEBROW_LEFT
  • gapi.hangout.av.effects.FaceTrakingFeature.LEFT_EYEBROW_RIGHT
  • gapi.hangout.av.effects.FaceTrakingFeature.LOWER_LIP
  • gapi.hangout.av.effects.FaceTrakingFeature.MOUTH_CENTER
  • gapi.hangout.av.effects.FaceTrakingFeature.MOUTH_LEFT
  • gapi.hangout.av.effects.FaceTrakingFeature.MOUTH_RIGHT
  • gapi.hangout.av.effects.FaceTrakingFeature.NOSE_ROOT
  • gapi.hangout.av.effects.FaceTrakingFeature.NOSE_TIP
  • gapi.hangout.av.effects.FaceTrakingFeature.RIGHT_EYE
  • gapi.hangout.av.effects.FaceTrakingFeature.RIGHT_EYEBROW_LEFT
  • gapi.hangout.av.effects.FaceTrakingFeature.RIGHT_EYEBROW_RIGHT
  • gapi.hangout.av.effects.FaceTrakingFeature.UPPER_LIP

フェイストラッキングを使わず、単に固定の位置に画像を表示するには、 ImageResource オブジェクトの createOverlay() / showOverlay() メソッドで Overlay オブジェクトを生成します。 Overlay オブジェクトのメソッドは以下のとおりです。

getImageResource()
FaceTrackingOverlay オブジェクトを生成した ImageResource オブジェクトを返す。
getPosition()
x と y のフィールドを持つオブジェクトで、画像の表示位置を返す。
getRotation()
画像の回転量をラジアンで返す。
getScale()
画像のスケーリング量を magnitude と reference のフィールドを持つオブジェクトで返す。それぞれのフィールドの意味は setScale() 参照。
isVisible()
表示中かどうかを返す。
setPosition(value, y)
画像の表示位置を指定する。 value が数値の場合はそれを X 座標、 y を Y 座標として扱う。 value がオブジェクトの場合は、その x, y フィールドをそれぞれ X 座標、 Y 座標として扱い、 y は無視する(省略可能)。
setRotation(rotation)
画像の回転量をラジアンで指定する。
setScale(value, reference)
画像のスケーリング量を指定する。 value が数値で、 reference が gapi.hangout.av.effects.ScaleReference.WIDTH のときはビデオの横幅に対する倍率、〜.height のときは高さに対する倍率として扱う。 value がオブジェクトの場合は、 magnitude フィールドを倍率、 reference フィールドを gapi.hangout.av.effects.ScaleReference の値として扱う。
setVisible(visible)
visible に true を指定すると、表示を開始する。 false なら表示を終了する。

以下は、テキストボックスに入力された URL の画像をフェイストラッキングで眉間のあたりに重ねる例です。オーバーレイ開始後の制御はとくに必要ないので、 showFaceTrackingOverlay() の返り値は保持していません。単に表示するだけならとても少ないコード量で実現できるのがわかるでしょう。

gapi.hangout.onApiReady.add(function(e) {
  $('#overlay-btn').click(function(e) {
    var url = $('#overlay-url').val();
    var rsc = gapi.hangout.av.effects.createImageResource(url);
    rsc.showFaceTrackingOverlay({
      trackingFeature: gapi.hangout.av.effects.FaceTrackingFeature.NOSE_ROOT,
      rotateWithFace:  true,
      scaleWithFace:   true
    });
  });
});

フェイストラッキングデータの取得 (gapi.hangout.av.effects)

フェイストラッキングの座標データを直接取得することも可能です。顔の傾きでアプリを制御したり、顔が映っていなければマイクをミュートする、などといったユニークな機能が実現できます。

フェイストラッキングデータを取得するには、まず onFaceTrackingDataChanged イベントにイベントハンドラを設定します。

onFaceTrackingDataChanged.add(callback)
フェイストラッキングのパラメータが変化した際に呼ばれるコールバック関数を追加する。
onFaceTrackingDataChanged.remove(callback)
フェイストラッキングのパラメータが変化した際に呼ばれるコールバック関数を削除する。

登録したイベントハンドラはフェイストラッキングのデータが更新されるたびに呼び出されます。座標データはイベントオブジェクトの以下のフィールドとして渡されます。ただし、 hasFace が false のとき(顔が認識できなかった時)のデータは信頼できないので注意してください。

フィールド名内容
hasFace顔が認識できたかどうか
leftEye{x: number, y: number}
leftEyebrowLeft{x: number, y: number}
leftEyebrowRight{x: number, y: number}
lowerLip{x: number, y: number}
mouthCenter{x: number, y: number}
mouthLeft{x: number, y: number}
mouseRight{x: number, y: number}
noseRoot{x: number, y: number}
noseTip{x: number, y: number}
pannumber
rightEye{x: number, y: number}
rightEyebrowLeft{x: number, y: number}
rightEyebrowRight{x: number, y: number}
rollnumber
tiltnumber
upperLip{x: number, y: number}

音声ファイルの再生 (gapi.hangout.av.effects)

画像のオーバーレイと同様の手順で、音声ファイル(16bit PCM エンコーディングの WAV ファイル)の再生も可能です。これはローカルで再生されるだけでなく、参加者全員のマシンで再生されるのが面白い点です。音声ファイルを再生するには、まず gapi.hangout.av.effects.createAudioResource() を呼び出して AudioResource オブジェクトを生成します。引数は音声ファイルの URL です。 AudioResource オブジェクトには以下のメソッドがあります。

createSound(params)
後述の Sound オブジェクトを生成する。 params.loop が true ならリピート再生、 params.volume には 0 〜 1 で音量を指定できる。
getUrl()
音声ファイルの URL を返す。
play(params)
createSound() と同じだが、同時に音声の再生を開始する。

createSound() もしくは play() メソッドで実際の再生を制御する Sound オブジェクトが生成できます。後者の場合は同時に再生も開始されます。 Sound オブジェクトには以下のメソッドがあります。

getAudioResource()
Sound オブジェクトを生成した AudioResource オブジェクトを返す。
getVolume()
再生ボリュームを 0 〜 1 の範囲で返す。
isLooped()
リピート再生なら true を返す。
play()
音声の再生を開始する。
setLoop(loop)
loop に true を指定するとリピート再生を有効にする。
setVolume(volume)
再生ボリュームを 0 〜 1 の範囲で設定する。
stop()
再生を停止する。

共有ステート (gapi.hangout.data)

gapi.hangout.data 名前空間には、 Hangout の参加者間でリアルタイムにデータ共有するための API が実装されています。共有するデータ(共有ステート)はキーと値の双方が文字列の key/value データで、値の追加・更新・削除がほぼリアルタイムで他の参加者に反映されます。共有ステートを利用することで、コラボレーション機能を持つアプリケーションが簡単に実現できます。

clearValue(key)
key で指定したキーの値を共有ステートから削除する。
getKeys()
共有ステートに登録されているキーの文字列配列を返す。
getValue(key)
key に対応する値を共有ステートから取得する。
getState()
現在の共有ステートをキーと値をオブジェクトで返す。
getStateMetadata()
現在の共有ステートのキーとメタデータをオブジェクトで返す。返り値のキーは getState と同じだが、値は後述の MetaData 型になる。
setValue(key, value)
共有ステートの key に対応する値として value を設定する。
submitDelta(opt_updates, opt_removes)
共有ステートオブジェクトを更新する。 opt_updates には追加もしくは上書きする共有ステートのキーと値を格納したオブジェクトを指定する。キーと値は必ず文字列でなければならない。 opt_removes には削除するキーを格納した配列を指定する。
sendMessage(message)
文字列 message をすべての参加者に送信する。共有ステートと異なり、このメソッドで送信した文字列は保持されず、場合によっては送信されない可能性もある。その代わりレイテンシは共有ステートよりも低いので、マウスカーソルの座標など、多少抜け落ちても問題ないデータのやり取りに向いている。
onMessageReceived.add(callback)
sendMessage() の受信時に呼ばれるコールバック関数を追加する。 e.senderId に送信者の ID が、 e.message に送信文字列が格納されている。
onMessageReceived.remove(callback)
sendMessage() の受信時に呼ばれるコールバック関数を削除する。
onStateChanged.add(callback)
新しいバージョンの共有オブジェクトをサーバーから受け取ったときに呼ばれるコールバック関数を追加する。コールバックには 4 つの引数が渡される。 e.addedKeys は新たに追加された値を格納した MetaData 型の配列。 e.removedKeys は削除されたキーの文字列配列、 e.state は現在の共有ステートのキーと値を格納したオブジェクト、 e.metadata は現在の共有ステートのキーと MetaData を格納したオブジェクト。
onStateChanged.remove(callback)
新しいバージョンの共有オブジェクトをサーバーから受け取ったときに呼ばれるコールバック関数を削除する。

getStateMetaData などで取得できる MetaData 型(これは私が勝手に付けた名前なのでご注意くだだい)には、以下のフィールドがあります。こちらには更新された時刻もあるので、上書きの制御などもある程度可能でしょう。

フィールド名説明
keyキー文字列
value値文字列
timestampkey/value が最も最近更新されたサーバータイム
timediffサーバータイムとローカルアプリケーションの時間の差

以下は、共有ステートを使った「へぇーボタン」の実装例です。それぞれの参加者がへぇーボタンを押した回数がリアルタイムで表示されます。

var countElementMap = {};
function updateCount(e) {
  for(key in (e.state || {})) {
    if(countElementMap[key]) {
      countElementMap[key].text(e.state[key] || '0');
    }
  }
};

function updateCountParticipants(participants) {
  countElementMap = {};
  var parentEl = $('#count').empty();
  var state    = gapi.hangout.data.getState() || {};
  $.each(participants, function() {
    var span = $('<span />').text(state[this.id] || '0');
    var name = ((this.person && this.person.displayName) || this.id) + ' : ';
    $('<li/>').text(name).append(span).appendTo(parentEl);
    countElementMap[this.id] = span;
  });
};

gapi.hangout.onApiReady.add(function(e) {
  updateCountParticipants(gapi.hangout.getEnabledParticipants());
  gapi.hangout.onEnabledParticipantsChanged.add(function(e) {
    updateCountParticipants(e.enabledParticipants);
  });
  gapi.hangout.data.onStateChanged.add(updateCount);
  $('#countup').click(function() {
    var id    = gapi.hangout.getParticipantId();
    var value = gapi.hangout.data.getValue(id) || 0;
    gapi.hangout.data.setValue(id, '' + ((value | 0) + 1));
  });
});

ビデオフィードの制御 (gapi.hangout.layout)

通常、アプリケーションを実行すると映像の拡大表示(ビデオフィード)は非表示になります。しかし、 gapi.hangout.layout 名前空間の機能を使えば、アプリケーション画面内の任意の位置にビデオフィードを再度表示することが可能です。このためのメソッドを以下に示します。

createParticipantVideoFeed(participantId)
participantId が示す参加者を常に表示する VideoFeed オブジェクトを返す。
getDefaultVideoFeed()
表示する参加者が自動で切り替わる VideoFeed オブジェクトを返す。
getVideoCanvas()
ビデオフィードを表示する領域を指定する VideoCanvas オブジェクトを返す。

実際にビデオフィードをアプリケーション画面内に表示するには、以下の手順を踏みます。

  1. getVideoCanvas() で VideoCanvas オブジェクトを取得する。
  2. 表示する VideoFeed オブジェクトを createParticipantVideoFeed() または getDefaultVideoFeed() で取得し、 Canvas オブジェクトの setVideoFeed() メソッドに渡す。
  3. Canvas オブジェクトのメソッドで表示位置などを設定する。
  4. Canvas オブジェクトの setVisible() メソッドで表示を開始する。

Canvas オブジェクトに以下のメソッドがあり、表示位置やサイズ、表示対象のビデオフィードの指定などが行えます。

getAspectRatio()
表示領域のアスペクト比を返す。
getHeight()
表示領域の縦サイズを返す。
getPosition()
表示位置を x, y のフィールドを持つオブジェクトで返す。
getSize()
表示領域のサイズを width, height のフィールドを持つオブジェクトで返す。
getWidth()
表示領域の横サイズを返す。
getVideoFeed()
表示中の VideoFeed オブジェクトを返す。
isVisible()
表示中なら true を返す。
setHeight(height)
表示領域の縦サイズを指定する。
setPosition(value, y)
表示領域の位置を指定する。 value が数値の場合はそれを X 座標、 y を Y 座標として扱う。 value がオブジェクトの場合は、その x, y フィールドをそれぞれ X 座標、 Y 座標として扱い、 y は無視する(省略可能)。
setVideoFeed(feed)
表示対象の VideoFeed オブジェクトを指定する。
setVisible(visible)
visible に true を指定すれば表示開始、 false なら表示停止。
setWidth(width)
表示領域の横サイズを指定する。

以下は自分の映像を画面左上に表示するコード例です。

gapi.hangout.onApiReady.add(function(e) {
  $('#show-feed').click(function(e) {
    var vc   = gapi.hangout.layout.getVideoCanvas();
    var feed = gapi.hangout.layout.createParticipantVideoFeed(
      gapi.hangout.getParticipantId());
    vc.setVideoFeed(feed);
    vc.setPosition(0, 0);
    vc.setWidth(200);
    vc.setHeight(150);
    vc.setVisible(true);
  });
});

ビデオフィードに表示中の参加者の取得

上で説明した VideoFeed オブジェクトには、表示対象の参加者 ID を返す getDisplayedParticipant() メソッドが実装されています。したがって、 VideoCanvas オブジェクトの getVideoFeed() で取得した VideoFeed オブジェクトに対して getDisplayedParticipant() を呼べば、現在表示中の参加者が特定できます。

また、 gapi.hangout.layout.getDefaultVideoFeed() で取得できる VideoFeed には以下のイベントがあり、自動で選択された参加者(アクティブスピーカー)の変更を検知できるようになっています。

onDisplayedParticipantChanged.add(callback)
アクティブスピーカー変更時に呼ばれるコールバック関数を追加する。 e.displayedParticipant にアクティブスピーカーの ID が設定されている。
onChatPaneVisible.remove(callback)
アクティブスピーカー変更時に呼ばれるコールバック関数を削除する。

通知やチャットウィンドウの制御 (gapi.hangout.layout)

ビデオフィードの制御以外にも、gapi.hangout.layout 名前空間には通知の表示やチャットパネルの制御といった機能があります。以下にそれらのメソッドを示します。

displayNotice(message, opt_permanent)
Hangout ウィンドウの上部に通知を表示する。 opt_permanent が true なら、通知は dismissNotice が呼ばれるか優先度の高い通知が表示されるまで残る。 false なら、通知は一定時間で消える。
dismissNotice()
現在表示中の通知を消す。
hasNotice()
現在、通知が表示されていれば true を、そうでなければ false を返す。
isChatPaneVisible()
チャットパネルが表示されていれば true 、そうでなければ false を返す。
setChatPaneVisible(visible)
チャットパネルの表示・非表示を切り替える。 visible に true を指定するとチャットパネルを表示、 false なら非表示。
onChatPaneVisible.add(callback)
チャットパネルの表示状態が変化したときに呼ばれるコールバック関数を追加する。 e.isChatPaneVisible にチャットパネルの状態が boolean 値で渡される。
onChatPaneVisible.remove(callback)
チャットパネルの表示状態が変化したときに呼ばれるコールバック関数を削除する。
onHasNotice.add(callback)
通知が表示された時、もしくは消されたときに呼び出されるコールバック関数を追加する。 e.hasNotice に現在の通知の表示状態を示す boolean 値が渡される。
removeHasNoticeListener(callback)
通知が表示された時、もしくは消されたときに呼び出されるコールバック関数を削除する。

以下はリンクがクリックされたときにメッセージを表示・削除するためのコードです。

gapi.hangout.onApiReady.add(function(e) {
  $('#notice').click(function() {
    if(gapi.hangout.layout.hasNotice()) {
      gapi.hangout.layout.dismissNotice();
    } else {
      gapi.hangout.layout.displayNotice('このように通知が表示できます', true);
    }
  });
});

ぜひ使ってみてください!

ここまで見てきたように、最新版の Hangouts API は、 Hangout のほとんどの機能を制御できる、かなり気合の入った API に仕上がっています。とくにアプリケーションの画面内にビデオフィードが配置できるようになったのは大きいですね。このおかげで、自分のアプリケーションと Hangout がひとつに融合しているように見せることが可能になっています。

API 自体も正式公開の扱いとなり、実用性が大幅にアップした Hangouts API ぜひ皆さんも使ってみてください!

関連記事

この記事にコメントする

Recommendations
Books
「Closure Library」の入門書です。
詳しくはこちらの記事をどうぞ!
Categories
Recent Articles