Attempt to handle false "ended" events with mkv files in Chrome due to blob bug

This commit is contained in:
Peter Stockings
2026-03-01 21:57:53 +11:00
parent 69feac1d55
commit bec873a9c7
4 changed files with 435 additions and 70 deletions

View File

@@ -27,6 +27,7 @@ interface ChatMessage {
// --- Room Management ---
const rooms = new Map<string, RoomState>();
const pendingDisconnects = new Map<string, ReturnType<typeof setTimeout>>(); // key: "roomCode:username"
function generateRoomCode(): string {
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // no ambiguous chars
@@ -348,6 +349,64 @@ const server = Bun.serve<WSData>({
);
break;
}
case "rejoin_room": {
const username = (msg.username || "").trim();
const code = (msg.code || "").trim().toUpperCase();
if (!username || !code) {
ws.send(JSON.stringify({ type: "error", message: "Username and room code required" }));
return;
}
const room = rooms.get(code);
if (!room) {
ws.send(JSON.stringify({ type: "error", message: "Room no longer exists" }));
return;
}
// Cancel any pending disconnect timer for this user
const disconnectKey = `${code}:${username}`;
const pendingTimer = pendingDisconnects.get(disconnectKey);
if (pendingTimer) {
clearTimeout(pendingTimer);
pendingDisconnects.delete(disconnectKey);
console.log(`[Room ${code}] ${username} reconnected (cancelled disconnect timer)`);
}
// Swap in the new WebSocket
ws.data.username = username;
ws.data.roomCode = code;
room.users.set(username, ws as unknown as WebSocket);
// Send full current state to the reconnected client
ws.send(
JSON.stringify({
type: "room_rejoined",
code,
fileInfo: room.fileInfo,
users: getUserList(room),
state: {
playing: room.playing,
position: getCurrentPosition(room),
speed: room.speed,
},
chatHistory: room.chatHistory.slice(-50),
})
);
// Notify others that user is back
broadcastToRoom(
room,
{
type: "user_rejoined",
username,
users: getUserList(room),
},
ws as unknown as WebSocket
);
console.log(`[Room ${code}] ${username} rejoined`);
break;
}
}
},
@@ -358,19 +417,35 @@ const server = Bun.serve<WSData>({
const room = rooms.get(roomCode);
if (!room) return;
room.users.delete(username);
console.log(`[Room ${roomCode}] ${username} left`);
// Don't remove immediately — give a 30s grace period for reconnection
const disconnectKey = `${roomCode}:${username}`;
console.log(`[Room ${roomCode}] ${username} disconnected (waiting 30s for reconnect)`);
if (room.users.size === 0) {
rooms.delete(roomCode);
console.log(`[Room ${roomCode}] Deleted (empty)`);
} else {
broadcastToRoom(room, {
type: "user_left",
username,
users: getUserList(room),
});
}
const timer = setTimeout(() => {
pendingDisconnects.delete(disconnectKey);
const currentRoom = rooms.get(roomCode);
if (!currentRoom) return;
// Only remove if the stored WS is still the old one (not swapped by rejoin)
const currentWs = currentRoom.users.get(username);
if (currentWs === (ws as unknown as WebSocket)) {
currentRoom.users.delete(username);
console.log(`[Room ${roomCode}] ${username} removed (grace period expired)`);
if (currentRoom.users.size === 0) {
rooms.delete(roomCode);
console.log(`[Room ${roomCode}] Deleted (empty)`);
} else {
broadcastToRoom(currentRoom, {
type: "user_left",
username,
users: getUserList(currentRoom),
});
}
}
}, 30_000);
pendingDisconnects.set(disconnectKey, timer);
},
},
});