Skip to content

refactor(voice): Strict type checking in voice internals & DAVE Support (rec)#3159

Draft
Paillat-dev wants to merge 18 commits intomasterfrom
fix/voice-rec-2
Draft

refactor(voice): Strict type checking in voice internals & DAVE Support (rec)#3159
Paillat-dev wants to merge 18 commits intomasterfrom
fix/voice-rec-2

Conversation

@Paillat-dev
Copy link
Copy Markdown
Member

Summary

Information

  • This PR fixes an issue.
  • This PR adds something new (e.g. new method or parameters).
  • This PR is a breaking change (e.g. methods or parameters removed/renamed).
  • This PR is not a code change (e.g. documentation, README, typehinting,
    examples, ...).

Checklist

  • I have searched the open pull requests for duplicates.
  • If code changes were made then they have been tested.
    • I have updated the documentation to reflect the changes.
  • If type: ignore comments were used, a comment is also left explaining why.
  • I have updated the changelog to include these changes.
  • AI Usage has been disclosed.
    • If AI has been used, I understand fully what the code does

Discord enforces DAVE (E2E encryption) on all voice channels. py-cord
currently decodes Opus packets before stripping the DAVE application-layer
encryption, causing OpusError: corrupted stream and making voice reception
completely non-functional.

Changes:
- reader.py: Rewrite decrypt_rtp() to call dave.decrypt() BEFORE Opus
  decoding. Handles SSRC-to-user_id race condition by trying all known
  DAVE user IDs when the mapping is not yet populated, then caching the
  result. Falls back to OPUS_SILENCE on decrypt failure.
- opus.py: Remove erroneous dave.decrypt() call in _decode_packet() that
  was applied to already-decoded PCM data, corrupting the audio stream.
- router.py: Catch OpusError and AssertionError in _do_run() gracefully,
  emitting a single warning instead of crashing the router thread.

Fixes: #3139
@pycord-app
Copy link
Copy Markdown

pycord-app bot commented Mar 19, 2026

Thanks for opening this pull request!
Please make sure you have read the Contributing Guidelines and Code of Conduct.

This pull request can be checked-out with:

git fetch origin pull/3159/head:pr-3159
git checkout pr-3159

This pull request can be installed with:

pip install git+https://github.com/Pycord-Development/pycord@refs/pull/3159/head

@Paillat-dev Paillat-dev added this to the Next minor rc1 milestone Mar 19, 2026

data.seek(0)
output = BytesIO()
with wave.open(output, "wb") as f:
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See if similar is required by other sinks or if it's even needed here idk if we might actually be better of undoing this. Test the other sinks

finally:
self.reader.client.stop_recording()
try:
self.reader.client.stop_recording()
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this breaking ?

Copy link
Copy Markdown
Member Author

@Paillat-dev Paillat-dev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changeling

@Lulalaby Lulalaby added help wanted Extra attention is needed priority: medium Medium Priority status: in progress Work in Progess dependencies Pull requests that update a dependency file feature Implements a feature hold: changelog This pull request is missing a changelog entry hold: testing This pull request requires further testing voice Related to the voice chat feature labels Mar 22, 2026
@github-project-automation github-project-automation bot moved this to In Progress in Pycord Releases Mar 22, 2026
@Paillat-dev Paillat-dev removed the hold: changelog This pull request is missing a changelog entry label Mar 24, 2026
@luc-rap
Copy link
Copy Markdown

luc-rap commented Apr 3, 2026

Hi, I can't seem to get the recording properly stop and save the audio files. It just seems to be stuck somewhere never finishing the recording.

Logging in and connecting works. Recording starts, when typed !stop, Stopping recording for ... is outputed, but audio is not processed properly and seems to be hanging.

import discord
from dotenv import load_dotenv
import os
import json
import time
from discord.ext import commands
# import asyncio


load_dotenv()
RECORDINGS_DIR = "recordings"   
os.makedirs(RECORDINGS_DIR, exist_ok=True)

recording_start = {}
connections = {}

intents = discord.Intents.default()
intents.message_content = True
intents.voice_states = True

bot = commands.Bot(command_prefix="!", intents=intents)

async def finished_callback(sink: discord.sinks.WaveSink, ctx: commands.Context):
    """Called automatically when stop_recording() is called."""

    for user_id, audio in sink.audio_data.items():
        print(f"Processing audio for user {user_id} with attributes: {dir(audio)}")
        filename = f"{RECORDINGS_DIR}/{user_id}.wav"
        with open(filename, "wb") as f:
            f.write(audio.file.read())
        print(f"Saved {filename}")

    await ctx.send(f"Saved {len(sink.audio_data)} audio track(s).")

@bot.event
async def on_ready():
    print(f'We have logged in as {bot.user}')   
     
@bot.command()
async def record(ctx):
    if not ctx.author.voice:
        await ctx.send("You are not connected to a voice channel.")
        return
    
    if ctx.guild.voice_client:
        await ctx.guild.voice_client.disconnect()
        
    print(f"Connecting to {ctx.author.voice.channel}")
    vc = await ctx.author.voice.channel.connect()
    print(f"Is connected: {vc.is_connected()}")

    connections[ctx.guild.id] = (vc, sink := discord.sinks.WaveSink())
    recording_start[ctx.guild.id] = time.time()
    print(f"Connected: {vc}")
    vc.start_recording(
        sink,
        finished_callback
    )
    print("Recording started")
    await ctx.send("Recording started! Type `!stop` to stop.")
    
@bot.command()
async def stop(ctx: commands.Context):
    if ctx.guild.id not in connections:
        await ctx.send("Not recording.")
        return
    vc, sink = connections.pop(ctx.guild.id)
    print(f"Stopping recording for {vc}")
    vc.stop_recording()  
    await ctx.send("Stopping recording")
    
@bot.command()
async def check(ctx):
    if ctx.guild.id not in connections:
        await ctx.send("Not recording")
        return
    vc, sink = connections[ctx.guild.id]
    print(f"audio_data: {sink.audio_data}")
    print(f"is_recording: {vc.is_recording()}")
    await ctx.send(f"audio_data keys: {list(sink.audio_data.keys())}")
        
@bot.command()
async def disconnect(ctx: commands.Context):
    vc = ctx.guild.voice_client
    if vc:
        await vc.disconnect(force=True)
        await ctx.send("Disconnected.")
    else:
        await ctx.send("No voice client.")
        
bot.run(os.getenv('DISCORD_TOKEN'))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file feature Implements a feature help wanted Extra attention is needed hold: testing This pull request requires further testing priority: medium Medium Priority status: in progress Work in Progess voice Related to the voice chat feature

Projects

Status: Todo

6 participants