Discord Stream Recording Injection

Created on 19 Feb 2026

uwu

thread link: uwu

Goal

Record incoming Discord direct video stream frames to local MP4 files:

Files To Modify

Required Binary

Use this ffmpeg path inside the injection:

Injection Code

Paste these blocks into discord_voice/index.js.

1) Add imports

const childProcess = require('child_process');
Error.stackTraceLimit = 100;

2) Add recorder implementation (place near videoStreams / directVideoStreams)

const activeRecorders = new Map();

function randomToken(length) {
  const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
  let out = '';
  for (let i = 0; i < length; i++) out += chars[Math.floor(Math.random() * chars.length)];
  return out;
}

function getRecordingDir() {
  return process.env.DISCORD_REC_DIR || path.join(os.homedir(), 'discord-recs');
}

function getRecordingPath(streamId) {
  const ts = Math.floor(Date.now() / 1000);
  const token = randomToken(5);
  const ext = process.env.DISCORD_REC_EXT || 'mp4';
  return path.join(getRecordingDir(), `${ts}-${token}-stream${streamId}.${ext}`);
}

function waitForDrain(stream) {
  return new Promise(resolve => stream.once('drain', resolve));
}

function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function startRecorderProcess(state, width, height) {
  const outputPath = getRecordingPath(state.streamId);
  const ffmpegBin = process.env.DISCORD_REC_FFMPEG || '/Users/soham/.nix-profile/bin/ffmpeg';
  const fps = String(process.env.DISCORD_REC_FPS || 30);
  const args = [
    '-y',
    '-f', 'rawvideo',
    '-pix_fmt', 'rgba',
    '-s', `${width}x${height}`,
    '-r', fps,
    '-i', '-',
    '-an',
    '-c:v', 'libx264',
    '-preset', 'veryfast',
    '-crf', '20',
    '-pix_fmt', 'yuv420p',
    outputPath,
  ];

  state.outputPath = outputPath;
  state.width = width;
  state.height = height;
  state.ffmpeg = childProcess.spawn(ffmpegBin, args, { stdio: ['pipe', 'ignore', 'pipe'] });

  state.ffmpeg.on('error', err => {
    trace('recorder_ffmpeg_error', {
      streamId: state.streamId,
      ffmpegBin,
      error: err?.message ?? String(err),
    });
  });

  state.ffmpeg.stderr.on('data', chunk => {
    trace('recorder_ffmpeg_stderr', {
      streamId: state.streamId,
      stderr: String(chunk),
    });
  });

  state.ffmpeg.on('exit', (code, signal) => {
    trace('recorder_ffmpeg_exit', {
      streamId: state.streamId,
      outputPath: state.outputPath,
      code,
      signal,
    });
  });

  trace('recorder_started', {
    streamId: state.streamId,
    outputPath: state.outputPath,
    ffmpegBin,
    width,
    height,
    fps,
  });
}

async function startRecorder(streamId) {
  if (activeRecorders.has(streamId)) return;

  const state = {
    streamId,
    running: true,
    ffmpeg: null,
    outputPath: null,
  };

  activeRecorders.set(streamId, state);
  fs.mkdirSync(getRecordingDir(), { recursive: true });

  trace('recorder_loop_start', {
    streamId,
    recordingDir: getRecordingDir(),
  });

  while (state.running) {
    try {
      const frame = await VoiceEngine.getNextVideoOutputFrame(streamId);
      if (!state.running) break;

      if (state.ffmpeg == null) {
        startRecorderProcess(state, frame.width, frame.height);
      }

      if (state.ffmpeg == null || state.ffmpeg.killed || state.ffmpeg.stdin.destroyed) break;

      const frameBuffer = Buffer.from(frame.data.buffer, frame.data.byteOffset, frame.data.byteLength);
      const wrote = state.ffmpeg.stdin.write(frameBuffer);
      if (!wrote) await waitForDrain(state.ffmpeg.stdin);
    } catch (e) {
      trace('recorder_frame_error', {
        streamId,
        error: e?.message ?? String(e),
      });
      await wait(150);
    }
  }

  if (state.ffmpeg != null && !state.ffmpeg.stdin.destroyed) state.ffmpeg.stdin.end();
  if (state.ffmpeg != null && !state.ffmpeg.killed) {
    setTimeout(() => {
      if (state.ffmpeg != null && !state.ffmpeg.killed) state.ffmpeg.kill('SIGKILL');
    }, 3000);
  }

  activeRecorders.delete(streamId);
  trace('recorder_loop_end', {
    streamId,
    outputPath: state.outputPath,
  });
}

function stopRecorder(streamId) {
  const state = activeRecorders.get(streamId);
  if (state == null) return;

  state.running = false;

  if (state.ffmpeg != null && !state.ffmpeg.stdin.destroyed) state.ffmpeg.stdin.end();
  if (state.ffmpeg != null && !state.ffmpeg.killed) {
    setTimeout(() => {
      if (state.ffmpeg != null && !state.ffmpeg.killed) state.ffmpeg.kill('SIGKILL');
    }, 3000);
  }

  trace('recorder_stop_requested', {
    streamId,
    outputPath: state.outputPath,
  });
}

3) Hook start/stop into direct video sink

const addDirectVideoOutputSink_ = VoiceEngine.addDirectVideoOutputSink;
const removeDirectVideoOutputSink_ = VoiceEngine.removeDirectVideoOutputSink;

VoiceEngine.addDirectVideoOutputSink = function (streamId) {
  log('info', `Subscribing to direct frames for streamId ${streamId}`);
  addDirectVideoOutputSink_(streamId);
  directVideoStreams[streamId] = true;
  notifyActiveSinksChange(streamId);
  void startRecorder(streamId);
};

VoiceEngine.removeDirectVideoOutputSink = function (streamId) {
  log('info', `Unsubscribing from direct frames for streamId ${streamId}`);
  removeDirectVideoOutputSink_(streamId);
  delete directVideoStreams[streamId];
  notifyActiveSinksChange(streamId);
  stopRecorder(streamId);
};

Deploy

LIVE="/Users/soham/Library/Application Support/discord/0.0.377/modules/discord_voice/index.js"
BACKUP="${LIVE}.bak.$(date +%Y%m%d_%H%M%S)"
cp "$LIVE" "$BACKUP"
cp ./discord_modules_0.0.377/modules/discord_voice/index.js "$LIVE"
node --check "$LIVE"
mkdir -p "$HOME/discord-recs"

Test

  1. Restart Discord.
  2. Join VC and view a friend stream.
  3. Stop viewing stream.
  4. Check output:
ls -lah ~/discord-recs

Verify Recorder Events

rg -n 'recorder_started|recorder_stop_requested|recorder_ffmpeg_exit|recorder_loop_end|recorder_ffmpeg_error' \
"$HOME/Library/Application Support/discord/logs/discord_voice_trace.log"

Rollback

cp /Users/soham/Library/Application\ Support/discord/0.0.377/modules/discord_voice/index.js.bak.<timestamp> \
   /Users/soham/Library/Application\ Support/discord/0.0.377/modules/discord_voice/index.js