feat: implement WebCodecs Opus decode for browser listen
- Add broadcastOpusToWeb to PcmBroadcaster interface for raw Opus packets - Server broadcasts Opus frames with mode byte (1) + user hash + packet data - Browser detects packet mode: mode=1 for Opus, mode=0 for legacy PCM - Implement WebCodecs AudioDecoder for Opus decoding in browser - Keep existing PCM playback as fallback for compatibility - Show error if WebCodecs unsupported - Fixes listen feature under Bun where native Opus decode unavailable
This commit is contained in:
@@ -10,6 +10,7 @@ import { AppError } from "./errors";
|
||||
import { createChildLogger, logger } from "./logger";
|
||||
import { getMetrics, uptimeGauge } from "./metrics";
|
||||
import { discordPlayer } from "./player";
|
||||
import { renderDashboardPage } from "./web/dashboardPage";
|
||||
import type { VoiceController } from "./voiceController";
|
||||
import { getDatabase } from "./muxer-queue";
|
||||
import { getMessagesByChannel, getAttachmentsByChannel } from "./moderation/messageStore";
|
||||
@@ -70,6 +71,39 @@ export function startWebserver(
|
||||
app.use(pinoHttp({ logger }));
|
||||
app.use(express.json());
|
||||
|
||||
app.get("/", async (req, res, next) => {
|
||||
try {
|
||||
const guilds = voiceController.listGuilds();
|
||||
const selectedGuildId =
|
||||
typeof req.query.guild === "string" ? req.query.guild : guilds[0]?.id || "";
|
||||
const selectedChannelId =
|
||||
typeof req.query.channel === "string" ? req.query.channel : "";
|
||||
const [voiceChannels, watchChannels] = selectedGuildId
|
||||
? await Promise.all([
|
||||
voiceController.listVoiceChannels(selectedGuildId),
|
||||
voiceController.listWatchableChannels(selectedGuildId),
|
||||
])
|
||||
: [[], []];
|
||||
const messages = selectedChannelId
|
||||
? getMessagesByChannel(getDatabase(), selectedChannelId, 80, 0)
|
||||
: [];
|
||||
|
||||
res.type("html").send(
|
||||
renderDashboardPage({
|
||||
guilds,
|
||||
voiceChannels,
|
||||
watchChannels,
|
||||
selectedGuildId,
|
||||
selectedChannelId,
|
||||
messages,
|
||||
status: voiceController.getStatus(),
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
app.use(express.static(path.join(__dirname, "../public")));
|
||||
|
||||
// Health check endpoint
|
||||
@@ -204,6 +238,22 @@ export function startWebserver(
|
||||
});
|
||||
};
|
||||
|
||||
// Inbound: Discord Opus → tagged chunks → browser (WebCodecs decode)
|
||||
(global as any).broadcastOpusToWeb = (chunk: Buffer, userId: string) => {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < userId.length; i++) {
|
||||
hash = (hash << 5) - hash + userId.charCodeAt(i);
|
||||
hash |= 0;
|
||||
}
|
||||
const header = Buffer.alloc(5);
|
||||
header.writeUInt8(1, 0); // mode: 1 = Opus
|
||||
header.writeInt32LE(hash, 1);
|
||||
const packet = Buffer.concat([header, chunk]);
|
||||
wsClients.forEach((client) => {
|
||||
if (client.readyState === 1) client.send(packet);
|
||||
});
|
||||
};
|
||||
|
||||
(global as any).updateActiveUser = (
|
||||
userId: string,
|
||||
data: { username: string; avatar: string; speaking: boolean },
|
||||
|
||||
Reference in New Issue
Block a user