feat: link to nests from live streams header

This commit is contained in:
2025-05-07 14:15:07 +01:00
parent fb844a5969
commit ba62f0ef74
4 changed files with 80 additions and 25 deletions

View File

@ -10,6 +10,7 @@ import useLiveStreams from "@/Hooks/useLiveStreams";
import { findTag } from "@/Utils";
import Avatar from "../User/Avatar";
import { NestsParticipants } from "./nests-participants";
export function LiveStreams() {
const streams = useLiveStreams();
@ -17,9 +18,18 @@ export function LiveStreams() {
return (
<div className="flex mx-2 gap-4 overflow-x-auto sm-hide-scrollbar">
{streams.map(v => (
<LiveStreamEvent ev={v} key={`${v.kind}:${v.pubkey}:${findTag(v, "d")}`} className="h-[80px]" />
))}
{streams.map(v => {
const k = `${v.kind}:${v.pubkey}:${findTag(v, "d")}`;
const isVideoStream = v.tags.some(a => a[0] === "streaming" && a[1].includes(".m3u8"));
if (isVideoStream) {
return <LiveStreamEvent ev={v} key={k} className="h-[80px]" />;
}
const isNests = v.tags.some(a => a[0] === "streaming" && a[1].startsWith("wss+livekit://"));
if (isNests) {
return <AudioRoom ev={v} key={k} className="h-[80px]" />;
}
})}
</div>
);
}
@ -66,3 +76,37 @@ export function LiveStreamEvent({ ev, className }: { ev: NostrEvent; className?:
</Link>
);
}
export function AudioRoom({ ev, className }: { ev: NostrEvent; className?: string }) {
const { proxy } = useImgProxy();
const title = findTag(ev, "title");
const image = findTag(ev, "image");
const link = NostrLink.fromEvent(ev).encode();
const imageProxy = proxy(image ?? "");
return (
<Link className={classNames("flex gap-2", className)} to={`/${link}`}>
<div className="relative aspect-video">
<div
className="absolute h-full w-full bg-center bg-cover bg-gray-ultradark rounded-lg flex items-end justify-center"
style={
{
backgroundImage: `url(${imageProxy})`,
} as CSSProperties
}>
<div className="flex items-center gap-1">
<NestsParticipants ev={ev} />
</div>
</div>
<div className="absolute left-0 top-0 w-full overflow-hidden">
<div
className="whitespace-nowrap px-1 text-ellipsis overflow-hidden text-xs font-medium bg-background opacity-70 text-center"
title={title}>
{title}
</div>
</div>
</div>
</Link>
);
}

View File

@ -6,7 +6,7 @@ import {
useParticipantPermissions,
useParticipants,
} from "@livekit/components-react";
import { dedupe, unixNow } from "@snort/shared";
import { unixNow } from "@snort/shared";
import { EventKind, EventPublisher, NostrLink, RequestBuilder, SystemInterface, TaggedNostrEvent } from "@snort/system";
import { useRequestBuilder, useUserProfile } from "@snort/system-react";
import classNames from "classnames";
@ -22,9 +22,9 @@ import AsyncButton from "../Button/AsyncButton";
import IconButton from "../Button/IconButton";
import { ProxyImg } from "../ProxyImg";
import Avatar from "../User/Avatar";
import { AvatarGroup } from "../User/AvatarGroup";
import DisplayName from "../User/DisplayName";
import ProfileImage from "../User/ProfileImage";
import { NestsParticipants } from "./nests-participants";
import VuBar from "./VU";
enum RoomTab {
@ -116,11 +116,15 @@ function RoomHeader({ ev }: { ev: TaggedNostrEvent }) {
const { image, title } = extractStreamInfo(ev);
return (
<div className="relative rounded-xl h-[140px] w-full overflow-hidden">
{image ? <ProxyImg src={image} className="w-full" /> : <div className="absolute bg-gray-dark w-full h-full" />}
{image ? (
<ProxyImg src={image} className="w-full h-full object-cover object-center" />
) : (
<div className="absolute bg-gray-dark w-full h-full" />
)}
<div className="absolute left-4 top-4 w-full flex justify-between pr-8">
<div className="text-2xl">{title}</div>
<div className="flex gap-2 items-center">
<NostrParticipants ev={ev} />
<NestsParticipants ev={ev} />
</div>
</div>
</div>
@ -287,23 +291,6 @@ function WriteChatMessage({ ev }: { ev: TaggedNostrEvent }) {
);
}
function NostrParticipants({ ev }: { ev: TaggedNostrEvent }) {
const link = NostrLink.fromEvent(ev);
const sub = useMemo(() => {
const sub = new RequestBuilder(`livekit-participants:${link.tagKey}`);
sub
.withFilter()
.replyToLink([link])
.kinds([10_312 as EventKind])
.since(unixNow() - 600);
return sub;
}, [link.tagKey]);
const presense = useRequestBuilder(sub);
const filteredPresence = presense.filter(ev => ev.created_at > unixNow() - 600);
return <AvatarGroup ids={dedupe(filteredPresence.map(a => a.pubkey))} size={32} />;
}
function LiveKitUser({ p }: { p: RemoteParticipant | LocalParticipant }) {
const pubkey = p.identity.startsWith("guest-") ? "anon" : p.identity;
const profile = useUserProfile(pubkey);

View File

@ -0,0 +1,24 @@
import { dedupe, unixNow } from "@snort/shared";
import { EventKind, NostrLink, RequestBuilder, TaggedNostrEvent } from "@snort/system";
import { useRequestBuilder } from "@snort/system-react";
import { useMemo } from "react";
import { AvatarGroup } from "../User/AvatarGroup";
export function NestsParticipants({ ev }: { ev: TaggedNostrEvent }) {
const link = NostrLink.fromEvent(ev);
const sub = useMemo(() => {
const sub = new RequestBuilder(`livekit-participants:${link.tagKey}`);
sub.withOptions({ leaveOpen: true });
sub
.withFilter()
.replyToLink([link])
.kinds([10_312 as EventKind])
.since(unixNow() - 600);
return sub;
}, [link.tagKey]);
const presense = useRequestBuilder(sub);
const filteredPresence = presense.filter(ev => ev.created_at > unixNow() - 600);
return <AvatarGroup ids={dedupe(filteredPresence.map(a => a.pubkey)).slice(0, 5)} size={32} />;
}

View File

@ -11,7 +11,7 @@ export default function useLiveStreams() {
const rb = new RequestBuilder("streams");
rb.withFilter()
.kinds([EventKind.LiveEvent])
.since(unixNow() - Hour);
.since(unixNow() - 4 * Hour);
return rb;
}, []);