はじめに

SPY×FAMILY 28 話、最高でしたね。 ヨルさん(CV: はやみん)の「お仕事お疲れさま」というセリフは全勤労者を救ってくれました。

さて、話は変わって今回は個人用 Chrome 拡張機能を作ったのでその備忘録です。

どんな拡張機能かというと、「ブラウザで表示したページの特定のボタンを押したら指定の音声ファイルを再生する」という単純なものです。 この特定のボタンというのは一日に一度しか押すことがなく、「あー今日も仕事がんばったなー、今日はここらへんにしとくかー」ってときに押すボタンです。 拡張機能によって流れるヒーリングサウンドが疲弊しきった心身に染み渡ります。

すんなり作成できるかと思ったのですが、思いの外つまづいてしまいました。

そのまま音声を流せない

現在 Chrome 拡張機能は Manifest v3 がメインバージョンとなっていますが、Chrome 拡張機能を作るためにググってみると v2 の情報が多くヒットします。 音声を再生する方法も v2 では

new Audio(url)

とするだけで良かったようですが、v3 の情報があまり見つかりませんでした。 見つかった StackOverflow によると offscreen という機能を使うことで Chrome 拡張機能 v3 でも音声再生できるとうことで、試してみたところうまくいきました。

iframe で読み込んだ別ドメインのボタンを指定する

今回 Chrome 拡張機能の対象としているページは画面の一部を iframe で読み込んでいました。 そして押したときに音声を流したい特定のボタンも iframe の読み込み先で描画されていたのですが、この読み込み先が対象ページとは異なるドメインで管理されていました。 所謂クロスドメインです。

クロスドメインの iframe の場合、Chrome 拡張機能の content_scripts でスクリプトを挿入する際に all_frames: true と指定する必要がありました。 また、matches で指定する URL はブラウザでアクセスするものではなく、iframe で読み込む URL を指定することでピンポイントにスクリプトを挿入することができました。

作成した Chrome 拡張機能

というわけで上記の対応をしたものが以下になります。 Javascript は平均以下なので書き方に問題があっても見逃してください。

manifest.json では offscreen の権限だったり content_scripts の設定だったりを追加します。

{
  "name": "greatest-sound",
  "version": "0.0.1",
  "manifest_version": 3,
  "permissions": [
    "activeTab",
    "offscreen"
  ],
  "content_scripts": [{
    "run_at":"document_end",
    "matches": [
      "https://example.com/"  // iframe で読み込む URL を指定する
    ],
    "js": ["content.js"],
    "all_frames": true
  }],
  "background": {
    "service_worker": "background.js"
  }
}

content.js は iframe で読み込んだ先のボタンに対してイベントリスナーを追加して、ボタンを押したら background.js が呼ばれるようにします。

var button = document.getElementById("special-button");

if (button != null) {
  button.addEventListener("click", function () {
    chrome.runtime.sendMessage({ action: "playAudio" });
  });
};

background.js では作成する音声ファイルを指定しつつ、offscreen を作成します。

chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  if (request.action === "playAudio") {
    playSound();
  }
});

async function playSound(source = 'greatest-sound.mp3', volume = 1) {
  await createOffscreen();
  await chrome.runtime.sendMessage({ play: { source, volume } });
}

async function createOffscreen() {
  if (await chrome.offscreen.hasDocument()) return;
  await chrome.offscreen.createDocument({
      url: 'offscreen.html',
      reasons: ['AUDIO_PLAYBACK'],
      justification: 'testing'
  });
}

offscreen.html は Javascript を読み込むだけです。

<script src="offscreen.js"></script>

offscreen.js で実際に音声ファイルを作成します。

chrome.runtime.onMessage.addListener(msg => {
  if ('play' in msg) playAudio(msg.play);
});

function playAudio({ source, volume }) {
  const audio = new Audio(source);
  audio.volume = volume;
  audio.play();
}

おわりに

短いですが Chrome 拡張機能で音声ファイルを再生するための備忘です。 定時退社が捗るね!