Pychromecastって何?サイネージ配信で四苦八苦した話

目次

自己紹介

 かれこれ10年以上サーバーエンジニアをやっている、でわnです。
 弊社R-FORCEではバックエンドエンジニア一員としてゲーム開発に携わっております。
 趣味を列挙していくと身バレするので、メイド喫茶に通うことが趣味とだけ。
 ただメイド喫茶といってもジャンルがありまして、コンセプトカフェ、コスチュームカフェなどがある上に、にぎやかわいわい系、まったり系、伝統的系などあります。
 でわnはメイド喫茶が趣味と話しても社内でも誤解されっぱなしなので、いつかライトニングトークやろうかなと画策中・・・
趣味の話は一旦おいておき、今回そんな私がサイネージシステムを作ったところ、なかなか思うような結果を出せず、四苦八苦したので記事に残すことにしました。

サイネージってどんなもの?

 これです。
f:id:rf-blog-sagyo:20201022202341j:plain  正式名はデジタルサイネージ。インターネットを介して映像を投影させるシステムのことを指します。
f:id:rf-blog-sagyo:20201022220833g:plain

概要

 『R-FORCEの社内にてサイネージ配信システム作れない? 』  2020年3月頃その一言から始まり、Chromecast内蔵の機器を幾つか購入しました。
store.google.com

 Googleにて検索して、Google Cast API を使えばサイネージ出来ると知り、Chromecast機器とのソケット接続まわりの通信を変わりに行なってくれるPythonのプログラムPychromecastライブラリを発見。

 簡単というGoogle検索結果見出しに惹かれて始めること数ヶ月、苦労した点困った点を何かに残そうってことで記事にしました。

 これから中心に話す内容は Pychromecastライブラリを使用した開発メインの話になりますので予めご理解の程よろしくお願いします。

Pychromecastって何?

 概要にてサラッと記載しましたが、Pythonで動かすことが出来るサイネージ配信ライブラリになります。
 github.com

 ネットワークの知識をそこまで必要とせず、Python言語が扱える人がChromecast内蔵の機器をネットワークにつないで、わずか30行あまりのPythonプログラムを書くことでサイネージ配信出来るという代物になります。

 ではなぜ苦労することになったのか。それは30行あまりのPythonプログラムでは実現不可能なことをこのPychromecastライブラリを使って実現しようとしたから苦労する羽目になってしまいました。

システム構成図

f:id:rf-blog-sagyo:20201126163042p:plain
システム構成図
 Googleドライブ上にあるメディアファイルを配信サーバーへダウンロードし、同一ネットワーク上に存在するChromecast機器に対し配信を行なっています。

Pychromecastライブラリのバージョン

これからお話するPychromecastライブラリのバージョンは次になります。

7.5.1 (GitTag: 7.5.1、コミットハッシュ: 7c2bec4af3eedd236c4295a750ea60d3798e1313)

https://github.com/home-assistant-libs/pychromecast/commit/7c2bec4af3eedd236c4295a750ea60d3798e1313

苦労点・困った点

マルチキャストを予定していたつもりがユニキャストだった

理想

f:id:rf-blog-sagyo:20201005164640g:plain
マルチキャスト

現実

f:id:rf-blog-sagyo:20201005164712g:plain
ユニキャスト
 作る当初はブロードキャスト送信が出来、全Chromecast機器へ一斉に信号を送れるマルチキャストとばかり考えていたが、Chromecast機器を検出し、その1台1台にメディアの再生命令を出すユニキャスト送信しか出来ず、少し物悲しいものが完成しました。

配信サーバーと同一ネットワーク上しか再生出来ない

 Zeroconfと呼ばれるネットワーク機器検出サービスを利用しているライブラリであり、中身を変えずそのまま利用すると同一ネットワークのChromecast機器検出として動作するため、同一ネットワーク上に存在するChromecast機器のみに配信可能と制約を受けてしまう。

 R-FORCEでは当初サーバー群とChromecast機器群を別々のネットワークにて管理を予定していたが、Zeroconfサービスをそのまま利用しているシステムである故に、同一ネットワーク上に配信サーバーが存在することを余儀なくされてしまいました。

 異なるネットワーク上への挑戦は他の方も挑戦されているが、どうすれば成功するのか未だ不明のまま・・・ hogehiga.hatenablog.com github.com

再生メディアへはWebアクセスが必須とされている

 Chromecast機器にメディアの再生命令という表現を使っておりますが、正確にはChromecast機器にメディアURIを送信しChromecast機器に対しメディアURIにアクセスし再生してという命令になります。

 サポートメディアとしては公式にて幾つも挙げておりますが、 developers.google.com メディアを再生するにはChromecast機器がアクセス出来るURIである必要があり、さらにURIがサポートメディアである必要があります。
 弊社R-FORCEではこの問題を解決するために、サポートメディアを配信サーバー上にダウンロードし、配信サーバー兼HTTPアクセス可能なWebサーバーとすることで、Chromecast機器は配信サーバーからメディアを取得することで解決しました。
 Googleドライブ上のメディアを直接再生など計画はあったのですが、Google認証はChromecastが行なうことが出来ないなど問題が浮上し断念しました。

Webサーバーにホスト名および固定IPがオススメ

 弊社R-FORCEではプライベートIPにて配信可能なものを作成しましたが、運用保守に関してはネットワーク知識が必須となってしまいました。
 理由については、先に述べたようにChromecast機器にてメディアを再生するには同一ネットワークである必要があるからです。
 配信サーバーのネットワークインターフェースが複数ある中で、Zeroconfサービスにより見つかった同一ネットワーク上のChromecast機器、これがどのネットワークに属しているのか分からなければなりません。これがもし分からなければ、Chromecast機器からみた配信サーバーのプライベートIPが分からず、配信サーバーからメディアを取得することが不可能に陥ってしまいます。
 これを解決するためにChromecast機器ホストのプライベートIPから、同一ネットワーク上の配信サーバーのネットワークインターフェースを特定し、配信サーバーに割り当てられたプライベートIPアドレスを取得。これにより各Chromecast機器に対しプライベートIP指定による配信サーバーへのHTTPアクセス、つまりメディアへのHTTPアクセスを可能としています。

 このネットワーク構成について、予め社内にて固定のIPアドレス(グローバルorプライベート)又は、ホスト名を配信サーバーに割り当てた方が作成時間を短縮することができます。Zeroconfサービスによる同一ネットワーク上の制限と配信サーバーのメディア取得は切り離すことが出来、Chromecast機器は固定のIPアドレス又はホスト名を指定しての再生が可能となります。
 ネットワーク環境を制限化することで、開発コストが変わってきますのでぜひご提案してみることをオススメします。

配信サーバーの再生命令はChromecast機器のスリープモードを解除してしまう

 Chromecast機器の電源がOFF、言い換えるとテレビの電源をリモコンでOFFにしてあっても、配信サーバーから再生命令を送信すると電源がONに切り替わりメディア再生を始めてしまいます。
 これにより24時間稼働させるには再生命令を送信しない時間帯制御を行なう実装をする必要となりました。
 弊社R-FORCEでは、配信サーバーにCron設定を行ない自動で配信システムの起動と停止の制御を行なっております。

配信サーバーの再生命令はChromecast機器の状態を上書きしてしまう

 スリープモード解除と似た話ですが、Chromecast機器にて何かしら利用していたとしても上書きしてしまいます。

f:id:rf-blog-sagyo:20201008154617p:plain
再生
 弊社R-FORCEではChromecast機器に再生命令を発信する前に、配信停止命令を受けているChromecast機器か判別を行ない再生除外を可能としています。

Chromecast機器との接続周りの内部実装が分かりにくい

 Chromecast機器とのソケット周りの接続実装。README.rst の How to Use を読んで実装は出来ました。
 しかし、弊社R-FORCEでは複数のChromecast機器が存在するため定期的にChromecast機器の検索が必要でした。
 実装当初Pychromecastライブラリのバージョン 6.0.1 にて実装していたget_chromecasts()関数、今の Pychromecastライブラリのバージョン 7.5.0 の How to Use では記載ないがないものの、このget_chromecasts()関数を利用して接続を行なっておりました。*1
 この関数は同一ネットワーク上すべてのChromecast機器をZeroconfサービスを通して検出し、ソケット接続まで行なってくれるため大変ありがたい機能です。
 これを利用してメディアを再生して次のメディア再生に進む時に、get_chromecasts()関数を使ってすべてのChromecastを検索、もしChromecast機器が追加されている場合はここで新たに再生するChromecastとして追加して再生という実装を施しました。

 しかし、これには大きな問題がありました。
 それは数回メディアを再生するとChromecast機器との接続エラーで100%再生不能に陥るという現象です。

 この接続エラー問題に対し再接続を試みましたが何をやっても解決に結びつかずもがき苦しみました。
 Pychromecastライブラリのバージョン v6.0.1 から v7.5.0 にバージョンアップしたり、Pychromecastライブラリ周りのインスタンスをメディア再生の度に全て削除(デストラクタ)して再接続したりと、再接続の失敗の度に悩むに悩まされ別の手法別の手法と数回に渡り修正実装に着手。
 そして、あまりにも失敗続きであることからネットワーク通信の監視ソフトWiresharkを導入し調査するまでに発展。
 このWiresharkによるとPychromecastライブラリが利用しているZeroconfサービスを通して、マルチスレッドにてChromecast機器へソケット接続。メディアの再生の度に、Pychromecastライブラリ側で新たな接続を行ない、あるところでChromecast機器との接続失敗が始まっていることが確認出来るようになりました。
 これもしかして、一度接続に成功したChromecast機器とのソケット接続、この接続が生きたままメディア再生の度にget_chromecasts()関数によってChromecast機器とのソケット接続を新しく確立してはいないかと。そしてこのことがChromecast機器との接続最大数の上限に達してしまい切断を引き起こしていないかと疑惑が浮上しました。
 この疑惑のキッカケとなったのは次のissueより github.com  issue情報を元に、実装をPychromecastライブラリのバージョンv7.5.0の How to Use を参考にしながら、discover_chromecasts()関数にて同一ネットワーク上のChromecast機器を探し、切断検出したChromecast機器を検出した場合、または新しくChromecast機器を検出した場合に接続を行なうように変更しました。
 このように処理を切り替えたところメディアが一定回数再生してもChromecast機器との切断がなくなり、ようやく安定的な配信が可能となりました。

Chromecast機器のステータス判断種類が少ない

 弊社R-FORCEではPychromecastライブラリ利用時から拡張しています。
 その理由としてはChromecast機器のステータスPychromecastライブラリが標準で実装しているのが、
   * 再生中か?(PLAYING*2またはBUFFERING*3か?)
   * 一時停止中か?(PAUSED*4か?)
   * 何も再生していないか?(IDLE*5か?)
 この3パターンを提供しているものの、Chromecast機器のステータスのIDLEBUFFERINGPLAYINGPAUSEDの4種類のステータス遷移が上記3パターンだけでは要望を満たせないため拡張を行ないました。
 「再生中のBUFFERINGとPLAYINGを分けたい」「PAUSEDは再生中だけれど例外として扱いたい」「IDLEは再生前後で分けたい」といった条件分けを行ないたかったために、Chromecast機器のステータスについてはPychromecastライブラリ標準のものはほぼ利用せず、Pychromecastライブラリの外部から直接Chromecast機器のステータスを確認するように独自実装を行なっています。

Chromecast機器の拡張ステータスの取得部分を独自実装

 Chromecast機器の再生ステータスとして

IDLE ⇛ BUFFERING ⇛ PLAYING(PAUSED) ⇛ IDLE

大まかにはこのようなステータス変化をメディア再生の度に繰り返していました。

 弊社R-FORCEでは上記の流れからChromecast機器のステータスを調べIDLEというステータスを軸に、再生命令からIDLEというステータスに切り替わったとき再生終了と見なし次のメディア再生という遷移を行なっておりました。
 しかし、メディア再生途中で次のメディアの再生を開始してしまうというバグ報告を受けて調査を行なったところ、実際には次のステータス遷移を行なっておりました。

IDLE ⇛ BUFFERING ⇛ IDLE ⇛ PLAYING(PAUSED) ⇛ IDLE

 BUFFERINGの後にまさかのIDLEというステータスを稀に使用しており、この稀の現象を引き当てるとメディアのBUFFERINGが完了し、PLAYINGに切り替わる途中で次のメディア再生命令を配信サーバーから受け取ってしまい、メディア再生途中で次のメディアの再生という現象を引き起こしておりました。
 稀という表現行ないましたが、一度BUFFERINGされたメディアを再度再生している時に起こしており、Chromecast機器にて同一メディアを複数回再生していると発生するように感じました。ただ、この現象について原因を確定し現象を必ず再現させるまでは至れず稀という表現を行なっております。
 バグの調査を続けるに当たり、BUFFERINGの後のIDLEの場合は

{..., 'playerState': 'IDLE', ..., 'extendedStatus': {'playerState': 'LOADING', ...}}

というextendedStatusというパラメータを使ってChromecast機器のステータスplayerStateを返却しているため、Chromecast機器のステータスplayerStateがIDLEでもextendedStatusというパラメータが返却されたら、extendedStatus内のステータスplayerStateを優先するという処理をPychromecastライブラリを拡張させることで解決をさせました。

結論

 

参考にした記事を書いた人と実現したい要望(実装する環境)が必ず一致するのか確認しよう
 今回ネット上にあるPychromecastを利用した簡単に配信という記事からスタートしましたが、開発着手よりかれこれ半年・・・
 確かに1台だけの配信や1メディアだけの1回だけ再生であれば非常にかなり簡単に実装が出来ました。
 しかし要望は再生リストを作成してループ再生や複数のChromecast機器へ配信などetc...

 GoogleCastApiを利用したライブラリはPychromecast以外にも存在していました。

 ネットにある情報を鵜呑みにせず、ライブラリの比較や検証を正しく行なっていれば違った結果に行きつけたのかも知れません。
 この記事を読んだ人が私と同じようなまだトゲが残っている茨の道を歩むことが無いよう心よりお祈り致します。

*1:バージョン6.0.1の How to Useバージョン7.5.0の How to Use

*2:PLAYING: 再生中

*3:BUFFERING: 再生に備えて再生メディアデータを取り込んでいる状態

*4:PAUSED: 一時停止中(再生メディアが画像の場合はPLAYINGとならずPAUSEDの一時停止となる。動画メディアなどの場合はPLAYINGのあと再生中のメディアを一時停止するとPAUSEDの一時停止した状態となる)

*5:IDLE: 何も再生していない