在上一篇文章中,我向您展示瞭如何設定 Chromium 擴展項目,因此它支援 TypeScript、盡可能的自動完成功能,並且作為入門工具可以很好地工作。現在,我將簡要展示我的簡單頁面音訊擴充的實現。
我對擴充功能的要求非常簡單 - 當我造訪特定網站時,它應該開始播放預先定義的音訊。硬編碼的網站名稱和音訊完全沒問題。
更詳細地說,當我打開 www.example.com 時,音訊應該開始播放,當我切換到不同的選項卡時停止,當我返回 www.example.com 時繼續播放。另外,如果我有兩個(或更多)打開 www.example.com 的選項卡並且我在它們之間切換,則音訊應該繼續播放而無需重新啟動。換句話說,音訊應該在整個擴展級別播放,而不是單個選項卡。
簡而言之,我們需要在某處建立 HTMLAudioElement 並根據目前標籤中的網站播放/暫停它。
它可以透過 Service Worker 和內容腳本實作 - 我們可以使用一個內容腳本在每個頁面上建立一個 HTMLAudioElement 元素,並使用 Service Worker 來協調播放。當選項卡失去焦點時,它會將當前媒體時間範圍傳遞給 Service Worker,當具有匹配 URL 的另一個選項卡獲得焦點時,它會向 Service Worker 詢問時間範圍並從那裡恢復播放。
但是,我認為這種方法有點複雜,而且可能容易出錯。如果我們可以只有一個 HTMLAudioElement 元素並全域播放/暫停它,而不是從單一選項卡播放/暫停,那就更好了。幸運的是,有一個有趣的 API 可以為我們提供很大幫助——offscreen API。
Offscreen API 允許擴充功能建立一個不可見的 HTML 文件。使用它,我們將有一個地方來保存 HTMLAudioElement 並在需要時播放/暫停它。請記住,Service Worker 仍然無法執行任何 DOM 操作,因此我們需要在螢幕外文件上新增一些輔助腳本來接收 Service Worker 訊息並充分控製播放器。
我的擴充功能需要權限數組中的兩個項目:
如果您在瀏覽器中打開擴展程序詳細信息,您將看到如下所示的權限:
閱讀您的瀏覽紀錄
它可能看起來有點可怕,但這就是添加選項卡權限的原因。不幸的是,我無法找到一種與權限無關的不同方法。我的其他想法導致了更可怕的權限集?在此線程中,您可以閱讀為什麼選項卡權限會導致該條目。
正如我所提到的,我只想擁有一個 HTMLAudioElement 並從中播放音訊。為了使其獨立於選項卡,我將使用離屏 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; } }
如您所見,唯一的公共方法是 setup ,呼叫時需要一些路徑。這是 HTML 文件範本的路徑,該範本將用於建立我們的螢幕外文件。在我們的例子中,這將非常簡單:
<!-- offscreen.html --> <script src="dist/offscreen.js" type="module"></script>
從字面上看,只是一個腳本標籤。此腳本將用於接收 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 是我們擴展的“大腦”,知道發生了什麼以及何時發生,並且應該根據需要發送適當的訊息。但具體是什麼時候呢?
我們應該在三種情況下改變播放狀態:
所有情況都意味著我們可能正在訪問我們想要播放音訊的網站或我們剛剛關閉/離開的網站。
言歸正傳,這裡是更新後的 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
也要注意頂部的兩個常數 - 在這裡您指定要播放的音訊以及在哪個網站。
最後,我們正在發送訊息,所以現在是時候接收它們並播放一些音樂了?
為此,我們需要實作 offscreen.html 使用的腳本。它是 dist/offscreen.js,所以這就是對應的 ts/offscreen.ts 的樣子:
<!-- offscreen.html --> <script src="dist/offscreen.js" type="module"></script>
簡而言之,如果我們還沒有建立 HTMLAudioElement,我們將使用提供的來源來建立 HTMLAudioElement,然後播放/暫停它。出於打字目的,需要返回未定義。如果您對不同傳回值的含義感興趣,請查看文件
試試看!請造訪 www.example.com(或您設定的任何網站)並查看音訊是否正在播放。嘗試來回切換選項卡並驗證它是否正確停止和恢復。
請考慮到,如果您暫停音樂超過 30 秒,它將重新啟動,因為瀏覽器將終止 Service Worker! 以下是一些相關文件。
總結我們所做的:
我希望它清晰易懂!此擴充功能有一個非常自然的進展 - 讓使用者指定不同的網站並為每個網站分配不同的音訊。希望當我有時間時我會補充這一點,並寫另一篇文章來描述我的方法。
現在,感謝您的閱讀!
以上是Chrome 擴充功能 - 實現擴充功能的詳細內容。更多資訊請關注PHP中文網其他相關文章!