bundle.discord.cogs.music.source¶
Buffered Opus audio source with jitter buffer for stable playback.
Discord.py’s AudioPlayer loop calls source.read() every 20 ms and
sends the resulting frame over UDP. When the source is an FFmpeg process
streaming from a remote URL, read() may block unpredictably due to
network latency or CDN throttling. When a read stalls, the AudioPlayer
compensates by sending subsequent frames faster to “catch up”, producing
audible speed-up / slow-down artefacts (see discord.py #9120).
This module solves the problem by inserting a jitter buffer between FFmpeg and the AudioPlayer:
[YouTube URL] ──▶ FFmpeg ──▶ _fill() thread ──▶ Queue ──▶ read() ──▶ AudioPlayer
A background thread reads Opus frames from FFmpeg into a bounded queue as fast as FFmpeg produces them.
read()pops from the queue, which is near-instant as long as the buffer has frames — so the AudioPlayer’s 20 ms cadence is never disrupted.Before playback starts,
make_source()blocks until the queue has pre-filled PREFILL_FRAMES (~1 s), eliminating the initial burst that causes the first-seconds speed-up.
FFmpeg flags¶
- before_options (input side):
- -reconnect / -reconnect_streamed / -reconnect_delay_max
Auto-reconnect on transient HTTP failures.
- -analyzeduration 0 -probesize 32768
Skip lengthy format probing — we already know it’s audio.
- -thread_queue_size 4096
Large input-thread packet queue inside FFmpeg itself.
- options (output side):
- -vn
Strip any video track.
-b:a 256k Opus bitrate (Discord supports up to 384 kbps). -bufsize 5M Encoder rate-control buffer. -application audio Use libopus MDCT mode optimised for music rather
than the default voice/low-delay mode.
Attributes¶
Classes¶
Jitter buffer that decouples FFmpeg pipe reads from the 20 ms playback loop. |
Functions¶
|
Create a buffered FFmpeg Opus source from a stream URL. |
Module Contents¶
- bundle.discord.cogs.music.source.log¶
- bundle.discord.cogs.music.source.FFMPEG_BEFORE_OPTIONS = '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5 -analyzeduration 0 -probesize 32768...¶
- bundle.discord.cogs.music.source.FFMPEG_OPTIONS = '-vn -b:a 256k -bufsize 5M -application audio'¶
- bundle.discord.cogs.music.source.JITTER_BUFFER_SIZE = 250¶
- bundle.discord.cogs.music.source.PREFILL_FRAMES = 250¶
- class bundle.discord.cogs.music.source.BufferedOpusAudio(source: discord.FFmpegOpusAudio)¶
Bases:
discord.AudioSourceJitter buffer that decouples FFmpeg pipe reads from the 20 ms playback loop.
Wraps any
discord.FFmpegOpusAudiosource with a bounded frame queue. A daemon thread (_fill) continuously reads Opus frames from FFmpeg and enqueues them. Theread()method — called by discord.py’s AudioPlayer every 20 ms — simply dequeues the next frame, returning near-instantly regardless of upstream network conditions.Lifecycle:
source = BufferedOpusAudio(ffmpeg_opus_source) source.wait_ready() # block until PREFILL_FRAMES are buffered vc.play(source, ...) # hand off to discord.py ... source.cleanup() # signals the reader thread and cleans up FFmpeg
- Parameters:
source – An already-constructed
discord.FFmpegOpusAudioinstance.
- wait_ready(timeout: float = 10.0) bool¶
Block until the pre-fill target is reached. Returns True if ready.
- bundle.discord.cogs.music.source.make_source(stream_url: str) BufferedOpusAudio¶
Create a buffered FFmpeg Opus source from a stream URL.
Blocks until the jitter buffer has pre-filled (~1 s of audio) so playback starts smoothly without initial speed fluctuations.