bundle.discord.cogs.music.source ================================ .. py:module:: bundle.discord.cogs.music.source .. autoapi-nested-parse:: 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 ---------- .. autoapisummary:: bundle.discord.cogs.music.source.log bundle.discord.cogs.music.source.FFMPEG_BEFORE_OPTIONS bundle.discord.cogs.music.source.FFMPEG_OPTIONS bundle.discord.cogs.music.source.JITTER_BUFFER_SIZE bundle.discord.cogs.music.source.PREFILL_FRAMES Classes ------- .. autoapisummary:: bundle.discord.cogs.music.source.BufferedOpusAudio Functions --------- .. autoapisummary:: bundle.discord.cogs.music.source.make_source Module Contents --------------- .. py:data:: log .. py:data:: FFMPEG_BEFORE_OPTIONS :value: '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5 -analyzeduration 0 -probesize 32768... .. py:data:: FFMPEG_OPTIONS :value: '-vn -b:a 256k -bufsize 5M -application audio' .. py:data:: JITTER_BUFFER_SIZE :value: 250 .. py:data:: PREFILL_FRAMES :value: 250 .. py:class:: BufferedOpusAudio(source: discord.FFmpegOpusAudio) Bases: :py:obj:`discord.AudioSource` Jitter buffer that decouples FFmpeg pipe reads from the 20 ms playback loop. Wraps any ``discord.FFmpegOpusAudio`` source with a bounded frame queue. A daemon thread (``_fill``) continuously reads Opus frames from FFmpeg and enqueues them. The ``read()`` 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 :param source: An already-constructed ``discord.FFmpegOpusAudio`` instance. .. py:method:: wait_ready(timeout: float = 10.0) -> bool Block until the pre-fill target is reached. Returns True if ready. .. py:method:: read() -> bytes Return the next Opus frame from the buffer. Called by discord.py's AudioPlayer every ~20 ms. Returns an empty ``bytes`` when the stream has ended and the buffer is drained, which signals the AudioPlayer to stop and fire the ``after`` callback. .. py:method:: is_opus() -> bool Tell discord.py that frames are already Opus-encoded. .. py:method:: cleanup() -> None Signal the reader thread to stop and release FFmpeg resources. .. py:function:: 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.