Woo, audio! Added a basic play command, which for now just loads a totally random video. Also added a kill command to forcefully stop a connection, might move that to debug

This commit is contained in:
Julius de Jeu 2019-05-27 23:06:45 +02:00
parent e8ae29d1ce
commit faab0c818c
9 changed files with 153 additions and 3 deletions

View file

@ -35,6 +35,7 @@ dependencies {
testImplementation "org.junit.jupiter:junit-jupiter:5.4.2"
implementation 'net.dv8tion:JDA:4.ALPHA.0_88'
implementation 'com.sedmelluq:lavaplayer:1.3.17'
implementation "com.h2database:h2"

View file

@ -1,12 +1,15 @@
package nl.voidcorp.discord
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers
import net.dv8tion.jda.api.sharding.DefaultShardManagerBuilder
import nl.voidcorp.discord.events.CommandListener
import nl.voidcorp.discord.events.OttoListener
import org.springframework.stereotype.Component
import org.springframework.stereotype.Service
@Component
class Loader(listener: CommandListener) {
@Service
class Loader(listener: CommandListener, playerManager: DefaultAudioPlayerManager) {
init {
val token = System.getenv("DISCORD_TOKEN") ?: throw RuntimeException("'DISCORD_TOKEN' not set!")
val builder = DefaultShardManagerBuilder(token)
@ -16,5 +19,6 @@ class Loader(listener: CommandListener) {
builder.addEventListeners(OttoListener, listener)
jda = builder.build()
AudioSourceManagers.registerRemoteSources(playerManager)
}
}

View file

@ -3,6 +3,7 @@ package nl.voidcorp.discord.command
enum class CommandGroup {
GENERAL,
FUN,
MUSIC,
ROLES,
ADMIN,
VeRY_hIdden_CaTegoRY_LoL,

View file

@ -0,0 +1,17 @@
package nl.voidcorp.discord.commands.music
import nl.voidcorp.discord.command.*
import org.springframework.stereotype.Service
@Service
class ForceLeave : Command(
"forceleave",
group = CommandGroup.MUSIC,
location = CommandSource.GUILD,
commandLevel = CommandLevel.MODERATOR
) {
override fun handle(event: CommandMessage): CommandResult {
event.guild!!.audioManager.closeAudioConnection()
return CommandResult.SUCCESS
}
}

View file

@ -0,0 +1,28 @@
package nl.voidcorp.discord.commands.music
import nl.voidcorp.discord.command.*
import nl.voidcorp.discord.music.AudioLoadHandler
import nl.voidcorp.discord.music.PlayerManager
import org.springframework.stereotype.Service
@Service
class Play(val playerManager: PlayerManager) :
Command("play", location = CommandSource.GUILD, group = CommandGroup.MUSIC) {
override fun handle(event: CommandMessage): CommandResult {
val chan = event.member!!.voiceState!!.channel
if (chan == null) {
event.reply("Please join a voice channel to play music!")
return CommandResult.SUCCESS
}
val am = event.guild!!.audioManager
am.openAudioConnection(chan)
val ts = playerManager.getGuildPlayer(event.guild)
playerManager.loadItem("https://www.youtube.com/watch?v=kPgiJUeSP6Q", AudioLoadHandler(ts))
return CommandResult.SUCCESS
}
}

View file

@ -0,0 +1,30 @@
package nl.voidcorp.discord.music
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist
import com.sedmelluq.discord.lavaplayer.track.AudioTrack
import nl.voidcorp.discord.logger
class AudioLoadHandler(private val trackScheduler: TrackScheduler) : AudioLoadResultHandler {
override fun loadFailed(exception: FriendlyException) {
throw exception
}
override fun trackLoaded(track: AudioTrack) {
logger.info("loaded track ${track.identifier}")
trackScheduler.queue(track)
}
override fun noMatches() {
}
override fun playlistLoaded(playlist: AudioPlaylist) {
for (t in playlist.tracks) {
trackScheduler.queue(t)
}
}
}

View file

@ -0,0 +1,22 @@
package nl.voidcorp.discord.music
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer
import com.sedmelluq.discord.lavaplayer.track.playback.AudioFrame
import net.dv8tion.jda.api.audio.AudioSendHandler
import java.nio.ByteBuffer
class AudioPlayerSendHandler(private val audioPlayer: AudioPlayer) : AudioSendHandler {
private var lastFrame: AudioFrame? = null
override fun canProvide(): Boolean {
lastFrame = audioPlayer.provide()
return lastFrame != null
}
override fun provide20MsAudio() = ByteBuffer.wrap(lastFrame!!.data)
override fun isOpus(): Boolean {
return true
}
}

View file

@ -0,0 +1,22 @@
package nl.voidcorp.discord.music
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager
import net.dv8tion.jda.api.entities.Guild
import org.springframework.stereotype.Service
@Service
class PlayerManager : DefaultAudioPlayerManager() {
val guildPlayMap = mutableMapOf<Long, TrackScheduler>()
fun getGuildPlayer(guild: Guild): TrackScheduler {
return if (guildPlayMap.containsKey(guild.idLong)) {
guildPlayMap[guild.idLong] ?: error("oof?")
} else {
val player = createPlayer()
val ts = TrackScheduler(player)
player.addListener(ts)
guild.audioManager.sendingHandler = AudioPlayerSendHandler(player)
guildPlayMap[guild.idLong] = ts
ts
}
}
}

View file

@ -0,0 +1,25 @@
package nl.voidcorp.discord.music
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter
import com.sedmelluq.discord.lavaplayer.track.AudioTrack
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason
import java.util.*
class TrackScheduler(private val player: AudioPlayer) : AudioEventAdapter() {
private val queue = ArrayDeque<AudioTrack>()
fun queue(track: AudioTrack) {
if (!player.startTrack(track, true)) {
queue.addLast(track)
}
}
fun pop() = queue.pop()
override fun onTrackEnd(player: AudioPlayer, track: AudioTrack, endReason: AudioTrackEndReason) {
if (endReason.mayStartNext && queue.isNotEmpty()) {
player.startTrack(pop(), true)
}
}
}