Source
A single-use stream of Opus packets for an audio player. audio.fromOpus and audio.fromPCM build one from Opus
packets or PCM; audio.fromFile, audio.fromEncoded and the named file sources (fromMP3, fromFLAC and the
rest) build one from an audio file, decoded with discord_luau/symphonia. The player that claims a source reads it
to its end and destroys it, so a source plays once.
A PCM source re-chunks its input into 20 ms frames of 48 kHz stereo 16-bit little-endian PCM (3840 bytes) as it is read, zero-pads the last frame and encodes each frame with one Opus encoder, created on the first frame. It holds at most one partial frame and a reference to the input buffer it is reading, however long the input is.
A file source holds a native decoder session, and for fromFile an open file, from the moment it is built until
destroy. A playAsync that starts destroys its source on every exit; a source never handed to one, or handed to
one that fails its start checks or is never awaited, must be released with source:destroy().
A stream source, from audio.fromStream, plays a file that arrives in pieces. Its feed, an Audio.StreamFeed,
opens a libsymphonia stream session on the first poll, which the player makes, runs the pump that calls the
stream's read, and reads decoded audio ahead into a queue of up to 6 seconds, before any volume, so a volume change
is heard within about 20 ms. The source itself limits, frames and encodes that audio, and decides when it is
starved, so the player waits until bufferSeconds of audio is buffered, and again whenever the queue and the
decoder both run dry. A failure after the stream opened plays the audio queued before it first. read never yields.
Summary
Properties
Source.format :: "opus" | "pcm"Source.items :: { buffer }?Source.iterator :: (() -> buffer?)?Source.itemCount :: numberSource.inputEnded :: booleanSource.chunk :: buffer?Source.chunkOffset :: numberSource.frame :: buffer?Source.frameFill :: numberSource.totalBytes :: numberSource.encoder :: opus.Encoder?Source.bitrate :: numberSource.volume :: numberSource.decoder :: symphonia.Decoder?Source.feed :: Audio.StreamFeed?Source.starved :: booleanSource.failure :: string?Source.bufferSeconds :: numberSource.limiter :: Symphonia.Limiter?Source.release :: (() -> ())?Source.played :: booleanSource.ended :: booleanSource.logger :: logger.LoggerMethods
Source:read() → buffer?Source:poll()Source:setVolume(volume: number)Source:claim()Source:destroy()Functions
Source.fromOpus(packets: { buffer } | () -> buffer?) → SourceSource.fromPCM(pcm: buffer | { buffer } | () -> buffer?, options: PCMOptions?) → SourceSource.fromFile(path: string, options: FileOptions?) → SourceSource.fromEncoded(bytes: buffer | string, options: FileOptions?) → SourceSource.fromMP3(bytes: buffer | string, options: NamedOptions?) → SourceSource.fromOgg(bytes: buffer | string, options: NamedOptions?) → SourceSource.fromWAV(bytes: buffer | string, options: NamedOptions?) → SourceSource.fromFLAC(bytes: buffer | string, options: NamedOptions?) → SourceSource.fromAAC(bytes: buffer | string, options: NamedOptions?) → SourceSource.fromM4A(bytes: buffer | string, options: NamedOptions?) → SourceSource.fromWebM(bytes: buffer | string, options: NamedOptions?) → SourceSource.fromMKV(bytes: buffer | string, options: NamedOptions?) → SourceSource.fromAIFF(bytes: buffer | string, options: NamedOptions?) → SourceSource.fromCAF(bytes: buffer | string, options: NamedOptions?) → SourceSource.fromStream(read: StreamReader, options: StreamOptions?) → SourceSource.fromProcess(command: string, arguments: { string }?, options: StreamOptions?) → SourceProperties
Section titled “Properties”format
Section titled “format”What the input holds: Opus packets, passed through as they are, or PCM, encoded as it is read. A stream source holds
"pcm" until its stream opens as Opus.
The input array; a whole PCM buffer is stored as a one-item array. Nil when the input is a function.
iterator
Section titled “iterator”The input function, which returns the next buffer and nil at the end. Nil when the input is an array.
itemCount
Section titled “itemCount”How many input items the source has taken.
inputEnded
Section titled “inputEnded”Whether the input gave nil, so an input function is never called again after its end.
The PCM input buffer being read.
chunkOffset
Section titled “chunkOffset”How many bytes of chunk are used.
The reusable 3840-byte PCM frame, created on the first PCM read.
frameFill
Section titled “frameFill”How many bytes frame holds.
totalBytes
Section titled “totalBytes”The PCM bytes taken so far.
encoder
Section titled “encoder”The Opus encoder of a PCM source, created on its first frame and freed when the source ends or is destroyed.
bitrate
Section titled “bitrate”The bitrate in bits per second that every encoder the source creates is set to; options.bitrate, or 128000.
A source that passes Opus through never encodes, so it holds the value without using it.
volume
Section titled “volume”The volume of a file or stream source's decoded audio, from 0 to 4; options.volume, or 1. Other sources hold 1.
decoder
Section titled “decoder”A file source's decoder, which setVolume reaches. Nil for every other source; a stream source's session belongs to
its feed.
A stream source's feed: its session, its pump and its read-ahead queue, with feed.bufferedSeconds,
feed.stopping and feed.producerEnded. Nil for every other source.
starved
Section titled “starved”Whether a stream source has no audio ready yet: true from the start until its first poll finds bufferSeconds
buffered, and again whenever read finds the queue empty and the decoder dry. Only poll clears it, once
bufferSeconds of audio is buffered, the stream can bring no more audio, or the source has ended. A read that
returns nil while it is true means "no data yet", not the end. Every other source holds false.
failure
Section titled “failure”Why a stream source failed, set once the failure is final and nothing queued before it is left to play: at once
for a failure before the stream opened, and after the queued audio for one after. read raises it from then on.
Nil for every other source.
bufferSeconds
Section titled “bufferSeconds”How many seconds of audio a stream source buffers before it stops being starved; options.bufferSeconds, or 1.
Every other source holds 0.
limiter
Section titled “limiter”A stream source's own limiter, holding its volume: read feeds it the queued audio and encodes what leaves it.
Nil for every other source.
release
Section titled “release”Frees what a file or stream source holds: a file source's decoder session, and for fromFile its open file, or a
stream source's feed. destroy clears it before it calls it, so it runs once. Nil for every other source.
played
Section titled “played”Whether a player has claimed the source.
Whether the source has ended or been destroyed; read returns nil from then on.
logger
Section titled “logger”The source's logger, named AudioSource and at Info; lower its level to see the debug lines.
Methods
Section titled “Methods”Source : read ( ) -> buffer?
Returns the next Opus packet, or nil once the source has ended. An Opus source returns its next input item; a PCM
source returns the next 20 ms of its input, encoded. Raises for an input item that is not a buffer, a PCM total that
is not a whole number of samples (4 bytes each), an input function's error and an encoder error. Any raise ends the
source and frees its encoder first, then passes the original error on, so a later read returns nil.
A stream source never yields here. It raises its failure first, on every call. Otherwise it fills an empty queue
from the decoder and returns the next packet, or nil with starved set when the queue and the decoder are both dry,
which means "no data yet" rather than the end. Once the stream can bring no more audio and nothing is left, it
raises a failure the feed met, after the queued audio and the limiter's last frames have played, or returns nil
at the true end.
Returns
| buffer? |
Source : poll ( ) -> ()
Moves a stream source along; any other source only logs at debug. It polls the feed: the first call opens the
stream's session, which loads libsymphonia on first use and so may yield, and starts the pump, and every call reads
the session's state and fills the queue. A failure the feed met becomes failure once it is final and nothing
queued before it is left to play. Then it clears starved once bufferSeconds of audio is buffered, the stream
can bring no more audio, or it has failed. The player calls it every 20 ms while the source is starved. It never
raises: the next read raises failure.
setVolume
Section titled “setVolume”Source : setVolume ( volume ) -> ()
Sets the volume of a file or stream source's decoded audio: a number from 0 to 4, where 1 leaves it as it is. A file
source hears it at the next 100 ms read of the file, and a stream source at its next packet, since its queue holds
audio before any volume; either way it is ramped in over 20 ms so it never clicks, and source.volume holds it.
Audio that would pass full scale is limited rather than clipped. Opus passed through is not decoded, so the volume
has no effect on it; the value is kept, and a file decoder warns once.
Raises volume must be a number from 0 to 4 for anything else, audio source has ended after the source has
ended or been destroyed, and audio source has no file decoder; volume applies to file sources for fromOpus and
fromPCM sources.
Parameters
| volume: number |
Source : claim ( ) -> ()
Marks the source as played. Raises audio source has already been played when a player has claimed it before.
destroy
Section titled “destroy”Source : destroy ( ) -> ()
Ends the source and frees what it holds: a file source's decoder session and open file, a stream source's feed, and
a PCM source's encoder. It acts even after the source has ended, while anything is left to free, and a call with
nothing left logs at debug and does nothing. The release runs once, and one that raises is warned and never raises
from destroy.
For a stream source it destroys the feed: its pump stops at its next check, without destroy waiting for it, so a
pump inside the stream's read stops once that read returns. A playback waiting on the starved source ends
without speaking.
Functions
Section titled “Functions”fromOpus
Section titled “fromOpus”Source . fromOpus ( packets ) -> Source
Builds a source from Opus packets: an array, read in order up to its first nil, or a function that returns the next
packet and nil at the end. Each packet passes through as it is. Raises unless packets is a table or a function;
for a buffer or string, the error points at fromOgg and fromFile, which play an .opus file.
A function input must return the next packet quickly: one that takes longer than a frame leaves a gap in the
audio, and one that never returns holds the playback, which stop cannot end.
Parameters
| packets: { buffer } | () -> buffer? |
Returns
| Source |
fromPCM
Section titled “fromPCM”Source . fromPCM ( pcm , options ) -> Source
Builds a source from 48 kHz stereo 16-bit little-endian PCM: one buffer, an array of buffers, or a function that
returns the next buffer and nil at the end. Buffers may be any length, empty ones included. Raises unless pcm is
one of those, and raises for a buffer whose length is not a whole number of samples (4 bytes each); an array's or a
function's total is checked when its input ends, so read raises instead.
options.bitrate sets the encoder's bitrate in bits per second, a whole number from 500 to 510000 (128000 by
default). It is checked at this call with opus.checkBitrate, because the encoder is created on the first read; a
bad value raises there. The encoder also gets the "music" signal hint.
A function input must return the next buffer quickly: one that takes longer than a frame leaves a gap in the
audio, and one that never returns holds the playback, which stop cannot end.
Parameters
| pcm: buffer | { buffer } | () -> buffer? | |
| options: PCMOptions? |
Returns
| Source |
fromFile
Section titled “fromFile”Source . fromFile ( path , options ) -> Source
Plays the audio file at path, streamed from the disk as it plays rather than read into Luau. Otherwise as
fromEncoded: the same formats, errors, Opus passthrough and release rule. A slow disk stalls the whole VM while a
read waits on it.
Raises could not open audio: <message> for a missing file or one that cannot be opened, and
audio.fromFile takes the file's path as a string; for the file's bytes use audio.fromEncoded for anything but a
string. The source holds the open file and a native decoder session from the moment it is built: a source not
handed to a playAsync that starts must be released with source:destroy().
options.bitrate is as for fromPCM, and applies to the audio that is encoded; a bad one raises before the file
is opened.
options.volume (1) is a number from 0 to 4, checked at the call like the bitrate, and sets the volume of the
decoded audio; see source:setVolume.
Parameters
| path: string | |
| options: FileOptions? |
Returns
| Source |
fromEncoded
Section titled “fromEncoded”Source . fromEncoded ( bytes , options ) -> Source
Plays an audio file held in memory, as a buffer or a string of its bytes, such as what fs.readFile returns. A
string is the file's bytes, never a path; use fromFile for a path. The file is decoded with
discord_luau/symphonia, which finds the format from the content alone:
- decoded to PCM and encoded to Opus as it plays: MP1, MP2 and MP3; AAC-LC, mono or stereo; FLAC; Vorbis; PCM; ADPCM, mono or stereo; ALAC; in WAV, AIFF, CAF, Ogg, Matroska, WebM, MP4 and M4A, or as a bare MP3, FLAC or ADTS stream. PCM, FLAC, Vorbis and ALAC in any channel layout are mixed to stereo; AAC and ADPCM with more than two channels fail to open. HE-AAC that states its SBR fails to open, and HE-AAC that signals it only inside the stream plays only its band-limited LC core;
- passed through as it is stored: single-stream Opus in Ogg, WebM, Matroska or MP4, without the pre-skip (about 6.5 ms), the end trimming (up to one packet) or the OpusHead output gain that an Opus decoder would apply.
The first file source loads libsymphonia, which always yields, may download it, and checks it, which blocks for
about 360 ms; call audio.loadFileDecoder() at startup to take that cost early. Opening and each read block on
native code.
Raises could not open audio: <message> for a file that cannot be opened, and
audio.fromEncoded takes the file's bytes as a buffer or string; for a path use audio.fromFile for anything else.
A file that fails to decode partway fails its playback with audio source failed: could not decode audio: <message>.
The source holds a native decoder session from the moment it is built: a source not handed to a playAsync that
starts must be released with source:destroy().
options.bitrate is as for fromPCM, and applies to the audio that is encoded; a bad one raises before the file
is decoded. The named sources take the same option.
options.volume (1) is a number from 0 to 4, checked at the call, and sets the volume of the decoded audio; see
source:setVolume. The named sources take it too.
Parameters
| bytes: buffer | string | |
| options: FileOptions? |
Returns
| Source |
fromMP3
Section titled “fromMP3”Source . fromMP3 ( bytes , options ) -> Source
Plays an MP3 file held in memory, as a buffer or a string of its bytes; MP1 and MP2 play too. An alias of
fromEncoded, whose rules apply: the format is found from the content, so any supported file plays here too, and a
source not handed to a playAsync that starts must be released with source:destroy().
Parameters
| bytes: buffer | string | |
| options: NamedOptions? |
Returns
| Source |
fromOgg
Section titled “fromOgg”Source . fromOgg ( bytes , options ) -> Source
Plays an Ogg file held in memory, as a buffer or a string of its bytes: Vorbis and FLAC are decoded, and Opus, an
.opus file, is passed through. An alias of fromEncoded, whose rules apply: the format is found from the
content, so any supported file plays here too, and a source not handed to a playAsync that starts must be
released with source:destroy().
Parameters
| bytes: buffer | string | |
| options: NamedOptions? |
Returns
| Source |
fromWAV
Section titled “fromWAV”Source . fromWAV ( bytes , options ) -> Source
Plays a WAV file held in memory, as a buffer or a string of its bytes: PCM, or ADPCM in mono or stereo. An alias of
fromEncoded, whose rules apply: the format is found from the content, so any supported file plays here too, and a
source not handed to a playAsync that starts must be released with source:destroy().
Parameters
| bytes: buffer | string | |
| options: NamedOptions? |
Returns
| Source |
fromFLAC
Section titled “fromFLAC”Source . fromFLAC ( bytes , options ) -> Source
Plays a FLAC file held in memory, as a buffer or a string of its bytes. An alias of fromEncoded, whose rules
apply: the format is found from the content, so any supported file plays here too, and a source not handed to a
playAsync that starts must be released with source:destroy().
Parameters
| bytes: buffer | string | |
| options: NamedOptions? |
Returns
| Source |
fromAAC
Section titled “fromAAC”Source . fromAAC ( bytes , options ) -> Source
Plays an ADTS AAC file (.aac) held in memory, as a buffer or a string of its bytes: AAC-LC, mono or stereo.
HE-AAC that states its SBR fails to open, and HE-AAC that signals it only inside the stream, as most ADTS files do,
plays only its band-limited LC core. An alias of fromEncoded, whose rules apply: the format is found from the
content, so any supported file plays here too, and a source not handed to a playAsync that starts must be
released with source:destroy().
Parameters
| bytes: buffer | string | |
| options: NamedOptions? |
Returns
| Source |
fromM4A
Section titled “fromM4A”Source . fromM4A ( bytes , options ) -> Source
Plays an M4A or MP4 file held in memory, as a buffer or a string of its bytes: AAC-LC in mono or stereo and ALAC are
decoded, and Opus is passed through. A 5.1 AAC track, common in video files, fails to open. An alias of
fromEncoded, whose rules apply: the format is found from the content, so any supported file plays here too, and a
source not handed to a playAsync that starts must be released with source:destroy().
Parameters
| bytes: buffer | string | |
| options: NamedOptions? |
Returns
| Source |
fromWebM
Section titled “fromWebM”Source . fromWebM ( bytes , options ) -> Source
Plays a WebM file held in memory, as a buffer or a string of its bytes: Opus is passed through, and Vorbis is
decoded. An alias of fromEncoded, whose rules apply: the format is found from the content, so any supported file
plays here too, and a source not handed to a playAsync that starts must be released with source:destroy().
Parameters
| bytes: buffer | string | |
| options: NamedOptions? |
Returns
| Source |
fromMKV
Section titled “fromMKV”Source . fromMKV ( bytes , options ) -> Source
Plays a Matroska file (.mkv or .mka) held in memory, as a buffer or a string of its bytes: Opus is passed
through, and the other supported codecs are decoded. An alias of fromEncoded, whose rules apply: the format is
found from the content, so any supported file plays here too, and a source not handed to a playAsync that starts
must be released with source:destroy().
Parameters
| bytes: buffer | string | |
| options: NamedOptions? |
Returns
| Source |
fromAIFF
Section titled “fromAIFF”Source . fromAIFF ( bytes , options ) -> Source
Plays an AIFF file held in memory, as a buffer or a string of its bytes. An alias of fromEncoded, whose rules
apply: the format is found from the content, so any supported file plays here too, and a source not handed to a
playAsync that starts must be released with source:destroy().
Parameters
| bytes: buffer | string | |
| options: NamedOptions? |
Returns
| Source |
fromCAF
Section titled “fromCAF”Source . fromCAF ( bytes , options ) -> Source
Plays a Core Audio Format file (.caf) held in memory, as a buffer or a string of its bytes. An alias of
fromEncoded, whose rules apply: the format is found from the content, so any supported file plays here too, and a
source not handed to a playAsync that starts must be released with source:destroy().
Parameters
| bytes: buffer | string | |
| options: NamedOptions? |
Returns
| Source |
fromStream
Section titled “fromStream”Source . fromStream ( read , options ) -> Source
Plays an audio file that arrives in pieces, such as internet radio or a program's output.
read returns the next chunk of the file's bytes, as a buffer or a string of any length, and nil at the end; it may
yield for as long as the next bytes take. The formats are those of fromEncoded. An MP4 or M4A with its index at
the end, and a CAF, cannot be read from the front: once the stream fails to open on one, the rest of the file is
read and the whole file plays, with memory up to about 4 times its size, and a file over 128 MiB fails with
could not open audio: this file must be complete before it plays and is larger than 128 MiB.
This call only checks and records the options: it never calls read, yields, opens a decoder session or starts a
thread. The player's first poll does that, inside playAsync, so a source that is never played holds nothing.
From then on a pump thread calls read and pushes the bytes, in slices of at most 64 KiB, into a libsymphonia
stream session, and decoded audio is read ahead up to 6 seconds. The playback sends Speaking once
options.bufferSeconds of audio is buffered; when the stream stalls, it sends its silence tail and Speaking 0,
waits, and speaks again on a fresh schedule once bufferSeconds is buffered again. player:stop() and
source:destroy() end those waits.
The playback fails with audio source failed: <reason>, where the reason is audio stream read failed: <error>
for a read that raises, could not open audio: <message> for bytes that are not audio,
could not open audio: the stream ended before any audio for a stream that gives no bytes,
could not decode audio: <message> for one that fails partway, and
invalid Opus packet from the audio source: <error> for an Opus stream with a bad packet. A failure before the
stream opened ends the playback at once, without Speaking. One after it plays the audio that came before it first:
what was queued, and for a read that raises, every byte it had already returned. A stream that ends early, such
as a WAV followed by other bytes, stops being read and plays the audio it decoded.
Options, each checked at the call:
volume(1), a number from 0 to 4, as forfromEncoded;source:setVolumechanges it mid-song, heard at the next packet;bitrate(128000), as forfromPCM;bufferSeconds(1), a number from 0.1 to 4: how much audio to buffer before playing, and again after a stall;logLevel, the level of the stream session's logger,SymphoniaDecoder.
Raises audio.fromStream takes a function that returns the next chunk of the file's bytes when read is not a
function, bufferSeconds must be a number from 0.1 to 4, and the volume, bitrate and log level messages.
Parameters
| read: StreamReader | |
| options: StreamOptions? |
Returns
| Source |
fromProcess
Section titled “fromProcess”Source . fromProcess ( command , arguments , options ) -> Source
Plays what a program writes to its stdout, such as yt-dlp or ffmpeg writing a file to -. The program runs
directly, without a shell, with stderr kept for errors and stdin ignored. Otherwise it is a stream source, as for
fromStream: the same formats, buffering, stalls, options and failures, read from the program's output, which is
joined into chunks of up to 64 KiB.
Nothing starts at this call. The player's first poll starts the program; one that cannot start fails the playback
with audio source failed: could not start <command>: <reason>, such as FileNotFound, without Speaking.
Once the program's stdout ends, the stream waits up to 5 seconds for its exit code, and the playback has not ended
until it is known. A non-zero code wins over every other outcome, a failure to open or decode and the clean end:
audio source failed: <command> exited with code <n>: <last stderr line>, or no error output. A program that does
not exit within 5 seconds is killed, with a warning, and the stream ends normally. The program is also killed when
the stream stops reading it while its stdout is still open, such as after bytes that are not audio or a failure met
while the stream waits for the program to write, and when the source is destroyed; a code that follows such a kill
is ignored.
Raises audio.fromProcess takes the program to run as a string and
audio.fromProcess takes the program's arguments as an array of strings for anything else, and the option messages
of fromStream.
Parameters
| command: string | |
| arguments: { string }? | |
| options: StreamOptions? |
Returns
| Source |