-
Notifications
You must be signed in to change notification settings - Fork 130
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement playing audio through discord voice connections. (#180)
* Implement playing audio through discord voice connections in Nostrum. * Improve voice api. Make switching voice channels painless * Improve docs * Add helpful functions for checking voice connections * Update guild cache on voice_state_update. Add logger config options, update docs. * Add support for youtube-dl * Fix typo and make youtube-dl be quiet * Add example bot for using voice channels. * Shore up docs * Format everything. Raise exception if execs not found. Warn on start if configured and not found. * cleanup * Detect end of input for pipe/youtubedl playing. Emit Voice Speaking Update events on start/stop. * Fix conflict
- Loading branch information
1 parent
d990570
commit d0decb5
Showing
26 changed files
with
1,528 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
## Voice Channels | ||
Discord voice channels allow audio data to be sent to the voice servers over UDP. | ||
A bot is able to connect to up to one voice channel per guild. One websocket | ||
connection will be opened and maintained for each voice channel the bot joins. | ||
The websocket connection should reconnect automatically the same way that the | ||
main Discord gateway websocket connections do. For available voice functions and | ||
usage see the `Nostrum.Voice` module. | ||
|
||
## FFmpeg | ||
Nostrum uses the powerful [ffmpeg](https://ffmpeg.org/) command line utility to | ||
encode any audio (or video) file for sending to Discord's voice servers. | ||
By default Nostrum will look for the executable `ffmpeg` in the system path. | ||
If the executable is elsewhere, the path may be configured via | ||
`config :nostrum, :ffmpeg, "/path/to/ffmpeg"`. | ||
The function `Nostrum.Voice.play/3` allows sound to played via files, local or | ||
remote, or via raw data that gets piped to `stdin` of the `ffmpeg` process. | ||
When playing from a url, the url can be a name of a file on the filesystem or a url | ||
of file on a remote server - [ffmpeg supports a ton of protocols](https://www.ffmpeg.org/ffmpeg-protocols.html), | ||
the most common of which are probably `http` or simply reading a file from the filesystem. | ||
|
||
## youtube-dl | ||
With only `ffmpeg` installed, Nostrum supports playing audio/video files or raw, piped | ||
data as discussed in the section above. Nostrum also has support for `youtube-dl`, another | ||
powerful command line utility for downloading audio/video from online video services. | ||
Although the name implies support for Youtube, `youtube-dl` supports downloading from | ||
[an immense list of sites](https://github.com/ytdl-org/youtube-dl/blob/master/docs/supportedsites.md). | ||
By default Nostrum will look for the executable `youtube-dl` in the system path. If the | ||
executable is elsewhere, the path may be configured via `config :nostrum, :youtubedl, "/path/to/youtube-dl"`. | ||
When `Nostrum.Voice.play/3` is called with `:ytdl` for the `type` parameter, `youtube-dl` will be | ||
run with options `-f bestaudio -q -o -`, which will attempt to download the audio at the given url and pipe it to `ffmpeg`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
# This file can be placed somewhere in ./lib, and it can be started | ||
# by running iex -S mix then calling AudioPlayerSupervisor.start_link([]). | ||
defmodule AudioPlayerSupervisor do | ||
use Supervisor | ||
|
||
def start_link(args) do | ||
Supervisor.start_link(__MODULE__, args, name: __MODULE__) | ||
end | ||
|
||
@impl true | ||
def init(_init_arg) do | ||
children = [AudioPlayerConsumer] | ||
|
||
Supervisor.init(children, strategy: :one_for_one) | ||
end | ||
end | ||
|
||
defmodule AudioPlayerConsumer do | ||
use Nostrum.Consumer | ||
|
||
alias Nostrum.Api | ||
alias Nostrum.Cache.GuildCache | ||
alias Nostrum.Voice | ||
|
||
require Logger | ||
|
||
# Soundcloud link will be fed through youtube-dl | ||
@soundcloud_url "https://soundcloud.com/fyre-brand/level-up" | ||
# Audio file will be fed directly to ffmpeg | ||
@nut_file_url "https://brandthill.com/files/nut.wav" | ||
|
||
def start_link do | ||
Consumer.start_link(__MODULE__) | ||
end | ||
|
||
def get_voice_channel_of_msg(msg) do | ||
msg.guild_id | ||
|> GuildCache.get!() | ||
|> Map.get(:voice_states) | ||
|> Enum.find(%{}, fn v -> v.user_id == msg.author.id end) | ||
|> Map.get(:channel_id) | ||
end | ||
|
||
def do_not_ready_msg(msg) do | ||
Api.create_message(msg.channel_id, "I need to be in a voice channel for that.") | ||
end | ||
|
||
def handle_event({:MESSAGE_CREATE, msg, _ws_state}) do | ||
case msg.content do | ||
# The bot will search through the guild cache's voice states to find | ||
# the voice channel that the message author is in to join. | ||
"!summon" -> | ||
case get_voice_channel_of_msg(msg) do | ||
nil -> | ||
Api.create_message(msg.channel_id, "Must be in a voice channel to summon") | ||
|
||
voice_channel_id -> | ||
Voice.join_channel(msg.guild_id, voice_channel_id) | ||
end | ||
|
||
"!leave" -> | ||
Voice.leave_channel(msg.guild_id) | ||
|
||
# Following play song/nut commands check if connected | ||
# and will let the user know in case of failure. | ||
"!play song" -> | ||
if Voice.ready?(msg.guild_id) do | ||
Voice.play(msg.guild_id, @soundcloud_url, :ytdl) | ||
else | ||
do_not_ready_msg(msg) | ||
end | ||
|
||
"!play nut" -> | ||
if Voice.ready?(msg.guild_id) do | ||
Voice.play(msg.guild_id, @nut_file_url, :url) | ||
else | ||
do_not_ready_msg(msg) | ||
end | ||
|
||
# Following commands don't check anything so they'll | ||
# fail quietly if nothing is playing/paused or in channel. | ||
"!pause" -> | ||
Voice.pause(msg.guild_id) | ||
|
||
"!resume" -> | ||
Voice.resume(msg.guild_id) | ||
|
||
"!stop" -> | ||
Voice.stop(msg.guild_id) | ||
|
||
_ -> | ||
:noop | ||
end | ||
end | ||
|
||
def handle_event({:VOICE_SPEAKING_UPDATE, payload, _ws_state}) do | ||
Logger.debug("VOICE SPEAKING UPDATE #{inspect(payload)}") | ||
end | ||
|
||
# Default event handler, if you don't include this, your consumer WILL crash if | ||
# you don't have a method definition for each event type. | ||
def handle_event(_event) do | ||
:noop | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.