viewer counts
This commit is contained in:
@ -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(
|
||||||
|
@ -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} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
26
src/element/stream-time.tsx
Normal file
26
src/element/stream-time.tsx
Normal 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
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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 (
|
||||||
<>
|
<>
|
||||||
|
Reference in New Issue
Block a user