LoginSignup
2
2

Chrome拡張でVOICEVOXに読み上げさせるためにはdeclarativeNetRequestでOriginを削除したりsendMessageでやり取りする必要がある

Posted at

まとめ

declarativeNetRequestでOriginを削除することで、利用者に手間をかけさせずVOICEVOXのAPIを利用することができる。

VOICEVOXのissueにもそういった対策が書かれているが、気づきにくいので本記事を書いて知見として残しておく。

またオーディオの再生まわりでも色々な制限が出て来るのでそれについても書いておく。

解説

VOICEVOX側での制限

VOICEVOXのエンジンではCORSの範囲の範囲を制限しておりそのままではブラウザ拡張からのAPI呼び出しが弾かれる。

一応エンジン側にて許可するOriginを追加することができるようになっているが、利用者に chrome-extension://... を追加してもらうというのは手間がかかるし好ましくない。

declarativeNetRequest

Chrome拡張側でfetch等のリクエストに対して細工を行うための機能が存在する。こちらで細工を行ってVOICEVOXが通してくれるリクエストを投げるようにする。

これによってbackgroundからVOICEVOXのAPI呼び出しが通るようになる。

設定例

manifest.json
{
  "permissions": [
    "declarativeNetRequest"
  ],
  "host_permissions": [
    "*://localhost/*"
  ],
  "declarative_net_request": {
    "rule_resources": [
      {
        "id": "ruleset",
        "enabled": true,
        "path": "rules.json"
      }
    ]
  }
}
rules.json
[
  {
    "id": 1,
    "priority": 1,
    "action": {
      "type": "modifyHeaders",
      "requestHeaders": [
        { "header": "Origin", "operation": "remove" }
      ]
    },
    "condition": {
      "urlFilter": "localhost"
    }
  }
]

backgroundではAudioContextが利用できない

ブラウザで音声を再生するにはAudioContextを利用するのがお手軽そう。

しかしbackgroundはManifest V3からServiceWorkerとなっているためページを持たなくなり、AudioContextも利用できなくなっている。

そのためoffscreenだったりcontent_scriptsを活用してページ側で音声を再生する必要がある。やり取りにはsendMessageを利用する。

ちなみに最初からページ側でAPIリクエストも行っていくというのはOriginヘッダの除去がcontent_scriptsでは機能しないためダメです。(offscreenは未検証)

メッセージはJSON対応のオブジェクトである必要がある

ArrayBufferをそのまま送ろうとしても失敗する。sendMessageはJSONとして取り扱えるプリミティブなデータしか取り扱えない。そのため送受信で変換する必要がある。

送信側
Array.from(new Uint8Array(arrayBuffer));
受信側
const arrayBuffer = Uint8Array.from(array).buffer;
再生
let currentSourceNode;

const context = new AudioContext();
const gainNode = context.createGain();
gainNode.connect(context.destination);

context.decodeAudioData(arrayBuffer).then(decoded => {
    if (currentSourceNode) {
        currentSourceNode.stop();
        currentSourceNode.disconnect();
    }
    const sourceNode = context.createBufferSource();
    sourceNode.connect(gainNode);
    sourceNode.buffer = decoded;
    sourceNode.start(0);
    currentSourceNode = sourceNode;
});
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2