feat: add voice connection retry logic and diagnostic tools

- Add 3-attempt retry logic with exponential backoff for voice connections
- Add connection health checking during stream creation
- Add specific error messages for different connection failure types
- Add 'test voice' command for connectivity diagnostics
- Better error handling for VOICE_CONNECTION_TIMEOUT scenarios
- Should significantly improve connection reliability
This commit is contained in:
Mikolaj Wojciech Gorski 2025-07-26 18:29:01 +02:00
parent 6b5b3d1867
commit c5cf8f0488
3 changed files with 147 additions and 12 deletions

@ -1 +1 @@
Subproject commit 943f9e54add4eda8983d83f78e0942ed9e137387 Subproject commit 5014823f695ddefdeba332450a59dfbc23b98b1b

View file

@ -398,6 +398,74 @@ class CommandHandler {
}, },
}); });
// Test voice connectivity command
this.commands.set("test_voice", {
trigger: (msg) => msg.content.toLowerCase() === "test voice",
execute: async (msg, context) => {
const member = msg.guild?.members.cache.get(msg.author.id);
const voiceChannel = member?.voice?.channel;
if (!voiceChannel) {
msg.channel.send(
"❌ You need to be in a voice channel to test voice connectivity!"
);
return null;
}
msg.channel.send("🔧 Testing voice server connectivity...");
try {
const testStart = Date.now();
const connection = await context.client.voice.joinChannel(
voiceChannel.id,
{
selfMute: true,
selfDeaf: true,
selfVideo: false,
}
);
const connectTime = Date.now() - testStart;
msg.channel.send(
`✅ Voice connection successful!\n` +
`• Connect time: ${connectTime}ms\n` +
`• Voice server: ${connection.voiceServerURL || "Unknown"}\n` +
`• Status: ${connection.status}\n` +
`• Channel: ${voiceChannel.name}`
);
// Disconnect after test
setTimeout(() => {
connection.disconnect();
}, 2000);
return {
action: {
message: `Voice connectivity test by ${msg.author.tag}`,
channel: `#${msg.channel.name}`,
icon: "🔧",
},
};
} catch (error) {
msg.channel.send(
`❌ Voice connection failed!\n` +
`• Error: ${error.message}\n` +
`• This may indicate Discord voice server issues\n` +
`• Try again in a few minutes`
);
return {
action: {
message: `Voice connectivity test failed by ${msg.author.tag}`,
channel: `#${msg.channel.name}`,
icon: "❌",
},
};
}
},
});
// Help command // Help command
this.commands.set("help", { this.commands.set("help", {
trigger: (msg) => trigger: (msg) =>
@ -419,6 +487,7 @@ class CommandHandler {
"`teto status` - Show overall recording status", "`teto status` - Show overall recording status",
"`webcam status` - Show detailed webcam status", "`webcam status` - Show detailed webcam status",
"`debug voice` - Show voice state debug info", "`debug voice` - Show voice state debug info",
"`test voice` - Test voice server connectivity",
"`teto help` or `help` - Show this help message", "`teto help` or `help` - Show this help message",
"", "",
"**💬 General:**", "**💬 General:**",

View file

@ -44,15 +44,43 @@ class WebcamRecordingService {
`[Docker] Attempting to join voice channel for webcam recording: ${voiceChannel.name}` `[Docker] Attempting to join voice channel for webcam recording: ${voiceChannel.name}`
); );
// Join the voice channel // Join the voice channel with retry logic
const connection = await client.voice.joinChannel( let connection;
let connectionRetries = 0;
const maxConnectionRetries = 3;
while (connectionRetries < maxConnectionRetries) {
try {
connection = await client.voice.joinChannel(
voiceChannel.id, voiceChannel.id,
VIDEO_CONFIG.VOICE_SETTINGS VIDEO_CONFIG.VOICE_SETTINGS
); );
console.log( console.log(
`[Docker] Successfully joined voice channel for webcam recording` `[Docker] Successfully joined voice channel for webcam recording`
); );
break;
} catch (error) {
connectionRetries++;
console.log(
`[Docker] Voice connection failed (attempt ${connectionRetries}/${maxConnectionRetries}):`,
error.message
);
if (connectionRetries === maxConnectionRetries) {
throw new Error(
`Failed to join voice channel after ${maxConnectionRetries} attempts: ${error.message}`
);
}
// Wait before retry with exponential backoff
const delay = Math.min(
5000 * Math.pow(2, connectionRetries - 1),
30000
);
console.log(`[Docker] Retrying voice connection in ${delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
// Wait longer for connection and SSRC mapping to be fully established // Wait longer for connection and SSRC mapping to be fully established
await new Promise((resolve) => setTimeout(resolve, 8000)); await new Promise((resolve) => setTimeout(resolve, 8000));
@ -142,8 +170,8 @@ class WebcamRecordingService {
// Wait before retry and check connection health // Wait before retry and check connection health
await new Promise((resolve) => setTimeout(resolve, 2000)); await new Promise((resolve) => setTimeout(resolve, 2000));
// Check if connection is still alive // Check if connection is still healthy
if (connection.status !== "connected") { if (!this._isConnectionHealthy(connection)) {
console.log( console.log(
`[Docker] Connection status changed to: ${connection.status}` `[Docker] Connection status changed to: ${connection.status}`
); );
@ -197,7 +225,7 @@ class WebcamRecordingService {
} catch (error) { } catch (error) {
console.error("[Docker] Error starting webcam recording:", error); console.error("[Docker] Error starting webcam recording:", error);
// Handle specific webcam errors // Handle specific error types
if (error.message === "VOICE_USER_NO_WEBCAM") { if (error.message === "VOICE_USER_NO_WEBCAM") {
return { return {
success: false, success: false,
@ -206,6 +234,32 @@ class WebcamRecordingService {
}; };
} }
if (error.message.includes("VOICE_CONNECTION_TIMEOUT")) {
return {
success: false,
message:
"❌ Voice connection timed out. Discord voice servers may be slow or unavailable.",
error: "VOICE_CONNECTION_TIMEOUT",
};
}
if (error.message.includes("Failed to join voice channel")) {
return {
success: false,
message:
"❌ Could not connect to voice channel after multiple attempts. Try again later.",
error: "VOICE_CONNECTION_FAILED",
};
}
if (error.message.includes("Voice connection lost")) {
return {
success: false,
message: "❌ Voice connection was lost during setup.",
error: "VOICE_CONNECTION_LOST",
};
}
return { return {
success: false, success: false,
message: "Failed to start webcam recording", message: "Failed to start webcam recording",
@ -595,6 +649,18 @@ class WebcamRecordingService {
); );
} }
} }
/**
* Check if voice connection is healthy
* @param {Object} connection - Voice connection object
* @returns {boolean} Whether connection is healthy
*/
_isConnectionHealthy(connection) {
if (!connection) return false;
const healthyStatuses = ["connected", "connecting", "ready"];
return healthyStatuses.includes(connection.status);
}
} }
// Export singleton instance // Export singleton instance