背景

chrome@v130 中的 chrome extension 在调用 chrome.offscreen.createDocument 执行 js 文件 new SpeechSynthesisUtterance 朗诵不生效问题

该问题只在 130 版本中出现,以此记录。

解决方法如下

//  offscreen.js
chrome.runtime.onMessage.addListener(msg => {
  if ('playOffscreenRing' in msg) playAudio(msg.playOffscreenRing);
});


//  ring 如果为文本字符串时,为播放 tts
//  如果为 chrome-extension 开头代表着播放音频
function playAudio(ring) {
  if(ring.startsWith('chrome-extension')){
    const audio = new Audio(ring);
    audio.play();
  }else{
    readTTS(ring)
  }
}

function readTTS(str) {
  //
  //
  //  这里要先初始化一次,什么都不朗读
  //
  //
  window.speechSynthesis.speak(new SpeechSynthesisUtterance(''));

  chrome.runtime.sendMessage({
    type: "FROM_OFFSCREEN",
    data: `朗读 ${str}`
  })

  try{
    return new Promise((resolve) => {
      const utterance = new SpeechSynthesisUtterance(str);
      utterance.lang = "zh-CN";
      utterance.volume = 1;
      utterance.rate = 1;

      window.speechSynthesis.cancel();

      if (window.speechSynthesis.speaking) {
        setTimeout(() => {
          window.speechSynthesis.speak(utterance);
          resolve();
        }, 100);
      } else {
        window.speechSynthesis.speak(utterance);
        resolve();
      }
    });
  }catch (e){
    chrome.runtime.sendMessage({
      type: "FROM_OFFSCREEN",
      data: `朗读错误 ${e.toString()}`
    })
  }
}
//  background.js
const needDoublePlay = await setupOffscreenDocument("offscreen/index.html");

//
//
//  这里是解决的关键,如果 offscreen 文件是第一次创建,那么就播放两次,这样就能解决第一次播放没声音问题
//
//
if (needDoublePlay) {
  await chrome.runtime.sendMessage({
    playOffscreenRing: source,
  });
  await new Promise((resolve) => setTimeout(resolve, 1500));
}

await chrome.runtime.sendMessage({
  playOffscreenRing: source,
});

async function setupOffscreenDocument(path: string) {
  const offscreenUrl = chrome.runtime.getURL(path);
  const existingContexts = await chrome.runtime.getContexts({
    contextTypes: [chrome.runtime.ContextType.OFFSCREEN_DOCUMENT],
    documentUrls: [offscreenUrl],
  });

  if (existingContexts.length > 0) {
    return false;
  }

  // create offscreen document
  if (creating) {
    await creating;
  } else {
    creating = chrome.offscreen.createDocument({
      url: path,
      reasons: [chrome.offscreen.Reason.AUDIO_PLAYBACK],
      justification: "play an audio",
    });
    await creating;
    creating = null;

    return true;
  }
}