KonomiTV
いろいろな場所とデバイスでテレビと録画を配信サイトのような操作感で快適に観れる、メディアサーバーソフトウェア。 (2021.05-鋭意開発中)
いろいろな場所とデバイスでテレビと録画を配信サイトのような操作感で快適に観れる、メディアサーバーソフトウェアです。現在、ひとまずテレビのライブ視聴のみを実装したβ版の状態です。
TVRemotePlus の後継という位置づけのソフトですが、TVRemotePlus を開発する中での反省を最大限に活かし、まったく新しく、使いやすくて快適なテレビ/録画の視聴体験を創出したいという想いから開発をはじめました。
詳細な設計思想は GitHub の Readme に譲りますが、満足するまで時間を掛けて UI デザインを練り、可能な限り機能を直感的に扱える範囲でシンプルにまとめ、まるで Amazon Prime や Netflix 、AbemaTV などの配信サイトのような感覚で使えるものを目指しています。特に UI デザインに関しては AbemaTV を参考にしました。
2022/11/13 にリリースした v0.6.0 以降では、新規にフルスクラッチで開発した インストーラー (Windows と Linux 両方をサポートするため、Python (Rich) 製の CLI アプリ) を使い、とても簡単にインストールできるようになりました。
NSIS など通常のインストーラー作成ツールを使うことも考えましたが、KonomiTV の構成の特殊性ゆえ (Python 3.10 がインストールされていない Windows 環境でもポータブル版 Python で FastAPI (Uvicorn) を動かす、Python 製ソフトを pipenv 環境で pywin32 を使って Windows サービス化し、インストーラーでのインストール時に自動的に Windows サービスをインストールする、など) インストールプロセスがイレギュラーかつかなり複雑なこと、Linux 向けに別の CLI インストーラーを作らないといけないことなどを鑑みて、Windows / Linux 共通の CLI インストーラーをフルスクラッチで開発しました。
あまりスポットライトの当たらない部分ですが開発が地味にめちゃくちゃ大変で、開発完了までに丸々1ヶ月掛かっています…。
まだ予定している機能の半分も実装できていませんが、おかげ様で GitHub の Star 数は 216 、v0.5.0 (2022年2月リリース) 以降のバージョンの累計ダウンロード回数は合計 2,800 回を超えるなど (いずれも2022年11月中旬時点) 、大変好評をいただいています。
サーバー(バックエンド)は Python + FastAPI + Tortoise ORM + Uvicorn 、クライアント(フロントエンド)は Vue.js + Vuetify の SPA です。
当初は TVRemotePlus 同様に PHP を採用することも考えはしましたが、TVRemotePlus での動作方法自体が相当に無理やりだったこと、Python の方がより柔軟かつ簡単に動かすことができ、Windows 特有のつらみも軽減できる事から Python を採用しました。
サーバー側のフレームワークは FastAPI です。
モダンで非同期 I/O (asyncio) に対応しており、さらに実装難易度の高い MPEG-TS ライブストリームを複数のクライアントに配信するというイレギュラーな API の実装が可能であった事が決め手となり採用しました。
クライアント側のフレームワークは Vue.js (2.x) です。
開発開始当初はモダンな JavaScript フレームワークに触れた事がなかったため、React と比較して比較的習得が容易だろうと考え採用しました。もう少し時間があれば React にしたかもしれませんが、素早く実装することを優先しました。
UI には Vuetify をある程度使ってはいますが、UI の大半は SCSS を数百行単位でゴリゴリ書いて実装しています(Vuetify 側で用意されているコンポーネントだけではなかなか思い通りデザインにできないことが多くて…)。
フォームやセレクトボックス、モーダルなどフルスクラッチで書くのが面倒なコンポーネントに関しては、配色を KonomiTV のものと合わせた上で Vuetify を活用しています。
実はいわゆる「モダンフロントエンド」(React or Vue / npm / webpack / TypeScript / SPA) なコードを KonomiTV の開発に着手するまで書いたことがなく(TVRemotePlus はレガシーな JavaScript 直書きでやっていた…)、慣れない中試行錯誤を繰り返し、なんとかこの形まで持って行くことができました。どう設計すればいいのかいまいち掴めず、設計的には見る人が見ればあまりよくないかもしれませんが…。
特にテレビのライブ視聴画面の開発が大変で、一番時間を費やしました。どうすればデザインのシンプルさと直感的な操作感を保ちながら多くの機能を実装できるか、デザイン的にも検討を重ねて今の UI になっています。
視聴周りの根幹である動画プレイヤーには、DPlayer というニコニコ風にコメントを流せる動画プレイヤーライブラリをフォークして使っています(本家の開発が3年ほど停止していたこと、日本語対応がされていないこともあって TVRemotePlus の頃からフォークを維持している)。
後述の mpegts.js や aribb24.js (日本のテレビ放送の字幕形式に対応した字幕レンダラーライブラリ) を組み込んだり UI を大幅にリファインしたりなど、KonomiTV / TVRemotePlus 向けに相当な改良を行いました。
DPlayer はバグが多くすべて JavaScript (not TypeScript) で書かれていたりはありますが、コード自体はシンプルかつとてもきれいに書かれていて、実際にコードを読んで改造していく中で設計面でもかなり勉強になりました。コードベースも機能の割にはそこまで大きくなく、改造しやすい構成になっていたのも大きいです。
細かなエンハンスが多いため一概に何をやったかは言いづらいのですが、DPlayer の改良をはじめた2019年当時は npm や webpack といったモダンフロントエンドの技術をまったく知らない状態で、フロントエンド技術の習得にあたり良い土台となりました。長年触っているだけあり、思い入れも深いです。
さらに mpegts.js という Media Source Extensions API を使って MPEG-TS (H.264 / H.265 + AAC) をブラウザでダイレクトに超低遅延でライブストリーミングできるライブラリを活用し、放送波 (TVTest) との比較でわずか最短 1.9 秒の超低遅延ストリーミングを実現しています。開発者 (&知人) の xqq さんにも多くのご協力をいただきました。
サーバー側のライブストリーミングの状態変更をいち早くユーザーに伝えるため(どうしてもストリーミング開始までには時間が掛かるため、体感速度の短縮という意味合いもある)、Server-Sent Events を使ってリアルタイムでサーバーから「エンコードを開始しています…」などのストリーミングの状態変更を受け取れるようにしています。また、エンコードプロセスがエラーで落ちてしまったときに視聴セッションを再起動するトリガーとしても使っています。
Server-Sent Events は WebSocket と比べてほとんど知られていないマイナーな技術ですが、WebSocket と比べて HTTP で通信する分比較的簡単に実装できることと、今回の用途ではサーバー→クライアントの片方向の通信だけで良かったのもあって採用しました。正直 WebSocket を使っている大半のユースケースでは Server-Sent Events で十分じゃね?とさえ思う…(枯れた技術とはいえもっと普及してもいいのに…)。
チャンネル選択/切り替えからストリーミング再生開始までの待機時間に関してはサーバー(チューナー・エンコーダー)側で相当調整を頑張って技術的に3秒~6秒ほどまで短縮しています。また少しでも体感を早く感じてもらうために、真っ黒な映像ではなく読み込み中にランダムで異なる風景写真(個人的に一眼で撮ってきたもの)を表示するようにしていたりと、いかにストレスなく使えるかを考えて開発しました。
このほか、まだ追加されたばかりで利用者が極めて少ない(マジで情報がない…)Web API を積極的に採用しています。
たとえば Media Session API でライブストリーミングの再生状態(番組名・チャンネル名など)をブラウザや OS に共有して、通知から再生 / 停止 / 前のチャンネルへの切り替え / 次のチャンネルへの切り替えができるようにしたり、Picture-in-Picture API で映像をフローティング表示して何か作業しながら見れるようにしたりしました。
利用者が少ないので情報もなく(MDN にすらまともに情報がない事すらある…)実装が大変でしたが、Web ブラウザで技術的に可能な限界まで使いやすくしているつもりです。
Picture-in-Picture API は特に Android では3~4年くらい chrome://flags 止まりで実装が遅れに遅れたこともあってかバグが多く、最近 Chromium のバグトラッカーに Issue を立てたり もしました(英文苦手なので全部 DeepL 丸投げだけど…)。できるだけ早く直ってくれればいいのだけど…。
PWA (Progressive Web Apps) に対応しているため、PC や Android では KonomiTV を [ホーム画面に追加] することで、まるでネイティブアプリのような操作感で使えます。
TVTest などの従来のテレビの視聴ソフトは Windows 向けが多く MacBook やスマホでは使えないのですが、KonomiTV でならどんなデバイスでも見れるので、自分でもかなり重宝しています(自分のユースケースにぴったり合うものを作ったので当然ではある)。
スマホやタブレットでは今のところ横画面表示のみ対応しています。最初に PC 向けに画面を作ったため、スマホやタブレットの縦画面表示に対応するには UI に大きく変更を加える必要が出てくるためです。
将来的には縦画面表示にも対応予定ですが、ひとまず横画面表示で快適に使えるように、デザインや余白などを調整しています。機能を損なわずに小さな画面でもできるだけ快適に使えるように調整するのがとても大変でした。主要な画面のデザインは一度 Figma で大枠を作ってから、実際に HTML / SCSS をゴリゴリ書いてひととおり実体化させ、適宜細部を詰めていく方針で開発しました。
スクリーンショットにある範囲のデザインは2021年の5月~8月ごろに作成しました(その後も実際の実装に合わせて少し調整しています)。さすがに毎回機能を付け足すたびにデザインの方にも反映していては手間が膨大になってしまうため、ある程度イメージがまとまってからは HTML / SCSS を直接編集してデザインを調整しています。
途切れ途切れの作業ではありますが、大枠のデザインを作ること自体にトータル1ヶ月弱は掛かっていたような記憶があります。Figma をまともに触るのもこれがはじめてで、Figma の使い方に慣れるのに苦労しました(Auto Layout と Components 機能がマジで便利…)。
デザインを作るのは(すごく頭を使うし疲れるけど)めちゃくちゃ楽しい時間のひとつです。頭の中にある漠然とした完成イメージをモックアップという形で現実に投影して実体化できた瞬間には、何とも言えない感慨があります。もちろんそのデザインを実際に動くものにできた感慨もひとしお。v0.1.0 を完成させたときの感動は忘れられません。
サーバー側のコードも、FastAPI 自体を使うのが初めてだったこと、Python の非同期 I/O である asyncio の API が分かりづらい(さらに元々情報が少ないし、古い Python と最新の Python で情報がかなり違ったりする…)こと、さらに元々「他のソフト (EDCB / Mirakurun) との連携」「テレビの放送波をチューナーソフトから受け取って、エンコーダーに渡す」「エンコードされたデータをエンコーダーから受け取って複数のクライアントに配信する」など高度な要件が積み重なっていることもあって、開発はかなり難航しました…。
同時に複数の端末が同じチャンネルを視聴している場合はチューナーとエンコーダーの出力を共有(共聴)したり、誰もそのチャンネルを視聴しなくなった場合はアイドル状態として『他のチャンネルの視聴リクエストが来たらチューナーリソースを他のチャンネルに委譲し、チャンネルを切り替える』『一定期間の間にもう一度視聴リクエストが来たら、オンエア状態に戻る』『一定期間の間に視聴リクエストが来なければチューナーリソースを解放し、エンコードを止める』といった高度なチューナーやエンコーダーのリソースの制御を行っているのがちょっとした自慢です。
共有できるチューナーやエンコーダーのリソースは極力共有し、限りあるチューナーやエンコーダーリソースを無駄に使うことがないような設計としています(こだわりポイントのひとつ)。
テレビの放送波の MPEG2-TS ストリームは FFmpeg などのエンコーダーではかなり扱いづらく、たとえば音声多重放送に切り替わったり、サブチャンネルが始まって画質が変わるとすぐエラーで落ちてしまったりします。さらに NHK のニュースなどデュアルモノ(音声のLチャンネルで主音声、Rチャンネルで副音声を流すこと)な番組や字幕への対応などイレギュラーな要素が相当あり、これをできるだけ中断させることなく、安定してストリーミングできるようにするまでに相当な時間と労力を費やしました。
もし落ちてしまった時もすぐにストリーミング配信プロセスを再起動してロスタイムができるだけ少なくなるようにしたり、ハードウェアエンコーダーごとにエンコードパラメーターを遅延が少なく最適な画質になるように調整したりなど…
また、KonomiTV で採用している QSVEncC / NVEncC / VCEEncC というハードウェアエンコーダーの開発者の rigaya さんと連絡を取って最適なエンコードパラメーターを調整したり、EDCB (予約録画ソフト) のメンテナの xtne6f さんに EDCB と通信するためのコード を寄稿していただいたり(EDCB は十数年以上の歴史を持つレガシーなソフトだからか通信プロトコルが完全に独自のバイナリプロトコルになっており、自力で実装するのは難しかった)tsreadex という放送波の MPEG2-TS をエンコーダーでエンコードしやすい形に整形するためのツールを作っていただくなど、ほかの開発者さんからも多大なご協力をいただいています。その節は大変ご迷惑をおかけしました……🙏
データベースは SQLite 、ORM には Tortoise ORM を使っています。
本当は MySQL あたりが使えればよかったのですが、ローカルで動かすという関係上、SQLite 以外の選択肢がありませんでした。SQLite を使い始めてから SQLite は他の RDB より SQL コマンドがかなり制限されていることに気づくという…。
Tortoise ORM は当時は珍しかった asyncio 対応の ORM です。
Python の ORM は歴史的にブロッキング I/O のものが多く、FastAPI のような非同期 I/O との相性がよくありませんでした。その点で Tortoise ORM は asyncio に対応しているので、FastAPI との相性が抜群です。
さらに Tortoise ORM は若干触ったことのある Django の QuerySet API を模倣して作られていて、比較的使いやすかったのも決め手になりました。
ただ、そもそも Python の非同期 I/O 自体が後付けな事もあり、たとえば同期側(イベントループの外)や別スレッドから DB へのアクセスコードを呼ぶのが面倒だったり、そもそもまだ成熟していないので若干バグを踏んでしまったり(特にマイグレーションツールの Aerich はバグが多くて大変だった…)と、このあたりでもかなり苦労しました。
実はまともに CRUD をひと通り実装したアプリケーションを作るのも KonomiTV がはじめてだったりします…。FastAPI の自動で API ドキュメントを生成してくれる機能が実装する上でとても役立ちました(FastAPI が好きなポイントのひとつ)。
正直(ストリーム配信周りを含め)Node.js の方が簡単に開発できたような気はしますが、とはいえ Python は Windows 固有の問題に対処できるライブラリが多く、Node.js と比べて ctypes / pywin32 / DLL 拡張モジュールなどの無理も効きます。
『Windows Server 以外の普通の Windows で C# や ASP.NET などの MS 系技術を使わず Web 系の技術で Web サービスに近いものを様々な環境で動くようにする』という要件自体が難易度がかなり高く、当然ながら情報も全然ありませんでした。
その点でも比較的 Python は Windows でのナレッジが多かったですし、技術選定自体は間違っていなかったと思っています。
Docker や GitHub Actions に触れたのも KonomiTV が最初です。これに限らず KonomiTV は個人的にまだ触ったことのない新しめの技術に広く触れる良い機会になっていて、開発開始前と比べるとかなりいろいろな方面の知識がつきました。
Docker は KonomiTV を Linux で動かすために使っています (Docker を使わずにインストールすることもできます) 。 プルリクエスト で Docker にマルチステージビルドという機能があることを知ったのもいい思い出です(マルチステージビルドの方が圧倒的にビルドが早くなる)。Docker 内でどうすればハードウェアエンコーダーを動かせるのかにかなり試行錯誤を重ね苦労しました…(特に手元に動作環境のない VCEEncC) 。
GitHub Actions は インストーラー や サードパーティーライブラリ をビルドするのに使っています。最近 フロントエンドをビルドしてからのリリースコミット や リリース作業を自動化する ワークフローも追加しました。
以前はどうしても敷居が高かったのですが、いざ使い始めると(書くこと自体はかなり面倒なものの)一度書けば手動でやる手間やミスから完全に解放されることに気づき、他のプロダクトでも GitHub Actions で諸々自動化したくなってきました。特に属人性(&ビルド環境依存)を排除できて、透明性も担保できるのがかなりいいな~と思います。
あとは Cloudflare Workers も使っています。
KonomiTV は基本的にローカルの PC で動くソフトですが、Twitter やニコニコアカウントとの OAuth 連携に関してはどうしても固定のコールバック URL が必要です。
そこで Cloudflare Workers にコールバック後(パラメータで指定された)KonomiTV のローカルサーバーの URL にリダイレクトするだけの簡易な API を立てて、コールバック対象の URL が一つに定まらなくても OAuth 連携ができるようにしました。ちょっと荒業ですが致し方ない…。
Cloudflare Workers は今回の「やってる事はめっちゃ簡素」「ただし落ちると OAuth 連携機能が使えないも同然になるので、絶対落とせない」という要件にぴったりでした。
開発はちょっと情報が少なくて苦戦しましたが、サーバーレスなのでサーバーのメンテが不要なのはかなり大きいですし、無料枠もかなり広くてこれくらいの用途なら十分無料で維持できます(ありがとう Cloudflare 先生、一生ついていきます…!)。
nasne / torne という KonomiTV と似たようなジャンルの商用プロダクト (SONY のプレステ部門が開発し、最近ハードだけ Buffalo が再設計して復活した) があるのですが、技術的には ARIB のガチガチの業界自主規制の枠の中でやっているにも関わらず、本当に快適なユーザー体験を提供できているんですよね。
KonomiTV が使っているいわゆる「TS 抜き」環境ではそうした制約がない分、技術的な自由度も高いですし、実際要素技術としては優れた OSS ソフトがいくつもあります。
ですが、それらのソフトは体験面では統合されておらずバラバラで、快適な体験で使えるソリューションとはほど遠いものです(所詮複数の個人開発者が自分が欲しいものを作って公開したというだけだから、当然といえばそうなんだけど)。
TS抜き: 簡単に言えば「市販されている PC 向けテレビチューナー/ソフトではなく、無名の有志らが DIY で何年もかけて作り上げてきた(業界規制によるコピープロテクトや再生ソフト縛り、HDCP などの不自由な制限がなく、フリーでオープンな)PC 向け OSS テレビ視聴/録画ソフト (例: TVTest / EDCB) を使い、テレビを見たり予約録画したりする」こと。
その特性上マイナーではあるが、「キーワード自動録画」など一般のレコーダーには搭載されていないような高度で非常に便利な機能があったり、自由に録画をエンコードできたり好きなシーンの切り抜きやキャプチャができたりなど自由度が極めて高いことから、2022年においても主にアニメオタク層 (私もですが…) で一定の需要がある。TS抜き向けのチューナーが PLEX などいくつかの会社から販売されている。
この「技術的には nasne / torne を超えられる土壌はあるし自由度も高いのに、体験面でも作り込みの面でも nasne / torne のそれをまったく超えられない」ことがとても悔しくて、「いつか nasne / torne を機能面でも体験面でも超えられるプロダクト」を目指しています。
著名な企業が相当な人員とリソースと資金をつぎ込んで作った本気のプロダクトに個人開発のプロダクトが(既存の OSS 資産をフル活用しつつも)挑むのが無謀で大変な道のりなことは分かりきっていますが、それでも自分自身の腕試しとして挑戦したいですし、やる価値はあると思っています。
β版につき機能も少ないですが、将来的にはテレビや録画番組、さらに予約録画やキャプチャ管理など、テレビに関連するすべての視聴体験を KonomiTV 1つで完結できるような、TVRemotePlus を機能面でもユーザー体験の面でも超えるプロダクトを目指して開発しています。私のこだわりが細部まで最大限に詰まったプロダクトです。
私が TVRemotePlus の使い勝手にまったく満足していないのと、現在 TS 抜き環境で主流である Windows PC でのファイルベースの視聴体験にも満足していないので、『自分が心の底から使いやすいと思える(そしてほかの人にも使いやすいと思ってもらえるような)プロダクトを、自分の技術力を駆使して、自分の手で作り上げてみたい』というモチベーションで開発を続けています。