[Signagify 3] Node.js で Sender アプリ作って mDNS + CastV2 で Chromecast とお話

More than 1 year has passed since last update.

この記事はファーストサーバのAdvent Calendar 2015の17日目として書きました。
http://qiita.com/advent-calendar/2015/firstserver

内容は16日目の続きです。
http://qiita.com/vanx2/items/00dcad135b9706d199a7

前回ざっくりとしたChromecastアプリの概要を書いたので、今回は実際にアプリを作ってみようと思います。いいぞもっとやれと大変不評だったので図をやめました。

やること

第一回で述べたとおり、スレート端末を使わずに複数台のChromecastへキャストをするためにはSDKを使わずに独自で実装する必要があります。1台のChromecastにキャストするだけなら提供されているSDKを使ったサンプルアプリを動かしてみてください。
https://developers.google.com/cast/docs/downloads

今回はSenderアプリをNode.jsで実装して、Default Media Receiverで動画を再生してみます。

Senderアプリ

■mDNSでChromecastを見つける

まずはネットワークに参加しているChromecastを見つけます。ChroemcastはAppleのBonjour(Zero-configuration networking)で利用されているmDNSという技術を利用しています。

mDNSはローカルネットワーク上の機器同士がサーバーを介さずにIPマルチキャストを使って名前とアドレスを交換しあう技術です。マルチキャストIPアドレス224.0.0.2515353ポートにUDPでクエリを投げると、同じローカルネットワーク内に接続されたmDNSに対応した機器がDNSのようなレコードを返します。

それでは実際にmDNSにqueryを投げて、返って来たresponseから必要な情報を抜き出してみましょう。

Node.jsのパッケージではmdnsが人気なようですが、
https://www.npmjs.com/package/mdns

外部モジュール(LinuxのmDNS実装であるAvahiやmDNSResponderなど)に依存していたので、私はpure javascriptだったmulticast-dnsを利用しました。
https://www.npmjs.com/package/multicast-dns

インストールしてー

$ npm i multicast-dns

name_googlecast._tcp.localPTRレコードをリクエストするクエリ投げるスクリプト書いてー

detect.js
var mdns = require('multicast-dns')()

mdns.on('response', function(response) {
  console.log('got a response packet:', response)
})

// lets query for an PTR record for '_googlecast._tcp.local'
mdns.query({
  questions:[{
    name: '_googlecast._tcp.local',
    type: 'PTR'
  }]
})

実行するとー

$ node detect.js
got a response packet: { type: 'response',
  qdcount: 0,
  ancount: 1,
  nscount: 0,
  arcount: 3,
  questions: [],
  answers:
   [ { name: '_googlecast._tcp.local',
       type: 'PTR',
       class: 1,
       ttl: 120,
       data: 'signage@ajito._googlecast._tcp.local' } ],
  authorities: [],
  additionals:
   [ { name: 'signage@ajito._googlecast._tcp.local',
       type: 'TXT',
       class: 99999,
       ttl: 4500,
       data: 'id=6b2e9253d1d818dd2f2436677a98b19a' },
     { name: 've=04.md=Chromecast.ic=/setup/icon.png.fn=signage@ajito.ca=5.st=1.bs=FA8FCA57FB52.rs=signage.signage@ajito._googlecast._tcp.local',
       type: 'SRV',
       class: 99999,
       ttl: 120,
       data: [Object] },
     { name: 'signage@ajito.local',
       type: 'A',
       class: 99999,
       ttl: 120,
       data: '192.168.xxx.51' } ] }

Chromecastがこんなレコードを返してきます。他の機器もクエリを投げるので実行し続けているとChromecast以外のmDNSに対応した機器もレコードを返してきます。

返してきたPTRレコードにのりづけされたadditionalsセクションのtypeAのレコードのdataからIPアドレスが、nameからは名前が取得できます。
この例ではsignage@ajitoという名前をつけたChromecastのIPアドレスが192.168.xxx.51だということがわかります。

■Senderアプリの実装

見つけたChromecastのIPアドレスに対してcastv2プロトコルで接続し、標準で提供されているDefault media receiverを起動して動画を再生させてみます。

node-castv2-clientを利用します。
https://github.com/thibauts/node-castv2-client

インストールしてー

$ npm i castv2-client

# Windowsの場合は
C:> npm install castv2-client --no-optional
# らしい

ほぼほぼExamplesに書いてある通りですが、動画を再生して5秒後に再生時間2:00時点へと動画をスキップするように書いてみます。時代はES6だというのに牧歌的な書き方をしておりますが。。。

sender.js
var Client                = require('castv2-client').Client;
var DefaultMediaReceiver  = require('castv2-client').DefaultMediaReceiver;

var content = 'http://commondatastorage.googleapis.com/gtv-videos-bucket/big_buck_bunny_1080p.mp4';
var host='192.168.1.96'; // your IP address of chromecast

var client = new Client();
// connect to Chromecast
client.connect(host, function() {
  console.log('connected! Launching DefaultMediaReceiver ...');
  // launch Default Media Receiver app
  client.launch(DefaultMediaReceiver, function(err, player) {
    if(err) {console.log(err);return;}
    console.log('app "%s" launched, loading media %s ...', player.session.displayName, content);

    player.on('status', function(status) {
      console.log('status broadcast playerState=%s', status.playerState);
    });

    // play a video
    player.load({contentId: content}, { autoplay: true }, function(err, status) {
      console.log('media loaded playerState=%s', status.playerState);

      // Seek to 2 minutes after 5 seconds playing
      setTimeout(function() {
        player.seek(2*60, function(err, status) {
          if(err) {console.log(err);return;}
          console.log('skip to 2:00 playerState=%s', status.playerState);
        });
      }, 5000);
    });
  });
});
client.on('error', function(err) {
  console.log('Error: %s', err.message);
  client.close();
});

実行するとー

$ node sender.js
connected! Launching DefaultMediaReceiver ...
app "Default Media Receiver" launched, loading media http://commondatastorage.googleapis.com/gtv-videos-bucket/big_buck_bunny_1080p.mp4 ...
status broadcast playerState=IDLE
status broadcast playerState=BUFFERING
media loaded playerState=BUFFERING
status broadcast playerState=PLAYING
skip to 2:00 playerState=BUFFERING
status broadcast playerState=BUFFERING
status broadcast playerState=PLAYING
  :

てな具合に ええ いきたいッスね いッスねー!イェー(以下略

Chromecastで動画は流れましたでしょうか。ネットワーク環境によってはバッファリングで時間がかかり、5秒以上経ってからスキップされるかもしれません。

というわけで。。。やっとmDNS触ってみましたが。。。Advent calenderの公開時間になってしまいそうなので今回はここまでとしてcastv2の詳細に関しては次回書いてみようと思います。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.