🤖

discord.js bot開発で二重録音される原因と解決策

2024/04/07に公開

前段

discord.jsで録音を行う際には最低限下記のようなコードを作成して、その後pipeを用いてデコードしたりファイル作成したり等行なっていくのが一般的かと思います。
下記のコードではAfterSilenceのdurationを100msにしていますがこの値を1000msなどに増やしてみると筆者は二重録音されるケースがありました。
記事などを調べても同じような事象が発生していそうなことは多数ヒットしましたが原因と解決法については見当たらなかったので残しておきます。
discord.jsの用意しているフィールドやメソッドでの解決を図りたかったのですがうまく使えそうなものを筆者は見つけられなかったので、もしより良いやり方をご存知の方いらっしゃいましたらアドバイスお待ちしております。

connection.receiver.speaking.on('start', (userId: string) => {
    const stream = connection.receiver.subscribe(userId, {
      end: {
        behavior: EndBehaviorType.AfterSilence,
        duration: 100,
      },
    })
})

原因

上記のsubscribe部分については、静寂後の100ms後にストリーム処理を終了するというものです。
startに関しては発話されたタイミングで、discordのアイコンの緑枠が光ったタイミングと考えてもらえればわかりやすいです。

二重録音される原因はこのstartとsubscribeにあります。
二重録音されるケースとされないケースの事象について図で説明します。
discordのアイコンの緑枠状態が図の緑背景にあたります。

二重録音されない

このケースでは喋り終わった後にdurationで指定した 100ms の時間後にアイコンが緑枠状態になっているので1つ目のストリームが終了した後に、次のストリームがsubscribe状態になっているため問題ありません。

二重録音される

このケースでは喋り終わった後にdurationで指定した 100ms 経つ前に発話を行いアイコンが緑枠状態になっています。そのためストリームが二重で並列して走っている状況です。
durationの時間を長くした場合に気づくのは、この並列で走るのが起こりやすくなるためです。
試しにdurationを10000(10秒)にしてみると二重で録音されるのがわかるかと思います。

解決法

既にsubscribe状態であれば新しいストリームのsubscribeを行わないようにすれば問題ありません。
startとsubscribeともにdiscordのuserIdを取得していますのでそれを用いて制御処理を追加します。

// 既にsubscribeされたユーザ
const subscribedUsers = new Set()

connection.receiver.speaking.on('start', (userId: string) => {
  // 既にsubscribeされたユーザーであればreturn
  if (subscribedUsers.has(userId)) {
    return
  }

  const stream = connection.receiver.subscribe(userId, {
    end: {
      behavior: EndBehaviorType.AfterSilence,
      duration: 100,
    },
  })

  // 新しいユーザーをsubscribe済みとして追加
  subscribedUsers.add(userId)

  // ファイルに書き出したら何らかの処理をこの辺で行う

  stream.on('end', () => {
    // ストリームが終了したら、subscribeしたユーザーを削除
      subscribedUsers.delete(userId)
  })

  stream.on('error', () => {
    // エラー時もsubscribeしたユーザーを削除
      subscribedUsers.delete(userId)
  })
})

これで durationの時間を伸ばしても二重録音が行われることは無くなりました。
お気軽に質問、ご指摘等のコメントお待ちしております。

Discussion