Auto hide next up card for Amazon Prime Video v2.14.2 リリース

JavaScriptchat_bubble0

Amazon Prime VideoのNext up等の邪魔な要素を非表示にする「Auto hide next up card for Amazon Prime Video v2.14.2」をリリースしました。

変更点

  • xhookをフォーク版に差し替え
  • ダイアログ内の「通信の監視・改変」のツールチップの更新

今回のリリースはxhookを採用した時から存在していた問題の解消です。Auto hide next up cardでは「通信の監視・改変」の各機能のためにxhookというライブラリを使用していますが、xhookが有効になっているとプライムビデオは再生開始前のプリロードで最低画質を取得してしまうという現象が発生していました。10秒程度再生したりシークバーを動かしたりすることで通常の画質(プライムビデオによる動的制御)になりますし、最高画質を強制する機能も実装しているため、大きな問題にはなっていませんでしたが、それでも私としてはずっと気になっていた問題でした。

重い腰を上げてプライムビデオ側の実装とxhookの実装を調べたところ、xhookの問題点が判明しました。最終的には、xhookをフォークして問題点を修正し、Auto hide next up cardでは私のフォーク版を使用することにしました。破壊的な変更を加えたつもりはないので、多分、おそらく、問題無いと思います…。
https://github.com/ryo-fujinone/xhook

一応プルリクを出しましたが、2023年8月で更新が止まっているリポジトリなので反応は期待してない。
https://github.com/jpillora/xhook/pull/180
https://github.com/jpillora/xhook/pull/181
それぞれのプルリクで2つずつ、計4つの問題を修正したつもりです。プライムビデオ側で問題になっていたのは#181の中で触れている内の後者の問題です。以下で何が問題になっていたのかを書いていきます。JavaScriptの話がメインです。

プライムビデオのプリロードの画質決定とxhookの実装

プライムビデオの実装を追ったところ、画質決定は以下のような感じで行われていました。

  • プライムビデオは動画の配信のためにマニフェストファイルであるmpdファイルを採用している。mpdでは帯域幅別に動画の品質を管理できる。
  • mpdはXMLHttpRequest(XHR)を使用して取得される。
  • プライムビデオは帯域幅を計算することによって品質(画質)を動的に制御する(mpdの動画リストの中から適切なものを使用する)。
  • 再生開始前のプリロードの画質決定では、mpdを取得した際にXHRが発行するloadイベントのloadedプロパティ(受信したバイト数)が帯域幅の計算に使用される。
  • 計算によって帯域幅がnullか0になった場合、品質リストの中間の品質に決定する。帯域幅が他の値の場合は、品質リストの帯域幅情報と比較して適切な品質に決定する。

xhookを使用していない状態では通信状況にあった画質がプリロードされます。しかしxhookを使用すると再生開始前のプリロードで問題が発生します。xhookはXHRとfetchに独自の処理を挟むことで通信のインターセプトを実現するライブラリです。プライムビデオではmpdの取得にXHRが使用されるのでxhookにはXHRの動作の模倣を行うことが期待されますが、模倣が不完全であるためにプライムビデオで以下の問題が発生していました。(プリロードより後の処理は追えていませんが、xhook使用時の挙動を見るにloadedプロパティが問題になるのはmpdの取得時のみっぽい?)

  • loadイベントのloadedプロパティが欠けている(undefinedになる)。
  • 帯域幅の計算にundefinedが混ざることで、NaNになる。
  • 帯域幅がnullか0の場合は中間の品質で決定するようになっているが、NaNの場合はそのまま品質リストとの比較処理に進んでしまう。
  • 帯域幅を数値として比較できず、品質リストの0番目、最低画質で決定してしまう。

xhookのイベント周りの実装を調べたところ、「ソースとなるオブジェクト」と「Eventインターフェイスのインスタンス」を用意し、後者にソースのプロパティをコピーし、それを最終的に各イベントとして発火させているということが分かりました。XHRのprogress/abort/error/timeoutイベントについては本物のイベントがソースとして使用されるっぽいですが、loadstart/load/loadendイベントに関しては本物のイベントではなく空のオブジェクトがソースとして使用されていました。

最終的に、xhookにおいて以下の問題があることが判明しました。

  • 全てのイベントがEventインターフェイスのインスタンスとして扱われているが、本来のXHRではreadystatechangeイベント以外は全てProgressEventインターフェイスのインスタンス。
  • loadstart/load/loadendイベントにおいて、ProgressEvent固有のプロパティ { lengthComputable, loaded, total } が欠けている。

この内、前者については基本的に問題になることは無いと思われ、私のフォークでも修正はしていません。前者が問題になるのは、リスナーとなる関数を使いまわしており、かつ渡ってくる引数をinstanceofでProgressEventかどうか判定している場合くらいではないかなと。リポジトリを見るにそのようなissueが出ているわけではなかったので今回はスルーしました。

私がやったのはProgressEvent固有のプロパティを含めることです。loadstartイベントでは初期値としての値を含めるようにしました。load/loadendイベントについては、xhookでは内部でProgressEventインスタンスとしての本物のprogressイベントを検出できる実装になっていたので、そこで必要なプロパティの値をキャッシュし、キャッシュの値をload/loadendイベントで使用するようにしました。xhookの実装を壊さない方向性でやるならこれが一番楽でした。

この修正により、プライムビデオはxhook使用時でも帯域幅を正常に計算できるようになり、プリロードの画質決定には影響を与えないようになりました。


とまあ、私一人が頑張ったかのような書き方になってしまいましたが、実際にはChatGPT o3の助けを借りました。プライムビデオのjsファイルやmpdファイル、xhookのリポジトリのzipなどをアップして何が問題になっているかを伝えて解析を頼みました。結局プライムビデオの実装についてはo3では正確な解析ができなかったので、最終的には自力で変数の中身やスタックトレースを追いましたが、それでもo3は解析の糸口となる変数やメソッドを見つけてくれていたのでそこは助かりました。

o3はxhookの実装についてはかなり正確に理解して解説してくれたように思います。とは言っても最初から期待通りの解説をしてくれたわけではありませんが…。xhookはCoffeeScriptからJavaScriptに移行したという歴史があるライブラリで、o3はこちらが最新のリポジトリのzipを提供しているにも関わらずCoffeeScript時代のxhookをWebから取得してきてそれをベースに解説を初めました。仕方が無いので「xhookは過去にCoffeeScriptで書かれていたが最新のものはJavaScriptで書かれている。Web上の情報を参照しても良いが、CoffeeScriptで書かれたxhookは参照しないで」というような指示を入れたところ、期待通りの解説をしてくれるようになりました。

まあそんな感じで問題の解決に至りました。問題を解決できましたし、マージされるかどうかはともかくxhookに貢献できたようにも思うので満足しています。

ブラウザ拡張機能

藤乃音りょう