前回の投稿では、Chromium 拡張機能プロジェクトをセットアップする方法を説明しました。これにより、可能な限り TypeScript とオートコンプリートがサポートされ、スターターとして適切に機能します。ここで、簡単な Page Audio 拡張機能の実装を簡単に示します。
私が拡張機能に求めていたものは非常にシンプルでした。特定の Web サイトにアクセスすると、事前定義されたオーディオの再生が開始されるはずです。ハードコーディングされた Web サイト名と音声はまったく問題ありません。
もう少し詳しく説明すると、www.example.com を開くと音声の再生が開始され、別のタブに切り替えると停止し、www.example.com に戻ると再開されます。また、www.example.com を開いたタブが 2 つ (またはそれ以上) あり、それらを切り替えると、再起動せずにオーディオが再生され続けるはずです。つまり、オーディオは個々のタブではなく、拡張機能レベル全体で再生される必要があります。
要するに、どこかに HTMLAudioElement を作成し、現在のタブの Web サイトに応じて再生/一時停止する必要があります。
これは、Service Worker とコンテンツ スクリプトを使用して実行できます。すべてのページに HTMLAudioElement 要素を作成するコンテンツ スクリプトを用意し、Service Worker を使用して再生を調整することができます。タブがフォーカスを失うと、現在のメディア タイム フレームが Service Worker に渡され、一致する URL を持つ別のタブがフォーカスを取得すると、Service Worker にタイム フレームを問い合わせて、そこから再生を再開します。
ただし、このアプローチは少し複雑で、エラーが発生しやすいと思います。 HTMLAudioElement 要素を 1 つだけ用意して、個々のタブからではなくグローバルに再生/一時停止できれば、さらに便利になります。幸いなことに、非常に役立つ興味深い API、オフスクリーン API があります。
オフスクリーン API を使用すると、拡張機能で 1 つの非表示の HTML ドキュメントを作成できます。これを使用すると、HTMLAudioElement を保持し、必要なときに再生/一時停止するだけの場所が得られます。 Service Worker はまだ DOM 操作を実行できないため、Service Worker メッセージを受信してプレーヤーを適切に制御するには、オフスクリーン ドキュメントにヘルパー スクリプトが必要になることに注意してください。
私の拡張機能には、権限配列に 2 つのエントリが必要です:
ブラウザで拡張機能の詳細を開くと、次のような権限が表示されます。
閲覧履歴を読む
少し怖く見えるかもしれませんが、タブの権限を追加するとそれが原因です。残念ながら、権限をそれほど気にしない別のアプローチを見つけることができませんでした。私が考えていた他のアイデアは、さらに恐ろしい権限セットをもたらしました。このスレッドでは、タブの権限によってそのエントリが発生する理由を読むことができます。
前述したように、HTMLAudioElement を 1 つだけ用意して、そこからオーディオを再生したいと考えています。タブに依存しないようにするには、オフスクリーン API を使用して、Service Worker からのメッセージによって保持および制御されるドキュメントを作成します。
オブジェクト指向プログラミングのような気がするので、オフスクリーン ドキュメント管理を支援する OffscreenDoc クラスを紹介します。基本的に、オフスクリーン ドキュメントがまだ作成されていない場合は、それを作成するだけです。
// ts/offscreen-doc.ts /** * Static class to manage the offscreen document */ export class OffscreenDoc { private static isCreating: Promise<boolean | void> | null; private constructor() { // private constructor to prevent instantiation } /** * Sets up the offscreen document if it doesn't exist * @param path - path to the offscreen document */ static async setup(path: string) { if (!(await this.isDocumentCreated(path))) { await this.createOffscreenDocument(path); } } private static async createOffscreenDocument(path: string) { if (OffscreenDoc.isCreating) { await OffscreenDoc.isCreating; } else { OffscreenDoc.isCreating = chrome.offscreen.createDocument({ url: path, reasons: ['AUDIO_PLAYBACK'], justification: 'Used to play audio independently from the opened tabs', }); await OffscreenDoc.isCreating; OffscreenDoc.isCreating = null; } } private static async isDocumentCreated(path: string) { // Check all windows controlled by the service worker to see if one // of them is the offscreen document with the given path const offscreenUrl = chrome.runtime.getURL(path); const existingContexts = await chrome.runtime.getContexts({ contextTypes: ['OFFSCREEN_DOCUMENT'], documentUrls: [offscreenUrl], }); return existingContexts.length > 0; } }
ご覧のとおり、唯一のパブリック メソッドがセットアップされており、呼び出されるときに何らかのパスが必要です。これは、オフスクリーン ドキュメントの作成に使用される HTML ドキュメント テンプレートへのパスです。私たちの場合は非常に簡単になります:
<!-- offscreen.html --> <script src="dist/offscreen.js" type="module"></script>
文字通り、スクリプト タグは 1 つだけです。このスクリプトは、Service Worker メッセージの受信、HTMLAudioElement の作成、音楽の再生/一時停止に使用されます。そこに何かをインポートするため、type="module" も含まれています。
しかし、メッセージを受信するには、おそらく最初にメッセージを送信する必要があります。
メッセージ用の厳密なインターフェイスはありません。必要なのは、それらが JSON シリアル化可能であることを確認することだけです。ただし、できる限りタイプセーフにしたいので、拡張機能で渡されるメッセージ用の単純なインターフェイスを定義しました。
// ts/audio-message.ts export interface AudioMessage { /** * Command to be executed on the audio element. */ command: 'play' | 'pause'; /** * Source of the audio file. */ source?: string; }
sendMessage メソッドは入力にはあまり適していないことがすぐにわかりますが、それでもタイプ セーフの恩恵を受ける簡単な回避策があります。
Service Worker は拡張機能の「頭脳」であり、いつ何が起こっているかを知っており、必要に応じて適切なメッセージを送信する必要があります。しかし、それはいつですか?
次の 3 つの状況で再生状態を変更する必要があります。
すべての状況は、音声を再生したい Web サイトにアクセスしている可能性があること、または Web サイトを閉じたか放置したところである可能性があることを意味します。
早速、2 つのイベントに反応する更新された ts/background.ts スクリプトを次に示します。
// ts/offscreen-doc.ts /** * Static class to manage the offscreen document */ export class OffscreenDoc { private static isCreating: Promise<boolean | void> | null; private constructor() { // private constructor to prevent instantiation } /** * Sets up the offscreen document if it doesn't exist * @param path - path to the offscreen document */ static async setup(path: string) { if (!(await this.isDocumentCreated(path))) { await this.createOffscreenDocument(path); } } private static async createOffscreenDocument(path: string) { if (OffscreenDoc.isCreating) { await OffscreenDoc.isCreating; } else { OffscreenDoc.isCreating = chrome.offscreen.createDocument({ url: path, reasons: ['AUDIO_PLAYBACK'], justification: 'Used to play audio independently from the opened tabs', }); await OffscreenDoc.isCreating; OffscreenDoc.isCreating = null; } } private static async isDocumentCreated(path: string) { // Check all windows controlled by the service worker to see if one // of them is the offscreen document with the given path const offscreenUrl = chrome.runtime.getURL(path); const existingContexts = await chrome.runtime.getContexts({ contextTypes: ['OFFSCREEN_DOCUMENT'], documentUrls: [offscreenUrl], }); return existingContexts.length > 0; } }
ご覧のとおり、ここでは toggleAudio 関数が最も重要です。まず、オフスクリーン ドキュメントを設定します。ドキュメントがすでに作成されている場合は何も行わないため、複数回呼び出しても安全です。次に、現在のタブの URL に応じて、「再生」または「一時停止」コマンドを送信するかどうかを決定します。最後にメッセージを送信します。すでに述べたように、sendMessage には汎用バリアント (sendMessage
上部の 2 つの定数にも注目してください。ここでは、どのオーディオをどの Web サイトで再生するかを指定します。
ついにメッセージを送信しました。今度はメッセージを受信して音楽を再生してみましょう?
これを行うには、offscreen.html で使用されるスクリプトを実装する必要があります。これは dist/offscreen.js なので、対応する ts/offscreen.ts は次のようになります:
<!-- offscreen.html --> <script src="dist/offscreen.js" type="module"></script>
つまり、HTMLAudioElement を作成していない場合は、提供されたソースを使用して作成し、それを再生/一時停止します。入力のために、unknown を返すことが必要です。さまざまな戻り値の意味に興味がある場合は、ドキュメントを確認してください
試してみてください! www.example.com (または設定した Web サイト) にアクセスして、音声が再生されているかどうかを確認します。タブを前後に切り替えて、正しく停止および再開できるかどうかを確認してください。
音楽を 30 秒以上一時停止すると、Service Worker がブラウザによって終了されるため、音楽が再開されることに注意してください。 これに関するドキュメントがいくつかあります。
私たちが行ったことを要約すると:
わかりやすくて分かりやすかったと思います!この拡張機能にはごく自然な流れがあり、ユーザーが異なる Web サイトを指定し、それぞれに異なる音声を割り当てることができます。時間ができたらそれを追加し、私のアプローチを説明する別の投稿を書きたいと思います。
今のところ、読んでいただきありがとうございます!
以上がChrome 拡張機能 - 拡張機能の実装の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。