X has gone away

長いことXubuntuを使ってきたのだけど、思うところがあり素のUbuntuに乗り換えた。この際新しいSSDにクリーンインストールしたので諸々の事柄を設定しなおしとなった。とても面倒であった…。

で、あらかた旧環境の再現が終わってはいるのだが、まだskk周りが残っている。旧環境ではfcitx(4)+fcitx-skkだったのだが、fcitx5+fcitx-skkになった。ここで問題があり…

明らかに壊れている

まず以前から辞書ツールが壊れているのだが、依然として壊れている。まあ壊れているのはボタンのアイコンだけで、機能自体に問題はないっぽい、たぶん。ちなみにボタンは上から追加、削除、上へ、下へ、リロードという感じ。

また、fcitx-skkが参照するdictionary_listの位置が~/.config/fcitx/skk/dictionary_listから~/.local/share/fcitx5/skk/dictionary_listに変わっているようだ。$FCITX_CONFIG_DIRが実際にどこなのかをこのダイアログに表示してくれるととっても嬉しいのだが。現在fcitx-skkを誰が作っているのか、libskkを誰が作っているのか、結局よく知らないので提案できない。

Wanna suspend a usb device

見ないといいつつまた地上波テレビチューナーについて。

以前も書いたがこのチューナー(PX-S1UD)、長時間稼動させるととたんに動作が不安定になる。柔らかcasの導入もましになった程度で根本的な解決には至っていない。つまるところ熱にすんごく弱いんじゃないのかしらこのハードウェア。

現状、このチューナーを通してテレビを見るために使っているプログラムはrecdvbというものだ。指定の時間分、あるいは垂れ流し状態で地上波テレビの動画をデコードし、ファイルか、任意のポート(udp)か、あるいはhttpサーバとして振る舞いhttpリクエストの応答として出力することができる。

で、現状はそれをhttpサーバとして常駐させて、vlcからhttp://127.0.0.1:8080/13 みたいな感じのURLで開いて見ているのだが、見終わった後もチューナーは熱いままなのである。もしかしたら見終わった(vlcを閉じた)ということがrecdvbに伝わらないまま、延々デコードして、データは捨てている状態になっているのかもしれない。

ということは、httpサーバとして使用するのではなく、視聴のたびにrecdvbを起動し、見終わったらrecdvbも終了させる方がよいのかもしれない。そういうやり方だとhttpサーバとして動作させる意味もあまりないのでudpで吐き出させる方がいいかな。とりあえずそんな感じの構成にしてみたのでしばらくこれで様子を見たい。

ちなみに、USBデバイスなので自動でサスペンドに入るモードがないのかpowertopで調べてみたが、tunableではないようだ。また、/sys/bus/usb/…/power/controlにautoを書き込んでも自動でサスペンドに入ったりはしないようだ。廉価な製品はそういうものなのかもしれない。

Prevent Apache from AH00144

たまにApache2がタイトルのエラーで起動に失敗することがあるのに対処した。何が原因でどうするかはApache が AH00144 で落ちる件の通り。

複数の対処方が述べられているが /etc/systemd/logind.conf.d/の下のファイルでRemoveIPC=noとする方法を採った。すでにこのディレクトリに、ラップトップのスクリーンを閉じても特に何もしないようにする設定のファイルを置いていたので。

それぞれの設定内容は以下の通り:

[Login]
RemoveIPC=no
[Login]
HandleLidSwitch=ignore

Jammy Jellyfish⁠ is approaching fast

xubuntu 22.04.1になった。

  • xfce4-terminalで太字属性が正しく太字で描画されるようになった。これが気になっていたので一時期mltermとかに移行してみたのだが、困ったことに若干不安定だったり背景の透明化が効かなかったり常用にはちょっと厳しかった
  • パネルからなぜかdatetimeプラグインが消えてしまった。入れ直せばまた入る
  • Thunarでサムネイル表示させてると割と不安定だったのが直っている…かもしれない
  • Firefoxがsnapアプリケーションになった。なんか事前の情報だとけっこうダメダメな出来だと聞いていたが上手く動いている気がする
  • Chromeで動画を再生する際にハードウェアアクセラレーションが効かなかったのでいろいろググってあらかた試したのだが全然だった。今回ふと確認してみたら普通に効いてた
  • xfce4-terminalにはドロップダウンモードというものがあり、つまりいつでもどこでもショートカットキーひとつで呼び出せる端末として振る舞う。これはけっこう便利なのだが、しばらく使っていると呼び出しても反応しなくなる。不思議なことに、マウスポインタをパネルの上に乗せると即反応する。なんだこれは。今のところそれが直っている気がする

特にトラブルとかなかった。16→18のときは盛大に問題続出だったのを振り返ると成熟あじがある。

22.04と関係ないのだけど、地上波テレビを見るためにいわゆる柔らかいアレを導入しているのだがシステムのlibpcsclite.soを置き換えるというワイルドな手段をとっていたためwifiが使えなくなる(wpa_supplicantがスマートカードからの読み込み機能を持っているらしいので)という不具合があった。そこで、件のライブラリを置き換えずに済むような感じにlibarib25.soのビルドを変更。まあそもそもマジで地上波テレビ見ないからどうでもいいといえばいいのだが。たまに何か見るときもありますしね。

Blocking something

今日のwwwでは、広告は貼られることが当然のものになっている。しかし広告がページの閲覧をどの程度邪魔するかとか、どの程度リソースを消費できるかとか、そういったものを規制する統一された基準は特にないように思える。ないので、やりようによっては全画面広告だとかいきなり動画が再生される広告とかマシンパワーをやたら横取りする広告とかなんでもできうる。

もちろんそんなことを勝手にされたらたまらないわけで、PCのブラウザでは広告ブロッカー拡張を導入するユーザーが少なくない。いやwebサイトの維持に広告収入が重要なのはよく分かるのですが。しかし広告をブロックするとページの読み込みが何倍も速くなってついでにバッテリーも保つんだからしょうがないよね。全然しょうがない。

などと言いつつうちではその手のブロッカーは入れていない。というのは、ブロッカーってブロックのためのリストを内包してるわけだが、ページを読むたびに何万件ものリストと照合するのそれはそれで無駄にメモリやCPUパワーを使ってないですか? と思うからだ。その代わりにjavascriptの実行自体をブロックする拡張を入れている。

javascriptの実行自体をブロックする拡張と言えば有名なのはNoscriptというやつなのだが、これは基本的に常にjavascriptの実行を禁止するが許可リストに登録されたサイトだけは例外扱いとするスタイルである。これだと知らないサイトに行くとだいたい動かない→許可リストに登録→リロードという作業を強いられるのでけっこうというかかなりうざったい。怪しげなサイトだろうが全うなサイトだろうがどこでもjavascriptでドライブされるのが前提である今時のwwwに適合していない。

そこで、NoscriptではなくScriptBlockという拡張を入れている。これはもうちょっとマイルドなルールでブロックするか否かを決めているらしく、たいていの場合そういうめんどくさい作業をしなくても済むようになっている。ただそのルールの詳細がよく分からないのと、あとページのあらゆる要素にdata-ssなんちゃらという属性を嵌めまくるのが若干胃がムカつく。つまるところ、広告をブロックしたい!という要件に対して満足するソリューションを見付けられていないのである。

  • 同一ドメイン上のスクリプトは実行を許可する
  • 有名所のCDNなんかのスクリプトなんかも許可する
  • ブラウジングコンテキストのドメインと異なる場所から読み込まれるスクリプトは明示的に許可されていない限りブロックする

くらいのシンプルな拡張があればいいのだけど。そういうのないかな。

Can’t register a service worker on Vivaldi

あべ☆アニ Extension のシリーズ予約機能は、ブラウザ拡張内のバックグラウンドページでサービスワーカーを登録し、そこでここのサーバからのプッシュ通知を受け取るような構造になっている。

サービスワーカーのハンドリングや、プッシュ通知をために各デバイスに固有のトークンを得る処理などは、Firebase のライブラリに任せている。このため、今のところシリーズ予約機能は Chrome ファミリーであるブラウザ上でのみ動作する。

さて Vivaldi という謎のブラウザがある。これもまた基本的には Chrome の一族なのだが…以前も似たような Vivaldi 特有の現象に悩まされたことがあったが、どうもえ?そんなところまで手を入れる必要あります?ってくらいいろいろ魔改造されているようだ。

具体的には Vivaldi 上ではサービスワーカーの登録に失敗するのである。こんなエラーが出る:

FirebaseError: Messaging: We are unable to register the default service worker. Failed to register a ServiceWorker for scope ('chrome-extension://feohncbfalalaopkicfimhiphlnffile/firebase-cloud-messaging-push-scope') with script ('chrome-extension://feohncbfalalaopkicfimhiphlnffile/firebase-messaging-sw.js'): An unknown error occurred when fetching the script. (messaging/failed-service-worker-registration).
at ot.<anonymous> (https://www.gstatic.com/firebasejs/8.2.1/firebase-messaging.js:1:36826)
at https://www.gstatic.com/firebasejs/8.2.1/firebase-messaging.js:1:1982
at Object.throw (https://www.gstatic.com/firebasejs/8.2.1/firebase-messaging.js:1:2087)
at i (https://www.gstatic.com/firebasejs/8.2.1/firebase-messaging.js:1:884)

つまるところ、Firebase のライブラリ内でデフォルトのサービスワーカーとしてサイトのルートにある firebase-messaging-sw.js を登録しようとして失敗している。失敗した理由は、”An unknown error occurred when fetching the script.” だそうである。

この文言でググってみると特に Firebase に限ったものではなく、とにかく指定されたファイルを読み込めなかった際に Chrome 自身が出力するメッセージのようである。読み込めない理由は実際にファイルがないとか、スキームが https ではないとかそんな感じになる。しかしあべ☆アニ Extension の場合はそういう理由とは違うようだ。なにしろ Vivaldi でだけ失敗するので。

ということで、何か Vivaldi が抱えてる問題なのでは? という気がする。こういうバグもある。

Hit here at 60 degree angle

USBのテレビチューナーを付けているのでXubuntu上で地上波テレビを見られるわけなのだが、困ったことに割と不安定なのである。

VLCでストリームを開くと、ノイズまみれになったり、何も映らなかったり、Xごと固まったりする、ときがある。再起動直後はそうなる可能性が高く、数日起動しっぱなしの後はうまく映る可能性の方が高くなるというのも妙にアナログ的で何が悪いのか目星を付けにくい。

特にXごと固まると復旧するのも大変だ。なにしろほぼどんな入力も受け付けなくなる。こうなった際はCtrl+Alt+F2で別のコンソールに切り替え(このキーストロークだけは生きている)、

sudo systemctl restart lightdm

とかするととりあえず入力を受け付ける所までは戻る。ただしこれでおかしなテレビが直ったりするわけではない。

さてワンセグを見る分には特に問題はなさそうなので、つまるところB-CASカードからのデータ読み込みと復号を担当するlibarib25.soに問題があるのかもしれない。そうすると、これを解決する手段としてはアレしかないわけで、あちこちからソースをかき集め、アレをビルドしてアレと差し替えたところ、安定するようになった。いやー…すごいなアレは。

Pushing latest episodes #3

次にアプリケーションサーバとなるここに関して。

アプリケーションサーバ側でもFirebaseが提供する、Firebase Admin SDKと呼ばれるライブラリを利用できる。ただしサポートされるのはNodeJavaPythonC#Goであり、phpはない。そんなわけで非公式のphp向けライブラリがあるのでそれを使う。どうしてphp版を公式に出さないんですか…どうして…。それはそれとして、php向けライブラリはドキュメントも充実しているのでありがたい。

アプリケーションサーバでやることは、クライアントから渡されたトークン、番組のシリーズIDをデータベースに保存し、番組表の取り込み時に該当するシリーズの最新話を見つけたらPush通知を行う…といった感じである。やること自体は割と簡単。

このシリーズIDとは何かというと。まず番組表は番組枠が列挙してある。番組枠とは時間で区切られた区間のことだ。この枠に番組が収まっている。この仕様から見れば、番組枠の中に複数の番組を入れることも不可能ではないので、データ上も番組枠(slot)にぶら下がっている番組(programs)は配列になっている。

シリーズIDはこのprogramsの各要素に生えているseriesオブジェクトのidプロパティであり、定義がないので断言はできないが、これが配信される作品全体に対して振られたユニークなIDのようである。また同様にprogramsの各要素に生えているepisodeオブジェクトのsequenceプロパティが該当シリーズにおける放送順を示している、ようである。

したがって、データベースにシリーズIDとその最新シーケンスナンバーのセットを保持しておき、番組表を取り込んだ際に照合して追跡中のシリーズでありかつ新しいシーケンスナンバーを持っている番組枠があれば、それが最新話ということになる(実際には、さらにmarkフラグなども見る)。

ところがここでひとつ問題がある。上記のシリーズIDの正規形は\d+-\d+らしいのだが、ときおりこのフォーマットから外れたものがある。例えばシリーズIDが175-1rthzhecdmeといったものになったり、シーケンスナンバーが20とか80とか突拍子もないものになったりする。これはどうも「仮のシリーズID」的なもので、未来の番組表に初めて現れた最新話とかがこの状態になったりするようだ。そして、実際の配信日になると本来のシリーズIDが割り振られる。

この仕様がどういう意味を持っているのか分からないが、とにかく不完全なシリーズIDは最新話を検知するための足掛かりにならない。そこで、シリーズIDを元にした検知と平行して、タイトルによるヒューリスティックな検知も行うことにする。これは、例えば検知を開始した番組のタイトルを覚えておいて、それと似た感じなら同一シリーズと見なすものだ。似た感じというのはリーベンシュタイン距離とかそんな感じのアレである。

ところでここで使用する番組のタイトルというのは、正確には番組枠のタイトルである。これは配信される作品のタイトルとは独立しているため、物によって【WEB最速・単独最速】だとか【地上波先行・先行配信】だとか、様々な枕詞が付いたりする。これはヒューリスティックな検知にはノイズなので困る。seriesオブジェクトに作品自体のタイトルを示すプロパティが格納されていると嬉しいのだけど。

というわけでAbema.tvの番組表データはいろいろと不思議な点が多い。

Pushing latest episodes #2

ということで、ブラウザ側としては拡張機能内でFirebase SDKを使用する構成にする。この組み合わせは、一応Firebaseのドキュメントでも公式に動作が保証されている。ただし、どういうわけか知らないが拡張機能内でFirebaseを使用する際のサンプルやガイダンスはドキュメントに一切ない。そのため備忘録として気付いたことをメモしておく。

ちなみに、上記の保証というのはSDKがchrome-extension:スキームを認識するということである。これは今のところSDKはFirefox上のWebExtensionsでは動作しないことを意味する。したがって、あべ☆アニの最新話の追跡機能もまた、Firefoxでは動作しない。

  • 拡張にSDKを組み込む。これは具体的には<script src="https://www.gstatic.com/firebasejs/8.2.1/firebase-app.js"></script>てな感じの要素をバックグラウンドページに仕込んでおくことである。この場合、外部サイトのファイルを直接指定しているので、manifest.jsに"content_security_policy": "script-src 'self' https://www.gstatic.com; object-src 'self'"といったCSPの記述も必要。また、この拡張をストアにアップする際にリモートコードを使用している旨の告知とその理由が必要になる。
  • Push通知を受けるためにどこかでサービスワーカーを登録しなければならないのだが、Firebase SDKを使う分には明示的にそれをする必要はない。firebase-messaging-sw.jsという名前のファイルをサイトのルート(つまり拡張のアーカイブのルート)に置き、必要な処理をそこに書いとけばいい。ワーカーの登録はFirebaseが勝手にやる。登録済みのregistrationを元にFirebaseアプリケーションオブジェクトを構築することもできるが、Firebase側でよしなにしてくれることをわざわざやることもないだろう。なお登録は不要だがサービスワーカーからのメッセージはリスンする必要があるので、navigator.serviceWorker.addEventListener('message', aFunctionDefinedSomewhereElse);とかはしておく。
  • Push通知を受けるためのトークンをfirebase.messaging().getToken()によって取得することができる。これによって得られる文字列は永続的なものであり、一度取得したら永続的なストレージに入れて使い回していい。この手のトークンにはたいていrefreshイベントをリスンして期限切れに備えましょう的なノウハウがあったりするが、必要ない。onTokenRefreshイベントは用意されているがすでにdeprecateである。
  • このトークンは通知の宛先となるものなので、通知を出す側であるアプリケーションサーバへ送り付ける。さてこれが漏れると勝手に通知を出されるので厳重に暗号化した上で適切に送信しなければならない…はずなのだが、ドキュメントでは単に「トークンを取得したら、それをアプリサーバーに送信して、適切な方法で保管します。」としか記述されていない。送信のためのベストプラクティスとかはないのである。単にpostすればいいらしい。本当? とりあえずあべ☆アニextensionではblowfishで暗号化した上でここのサーバに送信している。まあ拡張のソース見ればすぐバレちゃうけど。
  • Push通知を受けるには、サービスワーカーにおいてもFirebase SDKのコードをimportScript()して、必要な情報を元にFirebaseアプリケーションオブジェクトを生成し、onBackgroundMessageイベントをリスンする…とドキュメントには書いてあり、それが正道なんだろうが、実は単に通知を受けるだけならワーカー自身のpushイベントをリスンするだけで事足りる。FirebaseはPush通知以外にも様々な機能が統合されているので、通知を受けるとともにそれらの機能を利用する場合はドキュメント通りのやりかたが正しいのだろう。あべ☆アニextensionでは通知を単に受けるだけで良いので、直接pushイベントを見ている。importScript()云々もしていない。
  • サービスワーカーで通知を受けたらデータペイロードを拡張のバックグラウンドページへ送信する。ここで困るのはバックグラウンドページがイベントページ、つまり用が済んだら勝手にアンロードされるモードの場合だ。この場合、サービスワーカーのClients#matchAll()ではバックグラウンドページを取得できないし、起こすこともできない。どうするか。実はサービスワーカーのグローバルオブジェクトにchrome.runtime.getBackgroundClient()というものが生えており、それを呼ぶと必要ならバックグラウンドページを起こした上でそれに対応するClientオブジェクトで解決されるPromiseを返すようになっている。このメソッドはChromeの拡張APIドキュメントのどこにも載ってない、この記事だけのかなりのオトク情報である。まあいずれ載ると思うけど。

こんなもんかな。

Pushing latest episodes

あべ☆アニに、任意の番組の最新話を自動的に通知予約する機能を追加している。

そのためにはまず最新話を検知する処理をどこに置くかということを考えないといけない。番組の通知予約機能自体に立ち返ってみると登場人物が3者いる: ここのサーバ、ブラウザの拡張機能(のバックグラウンドページ)、あべ☆アニのページである。

番組の開始時刻になったことを検知し、タブを開く処理を置くのにどこが相応しいかを考えると、あべ☆アニのページはそれを開いている時しか機能しない。サーバ側でやってPush通知をするとなると通知が番組開始1分前に確実に届くかは保証できないし、通知を受けるサービスワーカーから行えるタブ操作が微妙に痒いところに手が届かない。と言うわけで、ブラウザの拡張機能にアラームをしかける実装になっている。

では、最新話の検知機能はどうか。それを行うには番組表の全ての枠を走査しなければならないが、まずあべ☆アニのページにしても拡張機能にしても個々のクライアントがやるべき処理ではない。一方で元々ここのサーバが番組表をキャッシュするために定期的に番組表を取り込んでいるのだから、その一環として最新話かどうかを判断する処理を入れるのが最も収まりがいいだろう。

そんなわけで検知機能はサーバ側に置き、見つけたらPush通知により個々のクライアントに送りつける構成にしよう。Push通知に関してはWeb PushのAPIをそのまま使うと妙にやることが多くてめんどくさいので、Firebase SDKを使うことにする。

ということでだいたいできて今dog-foodingしているところである。今月中くらいにはリリースしたい。