能否用service-worker.js实现音频后台播放?消息通信失败求助
问题概述
尝试通过Service Worker实现音频后台播放,本地MP3文件可直接在浏览器播放,但Service Worker无法接收postMessage发送的消息,控制台未打印对应的接收日志。相关代码如下:
service-worker.js
self.addEventListener('install', function (event) { console.log('Service Worker: Installed'); event.waitUntil(self.skipWaiting()); }); self.addEventListener('activate', function (event) { console.log('Service Worker: Activated'); event.waitUntil(self.clients.claim()); }); self.addEventListener('fetch', (event) => { // Handle fetch events if needed // console.log('Fetch event:', event.request.url); }); let audioContext; let audioBuffer; let source; self.addEventListener('message', async (event) => { console.log('Service Worker: Received message:', event.data); if (event.data.action === 'play') { const url = event.data.url; console.log('Play action received for URL:', url); if (!audioContext) { audioContext = new AudioContext(); } const response = await fetch(url); const arrayBuffer = await response.arrayBuffer(); audioBuffer = await audioContext.decodeAudioData(arrayBuffer); source = audioContext.createBufferSource(); source.buffer = audioBuffer; source.connect(audioContext.destination); source.start(0); console.log('Audio started playing.'); } if (event.data.action === 'stop') { if (source) { source.stop(); console.log('Audio stopped.'); } } });
./components/Layout.svelte
<script> import GlobalNavBar from './GlobalNavBar.svelte'; import NavBar from './NavBar.svelte'; import Sidebar from './Sidebar.svelte'; import MetaTags from './MetaTags.svelte'; import Footer from './Footer.svelte'; import { onMount } from 'svelte'; export let isLoggedIn, unverifiedUser, isAdmin; let hasServiceWorker = false; let audioUrl = '/086-Jailbreak.mp3'; function playAudio() { console.log('play clicked'); if (navigator.serviceWorker.controller) { console.log('controller1'); navigator.serviceWorker.controller.postMessage({ action: 'play', url: audioUrl }); console.log('Play message sent to Service Worker.'); } } function stopAudio() { console.log('stop clicked'); if (navigator.serviceWorker.controller) { console.log('controller2'); navigator.serviceWorker.controller.postMessage({ action: 'stop' }); console.log('Stop message sent to Service Worker.'); } } onMount(async () => { if ('serviceWorker' in navigator) { navigator.serviceWorker .register('/service-worker.js') .then(function (registration) { console.log('Service Worker registered with scope:', registration.scope); }) .catch(function (error) { console.error('Service Worker registration failed:', error); }); navigator.serviceWorker.ready .then(function (registration) { console.log('A service worker is active:', registration.active); }) .catch(function (error) { console.error('Service Worker ready failed:', error); }); navigator.serviceWorker.ready .then(function (registration) { console.log('Service Worker is ready:', registration.active); if (navigator.serviceWorker.controller) { console.log('A service worker is controlling this page.'); } else { console.log('No service worker is controlling this page.'); } }) .catch(console.error); } }); </script> <MetaTags /> <GlobalNavBar {isLoggedIn} {unverifiedUser} {isAdmin} /> <main> <Sidebar {isLoggedIn} {unverifiedUser} /> <div class="content"> {#if isLoggedIn} <NavBar {isLoggedIn} {isAdmin} /> {/if} <h1>Persistent Media Player</h1> <button on:click|preventDefault={playAudio}>Play</button> <button on:click|preventDefault={stopAudio}>Stop</button> <slot /> </div> </main> <style> .content { padding: 1.2rem 2.4rem; } </style>
排查与解决步骤
1. 检查Service Worker的日志输出位置
Service Worker的控制台日志不在页面的常规控制台,需要打开浏览器开发者工具的「Application」面板,找到「Service Workers」选项,点击对应的Service Worker条目,查看其独立的控制台输出。
2. 确保Service Worker已完成激活并控制页面
注册Service Worker后,首次加载页面时navigator.serviceWorker.controller可能为null,需要刷新页面让Service Worker接管控制。或者在注册代码中监听controllerchange事件,确保在Service Worker控制页面后再发送消息:
// 在Layout.svelte的onMount中添加 navigator.serviceWorker.addEventListener('controllerchange', () => { console.log('Service Worker now controlling the page'); });
3. 确保消息发送时机正确
不要依赖navigator.serviceWorker.controller直接发送消息,可通过navigator.serviceWorker.ready获取注册对象后,使用registration.active.postMessage发送,确保Service Worker处于活跃状态:
// 修改Layout.svelte中的playAudio和stopAudio函数 async function playAudio() { console.log('play clicked'); try { const registration = await navigator.serviceWorker.ready; registration.active.postMessage({ action: 'play', url: audioUrl }); console.log('Play message sent to Service Worker.'); } catch (err) { console.error('Failed to send message:', err); } } async function stopAudio() { console.log('stop clicked'); try { const registration = await navigator.serviceWorker.ready; registration.active.postMessage({ action: 'stop' }); console.log('Stop message sent to Service Worker.'); } catch (err) { console.error('Failed to send message:', err); } }
4. 处理AudioContext的用户交互限制
浏览器要求AudioContext必须在用户交互(如点击事件)中初始化或恢复。在Service Worker的play消息处理中,需检查audioContext.state,如果是suspended则调用resume():
// 修改service-worker.js中的play逻辑 if (!audioContext) { audioContext = new AudioContext(); } else if (audioContext.state === 'suspended') { await audioContext.resume(); }
5. 验证Service Worker的作用域
检查registration.scope是否覆盖了当前页面路径。如果Service Worker文件不在根目录,可能需要在注册时指定作用域:
// 修改Layout.svelte中的注册代码 navigator.serviceWorker.register('/service-worker.js', { scope: '/' })
内容的提问来源于stack exchange,提问作者chovy

