昨日書いた数百行のコードを削除し、32 行の新しいコードに置き換えました。これは、オーディオが再生されているかどうかを示すために使用されるTheOpenPresenterの機能用でした。
時々、実装が非常に簡単そうな機能に取り組むことがあります。この場合、オーディオの再生時にこのアイコンを表示するだけで済みます。
とても簡単です。これらはそれぞれ複数のプラグインを含むシーンです。各プラグインには、 isPlaying
などの独自のプロパティがあります。プラグイン間で値をマージし、フラグが true の場合はアイコンを表示できます。
主な問題は、このデータにどのようにアクセスするかです。データに直接アクセスすることもできますが、各プラグインは独自のスキーマを持つことができます。プラグインによっては、単純なisPlayingプロパティを持つものもありますが、再生ステータスを表すために、より複雑なものが必要なプラグインもあります。
簡単です。プラグインが状態を返すコールバック/関数を登録できるようにしてみませんか?
これは、TheOpenPresenter が多くのプラグインで使用しているのと同じパターンです。ついでに、これをSceneStateオブジェクトに抽象化することもできます。そのため、他の状態が必要になった場合は、ここで追加できます。プラグインの場合は次のようになります。
// The pattern we use for plugins serverPluginApi.onPluginDataCreated(pluginName, onPluginDataCreated); serverPluginApi.onPluginDataLoaded(pluginName, onPluginDataLoaded); serverPluginApi.registerRemoteViewWebComponent( pluginName, remoteWebComponentTag, ); // Example of how the new API might look like serverPluginApi.registerSceneState( pluginName, (_, rendererData) => { return { audioIsPlaying: !!rendererData.find((x) => x.isPlaying), } }, );
上記のコードはサーバー上で処理されることに注意してください。これは、TheOpenPresenter が 3 つの個別のコンポーネントで構成されているためです。
リモコン - この音声表示が表示される場所
レンダラー - オーディオを再生する
サーバー - 2つを接続する
理想的には、サーバーに余分な負荷をかけないように、これをフロントエンド (リモート) で処理する必要があります。ただし、この機能の登録は面倒になる可能性があります。当社のフロントエンドでは、Web コンポーネントがロードされたマイクロフロントエンド アーキテクチャを使用しています。
下の赤い部分は React シェルです。緑の部分は Web コンポーネントを通じて読み込まれ、各プラグインによって管理されます。
オーディオ アイコンがシェルの左側にあることに注目してください。シェルに必要な機能を提供するにはどうすればよいでしょうか。Web コンポーネント バンドルに JS 関数を含めることもできますが、長期的には混乱が生じそうです。
これをサーバー上で処理するのが適切な方法のようです。
それが決まったら、実装の段階です。やるべきことがいくつかあります。
詳細はここでは割愛しますので、概要を説明します。データが非常に複雑になる可能性があるため、API はそれほど簡単ではありませんでした。簡単に言うと、シーンには複数のプラグインが存在する可能性があります。また、シーンをそれぞれ異なる方法で表示する複数のレンダラーが存在する可能性があります。つまり、プラグインには、異なる方法で表示する複数のレンダラーが存在する可能性があります。ただし、少しデータを操作すれば、問題は解決します。
UIの使用と更新
値の使用は簡単でした。リアルタイムでフレームワークがすでに導入されているため、Yjs の認識プロトコルを使用してデータを提供することを検討しました。これが状態を保存する方法です。ただし、サーバーからこのデータを含めることはそれ自体が問題です。そのため、代わりに GraphQL を使用することにしました。これは、プラットフォームの他のすべてに使用しているプロトコルです。
したがって、必要なのはエンドポイントを呼び出して、GraphQL のサブスクリプションを使用してそれをリッスンし、必要に応じてアイコンを表示することだけです。完了です。
このデータをフロントエンドに提供する
ありがたいことに、私たちはPostgraphile を使用しているので、GraphQL スキーマの拡張が非常に簡単です。GraphQL スキーマに@pgSubscription
を追加するだけで、サブスクリプションにすることもできます。そうすると、トピックを監視し、そのトピックでpg_notify
呼び出すたびに値が更新されます。例:
await pgPool.query( `select pg_notify('graphql:sceneState:${id}','{}');`, [], );
データの操作は面倒でしたが、少しだけ忍耐すれば完了です。
パズルの最後のピースは、必要なときにpg_notify
呼び出すことです。
このためには、状態 (Yjs) にリスナーを追加し、何か変更があったときに通知を呼び出すことができます。
state.observeDeep(async () => { // Call pg_notify here });
残された作業はパフォーマンスの改善だけです。現在、関数は小さな変更ごとに呼び出され、フロントエンドにも更新されます。結果の状態を計算し、更新をプッシュする前に何か変更があったかどうかを比較することができます。
このソリューションは確かに機能します。しかし、すべての変更をリッスンしなければならないのが嫌でした。これは不必要であり、パフォーマンスがどのように拡張されるかわかりません。もっと良い解決策はありますか?
そこで私は少し立ち止まって、あるアイデアを思いつきました。基本に戻って、Yjs のデータを使用するのはどうでしょうか?
問題は、各プラグインが再生ステータスを示すために異なる方法を使用する可能性があることでした。そのため、結果の状態を自分で計算する方法を知る方法が必要でした。しかし、ユーザーに関数を渡させるのではなく、これを示すために使用できるプロパティを予約しておけばよいのではないでしょうか。
状態を計算する関数を渡すのではなく、各プラグインは__audioIsPlaying
などのプロパティを使用して、予約済みの状態を既存のデータと直接設定できます。この値を直接使用することも、次のように既存のプロパティと同期させることもできます。
const onRendererDataLoaded = ( rendererData, ) => { watchYjs( // Watch the isPlaying property (x) => x.isPlaying, () => { // And if it changes, sync the __audioIsPlaying property rendererData.set("__audioIsPlaying", rendererData.get("isPlaying")); }, ); };
新しいメソッドは素晴らしいです。追加のリスナーも、追加の API もなく、単純な予約済みプロパティだけです。
コストは?まあ、最初の実装の 95% はすでに書きました 🫣
「こんなに長い間取り組んできたのに、これを削除するのは残念だ。この1つ以外はすべて完璧だ!」 - 私の心
これは私にとって初めてのことではありません。2 回目でも 3 回目でもありません。今回はほんの数時間の作業でした。実装に時間がかかるほど、手放すのが難しくなります。しかし、サーバーに執着すべきでないなら、自分が書いたコードにも執着すべきではありません。
2 番目の実装の方が優れていることは明らかです。より高速で、可動部分が少なく、API サーフェスが少なく、保守するコードも少なくなっています。 最初の実装では289 行が追加されましたが、 2 番目の実装では 32 行しか追加されていません。
では、何を学ぶべきでしょうか?
そうですね、まずは最も簡単な解決策を見つけるのがいいかもしれません。しかし、考えただけでは最善の解決策にたどり着かないこともあります。その場合は、コードを愛さず、捨てることを恐れないでください。そして、ブログ記事を書いて、そこから何かを得てください。
ここまで読んできたなら、 TheOpenPresenter を試してみるといいかもしれません。これは、あらゆる画面をリモートで制御できるオープンソースのプレゼンテーション システムです。
スライドショーを表示したり、ビデオを再生したり、ダッシュボードとして使用したり、その他さまざまなことができます。この投稿からわかるように、このソフトウェアはまだ開発の初期段階ですが、定期的に使用できるほど安定しています。私は個人的に、毎週のミートアップでこれを使用しています。
ご質問があれば、お気軽にお問い合わせください。また、 Github リポジトリで問題を報告してください。