Skip to content

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

Source.format  :: "opus" | "pcm"

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.

Source.items  :: { buffer }?

The input array; a whole PCM buffer is stored as a one-item array. Nil when the input is a function.

Source.iterator  :: (() -> buffer?)?

The input function, which returns the next buffer and nil at the end. Nil when the input is an array.

Source.itemCount  :: number

How many input items the source has taken.

Source.inputEnded  :: boolean

Whether the input gave nil, so an input function is never called again after its end.

Source.chunk  :: buffer?

The PCM input buffer being read.

Source.chunkOffset  :: number

How many bytes of chunk are used.

Source.frame  :: buffer?

The reusable 3840-byte PCM frame, created on the first PCM read.

Source.frameFill  :: number

How many bytes frame holds.

Source.totalBytes  :: number

The PCM bytes taken so far.

Source.encoder  :: Encoder?

The Opus encoder of a PCM source, created on its first frame and freed when the source ends or is destroyed.

Source.bitrate  :: number

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.

Source.volume  :: number

The volume of a file or stream source's decoded audio, from 0 to 4; options.volume, or 1. Other sources hold 1.

Source.decoder  :: Decoder?

A file source's decoder, which setVolume reaches. Nil for every other source; a stream source's session belongs to its feed.

Source.feed  :: StreamFeed?

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.

Source.starved  :: boolean

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.

Source.failure  :: string?

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.

Source.bufferSeconds  :: number

How many seconds of audio a stream source buffers before it stops being starved; options.bufferSeconds, or 1. Every other source holds 0.

Source.limiter  :: 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.

Source.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.

Source.played  :: boolean

Whether a player has claimed the source.

Source.ended  :: boolean

Whether the source has ended or been destroyed; read returns nil from then on.

Source.logger  :: Logger

The source's logger, named AudioSource and at Info; lower its level to see the debug lines.

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.

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.

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.

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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 for fromEncoded; source:setVolume changes it mid-song, heard at the next packet;
  • bitrate (128000), as for fromPCM;
  • 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
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