feat: implement Opus demuxing for browser streams, cache headers for playback, and enable microphone input in recorder

This commit is contained in:
baharsah
2026-05-13 01:03:46 +07:00
parent 18cf941da0
commit c911b06b95
4 changed files with 18 additions and 14 deletions

View File

@@ -1,8 +0,0 @@
# Discord Bot Token (dari Discord Developer Portal)
DISCORD_TOKEN=""
# ID Channel Voice yang ingin di-join bot saat startup
VOICE_CHANNEL_ID=
GUILD_ID=
# Folder penyimpanan rekaman (opsional, default: ./recordings)
RECORDINGS_DIR=./recordings

View File

@@ -8,6 +8,8 @@ import {
} from "@discordjs/voice"; } from "@discordjs/voice";
import { Readable } from "stream"; import { Readable } from "stream";
import prism from "prism-media";
export class DiscordPlayer { export class DiscordPlayer {
private player: AudioPlayer; private player: AudioPlayer;
private connection: VoiceConnection | null = null; private connection: VoiceConnection | null = null;
@@ -30,12 +32,13 @@ export class DiscordPlayer {
} }
public playStream(stream: Readable) { public playStream(stream: Readable) {
// We assume the stream is Opus or PCM. // Use WebmDemuxer to extract Opus packets from browser stream
// For MediaRecorder (webm/opus), we might need to parse it. const demuxer = new prism.opus.WebmDemuxer();
// But let's start with a simple resource.
const resource = createAudioResource(stream, { const resource = createAudioResource(stream.pipe(demuxer), {
inputType: StreamType.WebmOpus, inputType: StreamType.Opus,
}); });
this.player.play(resource); this.player.play(resource);
} }

View File

@@ -29,7 +29,7 @@ export async function startRecording(client: Client, channel: VoiceChannel): Pro
guildId: channel.guild.id, guildId: channel.guild.id,
adapterCreator: channel.guild.voiceAdapterCreator as any, adapterCreator: channel.guild.voiceAdapterCreator as any,
selfDeaf: false, selfDeaf: false,
selfMute: true, selfMute: false,
debug: true, debug: true,
}); });

View File

@@ -11,12 +11,17 @@ export function startWebserver(port: number = 3000) {
const wss = new WebSocketServer({ server }); const wss = new WebSocketServer({ server });
const listeners = new Set<express.Response>(); const listeners = new Set<express.Response>();
let headerChunks: Buffer[] = [];
app.use(express.static(path.join(__dirname, "../public"))); app.use(express.static(path.join(__dirname, "../public")));
// Endpoint for receiving (listening) audio from Discord // Endpoint for receiving (listening) audio from Discord
app.get("/listen", (req, res) => { app.get("/listen", (req, res) => {
res.setHeader("Content-Type", "audio/ogg"); res.setHeader("Content-Type", "audio/ogg");
// Send cached headers so the browser can decode the stream
headerChunks.forEach(chunk => res.write(chunk));
listeners.add(res); listeners.add(res);
console.log(`[webserver] New listener connected. Total: ${listeners.size}`); console.log(`[webserver] New listener connected. Total: ${listeners.size}`);
@@ -28,6 +33,10 @@ export function startWebserver(port: number = 3000) {
// Function to broadcast audio chunks to all listeners // Function to broadcast audio chunks to all listeners
(global as any).broadcastToWeb = (chunk: Buffer) => { (global as any).broadcastToWeb = (chunk: Buffer) => {
// Store the first two chunks as headers (OpusHead and OpusTags)
if (headerChunks.length < 2) {
headerChunks.push(chunk);
}
listeners.forEach(res => res.write(chunk)); listeners.forEach(res => res.write(chunk));
}; };