在上一篇文章中,我向您展示了如何设置 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中文网其他相关文章!