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:
parent
6b5b3d1867
commit
c5cf8f0488
3 changed files with 147 additions and 12 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 943f9e54add4eda8983d83f78e0942ed9e137387
|
||||
Subproject commit 5014823f695ddefdeba332450a59dfbc23b98b1b
|
||||
|
|
@ -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
|
||||
this.commands.set("help", {
|
||||
trigger: (msg) =>
|
||||
|
|
@ -419,6 +487,7 @@ class CommandHandler {
|
|||
"`teto status` - Show overall recording status",
|
||||
"`webcam status` - Show detailed webcam status",
|
||||
"`debug voice` - Show voice state debug info",
|
||||
"`test voice` - Test voice server connectivity",
|
||||
"`teto help` or `help` - Show this help message",
|
||||
"",
|
||||
"**💬 General:**",
|
||||
|
|
|
|||
|
|
@ -44,15 +44,43 @@ class WebcamRecordingService {
|
|||
`[Docker] Attempting to join voice channel for webcam recording: ${voiceChannel.name}`
|
||||
);
|
||||
|
||||
// Join the voice channel
|
||||
const connection = await client.voice.joinChannel(
|
||||
voiceChannel.id,
|
||||
VIDEO_CONFIG.VOICE_SETTINGS
|
||||
);
|
||||
// Join the voice channel with retry logic
|
||||
let connection;
|
||||
let connectionRetries = 0;
|
||||
const maxConnectionRetries = 3;
|
||||
|
||||
console.log(
|
||||
`[Docker] Successfully joined voice channel for webcam recording`
|
||||
);
|
||||
while (connectionRetries < maxConnectionRetries) {
|
||||
try {
|
||||
connection = await client.voice.joinChannel(
|
||||
voiceChannel.id,
|
||||
VIDEO_CONFIG.VOICE_SETTINGS
|
||||
);
|
||||
console.log(
|
||||
`[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
|
||||
await new Promise((resolve) => setTimeout(resolve, 8000));
|
||||
|
|
@ -142,8 +170,8 @@ class WebcamRecordingService {
|
|||
// Wait before retry and check connection health
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
// Check if connection is still alive
|
||||
if (connection.status !== "connected") {
|
||||
// Check if connection is still healthy
|
||||
if (!this._isConnectionHealthy(connection)) {
|
||||
console.log(
|
||||
`[Docker] Connection status changed to: ${connection.status}`
|
||||
);
|
||||
|
|
@ -197,7 +225,7 @@ class WebcamRecordingService {
|
|||
} catch (error) {
|
||||
console.error("[Docker] Error starting webcam recording:", error);
|
||||
|
||||
// Handle specific webcam errors
|
||||
// Handle specific error types
|
||||
if (error.message === "VOICE_USER_NO_WEBCAM") {
|
||||
return {
|
||||
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 {
|
||||
success: false,
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue