Live event page
This commit is contained in:
11
packages/app/src/Element/LiveChat.css
Normal file
11
packages/app/src/Element/LiveChat.css
Normal 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;
|
||||
}
|
63
packages/app/src/Element/LiveChat.tsx
Normal file
63
packages/app/src/Element/LiveChat.tsx
Normal 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>
|
||||
:
|
||||
{ev.content}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Reference in New Issue
Block a user