mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-14 02:23:31 +01:00
feat(guide): port legacy guide (#10938)
* feat: initial attempt at porting legacy guide * feat: completion of legacy guide backport * chore: lockfile shenanigans * fix: handle svgs * fix: replace svg with mermaid integration * chore: format * chore: remove unnecssary bullet * chore: cleanup code highlights * chore: explicit return * chore: move display components after interactive components in sidebar * chore: voice * top link should be installation * add docs link to sidebar * feat: subguide-based accent styles * chore: don't list faq twice * chore: mention display components in interactive components * fix: remove unoccs/order rule from guide * chore: redirect to legacy guide instead of /guide root * refactor: use `<kbd>` * refactor: more kbd use * Update apps/guide/content/docs/legacy/app-creation/handling-events.mdx Co-authored-by: Naiyar <137700126+imnaiyar@users.noreply.github.com> * chore: fix typos Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com> * chore: fix typos * chore: fix links regarding secret stores across coding platforms * chore: fix typo * chore: link node method directly Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com> * chore: typos Co-authored-by: Vlad Frangu <me@vladfrangu.dev> * chore: typo Co-authored-by: Vlad Frangu <me@vladfrangu.dev> * fix: prevent v14 changes from being listed twice * chore: prefer relative links * chore: missed link conversion * chore: missed link conversion * chore: fix link * chore: remove legacy code highlight markers * chore: rephrase and extend contributing guidelines * feat(setup): suggest cli flag over dotenv package * chore: move introduction in sidebar better navigation experience if the 'next page' in intro refers to getting started vs. updating/faq * fix: replace outdated link * fix: update voice dependencies * chore: update node install instructions * fix: list in missing access callout * chore: match bun env file format * chore: restore ffmpeg disclaimer * fix: lockfile conflict * chore: action row typo Co-authored-by: Vlad Frangu <me@vladfrangu.dev> * chore: no longer use at-next for pino --------- Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com> Co-authored-by: Naiyar <137700126+imnaiyar@users.noreply.github.com> Co-authored-by: Vlad Frangu <me@vladfrangu.dev>
This commit is contained in:
143
apps/guide/content/docs/voice/audio-player.mdx
Normal file
143
apps/guide/content/docs/voice/audio-player.mdx
Normal file
@@ -0,0 +1,143 @@
|
||||
---
|
||||
title: Audio Player
|
||||
---
|
||||
|
||||
Audio players can be used to play audio across voice connections. A single audio player can play the same audio over multiple voice connections.
|
||||
|
||||
## Cheat sheet
|
||||
|
||||
### Creation
|
||||
|
||||
Creating an audio player is simple:
|
||||
|
||||
```js
|
||||
const { createAudioPlayer } = require('@discordjs/voice');
|
||||
|
||||
const player = createAudioPlayer();
|
||||
```
|
||||
|
||||
You can also customize the behaviors of an audio player. For example, the default behavior is to pause when there are no active subscribers for an audio player. This behavior can be configured to either pause, stop, or just continue playing through the stream:
|
||||
|
||||
```js
|
||||
const { createAudioPlayer, NoSubscriberBehavior } = require('@discordjs/voice');
|
||||
|
||||
const player = createAudioPlayer({
|
||||
behaviors: {
|
||||
noSubscriber: NoSubscriberBehavior.Pause,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Deletion
|
||||
|
||||
If you no longer require an audio player, you can `stop()` it and then remove references to it so that it gets garbage collected.
|
||||
|
||||
```js
|
||||
player.stop();
|
||||
```
|
||||
|
||||
### Playing audio
|
||||
|
||||
You can create [audio resources](./audio-resources) and then play them on an audio player.
|
||||
|
||||
```js
|
||||
const resource = createAudioResource('/home/user/voice/track.mp3');
|
||||
player.play(resource);
|
||||
|
||||
// Play "track.mp3" across two voice connections
|
||||
connection1.subscribe(player);
|
||||
connection2.subscribe(player);
|
||||
```
|
||||
|
||||
<Callout>
|
||||
**Audio players can play one audio resource at most.** If you try to play another audio resource while one is already
|
||||
playing on the same player, the existing one is destroyed and replaced with the new one.
|
||||
</Callout>
|
||||
|
||||
### Pausing/unpausing
|
||||
|
||||
You can call the `pause()` and `unpause()` methods. While the audio player is paused, no audio will be played. When it is resumed, it will continue where it left off.
|
||||
|
||||
```js
|
||||
player.pause();
|
||||
|
||||
// Unpause after 5 seconds
|
||||
setTimeout(() => player.unpause(), 5_000);
|
||||
```
|
||||
|
||||
## Life cycle
|
||||
|
||||
Voice connections have their own life cycle, with five distinct states. You can follow the methods discussed in the [life cycles](./life-cycles) section to subscribe to changes to voice connections.
|
||||
|
||||
- **Idle** - the initial state of an audio player. The audio player will be in this state when there is no audio resource for it to play.
|
||||
|
||||
- **Buffering** - the state an audio player will be in while it is waiting for an audio resource to become playable. The audio player may transition from this state to either the `Playing` state (success) or the `Idle` state (failure).
|
||||
|
||||
- **Playing** - the state a voice connection enters when it is actively playing an audio resource. When the audio resource comes to an end, the audio player will transition to the Idle state.
|
||||
|
||||
- **AutoPaused** - the state a voice connection will enter when the player has paused itself because there are no active voice connections to play to. This is only possible with the `noSubscriber` behavior set to `Pause`. It will automatically transition back to `Playing` once at least one connection becomes available again.
|
||||
|
||||
- **Paused** - the state a voice connection enters when it is paused by the user.
|
||||
|
||||
```js
|
||||
const { AudioPlayerStatus } = require('@discordjs/voice');
|
||||
|
||||
player.on(AudioPlayerStatus.Playing, () => {
|
||||
console.log('The audio player has started playing!');
|
||||
});
|
||||
```
|
||||
|
||||
## Handling errors
|
||||
|
||||
When an audio player is given an audio resource to play, it will propagate errors from the audio resource for you to handle.
|
||||
|
||||
In the error handler, you can choose to either play a new audio resource or stop playback. If you take no action, the audio player will stop itself and revert to the `Idle` state.
|
||||
|
||||
Additionally, the error object will also contain a `resource` property that helps you to figure out which audio resource created the error.
|
||||
|
||||
Two different examples of how you may handle errors are shown below.
|
||||
|
||||
### Taking action within the error handler
|
||||
|
||||
In this example, the audio player will only move on to playing the next audio resource if an error has occurred. If playback ends gracefully, nothing will happen. This example avoids a transition into the Idle state.
|
||||
|
||||
```js
|
||||
const { createAudioResource } = require('@discordjs/voice');
|
||||
|
||||
const resource = createAudioResource('/home/user/voice/music.mp3', {
|
||||
metadata: {
|
||||
title: 'A good song!',
|
||||
},
|
||||
});
|
||||
|
||||
player.play(resource);
|
||||
|
||||
player.on('error', (error) => {
|
||||
console.error(`Error: ${error.message} with resource ${error.resource.metadata.title}`);
|
||||
player.play(getNextResource());
|
||||
});
|
||||
```
|
||||
|
||||
### Taking action within the `Idle` state
|
||||
|
||||
In this example, the error event is used only for logging purposes. The audio player will naturally transition into the `Idle` state, and then another resource is played. This has the advantage of working with streams that come to an end gracefully, and those that are interrupted by errors.
|
||||
|
||||
```js
|
||||
const { createAudioResource } = require('@discordjs/voice');
|
||||
|
||||
const resource = createAudioResource('/home/user/voice/music.mp3', {
|
||||
metadata: {
|
||||
title: 'A good song!',
|
||||
},
|
||||
});
|
||||
|
||||
player.play(resource);
|
||||
|
||||
player.on('error', (error) => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
player.on(AudioPlayerStatus.Idle, () => {
|
||||
player.play(getNextResource());
|
||||
});
|
||||
```
|
||||
157
apps/guide/content/docs/voice/audio-resources.mdx
Normal file
157
apps/guide/content/docs/voice/audio-resources.mdx
Normal file
@@ -0,0 +1,157 @@
|
||||
---
|
||||
title: Audio Resources
|
||||
---
|
||||
|
||||
Audio resources contain audio that can be played by an audio player to voice connections.
|
||||
|
||||
## Cheat sheet
|
||||
|
||||
### Creation
|
||||
|
||||
There are many ways to create an audio resource. Below are some example scenarios:
|
||||
|
||||
```js
|
||||
const { createReadStream } = require('node:fs');
|
||||
const { join } = require('node:path');
|
||||
const { createAudioResource, StreamType } = require('@discordjs/voice');
|
||||
|
||||
// Basic, default options are:
|
||||
// Input type is unknown, so will use FFmpeg to convert to Opus under-the-hood
|
||||
// Inline volume is opt-in to improve performance
|
||||
let resource = createAudioResource(join(__dirname, 'file.mp3'));
|
||||
|
||||
// Will use FFmpeg with volume control enabled
|
||||
resource = createAudioResource(join(__dirname, 'file.mp3'), { inlineVolume: true });
|
||||
resource.volume.setVolume(0.5);
|
||||
|
||||
// Will play .ogg or .webm Opus files without FFmpeg for better performance
|
||||
// Remember, inline volume is still disabled
|
||||
resource = createAudioResource(
|
||||
createReadStream(join(__dirname, 'file.ogg'), {
|
||||
inputType: StreamType.OggOpus,
|
||||
}),
|
||||
);
|
||||
|
||||
// Will play with FFmpeg due to inline volume being enabled.
|
||||
resource = createAudioResource(
|
||||
createReadStream(join(__dirname, 'file.webm'), {
|
||||
inputType: StreamType.WebmOpus,
|
||||
inlineVolume: true,
|
||||
}),
|
||||
);
|
||||
|
||||
player.play(resource);
|
||||
```
|
||||
|
||||
### Deletion
|
||||
|
||||
The underlying streams of an audio resource are destroyed and flushed once an audio player is done playing their audio. Make sure to remove any references you've created to the resource to prevent memory leaks.
|
||||
|
||||
## Handling errors
|
||||
|
||||
For most scenarios, you will create an audio resource for immediate use by an audio player. The audio player will propagate errors from the resource for you, so you can attach `error` handlers to the player instead of the resource.
|
||||
|
||||
```js
|
||||
const { createAudioResource, createAudioPlayer } = require('@discordjs/voice');
|
||||
|
||||
const player = createAudioPlayer();
|
||||
// An AudioPlayer will always emit an "error" event with a .resource property
|
||||
player.on('error', (error) => {
|
||||
console.error('Error:', error.message, 'with track', error.resource.metadata.title);
|
||||
});
|
||||
|
||||
const resource = createAudioResource('/home/user/voice/music.mp3', {
|
||||
metadata: {
|
||||
title: 'A good song!',
|
||||
},
|
||||
});
|
||||
player.play(resource);
|
||||
```
|
||||
|
||||
However, you can also attach an error handler specifically to the audio resource if you'd like to. This is **not recommended**, as you are not allowed to change the state of an audio player from the error handlers of an audio resource (on the other hand, you are allowed to do this from the error handle of an audio player, as shown above.)
|
||||
|
||||
```js
|
||||
const { createAudioResource, createAudioPlayer } = require('@discordjs/voice');
|
||||
|
||||
const player = createAudioPlayer();
|
||||
|
||||
const resource = createAudioResource('/home/user/voice/music.mp3', {
|
||||
metadata: {
|
||||
title: 'A good song!',
|
||||
},
|
||||
});
|
||||
|
||||
// Not recommended - listen to errors from the audio player instead for most usecases!
|
||||
resource.playStream.on('error', (error) => {
|
||||
console.error('Error:', error.message, 'with track', resource.metadata.title);
|
||||
});
|
||||
|
||||
player.play(resource);
|
||||
```
|
||||
|
||||
## Optimizations
|
||||
|
||||
To improve performance, you can consider the following methods. They reduce the computational demand required to play audio, and could help to reduce jitter in the audio stream.
|
||||
|
||||
### Not using inline volume
|
||||
|
||||
By default, inline volume is disabled for performance reasons. Enabling it will allow you to modify the volume of your stream in realtime. This comes at a performance cost, even if you aren't actually modifying the volume of your stream.
|
||||
|
||||
Make sure you consider whether it is worth enabling for your use case.
|
||||
|
||||
### Playing Opus streams
|
||||
|
||||
If you are repeatedly playing the same resource, you may consider converting it to Ogg opus or WebM opus. Alternatively, if you are fetching an external resource and are able to specify a format that you'd like to stream the resource in, you should consider specifying Ogg opus or WebM opus.
|
||||
|
||||
The reason for this is that you can remove FFmpeg from the process of streaming audio. FFmpeg is used to convert unknown inputs into Opus audio which can be streamed to Discord. If your audio is already in the Opus format, this removes one of the most computationally demanding parts of the audio pipeline from the streaming process, which would surely improve performance.
|
||||
|
||||
Both of the examples below will skip the FFmpeg component of the pipeline to improve performance.
|
||||
|
||||
```js
|
||||
const { createReadStream } = require('node:fs');
|
||||
const { createAudioResource, StreamType } = require('@discordjs/voice');
|
||||
|
||||
let resource = createAudioResource(createReadStream('my_file.ogg'), {
|
||||
inputType: StreamType.OggOpus,
|
||||
});
|
||||
|
||||
resource = createAudioResource(createReadStream('my_file.webm'), {
|
||||
inputType: StreamType.WebmOpus,
|
||||
});
|
||||
```
|
||||
|
||||
<Callout type="warn">
|
||||
This optimization is useful if you do not want to use inline volume. Enabling inline volume will disable the
|
||||
optimization.
|
||||
</Callout>
|
||||
|
||||
### Probing to determine stream type
|
||||
|
||||
The voice library is also able to determine whether a readable stream is an Ogg/Opus or WebM/Opus stream. This means
|
||||
that you can still gain the performance benefits that come with playing an Opus stream, even if you aren't sure in
|
||||
advance what type of audio stream you'll be playing.
|
||||
|
||||
This is achieved by probing a small chunk of the beginning of the audio stream to see if it is suitable for demuxing:
|
||||
|
||||
```js
|
||||
const { createReadStream } = require('node:fs');
|
||||
const { demuxProbe, createAudioResource } = require('@discordjs/voice');
|
||||
|
||||
async function probeAndCreateResource(readableStream) {
|
||||
const { stream, type } = await demuxProbe(readableStream);
|
||||
return createAudioResource(stream, { inputType: type });
|
||||
}
|
||||
|
||||
async function createResources() {
|
||||
// Creates an audio resource with inputType = StreamType.Arbitrary
|
||||
const mp3Stream = await probeAndCreateResource(createReadStream('file.mp3'));
|
||||
|
||||
// Creates an audio resource with inputType = StreamType.OggOpus
|
||||
const oggStream = await probeAndCreateResource(createReadStream('file.ogg'));
|
||||
|
||||
// Creates an audio resource with inputType = StreamType.WebmOpus
|
||||
const webmStream = await probeAndCreateResource(createReadStream('file.webm'));
|
||||
}
|
||||
|
||||
createResources();
|
||||
```
|
||||
106
apps/guide/content/docs/voice/index.mdx
Normal file
106
apps/guide/content/docs/voice/index.mdx
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
title: Installation
|
||||
---
|
||||
|
||||
"Voice" refers to Discord bots being able to send audio in voice channels. This is supported in discord.js via @discordjs/voice](https://github.com/discordjs/discord.js/tree/main/packages/voice), a standalone library made by the developers of discord.js. While you can use it with any Node.js Discord API library, this guide will focus on using it with discord.js.
|
||||
|
||||
## Installation
|
||||
|
||||
### Barebones
|
||||
|
||||
To add voice functionality to your discord.js bot, you will need the `@discordjs/voice` package, as well as one of the encryption packages listed below. For example:
|
||||
|
||||
```sh tab="npm"
|
||||
npm install @discordjs/voice
|
||||
```
|
||||
|
||||
```sh tab="yarn"
|
||||
yarn add @discordjs/voice
|
||||
```
|
||||
|
||||
```sh tab="pnpm"
|
||||
pnpm add @discordjs/voice
|
||||
```
|
||||
|
||||
```sh tab="bun"
|
||||
bun add @discordjs/voice
|
||||
```
|
||||
|
||||
After this, you'll be able to play Ogg and WebM Opus files without any other dependencies. If you want to play audio from other sources, or want to improve performance, consider installing some of the extra dependencies listed below.
|
||||
|
||||
<Callout>
|
||||
This guide assumes you have installed at least FFmpeg for all the examples to work. You can find more information
|
||||
about extra dependencies in the next section.
|
||||
</Callout>
|
||||
|
||||
### Extra Dependencies
|
||||
|
||||
#### Opus encoding
|
||||
|
||||
- [`@discordjs/opus`](https://github.com/discordjs/opus) (best performance)
|
||||
- [`opusscript`](https://github.com/abalabahaha/opusscript/)
|
||||
|
||||
#### FFmpeg – allows you to play a range of media (e.g. MP3s).
|
||||
|
||||
- [`ffmpeg`](https://ffmpeg.org/) - install and add to your system environment
|
||||
- [`ffmpeg-static`](https://www.npmjs.com/package/ffmpeg-static) - to install FFmpeg via npm
|
||||
|
||||
#### Encryption libraries
|
||||
|
||||
<Callout>
|
||||
You only need to install one of these libraries if your system does not support `aes-256-gcm`. You can verify this by
|
||||
running `require('node:crypto').getCiphers().includes('aes-256-gcm')`
|
||||
</Callout>
|
||||
|
||||
- [`sodium-native`](https://www.npmjs.com/package/sodium-native)
|
||||
- [`sodium`](https://www.npmjs.com/package/sodium) (best performance)
|
||||
- [`@stablelib/xchacha20poly1305`](https://www.npmjs.com/package/@stablelib/xchacha20poly1305)
|
||||
- [`@noble/ciphers`](https://www.npmjs.com/package/@noble/ciphers)
|
||||
- [`libsodium-wrappers`](https://www.npmjs.com/package/libsodium-wrappers)
|
||||
|
||||
<Callout>
|
||||
If you are facing issues when installing these dependencies, make sure you ticked the box to install optional build
|
||||
tools when installing Node.js or try manually installing build tools and python: ```sh winget install "Visual Studio
|
||||
Community 2022" --override "--add Microsoft.VisualStudio.Workload.NativeDesktop " -s msstore ```
|
||||
</Callout>
|
||||
|
||||
## Debugging Dependencies
|
||||
|
||||
The library includes a helper function that helps you to find out which dependencies you've successfully installed. This information is also very helpful if you ever need to submit an issue on the `@discordjs/voice` issue tracker.
|
||||
|
||||
```js
|
||||
const { generateDependencyReport } = require('@discordjs/voice');
|
||||
|
||||
console.log(generateDependencyReport());
|
||||
|
||||
/*
|
||||
--------------------------------------------------
|
||||
Core Dependencies
|
||||
- @discordjs/voice: 0.3.1
|
||||
- prism-media: 1.2.9
|
||||
|
||||
Opus Libraries
|
||||
- @discordjs/opus: 0.5.3
|
||||
- opusscript: not found
|
||||
|
||||
Encryption Libraries
|
||||
- sodium: 3.0.2
|
||||
- libsodium-wrappers: not found
|
||||
- tweetnacl: not found
|
||||
|
||||
FFmpeg
|
||||
- version: 4.2.4-1ubuntu0.1
|
||||
- libopus: yes
|
||||
--------------------------------------------------
|
||||
*/
|
||||
```
|
||||
|
||||
- **Core Dependencies**
|
||||
- These are dependencies that should definitely be available.
|
||||
- **Opus Libraries**
|
||||
- If you want to play audio from many different file types, or alter volume in real-time, you will need to have one of these.
|
||||
- **Encryption Libraries**
|
||||
- You should have at least one encryption library installed to use `@discordjs/voice`.
|
||||
- **FFmpeg**
|
||||
- If you want to play audio from many different file types, you will need to have FFmpeg installed.
|
||||
- If `libopus` is enabled, you will be able to benefit from increased performance if real-time volume alteration is disabled.
|
||||
69
apps/guide/content/docs/voice/life-cycles.mdx
Normal file
69
apps/guide/content/docs/voice/life-cycles.mdx
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Life Cycles
|
||||
---
|
||||
|
||||
Two of the main components that you'll interact with when using `@discordjs/voice` are:
|
||||
|
||||
- **VoiceConnection** – maintains a network connection to a Discord voice server
|
||||
- **AudioPlayer** – plays audio resources across a voice connection
|
||||
|
||||
Both voice connections and audio players are _stateful_, and you can subscribe to changes in their state as they progress through their respective life cycles.
|
||||
|
||||
It's important to listen for state change events, as they will likely require you to take some action. For example, a voice connection entering the `Disconnected` state will probably require you to attempt to reconnect it.
|
||||
|
||||
Their individual life cycles with descriptions of their states are documented on their respective pages.
|
||||
|
||||
Listening to changes in the life cycles of voice connections and audio players can be done in two ways:
|
||||
|
||||
## Subscribing to individual events
|
||||
|
||||
```js
|
||||
const { VoiceConnectionStatus, AudioPlayerStatus } = require('@discordjs/voice');
|
||||
|
||||
connection.on(VoiceConnectionStatus.Ready, (oldState, newState) => {
|
||||
console.log('Connection is in the Ready state!');
|
||||
});
|
||||
|
||||
player.on(AudioPlayerStatus.Playing, (oldState, newState) => {
|
||||
console.log('Audio player is in the Playing state!');
|
||||
});
|
||||
```
|
||||
|
||||
<Callout>
|
||||
One advantage of listening for transitions to individual states is that it becomes a lot easier to write sequential logic. This is made easy using our [state transition helper](https://github.com/discordjs/discord.js/blob/main/packages/voice/src/util/entersState.ts). An example is shown below.
|
||||
|
||||
```js
|
||||
const { AudioPlayerStatus, entersState } = require('@discordjs/voice');
|
||||
|
||||
async function start() {
|
||||
player.play(resource);
|
||||
try {
|
||||
await entersState(player, AudioPlayerStatus.Playing, 5_000);
|
||||
// The player has entered the Playing state within 5 seconds
|
||||
console.log('Playback has started!');
|
||||
} catch (error) {
|
||||
// The player has not entered the Playing state and either:
|
||||
// 1) The 'error' event has been emitted and should be handled
|
||||
// 2) 5 seconds have passed
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
void start();
|
||||
```
|
||||
|
||||
</Callout>
|
||||
|
||||
## Subscribing to all state transitions
|
||||
|
||||
If you would prefer to keep a single event listener for all possible state transitions, then you can also listen to the `stateChange` event:
|
||||
|
||||
```js
|
||||
connection.on('stateChange', (oldState, newState) => {
|
||||
console.log(`Connection transitioned from ${oldState.status} to ${newState.status}`);
|
||||
});
|
||||
|
||||
player.on('stateChange', (oldState, newState) => {
|
||||
console.log(`Audio player transitioned from ${oldState.status} to ${newState.status}`);
|
||||
});
|
||||
```
|
||||
15
apps/guide/content/docs/voice/meta.json
Normal file
15
apps/guide/content/docs/voice/meta.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"title": "Voice",
|
||||
"description": "Working with the voice library",
|
||||
"pages": [
|
||||
"[LibraryBig][Documentation](https://discord.js.org/docs/packages/voice/main)",
|
||||
"---Working with Voice---",
|
||||
"index",
|
||||
"life-cycles",
|
||||
"voice-connections",
|
||||
"audio-player",
|
||||
"audio-resources"
|
||||
],
|
||||
"root": true,
|
||||
"icon": "Volume2"
|
||||
}
|
||||
122
apps/guide/content/docs/voice/voice-connections.mdx
Normal file
122
apps/guide/content/docs/voice/voice-connections.mdx
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
title: Voice Connections
|
||||
---
|
||||
|
||||
Voice connections represent connections to voice channels in a guild. You can only connect to one voice channel in a guild at a time.
|
||||
|
||||
Voice connections will automatically try their best to re-establish their connections when they move across voice channels, or if the voice server region changes.
|
||||
|
||||
## Cheat sheet
|
||||
|
||||
### Creation
|
||||
|
||||
Creating a voice connection is simple:
|
||||
|
||||
```js
|
||||
const { joinVoiceChannel } = require('@discordjs/voice');
|
||||
|
||||
const connection = joinVoiceChannel({
|
||||
channelId: channel.id,
|
||||
guildId: channel.guild.id,
|
||||
adapterCreator: channel.guild.voiceAdapterCreator,
|
||||
});
|
||||
```
|
||||
|
||||
If you try to call `joinVoiceChannel` on another channel in the same guild in which there is already an active voice connection, the existing voice connection switches over to the new channel.
|
||||
|
||||
### Access
|
||||
|
||||
You can access created connections elsewhere in your code without having to track the connections yourself. It is best practice to not track the voice connections yourself as you may forget to clean them up once they are destroyed, leading to memory leaks.
|
||||
|
||||
```js
|
||||
const { getVoiceConnection } = require('@discordjs/voice');
|
||||
|
||||
const connection = getVoiceConnection(myVoiceChannel.guild.id);
|
||||
```
|
||||
|
||||
### Deletion
|
||||
|
||||
You can destroy a voice connection when you no longer require it. This will disconnect its connection if it is still active, stop audio playing to it, and will remove it from the internal tracker for voice connections.
|
||||
|
||||
It's important that you destroy voice connections when they are no longer required so that your bot leaves the voice channel, and to prevent memory leaks.
|
||||
|
||||
```js
|
||||
connection.destroy();
|
||||
```
|
||||
|
||||
### Playing audio
|
||||
|
||||
You can subscribe voice connections to audio players as soon as they're created. Audio players will try to stream audio to all their subscribed voice connections that are in the Ready state. Destroyed voice connections cannot be subscribed to audio players.
|
||||
|
||||
```js
|
||||
// Subscribe the connection to the audio player (will play audio on the voice connection)
|
||||
const subscription = connection.subscribe(audioPlayer);
|
||||
|
||||
// subscription could be undefined if the connection is destroyed!
|
||||
if (subscription) {
|
||||
// Unsubscribe after 5 seconds (stop playing audio on the voice connection)
|
||||
setTimeout(() => subscription.unsubscribe(), 5_000);
|
||||
}
|
||||
```
|
||||
|
||||
<Callout>
|
||||
**Voice connections can be subscribed to one audio player at most.** If you try to subscribe to another audio player while already subscribed to one, the current audio player is unsubscribed and replaced with the new one.
|
||||
|
||||
It is also worth noting that the **GuildVoiceStates** [gateway intent](../legacy/popular-topics/intents) is required. Without it, the connection will permanently stay in the `signalling` state, and you'll be unable to play audio.
|
||||
|
||||
</Callout>
|
||||
|
||||
## Life cycle
|
||||
|
||||
Voice connections have their own life cycle, with five distinct states. You can follow the methods discussed in the [life cycles](./life-cycles) section to subscribe to changes to voice connections.
|
||||
|
||||
- **Signalling** - the initial state of a voice connection. The connection will be in this state when it is requesting permission to join a voice channel.
|
||||
|
||||
- **Connecting** - the state a voice connection enters once it has permission to join a voice channel and is in the process of establishing a connection to it.
|
||||
|
||||
- **Ready** - the state a voice connection enters once it has successfully established a connection to the voice channel. It is ready to play audio in this state.
|
||||
|
||||
- **Disconnected** - the state a voice connection enters when the connection to a voice channel has been severed. This can occur even if the connection has not yet been established. You may choose to attempt to reconnect in this state.
|
||||
|
||||
- **Destroyed** - the state a voice connection enters when it has been manually been destroyed. This will prevent it from accidentally being reused, and it will be removed from an in-memory tracker of voice connections.
|
||||
|
||||
```js
|
||||
const { VoiceConnectionStatus } = require('@discordjs/voice');
|
||||
|
||||
connection.on(VoiceConnectionStatus.Ready, () => {
|
||||
console.log('The connection has entered the Ready state - ready to play audio!');
|
||||
});
|
||||
```
|
||||
|
||||
## Handling disconnects
|
||||
|
||||
Disconnects can be quite complex to handle. There are 3 cases for handling disconnects:
|
||||
|
||||
1. **Resumable disconnects** - there is no clear reason why the disconnect occurred. In this case, voice connections will automatically try to resume the existing session. The voice connection will enter the `Connecting` state. If this fails, it may enter a `Disconnected` state again.
|
||||
|
||||
2. **Reconnectable disconnects** - Discord has closed the connection and given a reason as to why, and that the reason is recoverable. In this case, the voice connection will automatically try to rejoin the voice channel. The voice connection will enter the `Signalling` state. If this fails, it may enter a `Disconnected` state again.
|
||||
|
||||
3. **Potentially reconnectable disconnects** - the bot has either been moved to another voice channel, the channel has been deleted, or the bot has been kicked/lost access to the voice channel. The bot will enter the `Disconnected` state.
|
||||
|
||||
As shown above, the first two cases are covered automatically by the voice connection itself. The only case you need to think carefully about is the third case.
|
||||
|
||||
The third case can be quite problematic to treat as a disconnect, as the bot could simply be moving to another voice channel and so not "truly" disconnected.
|
||||
|
||||
An imperfect workaround to this is to see if the bot has entered a `Signalling` / `Connecting` state shortly after entering the `Disconnected` state. If it has, then it means that the bot has moved voice channels. Otherwise, we should treat it as a real disconnect and not reconnect.
|
||||
|
||||
```js
|
||||
const { VoiceConnectionStatus, entersState } = require('@discordjs/voice');
|
||||
|
||||
connection.on(VoiceConnectionStatus.Disconnected, async (oldState, newState) => {
|
||||
try {
|
||||
await Promise.race([
|
||||
entersState(connection, VoiceConnectionStatus.Signalling, 5_000),
|
||||
entersState(connection, VoiceConnectionStatus.Connecting, 5_000),
|
||||
]);
|
||||
// Seems to be reconnecting to a new channel - ignore disconnect
|
||||
} catch {
|
||||
// Seems to be a real disconnect which SHOULDN'T be recovered from
|
||||
connection.destroy();
|
||||
}
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user