feat: link to nests from live streams header
This commit is contained in:
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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} />;
|
||||
}
|
@ -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;
|
||||
}, []);
|
||||
|
||||
|
Reference in New Issue
Block a user