Copyright | (c) Yuto Takano (2021) |
---|---|
License | MIT |
Maintainer | moa17stock@gmail.com |
Safe Haskell | None |
Language | Haskell2010 |
WARNING
This module is considered internal.
The Package Versioning Policy does not apply.
The contents of this module may change in any way whatsoever and without any warning between minor versions of this package.
Description
This module is the internal entry point into discord-haskell-voice
. Any use of
this module (or other Internal modules) is discouraged. Please see Discord.Voice
for the public interface.
Synopsis
- liftDiscord :: DiscordHandler a -> Voice a
- runVoice :: Voice () -> DiscordHandler (Either VoiceError ())
- join :: GuildId -> ChannelId -> Voice (Voice ())
- updateSpeakingStatus :: Bool -> Voice ()
- updateStatusVoice :: GuildId -> Maybe ChannelId -> Bool -> Bool -> DiscordHandler ()
- play :: ConduitT () ByteString (ResourceT DiscordHandler) () -> Voice ()
- encodeOpusC :: ConduitT ByteString ByteString (ResourceT DiscordHandler) ()
- playPCMFile :: FilePath -> Voice ()
- playPCMFile' :: FilePath -> ConduitT ByteString ByteString (ResourceT DiscordHandler) () -> Voice ()
- playFile :: FilePath -> Voice ()
- playFile' :: FilePath -> ConduitT ByteString ByteString (ResourceT DiscordHandler) () -> Voice ()
- defaultFFmpegArgs :: FilePath -> [String]
- playFileWith :: String -> (String -> [String]) -> FilePath -> Voice ()
- playFileWith' :: String -> (String -> [String]) -> String -> ConduitT ByteString ByteString (ResourceT DiscordHandler) () -> Voice ()
- playYouTube :: String -> Voice ()
- playYouTube' :: String -> ConduitT ByteString ByteString (ResourceT DiscordHandler) () -> Voice ()
- playYouTubeWith :: String -> (String -> [String]) -> String -> String -> Voice ()
- playYouTubeWith' :: String -> (String -> [String]) -> String -> String -> ConduitT ByteString ByteString (ResourceT DiscordHandler) () -> Voice ()
Documentation
liftDiscord :: DiscordHandler a -> Voice a Source #
liftDiscord
lifts a computation in DiscordHandler into a computation in
Voice. This is useful for performing DiscordHandler actions inside the
Voice monad.
Usage:
runVoice $ do join (read "123456789012345") (read "67890123456789012") liftDiscord $ void $ restCall $ R.CreateMessage (read "2938481828383") "Joined!" liftIO $ threadDelay 5e6 playYouTube "Rate of Reaction of Sodium Hydroxide and Hydrochloric Acid" liftDiscord $ void $ restCall $ R.CreateMessage (read "2938481828383") "Finished!" void $ restCall $ R.CreateMessage (read "2938481828383") "Finished all voice actions!"
runVoice :: Voice () -> DiscordHandler (Either VoiceError ()) Source #
Execute the voice actions stored in the Voice monad.
A single mutex and sending packet channel is used throughout all voice connections within the actions, which enables multi-channel broadcasting. The following demonstrates how a single playback is streamed to multiple connections.
runVoice $ do join (read "123456789012345") (read "67890123456789012") join (read "098765432123456") (read "12345698765456709") playYouTube "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
The return type of runVoice
represents result status of the voice computation.
It is isomorphic to Maybe
, but the use of Either explicitly denotes that
the correct/successful/Right behaviour is (), and that the potentially-
existing value is of failure.
join :: GuildId -> ChannelId -> Voice (Voice ()) Source #
Join a specific voice channel, given the Guild and Channel ID of the voice channel. Since the Channel ID is globally unique, there is theoretically no need to specify the Guild ID, but it is provided until discord-haskell fully caches the mappings internally.
This function returns a Voice action that, when executed, will leave the joined voice channel. For example:
runVoice $ do leave <- join (read "123456789012345") (read "67890123456789012") playYouTube "https://www.youtube.com/watch?v=dQw4w9WgXcQ" leave
The above use is not meaningful in practice, since runVoice
will perform
the appropriate cleanup and leaving as necessary at the end of all actions.
However, it may be useful to interleave leave
with other Voice actions.
Since the leave
function will gracefully do nothing if the voice connection
is already severed, it is safe to escape this function from the Voice monad
and use it in a different context. That is, the following is allowed and
is encouraged if you are building a /leave
command of any sort:
-- On /play runVoice $ do leave <- join (read "123456789012345") (read "67890123456789012") liftIO $ putMVar futureLeaveFunc leave forever $ playYouTube "https://www.youtube.com/watch?v=dQw4w9WgXcQ" -- On /leave, from a different thread leave <- liftIO $ takeMVar futureLeaveFunc runVoice leave
The above will join a voice channel, play a YouTube video, but immediately
quit and leave the channel when the /leave
command is received, regardless
of the playback status.
updateSpeakingStatus :: Bool -> Voice () Source #
Helper function to update the speaking indicator for the bot. Setting the
microphone status to True is required for Discord to transmit the bot's
voice to other clients. It is done automatically in all of the play*
functions, so there should be no use for this function in practice.
Note: Soundshare and priority are const as False in the payload because I don't see bots needing them. If and when required, add Bool signatures to this function.
:: GuildId | Id of Guild |
-> Maybe ChannelId | Id of the voice channel client wants to join (Nothing if disconnecting) |
-> Bool | Whether the client muted |
-> Bool | Whether the client deafened |
-> DiscordHandler () |
Send a Gateway Websocket Update Voice State command (Opcode 4). Used to indicate that the client voice status (deaf/mute) as well as the channel they are active on. This is not in the Voice monad because it has to be used after all voice actions end, to quit the voice channels. It also has no benefit, since it would cause extra transformer wrapping/unwrapping.
play :: ConduitT () ByteString (ResourceT DiscordHandler) () -> Voice () Source #
play source
plays some sound from the conduit source
, provided in the
form of 16-bit Little Endian PCM. The use of Conduit allows you to perform
arbitrary lazy transformations of audio data, using all the advantages that
Conduit brings. As the base monad for the Conduit is ResourceT DiscordHandler
,
you can access any DiscordHandler effects (through lift
) or IO effects
(through liftIO
) in the conduit as well.
For a more specific interface that is easier to use, see the playPCMFile
,
playFile
, and playYouTube
functions.
import Conduit ( sourceFile ) runVoice $ do join gid cid play $ sourceFile "./audio/example.pcm"
encodeOpusC :: ConduitT ByteString ByteString (ResourceT DiscordHandler) () Source #
encodeOpusC
is a conduit that splits the ByteString into chunks of
(frame size * no of channels * 16/8) bytes, and encodes each chunk into
OPUS format. ByteStrings are made of CChars (Int8)s, but the data is 16-bit
so this is why we multiply by two to get the right amount of bytes instead of
prematurely cutting off at the half-way point.
playPCMFile file
plays the sound stored in the file located at file
,
provided it is in the form of 16-bit Little Endian PCM. playPCMFile
is
defined as a handy alias for the following:
playPCMFile ≡ play . sourceFile
For a variant of this function that allows arbitrary transformations of the
audio data through a conduit component, see playPCMFile'
.
To play any other format, it will need to be transcoded using FFmpeg. See
playFile
for such usage.
:: FilePath | The path to the PCM file to play |
-> ConduitT ByteString ByteString (ResourceT DiscordHandler) () | Any processing that needs to be done on the audio data |
-> Voice () |
playPCMFile' file processor
plays the sound stored in the file located at
file
, provided it is in the form of 16-bit Little Endian PCM. Audio data
will be passed through the processor
conduit component, allowing arbitrary
transformations to audio data before playback. playPCMFile'
is defined as
the following:
playPCMFile' file processor ≡ play $ sourceFile file .| processor
For a variant of this function with no processing, see playPCMFile
.
To play any other format, it will need to be transcoded using FFmpeg. See
playFile
for such usage.
playFile file
plays the sound stored in the file located at file
. It
supports any format supported by FFmpeg by transcoding it, which means it can
play a wide range of file types. This function expects "ffmpeg
" to be
available in the system PATH.
For a variant that allows you to specify the executable and/or any arguments,
see playFileWith
.
For a variant of this function that allows arbitrary transformations of the
audio data through a conduit component, see playFile'
.
If the file is already known to be in 16-bit little endian PCM, using
playPCMFile
is much more efficient as it does not go through FFmpeg.
:: FilePath | The path to the audio file to play |
-> ConduitT ByteString ByteString (ResourceT DiscordHandler) () | Any processing that needs to be done on the audio data |
-> Voice () |
playFile' file processor
plays the sound stored in the file located at
file
. It supports any format supported by FFmpeg by transcoding it, which
means it can play a wide range of file types. This function expects
"ffmpeg
" to be available in the system PATH. Audio data will be passed
through the processor
conduit component, allowing arbitrary transformations
to audio data before playback.
For a variant that allows you to specify the executable and/or any arguments,
see playFileWith'
.
For a variant of this function with no processing, see playFile
.
If the file is already known to be in 16-bit little endian PCM, using
playPCMFile'
is much more efficient as it does not go through FFmpeg.
defaultFFmpegArgs :: FilePath -> [String] Source #
defaultFFmpegArgs
is a generator function for the default FFmpeg
arguments used when streaming audio into 16-bit little endian PCM on stdout.
This function takes in the input file path as an argument, because FFmpeg
arguments are position sensitive in relation to the placement of -i
.
It is defined semantically as:
defaultFFmpegArgs FILE ≡ "-i FILE -f s16le -ar 48000 -ac 2 -loglevel warning pipe:1"
:: String | The name of the FFmpeg executable |
-> (String -> [String]) | FFmpeg argument generator function, given the filepath |
-> FilePath | The path to the audio file to play |
-> Voice () |
playFileWith exe args file
plays the sound stored in the file located at
file
, using the specified FFmpeg executable exe
and an argument generator
function args
(see defaultFFmpegArgs
for the default). It supports any
format supported by FFmpeg by transcoding it, which means it can play a wide
range of file types.
For a variant of this function that uses the "ffmpeg
" executable in your
PATH automatically, see playFile
.
For a variant of this function that allows arbitrary transformations of the
audio data through a conduit component, see playFileWith'
.
If the file is known to be in 16-bit little endian PCM, using playPCMFile
is more efficient as it does not go through FFmpeg.
:: String | The name of the FFmpeg executable |
-> (String -> [String]) | FFmpeg argument generator function, given the filepath |
-> String | The path to the audio file to play |
-> ConduitT ByteString ByteString (ResourceT DiscordHandler) () | Any processing that needs to be done on the audio data |
-> Voice () |
playFileWith' exe args file processor
plays the sound stored in the file
located at file
, using the specified FFmpeg executable exe
and an
argument generator function args
(see defaultFFmpegArgs
for the default).
It supports any format supported by FFmpeg by transcoding it, which means it
can play a wide range of file types. Audio data will be passed through the
processor
conduit component, allowing arbitrary transformations to audio
data before playback.
For a variant of this function that uses the "ffmpeg
" executable in your
PATH automatically, see playFile'
.
For a variant of this function with no processing, see playFileWith
.
If the file is known to be in 16-bit little endian PCM, using playPCMFile'
is more efficient as it does not go through FFmpeg.
playYouTube query
plays the first result of searching query
on YouTube.
If a direct video URL is given, YouTube will always return that as the first
result, which means playYouTube
also supports playing links. It supports
all videos, by automatically transcoding to PCM using FFmpeg. Since it
streams the data instead of downloading it first, it can play live videos as
well. This function expects "ffmpeg
" and "youtube-dl
" to be available in
the system PATH.
For a variant that allows you to specify the executable and/or any arguments,
see playYouTubeWith
.
For a variant of this function that allows arbitrary transformations of the
audio data through a conduit component, see playYouTube'
.
:: String | Search query (or video URL) |
-> ConduitT ByteString ByteString (ResourceT DiscordHandler) () | Any processing that needs to be done on the audio data |
-> Voice () |
playYouTube' query processor
plays the first result of searching query
on YouTube. If a direct video URL is given, YouTube will always return that
as the first result, which means playYouTube
also supports playing links.
It supports all videos, by automatically transcoding to PCM using FFmpeg.
Since it streams the data instead of downloading it first, it can play live
videos as well. This function expects "ffmpeg
" and "youtube-dl
" to be
available in the system PATH. Audio data will be passed through the
processor
conduit component, allowing arbitrary transformations to audio
data before playback.
For a variant that allows you to specify the executable and/or any arguments,
see playYouTubeWith'
.
For a variant of this function with no processing, see playYouTube
.
:: String | The name of the FFmpeg executable |
-> (String -> [String]) | FFmpeg argument generator function, given the URL |
-> String | The name of the youtube-dl executable |
-> String | The search query (or video URL) |
-> Voice () |
playYouTubeWith fexe fargs yexe query
plays the first result of searching
query
on YouTube, using the specified youtube-dl
executable yexe
,
FFmpeg executable fexe
and an argument generator function fargs
(see
defaultFFmpegArgs
for the default). If a direct video URL is given, YouTube
will always return that as the first result, which means playYouTube
also
supports playing links. It supports all videos, by automatically transcoding
to PCM using FFmpeg. Since it streams the data instead of downloading it
first, it can play live videos as well.
For a variant of this function that uses the "ffmpeg
" executable and
"youtube-dl
" executable in your PATH automatically, see playYouTube
.
For a variant of this function that allows arbitrary transformations of the
audio data through a conduit component, see playYouTubeWith'
.
:: String | The name of the FFmpeg executable |
-> (String -> [String]) | The arguments to pass to FFmpeg |
-> String | The name of the youtube-dl executable |
-> String | The search query (or video URL) |
-> ConduitT ByteString ByteString (ResourceT DiscordHandler) () | Any processing that needs to be done on the audio data |
-> Voice () |
playYouTubeWith' fexe fargs yexe query processor
plays the first result
of searching query
on YouTube, using the specified youtube-dl
executable
yexe
, FFmpeg executable fexe
and an argument generator function fargs
(see defaultFFmpegArgs
for the default). If a direct video URL is given,
YouTube will always return that as the first result, which means
playYouTube
also supports playing links. It supports all videos, by
automatically transcoding to PCM using FFmpeg. Since it streams the data
instead of downloading it first, it can play live videos as well. Audio data
will be passed through the processor
conduit component, allowing arbitrary
transformations to audio data before playback.
For a variant of this function that uses the "ffmpeg
" executable and
"youtube-dl
" executable in your PATH automatically, see playYouTube'
.
For a variant of this function with no processing, see playYouTubeWith
.