この記事はファーストサーバのAdvent Calendar 2016の19日目として書きました。
http://qiita.com/advent-calendar/2016/firstserver
内容は2015年(去年w)の18日目の続きです。
http://qiita.com/vanx2/items/2f8a55177e55f5b05179
前回作ったReceiverアプリとSenderアプリに手を加えて、独自で定義した namespace 経由で独自のメッセージを送ってみます。また、Receiverアプリのカスタマイズ方法を学ぶために動画再生時に処理を加えてみます。
Receiverアプリ
前回からの変更点は以下
- メッセージを表示するdivエレメントを設ける
- 動画ロードのリクエストが来たらどのイベントが呼ばれたか上記メッセージ欄に追記
- 動画がロードされたらどのイベントが呼ばれたか上記メッセージ欄に追記
- Senderアプリからメッセージを受け取ったらメッセージを上記メッセージ欄に追記
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="//www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js"></script>
</head>
<body>
<!-- メッセージを表示するエリア -->
<div id='message' style="background-color:white">x</div>
<video id="vid" />
<script type="text/javascript">
window.onload = function() {
cast.receiver.logger.setLevelValue(cast.receiver.LoggerLevel.DEBUG);
window.mediaElement = document.getElementById('vid');
// videoエレメントのloadstartイベントで実行される処理を追加
window.mediaElement.addEventListener('loadstart', function(e) {
// 呼びだされた時にメッセージエリアに文字列追加
getElementById("message").innerHTML .= "[loadstart]";
});
window.mediaManager = new cast.receiver.MediaManager(window.mediaElement);
// mediaManager の onLoad イベントをクロージャーでオーバーライドして
// LOAD リクエストが来た時に実行される処理を追加
window.mediaManager.onLoad = (function() {
// 元の処理を退避しておく
var orignalEvent = mediaManager.onLoad;
// 追加する処理を記述
return function(event) {
// 呼びだされた時にメッセージエリアに文字列追加
getElementById("message").innerHTML .= "[onLoad]";
// 退避しておいた元のイベントを実行
orignalEvent(event);
}
}());
window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance();
// 独自メッセージで使うnamespaceを指定してメッセージバスを取得
window.customMessageBus = castReceiverManager.getCastMessageBus('urn:x-cast:com.oreore.message');
// メッセージを受け取った場合の処理
customMessageBus.onMessage = function(event) {
// 受け取ったメッセージを画面に表示
getElementById("message").innerHTML .= event.data;
// 送信元に受け取ったメッセージをそのまま返す
customMessageBus.send(event.senderId, event.data);
// 受け取ったメッセージを全てのSenderにブロードキャスト
customMessageBus.broadcast(event.data);
}
castReceiverManager.start();
};
</script>
</body>
</html>
customMessageBus
を記述してからcastReceiverManager.start();
を呼ぶようにしてください。start
後だと反映されず激ハマリしました。。。
#Senderアプリ
CastV2の仕様がわかるように抽象化せずにつらつらと書いてみます。
前回からの変更点は以下
- 独自のnamespaceを定義
- 動画を再生したあとで
MEDIA_STATUS
が返って来たら独自メッセージを送る - Receiverアプリから独自メッセージが送られてきたらログに出力
var Client = require('castv2').Client;
var host = '192.168.1.64'; // Chromecastのアドレス
var appID = 'D86E10F8'; // Receiver アプリの appID
client = new Client();
client.connect(host, function() {
var connection = client.createChannel('sender-0', 'receiver-0', 'urn:x-cast:com.google.cast.tp.connection', 'JSON');
var heartbeat = client.createChannel('sender-0', 'receiver-0', 'urn:x-cast:com.google.cast.tp.heartbeat', 'JSON');
var receiver = client.createChannel('sender-0', 'receiver-0', 'urn:x-cast:com.google.cast.receiver', 'JSON');
var media = client.createChannel('sender-0', 'receiver-0', 'urn:x-cast:com.google.cast.media', 'JSON');
// 独自のチャンネルを定義
var original = client.createChannel('sender-0', 'receiver-0', 'urn:x-cast:com.oreore.message', 'JSON');
var clientId = 'client-1';
var transportId = '';
connection.on('message', function(data, broadcast) {
});
connection.send({ type: 'CONNECT' });
setInterval(function() {
heartbeat.send({ type: 'PING' });
}, 5000);
receiver.send({ type: 'LAUNCH', appId: appID, requestId: 5 });
receiver.on('message', function(data, broadcast) {
if (data.requestId == 5) {
transportId = data.status.applications[0].transportId;
connection.destinationId = transportId;
connection.sourceId = clientId;
connection.send({ type: 'CONNECT' });
media.destinationId = transportId;
media.sourceId = clientId;
media.send({
requestId: 9,
type: 'LOAD',
media: {contentId: "http://commondatastorage.googleapis.com/gtv-videos-bucket/big_buck_bunny_1080p.mp4",
streamType: "buffered",
contentType: "video/mp4",
metadata: null,
duration: 596,
customData: null},
autoplay: true,
currentTime: 0,
customData: null,
activeTrackIds: [],
});
}
});
media.on('message', function(data, broadcast) {
// 動画再生リクエストのレスポンスとして MEDIA_STATUS が返って来たら
if (data.requestId == 9) {
// 受け取った transportId を destinationId としてセットして
original.destinationId = transportId;
original.sourceId = clientId;
// 独自メッセージを投げる
original.send({type: 'text', message: 'hogehoge' });
}
});
original.on('message', function(data, broadcast) {
// Chromecast から送られてきた独自メッセージをログログ
console.log(data, broadcast);
});
});
実行!
$ DEBUG=* node sender.js
castv2 connecting to 192.168.x.64:8009 ... +0ms
castv2 connected +262ms
castv2 send message: protocolVersion=0 sourceId=sender-0 destinationId=receiver-0 namespace=urn:x-cast:com.google.cast.tp.connection data={"type":"CONNECT"} +2ms
castv2 send message: protocolVersion=0 sourceId=sender-0 destinationId=receiver-0 namespace=urn:x-cast:com.google.cast.receiver data={"type":"LAUNCH","appId":"D86E10F8","requestId":5} +4ms
castv2 recv message: protocolVersion=0 sourceId=receiver-0 destinationId=* namespace=urn:x-cast:com.google.cast.receiver data={"requestId":0,"status":{"isActiveInput":true,"isStandBy":false,"volume":{"level":1.0,"muted":false}},"type":"RECEIVER_STATUS"} +221ms
castv2 recv message: protocolVersion=0 sourceId=receiver-0 destinationId=* namespace=urn:x-cast:com.google.cast.receiver data={"requestId":5,"status":{"applications":[{"appId":"D86E10F8","displayName":"ac","namespaces":[{"name":"urn:x-cast:com.google.cast.media"},{"name":"urn:x-cast:com.oreore.message"}],"sessionId":"CCF4FCE6-885C-46C3-95D1-76EEC6813A9C","statusText":"ac","transportId":"web-48"}],"isActiveInput":true,"isStandBy":false,"volume":{"level":1.0,"muted":false}},"type":"RECEIVER_STATUS"} +1s
{ appId: 'D86E10F8',
displayName: 'ac',
namespaces:
[ { name: 'urn:x-cast:com.google.cast.media' },
{ name: 'urn:x-cast:com.oreore.message' } ],
sessionId: 'CCF4FCE6-885C-46C3-95D1-76EEC6813A9C',
statusText: 'ac',
transportId: 'web-48' }
castv2 send message: protocolVersion=0 sourceId=client-1 destinationId=web-48 namespace=urn:x-cast:com.google.cast.tp.connection data={"type":"CONNECT"} +5ms
castv2 send message: protocolVersion=0 sourceId=client-1 destinationId=web-48 namespace=urn:x-cast:com.google.cast.media data={"requestId":9,"type":"LOAD","media":{"contentId":"http://commondatastorage.googleapis.com/gtv-videos-bucket/big_buck_bunny_1080p.mp4","streamType":"buffered","contentType":"video/mp4","metadata":null,"duration":596,"customData":null},"autoplay":true,"currentTime":0,"customData":null,"activeTrackIds":[]} +0ms
castv2 recv message: protocolVersion=0 sourceId=web-48 destinationId=* namespace=urn:x-cast:com.google.cast.media data={"type":"MEDIA_STATUS","status":[{"mediaSessionId":1,"playbackRate":1,"playerState":"BUFFERING","currentTime":0,"supportedMediaCommands":15,"volume":{"level":1,"muted":false},"activeTrackIds":[],"media":{"contentId":"http://commondatastorage.googleapis.com/gtv-videos-bucket/big_buck_bunny_1080p.mp4","streamType":"buffered","contentType":"video/mp4","metadata":null,"duration":596.501333,"customData":null},"currentItemId":1,"items":[{"itemId":1,"media":{"contentId":"http://commondatastorage.googleapis.com/gtv-videos-bucket/big_buck_bunny_1080p.mp4","streamType":"buffered","contentType":"video/mp4","metadata":null,"duration":596.501333,"customData":null},"autoplay":true,"activeTrackIds":[],"customData":null}],"repeatMode":"REPEAT_OFF"}],"requestId":9} +2s
castv2 send message: protocolVersion=0 sourceId=client-1 destinationId=web-48 namespace=urn:x-cast:com.oreore.message data={"type":"text","message":"hogehoge"} +1ms
castv2 recv message: protocolVersion=0 sourceId=web-48 destinationId=client-1 namespace=urn:x-cast:com.oreore.message data={"type":"text","message":"hogehoge"} +140ms
{ type: 'text', message: 'hogehoge' } false
castv2 recv message: protocolVersion=0 sourceId=web-48 destinationId=* namespace=urn:x-cast:com.oreore.message data={"type":"text","message":"hogehoge"} +6ms
{ type: 'text', message: 'hogehoge' } true
castv2 send message: protocolVersion=0 sourceId=sender-0 destinationId=receiver-0 namespace=urn:x-cast:com.google.cast.tp.heartbeat data={"type":"PING"} +469ms
castv2 recv message: protocolVersion=0 sourceId=receiver-0 destinationId=sender-0 namespace=urn:x-cast:com.google.cast.tp.heartbeat data={"type":"PONG"} +110ms
castv2 recv message: protocolVersion=0 sourceId=web-48 destinationId=* namespace=urn:x-cast:com.google.cast.media data={"type":"MEDIA_STATUS","status":[{"mediaSessionId":1,"playbackRate":1,"playerState":"PLAYING","currentTime":0.536066,"supportedMediaCommands":15,"volume":{"level":1,"muted":false},"activeTrackIds":[],"currentItemId":1,"repeatMode":"REPEAT_OFF"}],"requestId":0} +353ms
前回と同様動画が再生され、画面上部に[onLoad][loadstart]{type: 'text', message: 'hogehoge'}
と表示されましたでしょうか。
実装のポイント
動画再生
今回Receiverアプリに加えた修正でmediaMnager
のonLoad
イベントで[onLoad]
を、video
エレメントのloadstart
イベントで[loadstart]
をdivエレメントに追記しています。このように動画をLoadするイベントでもmediaMnager
のイベントとvideo
エレメントのイベント両方で記述することができます。
公式サイトによると、Senderから送られるリクエストに起因する処理はmediaMnager
に、メディアの状態変化に伴う処理はvideo
エレメントのイベントに追加することが推奨されているようです。
https://developers.google.com/cast/docs/custom_receiver
オーバーライドできるイベントのプロパティは下記の通りです。
https://developers.google.com/cast/docs/reference/receiver/cast.receiver.MediaManager#properties
videoエレメントで発火するイベントは下記を参照してください。
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Media_events
このサイトで動画をいじってみるとvideoエレメントでどんなイベントやプロパティがいつどう変わるのか把握しやすいです。
http://www.w3.org/2010/05/video/mediaevents.html
公式サイトにはどういうUIにするべきかのチェックリストがあります。
https://developers.google.com/cast/docs/design_checklist#receiver
そうです。書くのが面倒になってきたので外部リンクに頼りました。。。
独自メッセージ
動画をLOAD
するリクエストを送ると、返事としてMEDIA_STATUS
が返って来ます。今回はそのメッセージを受け取ったタイミングで独自チャンネルを通して独自メッセージを投げています。
protocolVersion=0
sourceId=client-1
destinationId=web-48
namespace=urn:x-cast:com.oreore.message
data={"type":"text","message":"hogehoge"}
Receiverアプリでは受け取ったメッセージをdivエレメントに追記し、送信元へ受け取ったメッセージを返し、接続している全てのSenderへも受け取ったメッセージをブロードキャストしています。
- 送信元へ送信
protocolVersion=0
sourceId=web-48
destinationId=client-1
namespace=urn:x-cast:com.oreore.message
data={"type":"text","message":"hogehoge"}
ブロードキャストがfalse
になっているログが送信元へ返されたログです。
{ type: 'text', message: 'hogehoge' } false
- 全てのSenderへブロードキャスト
protocolVersion=0
sourceId=web-48
destinationId=*
namespace=urn:x-cast:com.oreore.message
data={"type":"text","message":"hogehoge"}
ブロードキャストがtrue
になっているログがブロードキャストのログです。
{ type: 'text', message: 'hogehoge' } true
というわけで
これらを駆使すれば動画の再生で何か問題が起きた時にSenderアプリと独自でやりとりをするなんてこともできるようになりますね!
mediaManager
を使わずに自由にTV上にHTMLで表現することもできます。
Game用のSDKも用意されています。
こういう仕組みでFirstserver社内のデジタルサイネージは運営されているのでした☆
https://github.com/vanx2/signagify