Skip to content

Instantly share code, notes, and snippets.

@quanticc
Created October 29, 2020 02:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save quanticc/f3ffa8f859624643faaad72490d48d03 to your computer and use it in GitHub Desktop.
Save quanticc/f3ffa8f859624643faaad72490d48d03 to your computer and use it in GitHub Desktop.
/*
* This file is part of Discord4J.
*
* Discord4J is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Discord4J is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Discord4J. If not, see <http://www.gnu.org/licenses/>.
*/
package discord4j.core;
import com.sedmelluq.discord.lavaplayer.format.StandardAudioDataFormats;
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.playback.MutableAudioFrame;
import com.sedmelluq.discord.lavaplayer.track.playback.NonAllocatingAudioFrameBuffer;
import discord4j.core.event.domain.message.MessageCreateEvent;
import discord4j.core.object.VoiceState;
import discord4j.core.object.entity.Member;
import discord4j.voice.AudioProvider;
import discord4j.voice.AudioReceiver;
import discord4j.voice.VoiceConnection;
import io.netty.buffer.ByteBufUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.Arrays;
public class ExampleVoice {
private static final Logger log = LoggerFactory.getLogger(ExampleVoice.class);
public static void main(String[] args) {
GatewayDiscordClient client = DiscordClient.create(System.getenv("token"))
.login()
.block();
AudioPlayerManager playerManager = new DefaultAudioPlayerManager();
playerManager.getConfiguration().setFrameBufferFactory(NonAllocatingAudioFrameBuffer::new);
AudioSourceManagers.registerRemoteSources(playerManager);
AudioPlayer player = playerManager.createPlayer();
AudioProvider provider = new LavaplayerAudioProvider(player);
AudioReceiver receiver = new AudioReceiver() {
@Override
public void receive(char sequence, int timestamp, int ssrc, byte[] audio) {
log.info("seq={} ts={} ssrc={} audio={}", sequence, timestamp, ssrc, ByteBufUtil.hexDump(audio));
}
};
Mono<Void> join = client.on(MessageCreateEvent.class)
.filter(e -> e.getMessage().getContent().equals("!join"))
.doOnNext(e -> log.info("Received voice join request"))
.flatMap(e -> Mono.justOrEmpty(e.getMember())
.flatMap(Member::getVoiceState)
.flatMap(VoiceState::getChannel)
.flatMap(channel -> channel.join(spec -> spec.setProvider(provider).setReceiver(receiver)))
.retryWhen(Retry.backoff(2, Duration.ofSeconds(2)))
.doFinally(s -> log.info("Finalized join request after {}", s))
.onErrorResume(t -> {
log.error("Failed to join voice channel", t);
return Mono.empty();
}))
.then();
Mono<Void> leave = client.on(MessageCreateEvent.class)
.filter(e -> e.getMessage().getContent().equals("!leave"))
.doOnNext(e -> log.info("Received voice leave request"))
.flatMap(e -> Mono.justOrEmpty(e.getMember())
.flatMap(Member::getVoiceState)
.flatMap(vs -> client.getVoiceConnectionRegistry()
.getVoiceConnection(vs.getGuildId())
.doOnSuccess(vc -> {
if (vc == null) {
log.info("No voice connection to leave!");
}
}))
.flatMap(VoiceConnection::disconnect))
.then();
Mono<Void> play = client.on(MessageCreateEvent.class)
.filter(e -> e.getMessage().getContent().startsWith("!play "))
.flatMap(e -> Mono.justOrEmpty(e.getMessage().getContent())
.map(content -> Arrays.asList(content.split(" ")))
.doOnNext(command -> playerManager.loadItem(command.get(1),
new MyAudioLoadResultHandler(player))))
.then();
Mono<Void> stop = client.on(MessageCreateEvent.class)
.filter(e -> e.getMessage().getContent().equals("!stop"))
.doOnNext(e -> player.stopTrack())
.then();
Mono<Void> exit = client.on(MessageCreateEvent.class)
.map(event -> event.getMessage().getContent())
.filter(content -> content.equals("!exit"))
.flatMap(presence -> client.logout())
.then();
Mono.when(join, leave, play, stop, exit).block();
}
private static class LavaplayerAudioProvider extends AudioProvider {
private final AudioPlayer player;
private final MutableAudioFrame frame = new MutableAudioFrame();
private LavaplayerAudioProvider(AudioPlayer player) {
super(ByteBuffer.allocate(StandardAudioDataFormats.DISCORD_OPUS.maximumChunkSize()));
this.player = player;
this.frame.setBuffer(getBuffer());
}
@Override
public boolean provide() {
boolean didProvide = player.provide(frame);
if (didProvide) {
getBuffer().flip();
}
return didProvide;
}
}
private static class MyAudioLoadResultHandler implements AudioLoadResultHandler {
private final AudioPlayer player;
private MyAudioLoadResultHandler(AudioPlayer player) {
this.player = player;
}
@Override
public void trackLoaded(AudioTrack track) {
player.playTrack(track);
}
@Override
public void playlistLoaded(AudioPlaylist playlist) {
}
@Override
public void noMatches() {
}
@Override
public void loadFailed(FriendlyException exception) {
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment