連載
» 2014年11月14日 18時00分 公開

スマホとWebSocketで連携するテレビ用SPA(Single-Page Application)の作り方Chromecastアプリ開発入門(2)(3/5 ページ)

[大西克己,オープンストリーム]

プロパティの取得

 メディアの再生時間/プログレスバーを更新する値は、MediaElementのプロパティから取得します。

 以下のコード3では、再生中の再生位置を更新が発生するたびに取得して現在の再生時間とプログレスバーの長さを変更しています。

〜省略〜
    // メディアの再生位置が変更された
    window.mediaElement.addEventListener('timeupdate', function() {
        //console.debug('######### MEDIA ELEMENT TIMEUPDATE ' + e);
        //(3.4)ステータスパネルの更新
 
        //(3.4.1)現在の再生時間とコンテンツ時間を取得
        var c = window.mediaElement.currentTime;
        var d = window.mediaElement.duration;
 
        //(3.4.2)ミリ秒の値を時刻フォーマットに変換して設定
        window.current.innerHTML = format(c);
        window.duration.innerHTML = format(d);
 
        //(3.4.3)プログレスの長さを変更
        window.progress.style.width = 950 * (c / d) + "px";
    });
 
〜省略〜
 
};
 
function format(ms) {
    return (ms / 36000 | 0) + (ms / 3600 % 10 | 0) + ":" 
        + (ms % 3600 / 600 | 0) + (ms % 3600 / 60 % 10 | 0) + ":" 
        + (ms % 60 / 10 | 0) + (ms % 60 % 10 | 0)
}
コード3 プログレスバーの処理

メディアを操作

 次に、再生するメディアの操作方法について解説します。メディアを操作するにはメソッド呼び出しとプロパティの変更で行います。以下のような操作があります。

  • load()メソッド:メディアの読み込みを行う
  • play()メソッド:メディアの再生を行う
  • pause()メソッド:メディアの一時停止を行う
  • currentTimeプロパティ:再生時間
  • volumeプロパティ:ボリューム
  • mutedプロパティ:ミュート

 Receiverアプリをユーザーは直接操作できません。このため、上記のようなメディア操作はユーザーがSenderアプリを操作することによって送信されるコマンドメッセージで行うことになります。

 このコマンドメッセージをSenderアプリから受け取るためには、Google Cast Receiver SDKを利用する必要があります。以降で、コマンドメッセージを受け取る方法について解説します。

Senderアプリからコマンドメッセージを受け取ってメディアを操作する

 Google Cast Receiver SDKで提供されている、以下の二つのクラスを使用することで、Senderからコマンドメッセージを受け取り、メディアの操作を行えます。

  • MediaManager
  • CastReceiverManager

 これらのクラスを使って、メディア操作を実装していきましょう。

MediaManager

 MediaManagerは、load、play、pause、stopなど基本的なメディア操作のメッセージとイベントの送受信のために使用します。サポートされている全てのコマンドは「Media Playback Messages」の「Commands from sender to receiver」を参照してください。

 コマンドメッセージを受け取ったときにビジネスロジックを実行したい場合は、処理したいイベントが発生した際にコールバックされるイベントハンドラーをオーバーライドします。

 以下のコード4は、「新しいメディアを再生する」というコマンドメッセージを受け取ったときに実行されるonLoadメソッドをオーバーライドして、メディアのメタ情報からタイトルを取得して画面に表示しています。

//(4)Videoエレメントを指定してメディアマネージャを作成
window.mediaManager = new cast.receiver.MediaManager(window.mediaElement);
 
//(4.1)onLoad をオーバーライド
window.mediaManager.onLoad = (function() {
    var origOnLoad = mediaManager.onLoad;
    return function(event) {
        //(4.2)メディアのメタ情報からタイトルを表示
        window.mediaTitle.innerHTML = event.data['media']['metadata']['title'];
        //(4.3)オリジナルのイベントを実行
        origOnLoad(event);
    }
}());
コード4 コマンドメッセージのハンドラー

コラム「MediaManagerとMediaElementのハンドラーの使い分けについて」

 MediaManagerはメディアコマンド発生をハンドラーで処理できますが、メディアの状態変更に伴うUIの更新はMediaElementのイベントハンドラーで処理することが推奨されています。それぞれのハンドラー内で推奨される操作は以下の通りです。

  • MediaManagerのコマンドメッセージハンドラー内で記述するコード:Senderから送られるコマンドをトリガーとしたメディアの操作など
  • MediaElementのイベントハンドラー内で記述するコード:メディアプレーヤーの状態変更に伴うUI更新

 例えば、今回のサンプルアプリケーションに、メディアのシーク時にプログレスバーを表示する仕様を追加する場合、メディアのシークのイベントハンドリングはMediaManagerでは行わず、コード5のようにMediaElementのコールバックのイベントハンドラー内に記述します。

// シークコマンド発生
window.mediaManager.onSeek = (function() {
    var origOnSeek = mediaManager.onSeek;
    return function(event) {
        // UIの更新は行わない
        origOnSeek(event);
    }
}());
 
// メディアのシークが始まった
window.mediaElement.addEventListener('seeking', function(e) {
    console.log("######### MEDIA ELEMENT SEEKING ", e);
    // プログレスバー画像を表示する。
    window.progress.style.display = "block";
});
 
// メディアのシークが終了した
window.mediaElement.addEventListener('seeked', function(e) {
    console.log("######### MEDIA ELEMENT SEEKED ", e);
    // プログレスバー画像を非表示にする。
    window.progress.style.display = "none";
});
コード5 UIの更新を行うハンドラー

 また、MediaManagerについての詳細は、以下のAPIリファレンス「Class cast.receiver.MediaManager」を参考にしてください。


CastReceiverManager

 CastReceiverManagerは、Senderと通信ができるようにする仕組みで、システムからのメッセージとイベント、独自に定義したメッセージをSenderと送受信するために使用します。

 Senderからコマンドメッセージを受け取ったときにビジネスロジックを実行する場合、イベントハンドラーをオーバーライドします。

 以下のコード6は、このイベントハンドラーを使用して以下の処理を実施しています。

  1. Senderが接続されたときに接続数をログに表示
  2. Senderが切断されたときに接続数が0件かつ切断の理由がSenderからのリクエストであった場合、Receiverを閉じる。また、このとき切断の理由を確認して、Wi-Fiの通信切断が原因であった場合は、Receiverアプリを終了せずにメディアの再生をし続ける
//(5)レシーバーマネージャのシングルトンインスタンスを作成
window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance();
//(5.1)onSenderConnected をオーバーライド
window.castReceiverManager.onSenderConnected = (function() {
    return function(event) {
        // 接続しているSenderの数をログに出力……1.
        var senders = castReceiverManager.getSenders();
        console.debug('SenderCount', '' + senders.length);
    }
}());
 
//(5.2)onSenderDisconnected をオーバーライド
window.castReceiverManager.onSenderDisconnected = (function() {
    return function(event) {
        // 接続しているSenderの数が0件 かつ 切断の理由はリクエストか?……2.
        var senders = castReceiverManager.getSenders();
        if(senders.length == 0 && 
            event.reason == cast.receiver.system.DisconnectReason.REQUESTED_BY_SENDER) {
            window.close();
        }
    }
}());
 
//(5.3)レシーバーマネージャのスタート
window.castReceiverManager.start();
コード6 コマンドメッセージのイベントハンドラー

 システムメッセージ/イベント以外に、カスタムメッセージ/イベントを使用したい場合、CastMessageBusクラスを使用します(コード7)。

//(5.4)カスタムメッセージのインスタンスを作成する
window.customMessageBus = 
    castReceiverManager.getCastMessageBus('urn:x-cast:com.example.mymediaplayer');
customMessageBus.onMessage = function(event) {
    // メッセージのハンドリング
}
コード7 カスタムメッセージイベント

 castReceiverManager.getCastMessageBusメソッドの引数にアプリケーションで定義したネームスペースを指定してカスタムチャンネルを取得します。ネームスペースは必ず「urn:x-cast:」から始まる任意の文字列を定義する必要があります。

 CastMessageBusを利用すると、接続中の全てのSenderにメッセージを送信したり、特定のSenderにだけメッセージを送信したりすることも可能です。Senderにメッセージを送信する例はコード8の通りです。

customMessageBus.broadcast(message); // 接続中の全てのSenderにメッセージ送信
 
customMessageBus.send(senderId, message); // 特定SenderIdにメッセージ送信
コード8 CastMessageBus

 CastReceiverManagerCastMessageBusの詳細については、各APIリファレンスを参照してください。

メディアプレーヤーのUI更新時にページ遷移させないように実装する

 一般的なメディアプレーヤーは、ユーザーがメディアを再生したり停止したりといったコマンドが発生するたびに状態が変化し、それに応じてUIの更新を行います。メディアプレーヤーの状態変化を状態遷移図/表に表すと以下のようになります。

図4 メディアプレーヤーの状態遷移図
表2 状態遷移表
イベント 状態 UI更新処理
load() LOADING ・処理中アニメーション画像を表示
・ステータスパネルを表示
・メディアのタイトルを表示
loaded
buffered
PLAYING ・処理中アニメーション画像を隠す
・再生状態の画像を表示
・一定時間後、ステータスパネルを隠す
pause() PAUSED ・ステータスパネルを表示
・一時停止状態の画像を表示
play() PLAYING ・再生状態の画像を表示
・一定時間後、ステータスパネルを隠す
buffering BUFFERING ・処理中アニメーション画像を表示
stop() IDLE ・アイドル画面を表示
※メディアが再生されていない待機画面

 状態遷移によりUIの更新が必要になりますが、UI更新のためにページ遷移させてはいけません。Receiverアプリは、必ずSPA(Single-Page Application)で実装する必要があります。なぜなら、ページ遷移してしまうと、Receiverアプリとして動作しなくなる仕様のため、Senderアプリからメッセージコマンドを受信できなくなってしまうからです。

 なお、SPAの基本的な要素は以下の通りです。

  • 単一ページによるWebアプリケーション
  • ページ(UIの更新)はDOMの操作により切り替えを行う
  • サーバーとの通信はAjax、スマホとの通信はWebSocketで行う

 Google Cast Receiver SDK(MediaManagerやCastReceiverManager)とMedia Player Libraryは、サーバーのやりとりはAjax(XMLHttpRequest)で、SenderアプリとのやりとりはWebSocketを用いています。

 メディアプレーヤーのイベント発生/状態変化によってUIを更新する際も、DOMを操作することで更新し、HTTPページ遷移は使わないでください。

 例えば、アイドル画面でプロモーション的なリッチなコンテンツを表示したいために、HTTPリクエストでページ遷移を行ってしまうと、上記の通り、Senderとの接続が切れてしまい、SenderからReceiverアプリの操作ができなくなってしまいます。

 「Google Cast Design Checklist - Google Cast - Google Developers」には、「メディアプレーヤーには、どのようなUIが必要で、ユーザーにどのように情報を伝えるべきか」の指針が記載されています。

 また、メディアのイベントと状態の詳しい内容は「Custom Receiver Application - Google Cast - Google Developers」に記載されています。

 これらは、デザイナーとプログラマーが、UIや状態遷移の設計するに当たり役立つと思います。メディアプレーヤーを実装する際は、ぜひ参考にしてください。

 続いて次ページでは、Adaptive Bitrate Streaming/DRMのメディアの再生の方法について解説します。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。