This commit is contained in:
parent
804d6aed2a
commit
ab1dd08225
7 changed files with 216 additions and 140 deletions
|
|
@ -162,6 +162,14 @@ class ChatWebSocket {
|
|||
break;
|
||||
|
||||
case 'auth_success':
|
||||
// Get old userId before updating (for participant cleanup)
|
||||
const oldUserId = get(chatUserInfo).userId;
|
||||
|
||||
// Remove old participant entry (guest identity)
|
||||
if (oldUserId) {
|
||||
participants.update((list) => list.filter((p) => p.userId !== oldUserId));
|
||||
}
|
||||
|
||||
// Update user info after successful authentication
|
||||
chatUserInfo.set({
|
||||
username: data.username,
|
||||
|
|
@ -174,6 +182,9 @@ class ChatWebSocket {
|
|||
avatarUrl: data.avatarUrl || '',
|
||||
userColor: data.userColor || '#FFFFFF'
|
||||
});
|
||||
|
||||
// Request fresh participants list to ensure consistency
|
||||
this.getParticipants();
|
||||
console.log('Authentication successful:', data.username);
|
||||
break;
|
||||
|
||||
|
|
@ -197,19 +208,27 @@ class ChatWebSocket {
|
|||
break;
|
||||
|
||||
case 'participants_list':
|
||||
participants.set(data.participants || []);
|
||||
// Sort by joinedAt ascending (oldest/longest-joined first)
|
||||
const sortedList = (data.participants || []).sort(
|
||||
(a, b) => (a.joinedAt || 0) - (b.joinedAt || 0)
|
||||
);
|
||||
participants.set(sortedList);
|
||||
console.log(`Participants in realm: ${data.count}`);
|
||||
break;
|
||||
|
||||
case 'participant_joined':
|
||||
// Add new participant to the list
|
||||
participants.update((list) => {
|
||||
// Check if already in list (avoid duplicates)
|
||||
const exists = list.some((p) => p.userId === data.participant.userId);
|
||||
if (!exists) {
|
||||
return [...list, data.participant];
|
||||
}
|
||||
return list;
|
||||
// Remove any existing entry with same userId OR same username
|
||||
// This handles auth transitions (userId changes) and reconnects
|
||||
const filtered = list.filter(
|
||||
(p) =>
|
||||
p.userId !== data.participant.userId &&
|
||||
p.username !== data.participant.username
|
||||
);
|
||||
// Add new participant and re-sort by joinedAt (oldest first)
|
||||
const updated = [...filtered, data.participant];
|
||||
return updated.sort((a, b) => (a.joinedAt || 0) - (b.joinedAt || 0));
|
||||
});
|
||||
console.log(`Participant joined: ${data.participant.username} (${data.participantCount} total)`);
|
||||
break;
|
||||
|
|
@ -431,6 +450,7 @@ class ChatWebSocket {
|
|||
|
||||
/**
|
||||
* Manually trigger reconnection - resets attempt counter and tries immediately
|
||||
* Also handles authentication on existing connection (e.g., when user logs in while connected as guest)
|
||||
*/
|
||||
async manualReconnect() {
|
||||
console.log('Manual reconnect triggered');
|
||||
|
|
@ -440,25 +460,28 @@ class ChatWebSocket {
|
|||
}
|
||||
this.reconnectAttempts = 0;
|
||||
|
||||
// Refresh token before reconnecting if we have one
|
||||
if (this.token && this.realmId) {
|
||||
try {
|
||||
const response = await fetch('/api/auth/refresh', {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.token) {
|
||||
this.token = data.token;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Token refresh error during manual reconnect:', e);
|
||||
}
|
||||
// Get fresh token from localStorage (may have been set by login)
|
||||
const freshToken =
|
||||
typeof localStorage !== 'undefined' ? localStorage.getItem('token') : null;
|
||||
|
||||
// If we have a token and connection is open, just send auth message
|
||||
// This handles the case where user logs in while already connected as guest
|
||||
if (freshToken && this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
console.log('[ChatWebSocket] Sending auth message on existing connection');
|
||||
this.token = freshToken;
|
||||
this.ws.send(JSON.stringify({ type: 'auth', token: freshToken }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, do full reconnect
|
||||
this.token = freshToken;
|
||||
if (this.realmId) {
|
||||
// Close existing connection to force fresh connect
|
||||
if (this.ws) {
|
||||
this.ws.onclose = null; // Prevent auto-reconnect
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
}
|
||||
this.connect(this.realmId, this.token);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -530,9 +530,11 @@
|
|||
}
|
||||
|
||||
if (result === '1-0') {
|
||||
return myColor === 'w' ? 'You win!' : 'You lose';
|
||||
const winText = myColor === 'w' ? 'You win!' : 'You lose';
|
||||
return reason ? `${winText} (${reason})` : winText;
|
||||
} else if (result === '0-1') {
|
||||
return myColor === 'b' ? 'You win!' : 'You lose';
|
||||
const winText = myColor === 'b' ? 'You win!' : 'You lose';
|
||||
return reason ? `${winText} (${reason})` : winText;
|
||||
} else if (result === 'timeout') {
|
||||
return reason || 'Match timed out';
|
||||
} else if (result === 'cancelled') {
|
||||
|
|
|
|||
|
|
@ -312,9 +312,35 @@
|
|||
startDurationTimer();
|
||||
}
|
||||
|
||||
// Only update isStreaming from stats if player isn't actively playing
|
||||
// This prevents stats from overriding the player's live state during connection
|
||||
if (!playerPlaying) {
|
||||
// Handle stream going OFFLINE - force states to show offline overlay
|
||||
if (!stats.isLive && wasLive) {
|
||||
console.log('Stream went offline - resetting player states');
|
||||
playerPlaying = false;
|
||||
playerBuffering = false;
|
||||
isStreaming = false;
|
||||
// Stop the player to release resources and clear stale buffers
|
||||
if (player) {
|
||||
try {
|
||||
player.stop();
|
||||
} catch (e) {
|
||||
console.error('Error stopping player:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle stream coming back ONLINE - reinitialize player
|
||||
else if (stats.isLive && !wasLive) {
|
||||
console.log('Stream came back online - reinitializing player');
|
||||
// Reset retry counters for fresh attempt
|
||||
llhlsRetryAttempts = 0;
|
||||
playerReconnectAttempts = 0;
|
||||
playerInitializing = true;
|
||||
isStreaming = true;
|
||||
|
||||
// Reinitialize player with fresh token and sources
|
||||
reinitializePlayerForReconnect();
|
||||
}
|
||||
// Normal update - only update isStreaming if player isn't actively playing
|
||||
else if (!playerPlaying) {
|
||||
isStreaming = stats.isLive;
|
||||
}
|
||||
|
||||
|
|
@ -323,6 +349,31 @@
|
|||
realm.viewerCount = stats.connections;
|
||||
}
|
||||
}
|
||||
|
||||
async function reinitializePlayerForReconnect() {
|
||||
// Remove old player instance
|
||||
if (player) {
|
||||
try {
|
||||
player.remove();
|
||||
} catch (e) {
|
||||
console.error('Error removing player:', e);
|
||||
}
|
||||
player = null;
|
||||
}
|
||||
|
||||
// Refresh viewer token (may have expired during offline period)
|
||||
const tokenSuccess = await getViewerToken();
|
||||
if (!tokenSuccess) {
|
||||
console.error('Failed to refresh viewer token for reconnect');
|
||||
playerInitializing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Small delay to ensure stream is fully available
|
||||
setTimeout(() => {
|
||||
initializePlayer();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function initializePlayer() {
|
||||
const playerElement = document.getElementById('player');
|
||||
|
|
@ -662,9 +713,7 @@
|
|||
|
||||
.player-wrapper {
|
||||
background: #000;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
margin-bottom: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
|
@ -695,27 +744,13 @@
|
|||
}
|
||||
|
||||
.stream-info-section {
|
||||
background: #111;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: #000;
|
||||
padding: 1rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stream-info-section::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background: var(--user-color, var(--primary));
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.stream-header {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.header-top {
|
||||
|
|
@ -868,9 +903,7 @@
|
|||
}
|
||||
|
||||
.chat-section {
|
||||
background: #111;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
flex: 1 1 0; /* Grow and shrink to fill remaining space */
|
||||
min-height: 0; /* Allow shrinking below content size */
|
||||
|
|
|
|||
|
|
@ -259,35 +259,19 @@
|
|||
|
||||
.player-wrapper {
|
||||
background: #000;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
.room-info-section {
|
||||
background: #111;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: #000;
|
||||
padding: 0.6rem 1rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.room-info-section::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background: var(--user-color, var(--primary));
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.room-header {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.header-top {
|
||||
|
|
@ -403,9 +387,7 @@
|
|||
}
|
||||
|
||||
.chat-section {
|
||||
background: #111;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: #000;
|
||||
overflow: hidden;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue