2

I am using the most recent version of the Discord.js API which requires the use of Discord.js/voice to play audio in a voice chat. I am trying to create my own music bot. I am, however, having trouble with actually playing the audio.

I think the issue is with how I create the AudioResource object although I have attempted to follow the examples on the discord guide.

Here are the relevant parts of the code:

    const discord = require("discord.js")
    const ytdl = require("ytdl-core")
    const MUSIC_PATH = "./music/song.webm"
    const {
        createWriteStream,
        createReadStream,
    } = require("fs")
    const {
        joinVoiceChannel,
        createAudioPlayer,
        createAudioResource,
        StreamType,
        AudioPlayerStatus,
    } = require("@discordjs/voice") 
    const {
        prefix,
        token
    } = require("./config.json")
    const client = new discord.Client({ intents: ["GUILDS", "GUILD_MESSAGES"] }) //Intention to interact with messages
    
    const audioPlayer = {
        musicStream: createAudioPlayer(),
        connection: null,
        connectionId: null,
    }

client.on('messageCreate', msg => {
    if (msg.author.bot || !msg.content.startsWith(prefix)) return
    let messageParts = msg.content.split(" ")

    const voiceChannel = msg.member.voice.channel
    switch (messageParts[0]) {
        case "!play":
            if (!canExecutePlayRequest(msg, voiceChannel)) return
            createChannelConnection(msg, voiceChannel)
            playMusic(messageParts[1])
            break;
        case "!skip":
            msg.reply("!skip")
            break;
        case "!stop":
            msg.reply("!stop")
            break;
        case "!disconnect":
            destroyChannelConnection(msg, voiceChannel)
            break;
        default:
            msg.reply("That's not a real command!")
    }

/**
 * Creates connection object for channel that user is currently in. Adds said connection to audioPlayer.
 * @param {*} msg Command message
 * @param {*} voiceChannel Current voice channel of user
 */
function createChannelConnection(msg, voiceChannel) { 
    //Check for existing connection
    if (audioPlayer.connection != null) {
        //If already connected to channel of user return
        if (audioPlayer.connectionId == voiceChannel.id) return //FIXME: channel checking update when user changes

        //If connected to different channel destroy that connection first
        destroyChannelConnection(msg, voiceChannel)
    }

    //Create and save connection
    const connection = joinVoiceChannel({
        channelId: voiceChannel.id,
        guildId: voiceChannel.guild.id,
        adapterCreator: voiceChannel.guild.voiceAdapterCreator,
    })
    connection.subscribe(audioPlayer.musicStream)

    audioPlayer.connection = connection
    audioPlayer.connectionId = voiceChannel.id
}
})

function playMusic(url){
    ytdl(url, { filter: 'audioonly' }).pipe(createWriteStream(MUSIC_PATH)) //works


    const resource = createAudioResource(createReadStream(MUSIC_PATH), {
        inputType: StreamType.WebmOpus,
    })
    console.log(resource)
    audioPlayer.musicStream.play(resource)
}

Some notes:

  1. I use my MUSIC_PATH instead of join(__dirname, 'file.webm') as they do on the discord guide I linked. I have used both and gotten the same output. Neither throws an error.

  2. The bot is able to join the voice chat no problem. Having used audio status updates I have also concluded that the audioPlayer.musicStream.play() indeed causes the audio player to go into play mode.

  3. Before executing a !play command the bot checks if it has connect and speak permissions which both pass.

  4. This is the output of the console.log(resource) when attempting to play Joyner Lucas' Will by url:

AudioResource {
  playbackDuration: 0,
  started: false,
  silenceRemaining: -1,
  edges: [
    {
      type: 'webm/opus demuxer',
      to: [Node],
      cost: 1,
      transformer: [Function: transformer],
      from: [Node]
    }
  ],
  playStream: WebmDemuxer {
    _readableState: ReadableState {
      objectMode: true,
      highWaterMark: 16,
      buffer: BufferList { head: null, tail: null, length: 0 },
      length: 0,
      pipes: [],
      flowing: false,
      ended: false,
      endEmitted: false,
      reading: false,
      constructed: true,
      sync: false,
      needReadable: true,
      emittedReadable: false,
      readableListening: true,
      resumeScheduled: false,
      errorEmitted: false,
      emitClose: true,
      autoDestroy: true,
      destroyed: false,
      errored: null,
      closed: false,
      closeEmitted: false,
      defaultEncoding: 'utf8',
      awaitDrainWriters: null,
      multiAwaitDrain: false,
      readingMore: false,
      dataEmitted: false,
      decoder: null,
      encoding: null,
      [Symbol(kPaused)]: null
    },
    _events: [Object: null prototype] {
      prefinish: [Function: prefinish],
      close: [Array],
      end: [Function: onend],
      finish: [Array],
      error: [Array],
      unpipe: [Function: onunpipe],
      readable: [Function]
    },
    _eventsCount: 7,
    _maxListeners: undefined,
    _writableState: WritableState {
      objectMode: false,
      highWaterMark: 16384,
      finalCalled: false,
      needDrain: false,
      ending: false,
      ended: false,
      finished: false,
      destroyed: false,
      decodeStrings: true,
      defaultEncoding: 'utf8',
      length: 0,
      writing: false,
      corked: 0,
      sync: true,
      bufferProcessing: false,
      onwrite: [Function: bound onwrite],
      writecb: null,
      writelen: 0,
      afterWriteTickInfo: null,
      buffered: [],
      bufferedIndex: 0,
      allBuffers: true,
      allNoop: true,
      pendingcb: 0,
      constructed: true,
      prefinished: false,
      errorEmitted: false,
      emitClose: true,
      autoDestroy: true,
      errored: null,
      closed: false,
      closeEmitted: false,
      [Symbol(kOnFinished)]: []
    },
    allowHalfOpen: true,
    _remainder: null,
    _length: 0,
    _count: 0,
    _skipUntil: null,
    _track: null,
    _incompleteTrack: {},
    _ebmlFound: false,
    [Symbol(kCapture)]: false,
    [Symbol(kCallback)]: null
  },
  metadata: null,
  silencePaddingFrames: 5
}

Needless to say no music is played in the voice chat. What am I doing wrong in creating this resource? Clearly it's not working very well. Is it something to do with discordjs/opus? I've seen mentions of that floating around, but don't know anything about it although the dependency is included in my project.

Thank you in advance for the help.

Ollie
  • 428
  • 2
  • 9
  • 26
GentleAutumnRain
  • 514
  • 1
  • 5
  • 16

1 Answers1

0

I have found the solution!

I was right in my assumption that the creation of the AudioResource was failing. Although it seems it was not me doing something wrong, rather the ytdl-core package. I haven't been able to find out what exactly was going wrong there, but having now switched to using the play-dl package to stream my music into an AudioResource as follows:

    //Create Stream from Youtube URL
    const stream = await play.stream(url)

    //Create AudioResource from Stream
    let resource = createAudioResource(stream.stream, {
        inputType: stream.type
    })

    //Play resource
    audioPlayer.musicStream.play(resource)

It is now creating functional AudioResources that play their music.

It is worth mentioning that I was also missing an intent in my Client creation. Apparently the "GUILD_VOICE_STATES" intent is necessary to play audio in the voice channel.

GentleAutumnRain
  • 514
  • 1
  • 5
  • 16