fix(gateway): re-assert server-undeafen+unmute before every video watch
The bot's own VOICE_STATE_UPDATE showed server-level deaf:true — a server-deafened member is NOT sent the streamer's audiovisual RTP by Discord, which is the likely reason no H264 arrives despite the DAVE watch reaching Ready. The previous fire-and-forget forceSelfServerUnmuteUndeafen() ran once after the first Ready join and silently reverted on reconnect/restart. - Export forceSelfServerUnmuteUndeafen from recorder.ts; re-assert it (with read-back verification logging stillDeaf) at the START of every startStreamWatch() before STREAM_WATCH is sent (dynamic import avoids the recorder <-> videoRecorder <-> streamWatchReceiver module cycle). - Re-assert it again after a successful voice reconnect. - startStreamWatch() is now async; callers use void.
This commit is contained in:
1 parent
9606187861
commit
2b6a1eca19
3 files changed
+32
-4
No files matched your search
@@ -45,8 +45,14 @@ export function setEventBroadcaster(broadcaster: EventBroadcaster | undefined) {
|
|||||||
* Requires the `MUTE_MEMBERS` + `DEAFEN_MEMBERS` permissions on the bot
|
* Requires the `MUTE_MEMBERS` + `DEAFEN_MEMBERS` permissions on the bot
|
||||||
* (user has granted them). Failures are logged, never fatal — an unmute
|
* (user has granted them). Failures are logged, never fatal — an unmute
|
||||||
* failure must not break the voice join/recording.
|
* failure must not break the voice join/recording.
|
||||||
|
*
|
||||||
|
* Exported so the video-receive path (videoRecorder.ts → streamWatch) can
|
||||||
|
* re-assert it right before requesting STREAM_WATCH — a server-deafened bot
|
||||||
|
* is NOT sent the streamer's audiovisual RTP, which is why no video arrives
|
||||||
|
* even when the DAVE watch reaches Ready. Server-deaf can silently revert on
|
||||||
|
* reconnect/restart, so this is re-invoked on every video watch attempt.
|
||||||
*/
|
*/
|
||||||
async function forceSelfServerUnmuteUndeafen(
|
export async function forceSelfServerUnmuteUndeafen(
|
||||||
client: Client,
|
client: Client,
|
||||||
guild: Guild,
|
guild: Guild,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@@ -54,7 +60,15 @@ async function forceSelfServerUnmuteUndeafen(
|
|||||||
const selfId = client.user?.id;
|
const selfId = client.user?.id;
|
||||||
if (!selfId) return;
|
if (!selfId) return;
|
||||||
await guild.members.edit(selfId, { mute: false, deaf: false });
|
await guild.members.edit(selfId, { mute: false, deaf: false });
|
||||||
logger.info({ guildId: guild.id }, "Forced bot server unmute + undeafen");
|
// Verify the PATCH actually landed — read back the live member voice state.
|
||||||
|
const fresh = guild.members.cache.get(selfId);
|
||||||
|
const stillDeaf = fresh?.voice?.deaf ?? false;
|
||||||
|
logger.info(
|
||||||
|
{ guildId: guild.id, stillDeaf },
|
||||||
|
stillDeaf
|
||||||
|
? "Forced bot server unmute + undeafen (but guild reports still deaf — will retry on next video)"
|
||||||
|
: "Forced bot server unmute + undeafen",
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
{
|
{
|
||||||
@@ -225,6 +239,9 @@ export async function startRecording(
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
// Reconnected successfully
|
// Reconnected successfully
|
||||||
|
// Server-deaf can silently revert across a reconnect — re-assert the bot
|
||||||
|
// is undeafened + unmuted so video receive keeps working.
|
||||||
|
void forceSelfServerUnmuteUndeafen(client, channel.guild);
|
||||||
} catch {
|
} catch {
|
||||||
logger.error("Could not reconnect. Destroying connection");
|
logger.error("Could not reconnect. Destroying connection");
|
||||||
connection.destroy();
|
connection.destroy();
|
||||||
|
|||||||
@@ -159,13 +159,24 @@ function handleRaw(packet: unknown): void {
|
|||||||
* waits for STREAM_CREATE / STREAM_SERVER_UPDATE (handled by handleRaw) to
|
* waits for STREAM_CREATE / STREAM_SERVER_UPDATE (handled by handleRaw) to
|
||||||
* actually open the DAVE connection. Best-effort.
|
* actually open the DAVE connection. Best-effort.
|
||||||
*/
|
*/
|
||||||
export function startStreamWatch(channel: VoiceChannel, userId: string): void {
|
export async function startStreamWatch(
|
||||||
|
channel: VoiceChannel,
|
||||||
|
userId: string,
|
||||||
|
): Promise<void> {
|
||||||
if (!_client) {
|
if (!_client) {
|
||||||
logger.warn("startStreamWatch: no client set");
|
logger.warn("startStreamWatch: no client set");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const watchKey = `${channel.guild.id}:${userId}`;
|
const watchKey = `${channel.guild.id}:${userId}`;
|
||||||
if (watches.has(watchKey)) return; // already watching
|
if (watches.has(watchKey)) return; // already watching
|
||||||
|
// Re-assert the bot is server-undeafened + unmuted BEFORE requesting video.
|
||||||
|
// A server-deafened bot is NOT sent the streamer's audiovisual RTP by
|
||||||
|
// Discord, so no video would arrive even though the DAVE watch reaches
|
||||||
|
// Ready. Server-deaf can silently revert on reconnect/restart, so this is
|
||||||
|
// re-invoked on every watch attempt. Dynamic import avoids a static module
|
||||||
|
// cycle (recorder ⇄ videoRecorder ⇄ streamWatchReceiver).
|
||||||
|
const { forceSelfServerUnmuteUndeafen } = await import("./recorder.js");
|
||||||
|
void forceSelfServerUnmuteUndeafen(_client, channel.guild);
|
||||||
const sk = streamKeyFor(channel.id, channel.guild.id, userId);
|
const sk = streamKeyFor(channel.id, channel.guild.id, userId);
|
||||||
logger.info(
|
logger.info(
|
||||||
{ userId, guildId: channel.guild.id, channelId: channel.id, streamKey: sk },
|
{ userId, guildId: channel.guild.id, channelId: channel.id, streamKey: sk },
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ export function startVideoRecording(
|
|||||||
try {
|
try {
|
||||||
const selfId = _client?.user?.id;
|
const selfId = _client?.user?.id;
|
||||||
if (selfId && userId === selfId) return;
|
if (selfId && userId === selfId) return;
|
||||||
startStreamWatch(channel, userId);
|
void startStreamWatch(channel, userId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user