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 { findTag } from "@/Utils";
|
||||||
|
|
||||||
import Avatar from "../User/Avatar";
|
import Avatar from "../User/Avatar";
|
||||||
|
import { NestsParticipants } from "./nests-participants";
|
||||||
|
|
||||||
export function LiveStreams() {
|
export function LiveStreams() {
|
||||||
const streams = useLiveStreams();
|
const streams = useLiveStreams();
|
||||||
@ -17,9 +18,18 @@ export function LiveStreams() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex mx-2 gap-4 overflow-x-auto sm-hide-scrollbar">
|
<div className="flex mx-2 gap-4 overflow-x-auto sm-hide-scrollbar">
|
||||||
{streams.map(v => (
|
{streams.map(v => {
|
||||||
<LiveStreamEvent ev={v} key={`${v.kind}:${v.pubkey}:${findTag(v, "d")}`} className="h-[80px]" />
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -66,3 +76,37 @@ export function LiveStreamEvent({ ev, className }: { ev: NostrEvent; className?:
|
|||||||
</Link>
|
</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,
|
useParticipantPermissions,
|
||||||
useParticipants,
|
useParticipants,
|
||||||
} from "@livekit/components-react";
|
} 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 { EventKind, EventPublisher, NostrLink, RequestBuilder, SystemInterface, TaggedNostrEvent } from "@snort/system";
|
||||||
import { useRequestBuilder, useUserProfile } from "@snort/system-react";
|
import { useRequestBuilder, useUserProfile } from "@snort/system-react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
@ -22,9 +22,9 @@ import AsyncButton from "../Button/AsyncButton";
|
|||||||
import IconButton from "../Button/IconButton";
|
import IconButton from "../Button/IconButton";
|
||||||
import { ProxyImg } from "../ProxyImg";
|
import { ProxyImg } from "../ProxyImg";
|
||||||
import Avatar from "../User/Avatar";
|
import Avatar from "../User/Avatar";
|
||||||
import { AvatarGroup } from "../User/AvatarGroup";
|
|
||||||
import DisplayName from "../User/DisplayName";
|
import DisplayName from "../User/DisplayName";
|
||||||
import ProfileImage from "../User/ProfileImage";
|
import ProfileImage from "../User/ProfileImage";
|
||||||
|
import { NestsParticipants } from "./nests-participants";
|
||||||
import VuBar from "./VU";
|
import VuBar from "./VU";
|
||||||
|
|
||||||
enum RoomTab {
|
enum RoomTab {
|
||||||
@ -116,11 +116,15 @@ function RoomHeader({ ev }: { ev: TaggedNostrEvent }) {
|
|||||||
const { image, title } = extractStreamInfo(ev);
|
const { image, title } = extractStreamInfo(ev);
|
||||||
return (
|
return (
|
||||||
<div className="relative rounded-xl h-[140px] w-full overflow-hidden">
|
<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="absolute left-4 top-4 w-full flex justify-between pr-8">
|
||||||
<div className="text-2xl">{title}</div>
|
<div className="text-2xl">{title}</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<NostrParticipants ev={ev} />
|
<NestsParticipants ev={ev} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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 }) {
|
function LiveKitUser({ p }: { p: RemoteParticipant | LocalParticipant }) {
|
||||||
const pubkey = p.identity.startsWith("guest-") ? "anon" : p.identity;
|
const pubkey = p.identity.startsWith("guest-") ? "anon" : p.identity;
|
||||||
const profile = useUserProfile(pubkey);
|
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");
|
const rb = new RequestBuilder("streams");
|
||||||
rb.withFilter()
|
rb.withFilter()
|
||||||
.kinds([EventKind.LiveEvent])
|
.kinds([EventKind.LiveEvent])
|
||||||
.since(unixNow() - Hour);
|
.since(unixNow() - 4 * Hour);
|
||||||
return rb;
|
return rb;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user