Live event page

This commit is contained in:
2023-06-18 14:14:34 +01:00
parent adaa8a71e7
commit 769e093663
11 changed files with 168 additions and 18 deletions

View File

@ -0,0 +1,11 @@
.live-chat {
height: calc(100vh - 73px);
display: flex;
flex-direction: column;
}
.live-chat > div:nth-child(1) {
flex-grow: 1;
display: flex;
flex-direction: column-reverse;
}

View File

@ -0,0 +1,63 @@
import "./LiveChat.css";
import { NostrLink, TaggedRawEvent } from "@snort/system";
import { useUserProfile } from "@snort/system-react";
import { useState } from "react";
import Textarea from "./Textarea";
import { useLiveChatFeed } from "Feed/LiveChatFeed";
import useEventPublisher from "Feed/EventPublisher";
import { System } from "index";
import { getDisplayName } from "Element/ProfileImage";
export function LiveChat({ ev, link }: { ev: TaggedRawEvent; link: NostrLink }) {
const [chat, setChat] = useState("");
const messages = useLiveChatFeed(link);
const pub = useEventPublisher();
async function sendChatMessage() {
const reply = await pub?.note(chat, eb => {
return eb.tag(["a", `${link.kind}:${link.author}:${link.id}`]);
});
if (reply) {
console.debug(reply);
System.BroadcastEvent(reply);
}
setChat("");
}
return (
<div className="live-chat">
<div>
{[...(messages.data ?? [])]
.sort((a, b) => b.created_at - a.created_at)
.map(a => (
<ChatMessage ev={a} key={a.id} />
))}
</div>
<div>
<Textarea
autoFocus={false}
className=""
onChange={v => setChat(v.target.value)}
value={chat}
onFocus={() => {}}
placeholder=""
onKeyDown={async e => {
if (e.code === "Enter") {
await sendChatMessage();
}
}}
/>
</div>
</div>
);
}
function ChatMessage({ ev }: { ev: TaggedRawEvent }) {
const profile = useUserProfile(System, ev.pubkey);
return (
<div>
<b>{getDisplayName(profile, ev.pubkey)}</b>
:&nbsp;
{ev.content}
</div>
);
}

View File

@ -1,11 +1,25 @@
import { NostrEvent } from "@snort/system";
import { findTag } from "SnortUtils";
import { LiveVideoPlayer } from "Element/LiveVideoPlayer";
import { NostrEvent, NostrPrefix, encodeTLV } from "@snort/system";
import { findTag, unwrap } from "SnortUtils";
import { FormattedMessage } from "react-intl";
import { Link } from "react-router-dom";
export function LiveEvent({ ev }: { ev: NostrEvent }) {
const stream = findTag(ev, "streaming");
if (stream) {
return <LiveVideoPlayer src={stream} />;
}
return null;
const title = findTag(ev, "title");
const d = unwrap(findTag(ev, "d"));
return (
<div className="text">
<div className="flex card">
<div className="f-grow">
<h3>{title}</h3>
</div>
<div>
<Link to={`/live/${encodeTLV(NostrPrefix.Address, d, undefined, ev.kind, ev.pubkey)}`}>
<button className="primary" type="button">
<FormattedMessage defaultMessage="Watch Live!" />
</button>
</Link>
</div>
</div>
</div>
);
}

View File

@ -1,19 +1,19 @@
import Hls from "hls.js";
import { HTMLProps, useEffect, useRef } from "react";
export function LiveVideoPlayer(props: HTMLProps<HTMLVideoElement>) {
export function LiveVideoPlayer(props: HTMLProps<HTMLVideoElement> & { stream: string }) {
const video = useRef<HTMLVideoElement>(null);
useEffect(() => {
if (props.src && video.current && !video.current.src && Hls.isSupported()) {
if (props.stream && video.current && !video.current.src && Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(props.src);
hls.loadSource(props.stream);
hls.attachMedia(video.current);
return () => hls.destroy();
}
}, [video, props]);
return (
<div className="w-max">
<video className="w-max" ref={video} controls={true} />
<div>
<video ref={video} {...props} controls={true} />
</div>
);
}

View File

@ -174,7 +174,7 @@ export function MediaElement(props: MediaElementProps) {
return <audio key={props.url} src={url} controls onError={() => probeFor402()} />;
} else if (props.mime.startsWith("video/")) {
if (props.url.endsWith(".m3u8")) {
return <LiveVideoPlayer src={props.url} />;
return <LiveVideoPlayer stream={props.url} />;
}
return <video key={props.url} src={url} controls onError={() => probeFor402()} />;
} else {