### Minimal Voice Recording Example Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/README.md A basic example demonstrating how to connect to a voice channel and start recording audio to a WAV file using WaveSink. ```python from discord.ext import commands, voice_recv import discord bot = commands.Bot(intents=discord.Intents.all()) @bot.command() async def record(ctx): # Connect with voice receive support vc = await ctx.author.voice.channel.connect(cls=voice_recv.VoiceRecvClient) # Record to file sink = voice_recv.WaveSink("output.wav") vc.listen(sink) ``` -------------------------------- ### Minimal Bot Setup Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/INDEX.md Set up a basic Discord bot with commands to join a voice channel, start recording, and stop recording. ```python from discord.ext import commands, voice_recv import discord bot = commands.Bot(intents=discord.Intents.all()) @bot.command() async def join(ctx): vc = await ctx.author.voice.channel.connect(cls=voice_recv.VoiceRecvClient) @bot.command() async def record(ctx): sink = voice_recv.WaveSink("output.wav") ctx.voice_client.listen(sink) @bot.command() async def stop(ctx): ctx.voice_client.stop_listening()) ``` -------------------------------- ### Install discord-ext-voice-recv Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/12-quick-start.md Install the library using pip. Extras can be installed for speech recognition or local playback. ```bash pip install discord-ext-voice-recv ``` ```bash pip install discord-ext-voice-recv[extras] # Or specific: pip install discord-ext-voice-recv[extras_speech] pip install discord-ext-voice-recv[extras_local] ``` -------------------------------- ### TimedFilter Example (Start Immediately) Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/3-builtin-sinks.md Use TimedFilter to record audio for a specified duration, starting immediately upon initialization. The duration is set in seconds. ```python from discord.ext import voice_recv # Record for 5 seconds starting immediately sink = voice_recv.WaveSink("5sec.wav") timed = voice_recv.TimedFilter(sink, duration=5.0, start_on_init=True) voice_client.listen(timed) ``` -------------------------------- ### Install discord-ext-voice-recv from GitHub Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/README.md Install the package directly from its GitHub repository using pip. ```bash python -m pip install git+https://github.com/imayhaveborkedit/discord-ext-voice-recv ``` -------------------------------- ### Example: Using PCMVolumeTransformer Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/3-builtin-sinks.md Demonstrates how to initialize PCMVolumeTransformer to double audio volume and how to change the volume dynamically. ```python from discord.ext import voice_recv # Double the volume wav_sink = voice_recv.WaveSink("output.wav") volume = voice_recv.PCMVolumeTransformer(wav_sink, volume=2.0) voice_client.listen(volume) # Change volume dynamically volume.volume = 0.5 # Set to 50% ``` -------------------------------- ### Full Voice Event Tracking Example Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/11-events.md An example demonstrating how to combine synchronous sink-only listeners for speaking start/stop and member disconnects with asynchronous bot events for member connect, video toggles, and platform changes. It includes a command to start tracking voice events. ```python from discord.ext import voice_recv, commands import discord bot = commands.Bot(intents=discord.Intents.all()) class EventTracker(voice_recv.AudioSink): """Sink that tracks multiple voice events""" def wants_opus(self) -> bool: return False def write(self, user, data): pass def cleanup(self): pass # Sync listeners (sink-only) @voice_recv.AudioSink.listener() def on_voice_member_speaking_start(self, member): print(f"[SINK] {member.name} started speaking") @voice_recv.AudioSink.listener() def on_voice_member_speaking_stop(self, member): print(f"[SINK] {member.name} stopped speaking") @voice_recv.AudioSink.listener() def on_voice_member_disconnect(self, member, ssrc): print(f"[SINK] {member.name} disconnected (SSRC {ssrc})") # Async listeners (via bot.event or add_listener) @bot.event async def on_voice_member_connect(member): print(f"[ASYNC] {member.name} joined voice") @bot.event async def on_voice_member_video(member, data): print(f"[ASYNC] {member.name} toggled video") @bot.event async def on_voice_member_platform(member, platform): print(f"[ASYNC] {member.name} on {platform.name if platform else 'unknown'}") @bot.command() async def start_tracking(ctx): vc = await ctx.author.voice.channel.connect(cls=voice_recv.VoiceRecvClient) sink = EventTracker() vc.listen(sink) await ctx.send("Voice event tracking started") ``` -------------------------------- ### Start Receiving Audio with BasicSink Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/1-voice-client.md Starts receiving audio into an AudioSink. Use BasicSink for simple callback-based audio reception. The after callback is invoked when listening stops. ```python from discord.ext import voice_recv # Basic usage with a callback sink def write_callback(user, data: voice_recv.VoiceData): print(f"Received audio from {user}") sink = voice_recv.BasicSink(write_callback) voice_client.listen(sink, after=lambda exc: print(f"Stopped: {exc}")) ``` -------------------------------- ### Install discord-ext-voice-recv Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/README.md Install the package using pip. Python 3.8 or higher is required. ```bash python -m pip install discord-ext-voice-recv ``` -------------------------------- ### Install discord-ext-voice-recv Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/README.md Install the library using pip. Optional dependencies can be included for extra features like speech recognition or local playback. ```bash pip install discord-ext-voice-recv ``` ```bash pip install discord-ext-voice-recv[extras] pip install discord-ext-voice-recv[extras_speech] pip install discord-ext-voice-recv[extras_local] ``` -------------------------------- ### TimedFilter Example (Start on First Packet) Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/3-builtin-sinks.md Use TimedFilter to record audio for a specified duration, starting when the first audio packet is received. The duration is set in seconds. ```python from discord.ext import voice_recv # Record for 10 seconds starting on first packet sink = voice_recv.WaveSink("10sec.wav") timed = voice_recv.TimedFilter(sink, duration=10.0, start_on_init=False) voice_client.listen(timed) ``` -------------------------------- ### Sink Pipeline Example Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/2-audio-sink.md Demonstrates how to create a chain of audio sinks for processing. ```APIDOC ## Sink Pipeline Example ```python from discord.ext import voice_recv # Create a chain: volume control -> user filter -> wav file wav_sink = voice_recv.WaveSink("output.wav") user_filter = voice_recv.UserFilter(wav_sink, target_user) volume_control = voice_recv.PCMVolumeTransformer(user_filter, volume=2.0) voice_client.listen(volume_control) ``` ``` -------------------------------- ### Example: Initializing LocalPlaybackSink and Registering Cleanup Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/9-extras-playback.md Demonstrates how to initialize a LocalPlaybackSink and ensure that the PyAudio instance is properly cleaned up upon program exit using atexit.register. ```python import atexit from discord.ext import voice_recv sink = voice_recv.extras.localplayback.LocalPlaybackSink() # Ensure cleanup on exit atexit.register(voice_recv.extras.localplayback.LocalPlaybackSink.terminate_pyaudio) ``` -------------------------------- ### Basic Speech-to-Text Integration Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/8-extras-speech.md Example of starting voice transcription in a Discord bot. It connects to a voice channel, sets up a SpeechRecognitionSink with a default Google recognizer, and listens for audio. ```python from discord.ext import voice_recv, commands import discord bot = commands.Bot(intents=discord.Intents.all()) @bot.command() async def start_transcription(ctx): """Start transcribing voice to text""" vc = await ctx.author.voice.channel.connect(cls=voice_recv.VoiceRecvClient) # Simple text callback def on_text(user, text): print(f"{user.display_name}: {text}") sink = voice_recv.extras.speechrecognition.SpeechRecognitionSink( text_cb=on_text, default_recognizer='google', phrase_time_limit=15 ) vc.listen(sink) await ctx.send("Transcription started") ``` -------------------------------- ### listen() Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/1-voice-client.md Starts receiving audio into a provided AudioSink. This method is essential for capturing and processing incoming audio streams from a voice channel. ```APIDOC ## listen() ### Description Start receiving audio into an AudioSink. ### Method `def listen(sink: AudioSink, *, after: Optional[AfterCB] = None) -> None` ### Parameters #### Path Parameters - **sink** (AudioSink) - Required - The sink to receive audio into - **after** (Optional[Callable[[Optional[Exception]], Any]]) - Optional - Callback invoked after listening stops, receives optional exception ### Response #### Success Response (None) ### Raises: - `discord.ClientException` — Not connected to voice - `TypeError` — sink is not an AudioSink instance - `discord.ClientException` — Already receiving audio ### Example: ```python from discord.ext import voice_recv # Basic usage with a callback sink def write_callback(user, data: voice_recv.VoiceData): print(f"Received audio from {user}") sink = voice_recv.BasicSink(write_callback) voice_client.listen(sink, after=lambda exc: print(f"Stopped: {exc}")) ``` ``` -------------------------------- ### UserFilter Example Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/3-builtin-sinks.md Use UserFilter to record audio from a specific user. Ensure the WaveSink is initialized before creating the filter. ```python from discord.ext import voice_recv # Record only one user target_user = ctx.author sink = voice_recv.WaveSink("single_user.wav") filtered = voice_recv.UserFilter(sink, target_user) voice_client.listen(filtered) ``` -------------------------------- ### AdvancedAudioSink Example Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/7-rtp-packets.md An example implementation of an AudioSink that processes incoming voice data, extracts packet information, checks for silence, handles extensions like audio power, and processes PCM audio. It also demonstrates how to handle RTCP packets, including SenderReportPacket and ReceiverReportPacket, using the @voice_recv.AudioSink.listener() decorator. ```python from discord.ext import voice_recv class AdvancedAudioSink(voice_recv.AudioSink): def wants_opus(self) -> bool: return False def write(self, user, data: voice_recv.VoiceData): packet = data.packet # Extract packet info print(f"From {user}: SSRC={packet.ssrc}, Seq={packet.sequence}") # Check for silence if packet.is_silence(): print("Silence frame received") return # Extract extensions if voice_recv.ExtensionID.audio_power in packet.extension_data: power_bytes = packet.extension_data[voice_recv.ExtensionID.audio_power] print(f"Audio power: {power_bytes.hex()}") # Process PCM print(f"PCM audio: {len(data.pcm)} bytes") @voice_recv.AudioSink.listener() def on_rtcp_packet(self, packet: voice_recv.rtp.RTCPPacket, guild): """Handle RTCP control packets""" if isinstance(packet, voice_recv.rtp.SenderReportPacket): print(f"Sender report from SSRC {packet.ssrc}") print(f" Packets sent: {packet.info.packet_count}") print(f" Bytes sent: {packet.info.octet_count}") for report in packet.reports: print(f" Report for SSRC {report.ssrc}:") print(f" Loss: {report.perc_loss}%") print(f" Total lost: {report.total_lost}") print(f" Jitter: {report.jitter}") elif isinstance(packet, voice_recv.rtp.ReceiverReportPacket): print(f"Receiver report from SSRC {packet.ssrc}") for report in packet.reports: print(f" Report for SSRC {report.ssrc}: {report.total_lost} packets lost") def cleanup(self): pass ``` -------------------------------- ### Complete Discord Bot with Voice Recording Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/12-quick-start.md This is a full example of a Discord bot using discord.py and the voice receive extension. It includes commands to join a voice channel, start and stop recording, and basic event handling. ```python from discord.ext import commands, voice_recv import discord bot = commands.Bot(command_prefix='!', intents=discord.Intents.all()) @bot.event async def on_ready(): print(f"Logged in as {bot.user}") @bot.command() async def join(ctx): """Join voice channel""" if not ctx.author.voice: return await ctx.send("Join a voice channel first") channel = ctx.author.voice.channel vc = await channel.connect(cls=voice_recv.VoiceRecvClient) await ctx.send(f"Joined {channel.mention}") @bot.command() async def record(ctx): """Start recording""" vc = ctx.voice_client if not vc: return await ctx.send("Not in voice") sink = voice_recv.WaveSink("recording.wav") vc.listen(sink) await ctx.send("Recording started") @bot.command() async def stop(ctx): """Stop recording and disconnect""" vc = ctx.voice_client if not vc: return vc.stop_listening() await vc.disconnect() await ctx.send("Stopped and disconnected") @bot.event async def on_voice_member_connect(member): print(f"{member.name} joined voice") bot.run("YOUR_TOKEN") ``` -------------------------------- ### Example: Using ConditionalFilter Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/3-builtin-sinks.md Shows how to create a ConditionalFilter that only passes audio packets where the PCM data exceeds 1000 bytes. ```python from discord.ext import voice_recv def filter_loud_audio(user, data): # Only pass audio above 1000 bytes return len(data.pcm) > 1000 sink = voice_recv.WaveSink("loud_only.wav") filtered = voice_recv.ConditionalFilter(sink, filter_loud_audio) voice_client.listen(filtered) ``` -------------------------------- ### Speech Recognition Service Configuration Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/8-extras-speech.md Examples of how to configure the SpeechRecognitionSink with different services. ```APIDOC ## Service Notes ### Google (free, no auth) ```python sink = voice_recv.extras.speechrecognition.SpeechRecognitionSink( default_recognizer='google' ) ``` No authentication needed. May rate-limit on high-volume usage. --- ### OpenAI Whisper (requires API key) ```python sink = voice_recv.extras.speechrecognition.SpeechRecognitionSink( default_recognizer='openai' ) ``` Requires `OPENAI_API_KEY` environment variable. --- ### Local Whisper (requires package) ```python # Install: pip install openai-whisper sink = voice_recv.extras.speechrecognition.SpeechRecognitionSink( default_recognizer='whisper' ) ``` Runs locally, no authentication needed, higher latency. --- ### Sphinx (offline, low accuracy) ```python sink = voice_recv.extras.speechrecognition.SpeechRecognitionSink( default_recognizer='sphinx' ) ``` No internet required. Lower accuracy, best for English. --- ``` -------------------------------- ### Example Usage of VoicePlatform Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/6-enums.md Demonstrates how to check a member's connection platform and handle unknown platforms. This function is called when a member joins or their platform changes. ```python from discord.ext import voice_recv async def on_voice_member_platform(member: discord.Member, platform: voice_recv.VoicePlatform): """Called when member joins or platform changes""" if platform is None: print(f"{member} platform unknown") elif platform == voice_recv.VoicePlatform.desktop: print(f"{member} is on desktop") elif platform == voice_recv.VoicePlatform.mobile: print(f"{member} is on mobile") elif platform == voice_recv.VoicePlatform.xbox: print(f"{member} is on Xbox") elif platform == voice_recv.VoicePlatform.playstation: print(f"{member} is on PlayStation") # Or use value platform_names = {0: 'desktop', 1: 'mobile', 2: 'xbox', 3: 'playstation'} if platform: print(f"Platform: {platform_names.get(platform.value, 'unknown')}") # As a sink listener class PlatformTracker(voice_recv.AudioSink): def wants_opus(self) -> bool: return False def write(self, user, data): pass def cleanup(self): pass @voice_recv.AudioSink.listener() def on_voice_member_platform(self, member, platform): """Track which platform members are using""" if platform: self.platform_stats[platform.value] = self.platform_stats.get(platform.value, 0) + 1 ``` -------------------------------- ### Basic Local Audio Playback Example Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/9-extras-playback.md Demonstrates how to connect to a voice channel and play all incoming audio streams through the local speakers using LocalPlaybackSink. Ensure to stop listening and terminate PyAudio when done. ```python from discord.ext import voice_recv, commands import discord bot = commands.Bot(intents=discord.Intents.all()) @bot.command() async def listen_local(ctx): """Play voice channel audio through speakers""" vc = await ctx.author.voice.channel.connect(cls=voice_recv.VoiceRecvClient) # Play all users sink = voice_recv.extras.localplayback.LocalPlaybackSink() vc.listen(sink) await ctx.send("Playing audio to speakers") @bot.command() async def stop_playback(ctx): """Stop playback""" vc = ctx.voice_client vc.stop_listening() # Important: cleanup PyAudio voice_recv.extras.localplayback.LocalPlaybackSink.terminate_pyaudio() await vc.disconnect() ``` -------------------------------- ### Record Audio with Volume Boost Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/12-quick-start.md Chain sinks to record audio with a volume boost. This example uses a PCMVolumeTransformer to double the volume before saving to a WAV file. ```Python @bot.command() async def record_loud(ctx): """Record with volume boost""" vc = await ctx.author.voice.channel.connect(cls=voice_recv.VoiceRecvClient) # Chain: volume doubler -> wav file wav_sink = voice_recv.WaveSink("loud_output.wav") volume = voice_recv.PCMVolumeTransformer(wav_sink, volume=2.0) vc.listen(volume) await ctx.send("Recording (2x volume)") ``` -------------------------------- ### Voice Channel Listening with Shutdown Cleanup Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/9-extras-playback.md Demonstrates setting up a voice channel listener using LocalPlaybackSink and registering a cleanup function to terminate PyAudio instances on bot shutdown. Ensure PyAudio is installed. ```python from discord.ext import voice_recv, commands import discord import atexit bot = commands.Bot(intents=discord.Intents.all()) # Register cleanup on bot shutdown def cleanup_audio(): voice_recv.extras.localplayback.LocalPlaybackSink.terminate_pyaudio() voice_recv.extras.localplayback.SimpleLocalPlaybackSink.terminate_pyaudio() atexit.register(cleanup_audio) @bot.command() async def listen(ctx): vc = await ctx.author.voice.channel.connect(cls=voice_recv.VoiceRecvClient) sink = voice_recv.extras.localplayback.LocalPlaybackSink() vc.listen(sink) await ctx.send("Listening...") bot.run("token") ``` -------------------------------- ### Create Audio Sink Pipeline Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/2-audio-sink.md Construct a pipeline of audio sinks for processing, starting from a WaveSink and adding a UserFilter and PCMVolumeTransformer. This demonstrates how to chain sinks for different audio manipulations. ```python from discord.ext import voice_recv # Create a chain: volume control -> user filter -> wav file wav_sink = voice_recv.WaveSink("output.wav") user_filter = voice_recv.UserFilter(wav_sink, target_user) volume_control = voice_recv.PCMVolumeTransformer(user_filter, volume=2.0) voice_client.listen(volume_control) ``` -------------------------------- ### Audio Power Extension Example Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/6-enums.md Processes the audio power level extension data from a voice packet. Calculates the volume level from raw byte data. ```python def on_audio(user, data: voice_recv.VoiceData): if voice_recv.ExtensionID.audio_power in data.packet.extension_data: raw = data.packet.extension_data[voice_recv.ExtensionID.audio_power] power = int.from_bytes(raw, 'big') volume_level = 127 - (power & 127) print(f"Volume: {volume_level}/127") ``` -------------------------------- ### Custom Audio Sink Implementation Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/2-audio-sink.md Example of creating a custom AudioSink by subclassing the base class and implementing abstract methods. This sink does not want Opus data and prints the user for each audio chunk. ```python from discord.ext import voice_recv # Create a custom sink class MyAudioSink(voice_recv.AudioSink): def __init__(self): super().__init__() def wants_opus(self) -> bool: return False def write(self, user, data): print(f"Audio from {user}") def cleanup(self): pass ``` -------------------------------- ### Record for Limited Time Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/12-quick-start.md Connects to a voice channel and records audio for a specified duration, saving it to a WAV file. The recording starts automatically upon initialization. ```python vc = await ctx.author.voice.channel.connect(cls=voice_recv.VoiceRecvClient) wav_sink = voice_recv.WaveSink("timed_recording.wav") timed = voice_recv.TimedFilter(wav_sink, duration=seconds, start_on_init=True) vc.listen(timed) await ctx.send(f"Recording for {seconds} seconds") ``` -------------------------------- ### Process Voice with FFmpeg Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/INDEX.md Use `FFmpegSink` to process voice data through FFmpeg, for example, to save as an MP3 file. ```python FFmpegSink(filename="output.mp3", ...) ``` -------------------------------- ### Listen to Voice Events with EventSink Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/12-quick-start.md Implement an AudioSink to track when members start or stop speaking, or disconnect from voice. This is useful for real-time voice activity monitoring. ```Python class EventSink(voice_recv.AudioSink): """Sink that tracks member voice activity""" def wants_opus(self) -> bool: return False def write(self, user, data): pass # Ignore audio data def cleanup(self): pass @voice_recv.AudioSink.listener() def on_voice_member_speaking_start(self, member): """Member started speaking (green circle)""" print(f"{member} is speaking") @voice_recv.AudioSink.listener() def on_voice_member_speaking_stop(self, member): """Member stopped speaking""" print(f"{member} stopped speaking") @voice_recv.AudioSink.listener() def on_voice_member_disconnect(self, member, ssrc): """Member left voice""" print(f"{member} left (SSRC {ssrc})") @bot.command() async def track_activity(ctx): vc = await ctx.author.voice.channel.connect(cls=voice_recv.VoiceRecvClient) sink = EventSink() vc.listen(sink) await ctx.send("Tracking voice activity") ``` -------------------------------- ### Handle on_voice_member_video Event Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/5-video-and-streams.md Example of how to use the on_voice_member_video event to process video stream data. This can be done directly within a bot event or as a listener within a custom AudioSink. ```python from discord.ext import voice_recv, commands import discord bot = commands.Bot(intents=discord.Intents.all()) @bot.event async def on_voice_member_video(member: discord.Member, data: voice_recv.VoiceVideoStreams): """Called when a member toggles video on/off""" print(f"{member.name} video event") print(f" Audio SSRC: {data.audio_ssrc}") print(f" Video SSRC: {data.video_ssrc}") # Check active streams for stream in data.streams: status = "active" if stream.active else "inactive" resolution = f"{stream.max_resolution.width}x{stream.max_resolution.height}" print(f" Stream {stream.rid}: {status}, {resolution}, {stream.max_framerate}fps") # Or as a sink listener class VideoTrackingSink(voice_recv.AudioSink): def wants_opus(self) -> bool: return False def write(self, user, data): pass def cleanup(self): pass @voice_recv.AudioSink.listener() def on_voice_member_video(self, member, streams): """Called from sink's event processing thread""" for stream in streams.streams: if stream.active: print(f"{member.name} is streaming at {stream.max_framerate}fps") ``` -------------------------------- ### Handle Voice Member Connect Event Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/11-events.md An example of how to implement the on_voice_member_connect event handler in a bot. This snippet prints a message indicating which member joined the voice channel when the event occurs. ```python @bot.event async def on_voice_member_connect(member): print(f"{member} joined voice") ``` -------------------------------- ### Speaking State Extension Example Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/6-enums.md Processes the speaking state extension data from a voice packet. Prints the raw hexadecimal representation of the state data. ```python def on_audio(user, data: voice_recv.VoiceData): if voice_recv.ExtensionID.speaking_state in data.packet.extension_data: state_data = data.packet.extension_data[voice_recv.ExtensionID.speaking_state] print(f"Speaking state bytes: {state_data.hex()}") ``` -------------------------------- ### Example Usage of VoiceFlags Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/6-enums.md Demonstrates how to check individual voice flags and the raw value of a VoiceFlags object. This function is called when a member joins or their flags change. ```python from discord.ext import voice_recv async def on_voice_member_flags(member: discord.Member, flags: voice_recv.VoiceFlags): """Called when member joins or flags change""" if flags.clips_enabled: print(f"{member} has clips enabled") if flags.allow_voice_recording: print(f"{member} allows voice recording") if flags.allow_any_viewer_clips: print(f"{member} allows any viewer clips") # Check raw value print(f"Raw flags value: {flags.value}") # Iterate over set flags for flag_name in ['clips_enabled', 'allow_voice_recording', 'allow_any_viewer_clips']: if getattr(flags, flag_name): print(f" {flag_name} is enabled") ``` -------------------------------- ### Real-Time Transcription with Fallback Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/13-advanced-patterns.md Implement robust speech recognition by automatically falling back to a secondary service if the primary service fails. Ensure the necessary speech recognition libraries are installed for the chosen services. ```python from discord.ext import voice_recv class RobustTranscriber(voice_recv.extras.speechrecognition.SpeechRecognitionSink): """Transcribe with automatic fallback to secondary service""" def __init__(self, primary='google', secondary='sphinx'): def process_cb(recognizer, audio, user): try: # Try primary func = getattr(recognizer, f'recognize_{primary}') text = func(audio) return text except Exception as e: print(f"Primary ({primary}) failed: {e}") try: # Fallback func = getattr(recognizer, f'recognize_{secondary}') text = func(audio) print(f"Fallback ({secondary}) succeeded") return text except Exception as e2: print(f"Fallback ({secondary}) also failed: {e2}") return None super().__init__(process_cb=process_cb) ``` -------------------------------- ### SpeechRecognitionSink with Local Whisper Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/8-extras-speech.md Instantiate the SpeechRecognitionSink using the 'whisper' recognizer for local processing. Ensure the 'openai-whisper' package is installed. This method has higher latency but requires no authentication. ```python # Install: pip install openai-whisper sink = voice_recv.extras.speechrecognition.SpeechRecognitionSink( default_recognizer='whisper' ) ``` -------------------------------- ### Network-Aware Audio Recording Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/13-advanced-patterns.md Implement a custom AudioSink to adjust recording quality based on network packet loss. This example analyzes RTCP reports to dynamically set a quality level. ```python class NetworkAwareRecorder(voice_recv.AudioSink): """Adjust recording parameters based on packet loss""" def __init__(self): super().__init__() self.loss_samples = [] self.quality_level = 1 # 0=low, 1=medium, 2=high def wants_opus(self) -> bool: return False def write(self, user, data): # Analyze RTCP reports for loss pass @voice_recv.AudioSink.listener() def on_rtcp_packet(self, packet, guild): if isinstance(packet, voice_recv.rtp.SenderReportPacket): for report in packet.reports: loss_pct = report.perc_loss self.loss_samples.append(loss_pct) # Keep only recent samples if len(self.loss_samples) > 10: self.loss_samples.pop(0) # Adjust quality based on loss avg_loss = sum(self.loss_samples) / len(self.loss_samples) if avg_loss > 5: self.quality_level = 0 # Low quality elif avg_loss > 2: self.quality_level = 1 # Medium else: self.quality_level = 2 # High quality print(f"Network loss: {avg_loss:.1f}% -> Quality: {self.quality_level}") def cleanup(self): pass ``` -------------------------------- ### Get Associated Discord Client Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/2-audio-sink.md Retrieve the discord.Client instance. This is guaranteed to be non-None during the write() operation. ```python @property client -> Optional[discord.Client] ``` -------------------------------- ### AudioSink Constructor Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/2-audio-sink.md Initializes an AudioSink, optionally chaining it to a destination sink. ```python def __init__(self, destination: Optional[AudioSink] = None, /): pass ``` -------------------------------- ### AudioSink Constructor Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/2-audio-sink.md Initializes an AudioSink. It can optionally chain to another AudioSink. ```APIDOC ## AudioSink Constructor ### Description Initializes an AudioSink. It can optionally chain to another AudioSink. ### Method __init__ ### Parameters #### Path Parameters - **destination** (Optional[AudioSink]) - Optional - Optional child sink to chain to this sink ``` -------------------------------- ### BasicSink Initialization with Callbacks Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/3-builtin-sinks.md BasicSink provides a callback-based approach for custom audio handling. It accepts a main audio data callback and an optional RTCP packet callback. Set `decode=False` to receive raw Opus data instead of PCM. ```python class BasicSink(AudioSink): def __init__( self, event: Callable[[Optional[User], VoiceData], Any], *, rtcp_event: Optional[Callable[[RTCPPacket], Any]] = None, decode: bool = True, ) ``` ```python # event callback def my_write_cb(user: Optional[User], data: VoiceData) -> Any: pass # rtcp_event callback def my_rtcp_cb(packet: RTCPPacket) -> Any: pass ``` ```python from discord.ext import voice_recv def on_audio_data(user, data: voice_recv.VoiceData): if user: print(f"Audio from {user}: {len(data.pcm)} bytes") def on_rtcp(packet): print(f"RTCP packet: {packet.type}") sink = voice_recv.BasicSink( on_audio_data, rtcp_event=on_rtcp, decode=True ) voice_client.listen(sink) ``` -------------------------------- ### RTPPacket Data Property Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/7-rtp-packets.md Gets the raw, encrypted RTP payload data, usually containing Opus audio. ```python @property data: bytes ``` -------------------------------- ### Get Direct Child Audio Sink Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/2-audio-sink.md Access the immediate child sink. Returns None if there is no child sink. ```python @property child -> Optional[AudioSink] ``` -------------------------------- ### SpeechRecognitionSink Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/INDEX.md The SpeechRecognitionSink allows for real-time speech-to-text transcription of received audio using the SpeechRecognition library. It requires an extra installation. ```APIDOC ## SpeechRecognitionSink ### Description Provides real-time audio transcription using the SpeechRecognition library. ### Installation `pip install discord-ext-voice-recv[extras_speech]` ``` -------------------------------- ### MultiAudioSink Initialization Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/3-builtin-sinks.md Use MultiAudioSink to route audio to multiple destination sinks simultaneously. Initialize it with a sequence of AudioSink instances. ```python class MultiAudioSink(AudioSink): def __init__(self, destinations: Sequence[AudioSink], /) ``` ```python from discord.ext import voice_recv sink1 = voice_recv.WaveSink("output1.wav") sink2 = voice_recv.WaveSink("output2.wav") multi = voice_recv.MultiAudioSink([sink1, sink2]) voice_client.listen(multi) ``` -------------------------------- ### RTPPacket SSRC Property Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/7-rtp-packets.md Gets the 32-bit Synchronization Source (SSRC) identifier, which uniquely identifies the sender within a session. ```python @property ssrc: int ``` -------------------------------- ### Get Associated Voice Client Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/2-audio-sink.md Access the VoiceRecvClient instance associated with the audio sink. This is guaranteed to be non-None during the write() operation. ```python @property voice_client -> Optional[VoiceRecvClient] ``` -------------------------------- ### Instantiate VoiceRecvClient Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/1-voice-client.md This class should be used as a custom voice client through VoiceChannel.connect(cls=VoiceRecvClient). It should not be instantiated directly. ```python from discord.ext import voice_recv import discord # Connect with custom voice client voice_channel = ctx.author.voice.channel voice_client = await voice_channel.connect(cls=voice_recv.VoiceRecvClient) ``` -------------------------------- ### Accessing Decoded PCM Audio Data Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/4-voice-data.md Get the raw PCM audio data. This is only populated when the sink specifies `wants_opus=False`. ```python def on_audio(user, data: voice_recv.VoiceData): pcm_data = data.pcm sample_count = len(pcm_data) // 2 # 2 bytes per sample print(f"Received {sample_count} samples") ``` -------------------------------- ### Speech Transcription with User Filtering Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/8-extras-speech.md Example of filtering transcriptions to only process audio from a specific user. It uses a UserFilter to wrap the SpeechRecognitionSink. ```python from discord.ext import voice_recv # Only transcribe one user target_user = ctx.author sink = voice_recv.extras.speechrecognition.SpeechRecognitionSink( text_cb=lambda user, text: print(f"{user}: {text}") ) filtered = voice_recv.UserFilter(sink, target_user) vc.listen(filtered) ``` -------------------------------- ### PCMVolumeTransformer Initialization Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/3-builtin-sinks.md Initializes a PCMVolumeTransformer to scale audio volume. The destination is the child sink, and volume is a multiplier. ```python class PCMVolumeTransformer(AudioSink): def __init__(self, destination: AudioSink, volume: float = 1.0) ``` -------------------------------- ### RTPPacket Sequence Number Property Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/7-rtp-packets.md Gets the 16-bit sequence number used for ordering RTP packets. It increments with each packet sent. ```python @property sequence: int ``` -------------------------------- ### Lazy PyAudio Initialization for Sinks Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/9-extras-playback.md Shows how sinks automatically create and reuse a PyAudio instance if none is explicitly provided. The first sink created will initialize PyAudio, and subsequent sinks will use that same instance. ```python sink1 = voice_recv.extras.localplayback.LocalPlaybackSink() # Creates PyAudio sink2 = voice_recv.extras.localplayback.LocalPlaybackSink() # Reuses same instance ``` -------------------------------- ### Shared PyAudio Instance for Sinks Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/9-extras-playback.md Illustrates creating a single PyAudio instance and passing it to multiple sink objects for reuse. This is recommended for efficient resource management. ```python import pyaudio from discord.ext import voice_recv pa = pyaudio.PyAudio() sink1 = voice_recv.extras.localplayback.LocalPlaybackSink(py_audio=pa) sink2 = voice_recv.extras.localplayback.SimpleLocalPlaybackSink(py_audio=pa) # Both use the same 'pa' instance ``` -------------------------------- ### Get Parent Audio Sink Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/2-audio-sink.md Retrieve the parent sink in the audio sink chain. Returns None if the current sink is the root. ```python @property parent -> Optional[AudioSink] ``` -------------------------------- ### Get Member Speaking State Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/README.md The get_speaking() function retrieves the speaking state (voice activity) of a specified member. It returns None if the member is not found. ```python def get_speaking(member: discord.Member | discord.User) -> bool | None ``` -------------------------------- ### Get Root Audio Sink Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/2-audio-sink.md Access the topmost sink in the audio sink hierarchy. This is useful for retrieving the main sink when working with nested sinks. ```python @property root -> AudioSink ``` ```python root_sink = child_sink.root ``` -------------------------------- ### BasicSink Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/3-builtin-sinks.md A simple callback-based sink for custom audio handling. It allows users to define callbacks for receiving audio data and optionally RTCP packets. ```APIDOC ## BasicSink ### Description Simple callback-based sink for custom audio handling. ### Class Signature ```python BasicSink(event: Callable[[Optional[User], VoiceData], Any], *, rtcp_event: Optional[Callable[[RTCPPacket], Any]] = None, decode: bool = True) ``` ### Parameters #### Path Parameters - **event** (Callable) - Required - Main audio data callback - **rtcp_event** (Optional[Callable]) - Optional - RTCP packet callback - **decode** (bool) - Optional - Whether to decode Opus to PCM (False = raw Opus). Default: True ### Callback Signatures: ```python # event callback def my_write_cb(user: Optional[User], data: VoiceData) -> Any: pass # rtcp_event callback def my_rtcp_cb(packet: RTCPPacket) -> Any: pass ``` ### Example ```python from discord.ext import voice_recv def on_audio_data(user, data: voice_recv.VoiceData): if user: print(f"Audio from {user}: {len(data.pcm)} bytes") def on_rtcp(packet): print(f"RTCP packet: {packet.type}") sink = voice_recv.BasicSink( on_audio_data, rtcp_event=on_rtcp, decode=True ) voice_client.listen(sink) ``` ``` -------------------------------- ### write() Method Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/2-audio-sink.md Main callback invoked when audio data is available. This method is called from a worker thread and must be thread-safe. Keep processing minimal to avoid audio delays. ```python def write(self, user: Optional[Union[discord.Member, discord.User]], data: VoiceData): pass ``` -------------------------------- ### Handle Member Platform Update Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/11-events.md Fired when a member's platform information is received. Use this to react to users joining or changing platforms. ```python async def on_voice_member_platform( member: discord.Member, platform: voice_recv.VoicePlatform | None ) ``` ```python @bot.event async def on_voice_member_platform(member, platform): if platform: print(f"{member} is on {platform.name}") ``` -------------------------------- ### LocalPlaybackSink Initialization Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/9-extras-playback.md Initializes a LocalPlaybackSink to play received voice audio directly through a system audio output device. It supports multiple simultaneous users and allows specifying an output device ID or reusing an existing PyAudio instance. ```APIDOC ## LocalPlaybackSink Play received voice audio directly through a system audio output device, supporting multiple simultaneous users. ### Description Initializes a LocalPlaybackSink to play received voice audio directly through a system audio output device. It supports multiple simultaneous users and allows specifying an output device ID or reusing an existing PyAudio instance. ### Method Signature ```python __init__( self, output_device_id: Optional[int] = None, *, py_audio: Optional[pyaudio.PyAudio] = None ) ``` ### Parameters #### Arguments - **output_device_id** (Optional[int]) - Optional - None - Device ID (None = system default) - **py_audio** (Optional[pyaudio.PyAudio]) - Optional - None - Reuse existing PyAudio instance ### Raises - `RuntimeError` — Conflicting PyAudio instances if py_audio differs from class-level instance ``` -------------------------------- ### Process Voice via Callback Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/INDEX.md Utilize `BasicSink` to process incoming voice data by passing a callback function. ```python BasicSink(callback_function) ``` -------------------------------- ### Process Audio via Callback with BasicSink Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/README.md Utilize BasicSink to process audio data through a custom callback function. This allows for real-time audio manipulation or analysis. ```python BasicSink(my_callback) ``` -------------------------------- ### Connect to Voice Channel with VoiceRecvClient Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/README.md Use VoiceRecvClient as the cls argument in VoiceChannel.connect() to enable voice receive functionality. This is the primary method for integrating voice reception. ```python from discord.ext import voice_recv voice_client = await voice_channel.connect(cls=voice_recv.VoiceRecvClient) ``` -------------------------------- ### Get All Child Audio Sinks Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/2-audio-sink.md Retrieve a sequence of all child sinks. For single-child sinks, this will contain zero or one item. For multi-child sinks, it contains all children. ```python @property children -> Sequence[AudioSink] ``` -------------------------------- ### Handling User Audio with Type Hints Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/10-types.md Utilize `MemberOrUser` and `VoiceData` type hints for audio processing functions. Access user display name or name based on available attributes. ```python from discord.ext import voice_recv from discord.ext.voice_recv.types import VideoStream, MemberOrUser def handle_user_audio(user: MemberOrUser, data: voice_recv.VoiceData) -> None: """Handle audio from any user type""" name = user.display_name if hasattr(user, 'display_name') else user.name print(f"Audio from {name}") ``` -------------------------------- ### Record to Multiple Files Simultaneously Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/12-quick-start.md This snippet demonstrates how to route audio to multiple sinks at once, allowing for simultaneous recording to different files or filtering specific users' audio. ```python import discord from discord.ext import commands import voice_recv @bot.command() async def record_split(ctx): """Write to multiple files simultaneously""" vc = await ctx.author.voice.channel.connect(cls=voice_recv.VoiceRecvClient) # Create multiple sinks sink1 = voice_recv.WaveSink("all_users.wav") sink2 = voice_recv.WaveSink("your_audio.wav") filtered = voice_recv.UserFilter(sink2, ctx.author) # Route to both multi = voice_recv.MultiAudioSink([sink1, filtered]) vc.listen(multi) await ctx.send("Recording to multiple files") ``` -------------------------------- ### Play Audio to Speakers with LocalPlaybackSink Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/README.md Direct audio output to local speakers using LocalPlaybackSink. This enables real-time audio playback from the bot. ```python LocalPlaybackSink() ``` -------------------------------- ### Get Member Speaking State Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/1-voice-client.md Determines if a specific member is currently speaking using voice activity indicators. Returns True if speaking, False if not, and None if the member is not found. ```python speaking_state = voice_client.get_speaking(member) if speaking_state is True: print(f"{member} is speaking") elif speaking_state is False: print(f"{member} is not speaking") else: print(f"{member} not found in voice data") ``` -------------------------------- ### on_voice_member_speaking_start Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/11-events.md Fired when voice activity is detected (green speaking indicator starts). This is a sink-only, synchronous event synthesized from packet activity and may not perfectly match the Discord UI indicator. ```APIDOC ## on_voice_member_speaking_start() ### Description Fired when voice activity is detected (green speaking indicator starts). ### Method Signature ```python @AudioSink.listener() def on_voice_member_speaking_start(self, member: discord.Member) ``` ### Parameters #### Path Parameters - **member** (discord.Member) - Required - Member who started speaking ### Notes - Sink-only, synchronous - Synthesized from packet activity - May not perfectly match Discord UI indicator ### Example ```python class MyAudioSink(voice_recv.AudioSink): @voice_recv.AudioSink.listener() def on_voice_member_speaking_start(self, member): print(f"{member} is speaking") ``` ``` -------------------------------- ### Implement Custom Statistics Sink Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/INDEX.md Develop a custom sink inheriting from `AudioSink` to track and report audio statistics. ```python Implement custom sink with counters ``` -------------------------------- ### Handle Voice Member Speaking State Event Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/11-events.md An example of how to implement the on_voice_member_speaking_state event handler in a bot. This snippet prints the member's speaking state and SSRC when the event is triggered. ```python @bot.event async def on_voice_member_speaking_state(member, ssrc, state): print(f"{member} speaking state: {state} (SSRC: {ssrc})") ``` -------------------------------- ### Record All Voice Audio Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/INDEX.md Use `WaveSink` to record all incoming voice data to a WAV file. Ensure the `voice_client` is an instance of `VoiceRecvClient`. ```python voice_client.listen(WaveSink("file.wav")) ``` -------------------------------- ### Voice Member Speaking Start Listener Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/11-events.md Fired when voice activity is detected for a member. This is a sink-only, synchronous event synthesized from packet activity and may not perfectly match the Discord UI indicator. ```python @AudioSink.listener() def on_voice_member_speaking_start(self, member: discord.Member) ``` ```python class MyAudioSink(voice_recv.AudioSink): @voice_recv.AudioSink.listener() def on_voice_member_speaking_start(self, member): print(f"{member} is speaking") ``` -------------------------------- ### write() Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/2-audio-sink.md The main callback invoked when audio data is available. This method is called from a worker thread and must be thread-safe. ```APIDOC ## write() ### Description Main callback invoked when audio data is available. This method is called from a worker thread and must be thread-safe. Keep processing minimal to avoid audio delays. ### Method write ### Parameters #### Path Parameters - **user** (Optional[Union[discord.Member, discord.User]]) - The source user, or None if unavailable - **data** (VoiceData) - Container with audio packet and decoded PCM (if wants_opus()=False) ``` -------------------------------- ### Device Selection for Playback Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/9-extras-playback.md Select a specific audio output device for playback using PyAudio. You can list available devices and then instantiate LocalPlaybackSink with the desired output_device_id. You can also pass an existing PyAudio instance. ```python import pyaudio from discord.ext import voice_recv # List available output devices pa = pyaudio.PyAudio() for i in range(pa.get_device_count()): info = pa.get_device_info_by_index(i) print(f"{i}: {info['name']} ({info['maxOutputChannels']} channels)") # Get default device ID default_id = pa.get_default_output_device_info()["index"] print(f"Default device: {default_id}") # Create sink on specific device sink = voice_recv.extras.localplayback.LocalPlaybackSink(output_device_id=2) # Or reuse existing PyAudio sink = voice_recv.extras.localplayback.LocalPlaybackSink( output_device_id=2, py_audio=pa ) ``` -------------------------------- ### Get and Set AudioSink Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/1-voice-client.md Use this property to retrieve the current audio sink or assign a new one. The setter raises TypeError if the value is not an AudioSink and ValueError if the client is not currently receiving audio. ```python @property sink -> Optional[AudioSink] @sink.setter sink(value: AudioSink) -> None ``` ```python # Get current sink current_sink = voice_client.sink # Switch to a new sink new_sink = voice_recv.WaveSink("output.wav") voice_client.sink = new_sink ``` -------------------------------- ### Record with Time Limit Source: https://github.com/imayhaveborkedit/discord-ext-voice-recv/blob/main/_autodocs/INDEX.md Use TimedFilter with WaveSink to record audio for a specified duration to a WAV file. ```python sink = voice_recv.WaveSink("timed.wav") timed = voice_recv.TimedFilter(sink, duration=30.0, start_on_init=True) voice_client.listen(timed) ```