feat: show viewers

This commit is contained in:
2025-03-16 16:05:16 +01:00
parent acd5f5b5f4
commit a9924d19e4
8 changed files with 63 additions and 44 deletions

View File

@@ -37,11 +37,7 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64
build-args: |
BUILDKIT_INLINE_CACHE=1
- name: Emit a webhook to the server
env:

View File

@@ -12,7 +12,8 @@ const nextConfig = {
},
env: {
LIVE_SERVER_URL: process.env.NODE_ENV === 'production' ? 'https://backend.hctv.srizan.dev' : 'http://localhost:8888',
}
},
reactStrictMode: false,
};
export default nextConfig;

View File

@@ -1,4 +1,5 @@
import { lucia } from '@/lib/auth';
import prisma from '@/lib/db';
import { resolveUserPersonalChannel } from '@/lib/db/resolve';
import type { WebSocket } from 'ws';
@@ -54,6 +55,40 @@ export async function SOCKET(
}
});
});
await prisma.streamInfo.update({
where: {
username,
},
data: {
viewers: {
increment: 1,
},
},
});
client.on('close', async () => {
console.log('client disconnected');
const { viewers } = (await prisma.streamInfo.findUnique({
where: {
username,
},
select: {
viewers: true,
},
}))!;
await prisma.streamInfo.update({
where: {
username,
},
data: {
viewers: {
decrement: viewers > 0 ? 1 : 0,
set: viewers === 0 ? 0 : undefined,
},
},
});
});
}
function parseCookieString(cookie: string) {
@@ -66,4 +101,4 @@ function parseCookieString(cookie: string) {
interface ExtendedWebSocket extends WebSocket {
targetUsername: string;
}
}

View File

@@ -11,6 +11,7 @@ export async function GET(request: Request): Promise<Response> {
const state = url.searchParams.get("state");
const storedState = cookies.get("slack_oauth_state")?.value ?? null;
if (!code || !state || !storedState || state !== storedState) {
console.log('invalid state stuff');
return new Response(null, {
status: 400
});

View File

@@ -22,11 +22,6 @@ export default function Sidebar({ ...props }: React.ComponentProps<typeof UISide
const { stream, isLoading } = useStreams();
const [followedExpanded, setFollowedExpanded] = React.useState(true);
// console log stream every time it changes
React.useEffect(() => {
console.log('stream info', stream);
}, [stream]);
if (isLoading) return <SidebarSkeleton />;
const liveStreamers = stream?.filter((s) => s.isLive) || [];
@@ -97,7 +92,7 @@ function StreamerItem({ streamer }: { streamer: StreamInfoResponse[0] }) {
<div className="flex-1">
<p className="font-medium truncate">{streamer.username}</p>
<p className="text-sm truncate">{streamer.category}</p>
{false && streamer.isLive && (
{streamer.isLive && (
<p className="text-sm">
{streamer.viewers} viewer{streamer.viewers === 1 ? '' : 's'}
</p>

View File

@@ -2,6 +2,7 @@ import { Avatar, AvatarImage } from '@/components/ui/avatar';
import type { StreamInfo, User } from '@prisma/client';
import FollowButton from './follow';
import FollowCountText from './followCount';
import ViewerCount from './viewerCount';
export default function UserInfoCard(props: Props) {
return (
@@ -17,23 +18,12 @@ export default function UserInfoCard(props: Props) {
<FollowCountText channel={props.streamInfo.username} />
</div>
</div>
<FollowButton channel={props.streamInfo.username} />
<div className="flex items-center space-x-4">
<ViewerCount />
<FollowButton channel={props.streamInfo.username} />
</div>
</div>
<p className="mb-4">markdown description here</p>
{/* <div className="flex items-center space-x-4 text-gray-400">
<div className="flex items-center">
<Users className="h-5 w-5 mr-2" />
<span>1.2K viewers</span>
</div>
<Button variant="ghost" size="sm" className="text-gray-400 hover:text-white">
<Heart className="h-5 w-5 mr-2" />
Like
</Button>
<Button variant="ghost" size="sm" className="text-gray-400 hover:text-white">
<Share2 className="h-5 w-5 mr-2" />
Share
</Button>
</div> */}
</div>
);
}

View File

@@ -0,0 +1,17 @@
import { useStreams } from "@/lib/providers/StreamInfoProvider";
import { User } from "lucide-react";
export default function ViewerCount() {
const streamInfo = useStreams();
if (streamInfo.isLoading) return null;
const viewerCount = streamInfo.stream!.reduce((acc, stream) => acc + stream.viewers, 0);
return (
<div className="flex items-center space-x-2 *:text-destructive">
<span className="text-sm font-semibold"><User /></span>
<span className="text-sm">{viewerCount}</span>
</div>
);
}

View File

@@ -60,7 +60,6 @@ export async function syncStream() {
const data = await response.json();
const httpFlv = data['http-flv'] as HttpFlv;
// Handle case where the RTMP server is not available or doesn't have the expected data structure
if (!httpFlv?.servers?.[0]?.applications) {
return;
}
@@ -68,12 +67,10 @@ export async function syncStream() {
const channelLiveApp = httpFlv.servers[0].applications.find(app => app.name === 'channel-live');
const activeStreams = channelLiveApp?.live?.streams || [];
// Get all streams that are currently marked as live in the database
const currentLiveStreams = await prisma.streamInfo.findMany({
where: { isLive: true },
});
// Create a map of active streams from the RTMP server
const activeStreamMap = new Map();
for (const stream of activeStreams) {
activeStreamMap.set(stream.name, {
@@ -82,12 +79,10 @@ export async function syncStream() {
});
}
// Update all streams
for (const dbStream of currentLiveStreams) {
const streamStats = activeStreamMap.get(dbStream.username);
if (!streamStats || !streamStats.isLive) {
// Stream is no longer active, mark it as offline
await prisma.streamInfo.update({
where: { username: dbStream.username },
data: {
@@ -96,18 +91,9 @@ export async function syncStream() {
startedAt: new Date(0),
},
});
} else {
// Stream is still active, update viewers
await prisma.streamInfo.update({
where: { username: dbStream.username },
data: {
viewers: streamStats.viewers,
},
});
}
}
// Process new streams that aren't in the database yet
for (const stream of activeStreams) {
if (stream.active) {
const existingStream = await prisma.streamInfo.findUnique({
@@ -115,13 +101,11 @@ export async function syncStream() {
});
if (existingStream && !existingStream.isLive) {
// Stream just went live
await prisma.streamInfo.update({
where: { username: stream.name },
data: {
isLive: true,
startedAt: new Date(),
viewers: stream.clients.filter(c => !c.publishing).length,
},
});
}