viewer counts

This commit is contained in:
2023-07-06 13:01:37 +01:00
parent ae4815de46
commit ac08d83231
6 changed files with 56 additions and 17 deletions

View File

@ -80,7 +80,7 @@ export function LiveChat({
(a, b) => b.created_at - a.created_at (a, b) => b.created_at - a.created_at
); );
}, [feed.messages, feed.zaps]); }, [feed.messages, feed.zaps]);
const { data: ev } = useEventFeed(link); const { data: ev } = useEventFeed(link, true);
const streamer = getHost(ev); const streamer = getHost(ev);
const naddr = useMemo(() => { const naddr = useMemo(() => {
return encodeTLV( return encodeTLV(

View File

@ -7,19 +7,22 @@ export enum VideoStatus {
Offline = "offline", Offline = "offline",
} }
export interface VideoPlayerProps {
stream?: string, status?: string, poster?: string
}
export function LiveVideoPlayer( export function LiveVideoPlayer(
props: HTMLProps<HTMLVideoElement> & { stream?: string } props: VideoPlayerProps
) { ) {
const video = useRef<HTMLVideoElement>(null); const video = useRef<HTMLVideoElement>(null);
const streamCached = useMemo(() => props.stream, [props.stream]); const streamCached = useMemo(() => props.stream, [props.stream]);
const [status, setStatus] = useState<VideoStatus>(); const [status, setStatus] = useState<VideoStatus>();
const [src, setSrc] = useState(props.src); const [src, setSrc] = useState<string>();
useEffect(() => { useEffect(() => {
if ( if (
streamCached && streamCached &&
video.current && video.current
!video.current.src
) { ) {
if (Hls.isSupported()) { if (Hls.isSupported()) {
try { try {
@ -50,22 +53,19 @@ export function LiveVideoPlayer(
video.current.load(); video.current.load();
} }
} }
}, [video, streamCached]); }, [video, streamCached, props.status]);
return ( return (
<> <>
<div className={status}> <div className={status}>
<div>{status}</div> <div>{status}</div>
</div> </div>
<video ref={video} {...{ <video ref={video} autoPlay={true} poster={props.poster} src={src} playsInline={true} controls={status === VideoStatus.Online} />
...props,
stream: undefined
}} src={src} playsInline={true} controls={status === VideoStatus.Online} />
</> </>
); );
} }
export function WebRTCPlayer(props: HTMLProps<HTMLVideoElement> & { stream?: string }) { export function WebRTCPlayer(props: VideoPlayerProps) {
const video = useRef<HTMLVideoElement>(null); const video = useRef<HTMLVideoElement>(null);
const streamCached = useMemo(() => "https://customer-uu10flpvos4pfhgu.cloudflarestream.com/7634aee1af35a2de4ac13ca3d1718a8b/webRTC/play", [props.stream]); const streamCached = useMemo(() => "https://customer-uu10flpvos4pfhgu.cloudflarestream.com/7634aee1af35a2de4ac13ca3d1718a8b/webRTC/play", [props.stream]);
const [status, setStatus] = useState<VideoStatus>(); const [status, setStatus] = useState<VideoStatus>();
@ -91,7 +91,7 @@ export function WebRTCPlayer(props: HTMLProps<HTMLVideoElement> & { stream?: str
<div className={status}> <div className={status}>
<div>{status}</div> <div>{status}</div>
</div> </div>
<video ref={video} {...props} controls={status === VideoStatus.Online} /> <video ref={video} autoPlay={true} poster={props.poster} controls={status === VideoStatus.Online} />
</> </>
); );
} }

View File

@ -0,0 +1,26 @@
import { useEffect, useState } from "react";
import { NostrEvent } from "@snort/system";
import { unixNow } from "@snort/shared";
import { findTag } from "../utils";
export function StreamTimer({ ev }: { ev?: NostrEvent }) {
const [time, setTime] = useState("");
function updateTime() {
const starts = Number(findTag(ev, "starts") ?? unixNow());
const diff = unixNow() - starts;
const hours = Number(diff / 60.0 / 60.0);
const mins = Number((diff / 60) % 60);
setTime(`${hours.toFixed(0).padStart(2, "0")}:${mins.toFixed(0).padStart(2, "0")}`);
}
useEffect(() => {
updateTime();
const t = setInterval(() => {
updateTime();
}, 1000);
return () => clearInterval(t);
}, []);
return time
}

View File

@ -35,7 +35,9 @@ export function RootPage() {
const aStatus = findTag(a, "status")!; const aStatus = findTag(a, "status")!;
const bStatus = findTag(b, "status")!; const bStatus = findTag(b, "status")!;
if (aStatus === bStatus) { if (aStatus === bStatus) {
return b.created_at > a.created_at ? 1 : -1; const aStart = Number(findTag(a, "starts") ?? "0");
const bStart = Number(findTag(b, "starts") ?? "0");
return bStart > aStart ? 1 : -1;
} else { } else {
return aStatus === "live" ? -1 : 1; return aStatus === "live" ? -1 : 1;
} }

View File

@ -20,7 +20,7 @@
width: 100%; width: 100%;
} }
@media (min-width: 768px){ @media (min-width: 768px) {
.video-content { .video-content {
height: calc(100vh - 64px); height: calc(100vh - 64px);
} }
@ -46,6 +46,11 @@
text-transform: uppercase; text-transform: uppercase;
} }
.pill.viewers {
color: white;
background: rgba(23, 23, 23, 0.70);
}
@media (min-width: 1020px) { @media (min-width: 1020px) {
.info { .info {
display: flex; display: flex;

View File

@ -16,6 +16,8 @@ import { useUserProfile } from "@snort/system-react";
import { NewStreamDialog } from "element/new-stream"; import { NewStreamDialog } from "element/new-stream";
import { Tags } from "element/tags"; import { Tags } from "element/tags";
import { StatePill } from "element/state-pill"; import { StatePill } from "element/state-pill";
import { formatSats } from "number";
import { StreamTimer } from "element/stream-time";
function ProfileInfo({ ev }: { ev?: NostrEvent }) { function ProfileInfo({ ev }: { ev?: NostrEvent }) {
const login = useLogin(); const login = useLogin();
@ -37,6 +39,7 @@ function ProfileInfo({ ev }: { ev?: NostrEvent }) {
} }
} }
const viewers = Number(findTag(ev, "current_participants") ?? "0");
return ( return (
<> <>
<div className="flex info"> <div className="flex info">
@ -46,6 +49,8 @@ function ProfileInfo({ ev }: { ev?: NostrEvent }) {
{ev && ( {ev && (
<Tags ev={ev}> <Tags ev={ev}>
<StatePill state={status as StreamState} /> <StatePill state={status as StreamState} />
{viewers > 0 && <span className="pill viewers">{formatSats(viewers)} viewers</span>}
{status === StreamState.Live && <span className="pill"><StreamTimer ev={ev} /></span>}
</Tags> </Tags>
)} )}
{isMine && ( {isMine && (
@ -85,10 +90,11 @@ function ProfileInfo({ ev }: { ev?: NostrEvent }) {
function VideoPlayer({ ev }: { ev?: NostrEvent }) { function VideoPlayer({ ev }: { ev?: NostrEvent }) {
const stream = findTag(ev, "streaming"); const stream = findTag(ev, "streaming");
const image = findTag(ev, "image"); const image = findTag(ev, "image");
const status = findTag(ev, "status");
return ( return (
<div className="video-content"> <div className="video-content">
<LiveVideoPlayer stream={stream} autoPlay={true} poster={image} /> <LiveVideoPlayer stream={stream} poster={image} status={status} />
</div> </div>
); );
} }
@ -96,7 +102,7 @@ function VideoPlayer({ ev }: { ev?: NostrEvent }) {
export function StreamPage() { export function StreamPage() {
const params = useParams(); const params = useParams();
const link = parseNostrLink(params.id!); const link = parseNostrLink(params.id!);
const { data: ev } = useEventFeed(link); const { data: ev } = useEventFeed(link, true);
return ( return (
<> <>