This commit is contained in:
parent
24d9a945b3
commit
c2bcc86527
12 changed files with 252 additions and 83 deletions
|
|
@ -83,7 +83,8 @@ export const filteredMessages = derived(
|
|||
const prev = filtered[i - 1];
|
||||
const sameUser = String(prev.userId) === String(msg.userId);
|
||||
const sameRealm = String(prev.realmId) === String(msg.realmId);
|
||||
const showHeader = !sameUser || !sameRealm || msg.usedRoll || msg.usedRtd;
|
||||
const hasSelfDestruct = msg.selfDestructAt && msg.selfDestructAt > 0;
|
||||
const showHeader = !sameUser || !sameRealm || msg.usedRoll || msg.usedRtd || hasSelfDestruct;
|
||||
return { ...msg, showHeader };
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@
|
|||
const sources = [
|
||||
{
|
||||
type: 'hls',
|
||||
file: `${proto}://${host}:${STREAM_PORT}/app/${actualStreamKey}/llhls.m3u8?token=${viewerToken}`,
|
||||
file: `${proto}://${host}:${STREAM_PORT}/app/${actualStreamKey}/llhls.m3u8?token=${encodeURIComponent(viewerToken)}`,
|
||||
label: 'LLHLS'
|
||||
}
|
||||
];
|
||||
|
|
@ -151,7 +151,7 @@
|
|||
// Only add token if not already present (segments don't have it)
|
||||
if (viewerToken && url.includes('/app/') && !url.includes('token=')) {
|
||||
const separator = url.includes('?') ? '&' : '?';
|
||||
xhr.open('GET', url + separator + 'token=' + viewerToken, true);
|
||||
xhr.open('GET', url + separator + 'token=' + encodeURIComponent(viewerToken), true);
|
||||
}
|
||||
xhr.withCredentials = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@
|
|||
const sources = [
|
||||
{
|
||||
type: 'hls',
|
||||
file: `${proto}://${host}:${STREAM_PORT}/app/${stream.streamKey}/llhls.m3u8?token=${token}`,
|
||||
file: `${proto}://${host}:${STREAM_PORT}/app/${stream.streamKey}/llhls.m3u8?token=${encodeURIComponent(token)}`,
|
||||
label: 'LLHLS'
|
||||
}
|
||||
];
|
||||
|
|
@ -163,9 +163,9 @@
|
|||
lowLatencyMode: true,
|
||||
backBufferLength: 30,
|
||||
xhrSetup: function(xhr, url) {
|
||||
if (token && url.includes('/app/')) {
|
||||
if (token && url.includes('/app/') && !url.includes('token=')) {
|
||||
const separator = url.includes('?') ? '&' : '?';
|
||||
xhr.open('GET', url + separator + 'token=' + token, true);
|
||||
xhr.open('GET', url + separator + 'token=' + encodeURIComponent(token), true);
|
||||
}
|
||||
xhr.withCredentials = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
} from '$lib/chat/chatStore';
|
||||
import { chatWebSocket } from '$lib/chat/chatWebSocket';
|
||||
import { chatLayout } from '$lib/stores/chatLayout';
|
||||
import { auth } from '$lib/stores/auth';
|
||||
import { auth, isAuthenticated } from '$lib/stores/auth';
|
||||
import {
|
||||
ttsEnabled,
|
||||
ttsSettings,
|
||||
|
|
@ -55,6 +55,7 @@
|
|||
let honkAudio = null;
|
||||
let honkSoundUrl = null;
|
||||
let mentionedMessageIds = new Set(); // Track which messages have already played honk
|
||||
let wasAuthenticated = false; // Track previous auth state for reconnect detection
|
||||
|
||||
$: isConnected = $connectionStatus === 'connected';
|
||||
|
||||
|
|
@ -63,6 +64,16 @@
|
|||
chatWebSocket.getParticipants();
|
||||
}
|
||||
|
||||
// Reconnect WebSocket when user logs in or registers while already connected as guest
|
||||
$: {
|
||||
const nowAuthenticated = $isAuthenticated;
|
||||
if (nowAuthenticated && !wasAuthenticated && isConnected) {
|
||||
console.log('[ChatPanel] Auth state changed to authenticated, reconnecting...');
|
||||
chatWebSocket.manualReconnect();
|
||||
}
|
||||
wasAuthenticated = nowAuthenticated;
|
||||
}
|
||||
|
||||
function toggleMenu() {
|
||||
showMenu = !showMenu;
|
||||
if (showMenu) {
|
||||
|
|
@ -192,11 +203,17 @@
|
|||
console.log('Chat connecting with realmId:', realmId, token ? '(authenticated)' : '(guest)');
|
||||
chatWebSocket.connect(realmId, token);
|
||||
|
||||
// Function to scroll to bottom
|
||||
// Function to scroll to newest messages (bottom for UP flow, top for DOWN flow)
|
||||
const scrollToBottom = () => {
|
||||
if (autoScroll && messagesContainer) {
|
||||
requestAnimationFrame(() => {
|
||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||
if ($chatLayout.messagesFromTop) {
|
||||
// column-reverse: newest at top, scroll to top
|
||||
messagesContainer.scrollTop = 0;
|
||||
} else {
|
||||
// normal: newest at bottom, scroll to bottom
|
||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -257,7 +274,13 @@
|
|||
|
||||
function handleScroll() {
|
||||
const { scrollTop, scrollHeight, clientHeight } = messagesContainer;
|
||||
autoScroll = scrollTop + clientHeight >= scrollHeight - 50;
|
||||
if ($chatLayout.messagesFromTop) {
|
||||
// column-reverse: user at "newest" means scrollTop near 0
|
||||
autoScroll = scrollTop <= 50;
|
||||
} else {
|
||||
// normal: user at bottom means scrollTop + clientHeight near scrollHeight
|
||||
autoScroll = scrollTop + clientHeight >= scrollHeight - 50;
|
||||
}
|
||||
}
|
||||
|
||||
function handleDeleteMessage(messageId) {
|
||||
|
|
@ -640,7 +663,6 @@
|
|||
<ChatMessage
|
||||
{message}
|
||||
showHeader={message.showHeader ?? false}
|
||||
headerBelow={$chatLayout.messagesFromTop}
|
||||
currentUserId={$chatUserInfo.userId}
|
||||
currentUsername={$chatUserInfo.username}
|
||||
currentRealmId={realmId}
|
||||
|
|
|
|||
|
|
@ -268,6 +268,11 @@
|
|||
}
|
||||
|
||||
.stream-thumbnail .placeholder-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
|
@ -276,6 +281,16 @@
|
|||
z-index: 0;
|
||||
}
|
||||
|
||||
.stream-thumbnail .offline-placeholder-img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.stream-thumbnail .placeholder-initial {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
|
|
@ -288,6 +303,7 @@
|
|||
font-weight: 700;
|
||||
color: white;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.stream-thumbnail .live-pulse {
|
||||
|
|
@ -984,13 +1000,15 @@
|
|||
<a href={`/${stream.name}/live`} class="stream-card">
|
||||
<div class="stream-thumbnail">
|
||||
<div class="live-badge">LIVE</div>
|
||||
{#if stream.streamKey}
|
||||
<img
|
||||
src={`/thumb/${stream.streamKey}.webp`}
|
||||
alt={stream.name}
|
||||
/>
|
||||
{/if}
|
||||
<img
|
||||
src={`/thumb/${encodeURIComponent(stream.name)}.webp`}
|
||||
alt={stream.name}
|
||||
on:error={(e) => e.target.style.display = 'none'}
|
||||
/>
|
||||
<div class="placeholder-container">
|
||||
{#if stream.offlineImageUrl}
|
||||
<img src={stream.offlineImageUrl} alt="" class="offline-placeholder-img" />
|
||||
{/if}
|
||||
<div class="placeholder-initial">
|
||||
<div class="live-pulse"></div>
|
||||
{stream.name.charAt(0).toUpperCase()}
|
||||
|
|
|
|||
|
|
@ -322,12 +322,12 @@
|
|||
sources.push(
|
||||
{
|
||||
type: 'hls',
|
||||
file: `${httpProto}://${host}:${STREAM_PORT}/app/${streamKey}/llhls.m3u8?token=${viewerToken}`,
|
||||
file: `${httpProto}://${host}:${STREAM_PORT}/app/${streamKey}/llhls.m3u8?token=${encodeURIComponent(viewerToken)}`,
|
||||
label: 'LLHLS (Low Latency)'
|
||||
},
|
||||
{
|
||||
type: 'hls',
|
||||
file: `${httpProto}://${host}:${STREAM_PORT}/app/${streamKey}/ts:playlist.m3u8?token=${viewerToken}`,
|
||||
file: `${httpProto}://${host}:${STREAM_PORT}/app/${streamKey}/ts:playlist.m3u8?token=${encodeURIComponent(viewerToken)}`,
|
||||
label: 'HLS (Standard)'
|
||||
},
|
||||
{
|
||||
|
|
@ -361,7 +361,7 @@
|
|||
// Only add if token is not already present in the URL
|
||||
if (viewerToken && url.includes('/app/') && !url.includes('token=')) {
|
||||
const separator = url.includes('?') ? '&' : '?';
|
||||
xhr.open('GET', url + separator + 'token=' + viewerToken, true);
|
||||
xhr.open('GET', url + separator + 'token=' + encodeURIComponent(viewerToken), true);
|
||||
}
|
||||
xhr.withCredentials = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@
|
|||
// Helper to get auth token for WebSocket connections
|
||||
async function getAuthToken() {
|
||||
if (!browser) return null;
|
||||
// Guests don't have auth tokens - skip the API call to avoid 401 error
|
||||
if (!$auth.user) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/api/user/token', { credentials: 'include' });
|
||||
if (response.ok) {
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@
|
|||
import ChatPanel from '$lib/components/chat/ChatPanel.svelte';
|
||||
|
||||
let realmId = null;
|
||||
let ready = false;
|
||||
|
||||
onMount(() => {
|
||||
// Parse realm ID from URL parameter
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
realmId = params.get('realm');
|
||||
realmId = params.get('realm') || ''; // Empty string for global chat
|
||||
ready = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -18,7 +20,11 @@
|
|||
</svelte:head>
|
||||
|
||||
<div class="popout-container">
|
||||
<ChatPanel {realmId} chatEnabled={true} chatGuestsAllowed={true} hideTheaterMode={true} />
|
||||
{#if ready}
|
||||
<ChatPanel {realmId} chatEnabled={true} chatGuestsAllowed={true} hideTheaterMode={true} />
|
||||
{:else}
|
||||
<div class="loading-chat">Connecting to chat...</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
@ -64,4 +70,13 @@
|
|||
.popout-container :global(.chat-input) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.loading-chat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: #888;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue