JavaScript媒体会话API:暂停可用,播放不可用

人工智能 2026-07-10

我在尝试使用 Media Session API。这是浏览器内置的播放控制:

桌面浏览器中的 Media Session API 截图

暂停按钮确实可以把声音和进度条暂停,但暂停按钮在继续播放时不会变成播放按钮。用Chrome测试过。如何解决?

if ('mediaSession' in navigator) {
  let audioCtx, oscillator, gainNode;

  navigator.mediaSession.setActionHandler('play', () => {
    audioCtx.resume();
    navigator.mediaSession.playbackState = 'playing';
  });

  navigator.mediaSession.setActionHandler('pause', () => {
    audioCtx.suspend();
    navigator.mediaSession.playbackState = 'paused';
  });

  // set some `duration` so that the seek bar serves
  // as an additional indicator of the paused state
  if ('setPositionState' in navigator.mediaSession) {
    navigator.mediaSession.setPositionState({
      duration: 20,
      position: 0,
    });
  }

  button.onclick = async () => {
    if (!audioCtx) {
      audioCtx = new AudioContext();
      oscillator = audioCtx.createOscillator();
      gainNode = audioCtx.createGain();
      oscillator.connect(gainNode);
      gainNode.connect(audioCtx.destination);
      oscillator.frequency.value = 440;
      gainNode.gain.value = 0.1;
      oscillator.start();
      await audioCtx.resume();
      navigator.mediaSession.playbackState = 'playing';
    }
  };
}
<button id="button">Initialize</button>

Stack Snippets的警告:

  • 在编写新答案时,Stack Snippets的模态对话框似乎无法正确运行。选项“保存并插入到帖子”或编辑现有帖子似乎可以正常工作。
  • 每个浏览器标签页只有一个媒体控件。在这个SO页面上同时运行多个片段可能会相互干扰。点击“隐藏结果”或暂停声音都无法防止干扰。请重新加载页面,但要小心未保存的文本。

类似但并不完全相同的问题:

解决方案

需要说明的是,这是我第一次接触这个API,并且在一些研究和试验中依赖了AI工具……

据我所知,这似乎需要把它附着到一个 <audio> 元素上才能完全发挥作用。浏览器似乎通过该元素来真正维持播放,至少这是我所看到的。

不过我能够让它完全正常工作的唯一办法,是再给那个 <audio> 元素提供一个循环的空音源。这有点像hack,我希望有更干净的实现方式,但我还没找到。

所有这些最终把你的示例推到了以下更新:

if ('mediaSession' in navigator) {
  let audioCtx, oscillator, gainNode;

  // Build a 1-sample silent WAV as a data URI to anchor the media session UI.
  // NOTE: This part was entirely AI-built for this self-contained example
  const silentBytes = new Uint8Array([
    0x52,0x49,0x46,0x46, // "RIFF"
    0x26,0x00,0x00,0x00, // file size − 8
    0x57,0x41,0x56,0x45, // "WAVE"
    0x66,0x6D,0x74,0x20, // "fmt "
    0x10,0x00,0x00,0x00, // fmt chunk size = 16
    0x01,0x00,           // PCM
    0x01,0x00,           // mono
    0x44,0xAC,0x00,0x00, // 44100 Hz sample rate
    0x88,0x58,0x01,0x00, // byte rate
    0x02,0x00,           // block align
    0x10,0x00,           // 16 bits per sample
    0x64,0x61,0x74,0x61, // "data"
    0x02,0x00,0x00,0x00, // data chunk size = 2
    0x00,0x00,           // 1 silent sample
  ]);
  const anchor = new Audio(
    `data:audio/wav;base64,${btoa(String.fromCharCode(...silentBytes))}`
  );
  anchor.loop = true;

  // Update the play/pause actions here to also play/pause the audio element
  navigator.mediaSession.setActionHandler('play', async () => {
    await audioCtx.resume();
    await anchor.play();
    navigator.mediaSession.playbackState = 'playing';
  });
  navigator.mediaSession.setActionHandler('pause', () => {
    audioCtx.suspend();
    anchor.pause();
    navigator.mediaSession.playbackState = 'paused';
  });

  // Replace `duration` inferred from the tiny looping `Audio`
  // (which causes seek-bar flicker) by some value
  if ('setPositionState' in navigator.mediaSession) {
    navigator.mediaSession.setPositionState({
      duration: 20,
      position: 0,
    });
  }

  button.onclick = async () => {
    if (!audioCtx) {
      audioCtx = new AudioContext();
      oscillator = audioCtx.createOscillator();
      gainNode = audioCtx.createGain();
      oscillator.connect(gainNode);
      gainNode.connect(audioCtx.destination);
      oscillator.frequency.value = 440;
      gainNode.gain.value = 0.1;
      oscillator.start();

      // As above, also play the audio element
      await audioCtx.resume();
      await anchor.play();
      navigator.mediaSession.playbackState = 'playing';
    }
  };
}
<button id="button">Initialize</button>
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章