休みの日は終日ネットラジオを鳴らしていることが多い。
ハードはちょっと前までRaspberry Pi Zero Wだった。これはOLEDを繋いで時計と温度計も兼ねさせた多機能なもので、それなりに重宝していた。しかし先日、Raspberry Pi Pico で温度計付き時計を作ったことから一時引退にしてしまった。
代わりに使っているのが、Atom時代のタブレットPCで、LMDE(32bit)に自作のネットラジオ(アプリケーションソフト)を入れて鳴らしている。
この自作アプリはmpvをバックエンドにしたフロントエンドで、GTKとCで書いたなんの変哲もないものである。
mpv はsystemdにてデーモンにしておいて、UNIX Socket でフロントエンドと通信して動かしている。
mpv はそのままでもストリーミングに対応しているので、普通の(?)ネットストリーミングならURLを渡せばすぐに音が出てくる。
しかし、日本国内で使われているradikoは、フリーであるにもかかわらず地域判定の必要性からか認証が必要で簡単にはいかない。
といっても、然程難しいものでもないので、ネットを漁ればradiko聴取用のスクリプトはいくらでも出てくるのであった。
今まで使っていたのは
NanoPi NEOにインストールしたMPDでradikoを聞く
https://burro.hatenablog.com/entry/2019/02/16/175836
で紹介されている中継サーバーだった。これはサーバーなので、ストリーミングを解するソフトであれば何でもクライアントになれる利点がある。
その一方、ラジオとして使わない時も一定量のメモリを消費する(はず)というデメリットもあった。
一定量のメモリ、などと言っても昨今のハード事情であれば然程問題になることもなく、実際、上記のRaspiZeroWでも使っていたくらいである。
ところが、そのRaspiZeroWの挙動を見ていておかしな事に気づいたのですね。それは1日中Radikoを鳴らしておくとメモリの消費量がすごいというものです。RAMで不足してSWAPし始める程でした。
以前はこんな事はなかったと思うのですが。まあ、PCで使っていた時はデスクトップは1日に1回は再起動する(あるいは電源を切る)ので気づかなかったのかも知れません。中継サーバーそのものはスクリプトもフレームワークも旧いままですが、RaspiのOSは都度新しいものを使うようにしていたので、その辺りでリグレッションが生じたのかも知れません。
いずれにせよ、この中継サーバーをこれ以上使い続けるのは困難であるのは変わらない(実はこれもRaspiZeroWを一時引退させた理由の一つ)ので、代替手段を講じなくてはなりませんでした。
radikoが認証後に返してくるのはHLSなのでmpvで扱うことができます。であれば、認証部分だけ書いて自作ネットラジオに実装すれば良いのですが、地味に面倒なので手を付けてませんでした。
しかし、ネットを漁っていて見つけたスクリプトがとても使いやすそうだったので、借用して取り組んでみることにしました。
radikoの再生や録音をツールで自動化
https://kakurasan.tk/raspberrypi/raspberrypi-automate-playing-and-recording-radiko/
上記スクリプトでは再生にffplayを使っていますが、mpvで同様のことを行うには次の様にします。
os.system( f"mpv -http-header-fields='X-Radiko-Authtoken:{token}' '{m3u8}'")
しかし、これだとデーモナイズされているmpvと通信して再生、ということができません。
なので、このようにしました。
s0 = f"http-header-fields='X-Radiko-Authtoken:{token}'"
s1 = f"{m3u8}"
s2="{%s}¥n" % '"command": ["loadfile", "{0}"]'.format(s1)
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect("/run/user/1000/mpvsocket") # mpvとの通信ソケット
s.send(s2.encode())
d = s.recv(1024)
s.close()
変数s0にhttpヘッダを入れていますが使っていません。mpvでJSON IPCを使って渡す方法を見つけられなかった(探し方が悪いのかも知れない)からです。とりあえず無くても音は出ますが、ひょっとしたら何か不具合があるかも知れません(長時間接続していると切断される、とか)。
次にこのスクリプトを自作ネットラジオにどのように渡すか、です。埋め込んでしまう事も考えたのですが、後々のメンテナンスを考えてボツにしました。
いろいろ考えてプレイリスト側に細工することにしました。
通常、プレイリストはこのように書きます。(m3uの場合)
#EXTINF:-1,radioparadise.com(main mix) / radioparadaise.com(main mix,320k,aac)
http://stream.radioparadise.com/aac-320
radikoの場合はこんな風に書くことにしました。
#EXTINF:-1,Tokyo / 文化放送
plugin:/radiko.py/QRR
自作ネットラジオ側でプレイリストを読んで、スキーム部分が「plugin」なら続く部分をスクリプト名とみなして処理する、という、自分勝手なものです。自分で作って自分で使うのでこれで充分です。なお、スキームの「:」の後ろのスラッシュが一つ少ないのは、pathを作る際の手間を減らす工夫(単なる手抜き)です。
コード(抜粋)はこんな感じです
gchar *scheme = g_uri_parse_scheme (url);
if (!strcmp (scheme, "plugin")) {
// url の先頭がpluginであれば呼び出しにかかる
gchar *station = g_path_get_basename (url); // basename をplugin の引数にする
gchar *p_path = g_path_get_dirname (url);
gchar **plugin = g_strsplit (p_path, ":", 2);
if (plugin != NULL) {
gchar *command = g_strdup_printf ("%s/mpvradio/plugins%s %s",
DATADIR, plugin[1], station);
system (command);
g_free (command);
g_strfreev (plugin);
}
g_free (p_path);
g_free (station);
}
else {
// url や playlist であればそのまま mpv に送る
mpvradio_ipc_send ("{¥"command¥": [¥"set_property¥", ¥"pause¥", false]}¥x0a");
message = g_strdup_printf ("{¥"command¥": [¥"loadfile¥",¥"%s¥"]}¥x0a", url);
mpvradio_ipc_send (message);
g_free (message);
}
プラグインスクリプトの格納先が固定だったり、処理の呼び出しが system にそのまま投げたりとαバージョン感が色濃く漂ってますね。
使い勝手ですが、遅いハードだとどうしてもpythonのロード待ちが生じて選局の時に一瞬の間が空いてしまいます。OS側でキャッシュしてくれているはず、と思うのですが。
しばらく使い込んでヘッダを渡していない事なんかの不具合を見極めたいと思います。
既知の問題点
- system()で直接呼び出しているので、スクリプトに実行許可がないと失敗する。
- mpvから、現在再生中の曲情報を返すようにしている(mpvのluaスクリプト)が、radikoの場合返されるのはHLSのチャンクファイル名そのもので、曲名はおろか局名すら得られない。
参考URL
NanoPi NEOにインストールしたMPDでradikoを聞く
https://burro.hatenablog.com/entry/2019/02/16/175836
radikoの再生や録音をツールで自動化
https://kakurasan.tk/raspberrypi/raspberrypi-automate-playing-and-recording-radiko/