forked from Kieran/zap.stream
fix: redirect to stream after starting it
This commit is contained in:
parent
15ac0cacb3
commit
28e8b5fffa
@ -8,6 +8,7 @@
|
|||||||
"@testing-library/react": "^13.0.0",
|
"@testing-library/react": "^13.0.0",
|
||||||
"@testing-library/user-event": "^13.2.1",
|
"@testing-library/user-event": "^13.2.1",
|
||||||
"hls.js": "^1.4.6",
|
"hls.js": "^1.4.6",
|
||||||
|
"nostr-tools": "^1.12.0",
|
||||||
"qr-code-styling": "^1.6.0-rc.1",
|
"qr-code-styling": "^1.6.0-rc.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
@ -6,93 +6,115 @@ import AsyncButton from "./async-button";
|
|||||||
import { System } from "index";
|
import { System } from "index";
|
||||||
import { findTag } from "utils";
|
import { findTag } from "utils";
|
||||||
|
|
||||||
export function NewStream({ ev, onFinish }: { ev?: NostrEvent, onFinish: () => void }) {
|
export function NewStream({
|
||||||
const [title, setTitle] = useState(findTag(ev, "title") ?? "");
|
ev,
|
||||||
const [summary, setSummary] = useState(findTag(ev, "summary") ?? "");
|
onFinish,
|
||||||
const [image, setImage] = useState(findTag(ev, "image") ?? "");
|
}: {
|
||||||
const [stream, setStream] = useState(findTag(ev, "streaming") ?? "");
|
ev?: NostrEvent;
|
||||||
const [isValid, setIsValid] = useState(false);
|
onFinish: (ev: NostrEvent) => void;
|
||||||
|
}) {
|
||||||
|
const [title, setTitle] = useState(findTag(ev, "title") ?? "");
|
||||||
|
const [summary, setSummary] = useState(findTag(ev, "summary") ?? "");
|
||||||
|
const [image, setImage] = useState(findTag(ev, "image") ?? "");
|
||||||
|
const [stream, setStream] = useState(findTag(ev, "streaming") ?? "");
|
||||||
|
const [isValid, setIsValid] = useState(false);
|
||||||
|
|
||||||
function validate() {
|
function validate() {
|
||||||
if (title.length < 2) {
|
if (title.length < 2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
if (stream.length < 5 || !stream.match(/^https?:\/\/.*\.m3u8?$/i)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (image.length > 0 && !image.match(/^https?:\/\//i)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
if (stream.length < 5 || !stream.match(/^https?:\/\/.*\.m3u8?$/i)) {
|
||||||
useEffect(() => {
|
return false;
|
||||||
setIsValid(validate());
|
|
||||||
}, [title, summary, image, stream]);
|
|
||||||
|
|
||||||
async function publishStream() {
|
|
||||||
const pub = await EventPublisher.nip7();
|
|
||||||
if (pub) {
|
|
||||||
const evNew = await pub.generic(eb => {
|
|
||||||
const now = unixNow();
|
|
||||||
const dTag = findTag(ev, "d") ?? now.toString();
|
|
||||||
return eb.kind(30_311)
|
|
||||||
.tag(["d", dTag])
|
|
||||||
.tag(["title", title])
|
|
||||||
.tag(["summary", summary])
|
|
||||||
.tag(["image", image])
|
|
||||||
.tag(["streaming", stream])
|
|
||||||
.tag(["status", "live"])
|
|
||||||
});
|
|
||||||
console.debug(evNew);
|
|
||||||
System.BroadcastEvent(evNew);
|
|
||||||
onFinish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (image.length > 0 && !image.match(/^https?:\/\//i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="new-stream">
|
useEffect(() => {
|
||||||
<h3>
|
setIsValid(validate());
|
||||||
{ev ? "Edit Stream" : "New Stream"}
|
}, [title, summary, image, stream]);
|
||||||
</h3>
|
|
||||||
<div>
|
async function publishStream() {
|
||||||
<p>
|
const pub = await EventPublisher.nip7();
|
||||||
Title
|
if (pub) {
|
||||||
</p>
|
const evNew = await pub.generic((eb) => {
|
||||||
<div className="input">
|
const now = unixNow();
|
||||||
<input type="text" placeholder="What are we steaming today?" value={title} onChange={e => setTitle(e.target.value)} />
|
const dTag = findTag(ev, "d") ?? now.toString();
|
||||||
</div>
|
return eb
|
||||||
|
.kind(30_311)
|
||||||
|
.tag(["d", dTag])
|
||||||
|
.tag(["title", title])
|
||||||
|
.tag(["summary", summary])
|
||||||
|
.tag(["image", image])
|
||||||
|
.tag(["streaming", stream])
|
||||||
|
.tag(["status", "live"]);
|
||||||
|
});
|
||||||
|
console.debug(evNew);
|
||||||
|
//System.BroadcastEvent(evNew);
|
||||||
|
onFinish(evNew);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="new-stream">
|
||||||
|
<h3>{ev ? "Edit Stream" : "New Stream"}</h3>
|
||||||
|
<div>
|
||||||
|
<p>Title</p>
|
||||||
|
<div className="input">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="What are we steaming today?"
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<p>
|
<div>
|
||||||
Summary
|
<p>Summary</p>
|
||||||
</p>
|
<div className="input">
|
||||||
<div className="input">
|
<input
|
||||||
<input type="text" placeholder="A short description of the content" value={summary} onChange={e => setSummary(e.target.value)} />
|
type="text"
|
||||||
</div>
|
placeholder="A short description of the content"
|
||||||
|
value={summary}
|
||||||
|
onChange={(e) => setSummary(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<p>
|
<div>
|
||||||
Cover image
|
<p>Cover image</p>
|
||||||
</p>
|
<div className="input">
|
||||||
<div className="input">
|
<input
|
||||||
<input type="text" placeholder="https://" value={image} onChange={e => setImage(e.target.value)} />
|
type="text"
|
||||||
</div>
|
placeholder="https://"
|
||||||
|
value={image}
|
||||||
|
onChange={(e) => setImage(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<p>
|
<div>
|
||||||
Stream Url
|
<p>Stream Url</p>
|
||||||
</p>
|
<div className="input">
|
||||||
<div className="input">
|
<input
|
||||||
<input type="text" placeholder="https://" value={stream} onChange={e => setStream(e.target.value)} />
|
type="text"
|
||||||
</div>
|
placeholder="https://"
|
||||||
<small>
|
value={stream}
|
||||||
Stream type should be HLS
|
onChange={(e) => setStream(e.target.value)}
|
||||||
</small>
|
/>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<AsyncButton type="button" className="btn btn-primary" disabled={!isValid} onClick={publishStream}>
|
|
||||||
{ev ? "Save" : "Start Stream"}
|
|
||||||
</AsyncButton>
|
|
||||||
</div>
|
</div>
|
||||||
|
<small>Stream type should be HLS</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<AsyncButton
|
||||||
|
type="button"
|
||||||
|
className="btn btn-primary"
|
||||||
|
disabled={!isValid}
|
||||||
|
onClick={publishStream}
|
||||||
|
>
|
||||||
|
{ev ? "Save" : "Start Stream"}
|
||||||
|
</AsyncButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { Icon } from "element/icon";
|
import { Icon } from "element/icon";
|
||||||
import "./layout.css";
|
import "./layout.css";
|
||||||
import { EventPublisher } from "@snort/system";
|
import { EventPublisher, NostrEvent } from "@snort/system";
|
||||||
|
import { nip19 } from "nostr-tools";
|
||||||
import { Outlet, useNavigate } from "react-router-dom";
|
import { Outlet, useNavigate } from "react-router-dom";
|
||||||
import AsyncButton from "element/async-button";
|
import AsyncButton from "element/async-button";
|
||||||
import { Login } from "index";
|
import { Login } from "index";
|
||||||
@ -11,59 +12,83 @@ import { NewStream } from "element/new-stream";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export function LayoutPage() {
|
export function LayoutPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const [newStream, setNewStream] = useState(false);
|
const [newStream, setNewStream] = useState(false);
|
||||||
|
|
||||||
async function doLogin() {
|
async function doLogin() {
|
||||||
const pub = await EventPublisher.nip7();
|
const pub = await EventPublisher.nip7();
|
||||||
if (pub) {
|
if (pub) {
|
||||||
Login.loginWithPubkey(pub.pubKey);
|
Login.loginWithPubkey(pub.pubKey);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function loggedIn() {
|
function loggedIn() {
|
||||||
if (!login) return;
|
if (!login) return;
|
||||||
|
|
||||||
return <>
|
return (
|
||||||
<button type="button" className="btn btn-primary" onClick={() => setNewStream(true)}>
|
<>
|
||||||
New Stream
|
<button
|
||||||
<Icon name="signal" />
|
type="button"
|
||||||
</button>
|
className="btn btn-primary"
|
||||||
<Profile pubkey={login.pubkey} options={{
|
onClick={() => setNewStream(true)}
|
||||||
showName: false
|
>
|
||||||
}} />
|
New Stream
|
||||||
</>
|
<Icon name="signal" />
|
||||||
|
</button>
|
||||||
|
<Profile
|
||||||
|
pubkey={login.pubkey}
|
||||||
|
options={{
|
||||||
|
showName: false,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loggedOut() {
|
||||||
|
if (login) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AsyncButton type="button" className="btn btn-border" onClick={doLogin}>
|
||||||
|
Login
|
||||||
|
<Icon name="login" />
|
||||||
|
</AsyncButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToStream(ev: NostrEvent) {
|
||||||
|
const addr = {
|
||||||
|
pubkey: ev.pubkey,
|
||||||
|
kind: ev.kind,
|
||||||
|
identifier: ev.tags.find(t => t.at(0) === "d")?.at(1) || "",
|
||||||
}
|
}
|
||||||
|
const naddr = nip19.naddrEncode(addr)
|
||||||
|
navigate(`/live/${naddr}`);
|
||||||
|
setNewStream(false)
|
||||||
|
}
|
||||||
|
|
||||||
function loggedOut() {
|
return (
|
||||||
if (login) return;
|
<>
|
||||||
|
<header>
|
||||||
return <>
|
<div onClick={() => navigate("/")}>S</div>
|
||||||
<AsyncButton type="button" className="btn btn-border" onClick={doLogin}>
|
<div className="input">
|
||||||
Login
|
<input type="text" placeholder="Search" />
|
||||||
<Icon name="login" />
|
<Icon name="search" size={15} />
|
||||||
</AsyncButton>
|
</div>
|
||||||
</>
|
<div>
|
||||||
}
|
{loggedIn()}
|
||||||
|
{loggedOut()}
|
||||||
return <>
|
</div>
|
||||||
<header>
|
</header>
|
||||||
<div onClick={() => navigate("/")}>
|
<Outlet />
|
||||||
S
|
{newStream && (
|
||||||
</div>
|
<Modal onClose={() => setNewStream(false)}>
|
||||||
<div className="input">
|
<NewStream onFinish={goToStream} />
|
||||||
<input type="text" placeholder="Search" />
|
</Modal>
|
||||||
<Icon name="search" size={15} />
|
)}
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{loggedIn()}
|
|
||||||
{loggedOut()}
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<Outlet />
|
|
||||||
{newStream && <Modal onClose={() => setNewStream(false)} >
|
|
||||||
<NewStream onFinish={() => navigate("/")} />
|
|
||||||
</Modal>}
|
|
||||||
</>
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
44
yarn.lock
44
yarn.lock
@ -1646,6 +1646,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
eslint-scope "5.1.1"
|
eslint-scope "5.1.1"
|
||||||
|
|
||||||
|
"@noble/curves@1.0.0", "@noble/curves@~1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.0.0.tgz#e40be8c7daf088aaf291887cbc73f43464a92932"
|
||||||
|
integrity sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==
|
||||||
|
dependencies:
|
||||||
|
"@noble/hashes" "1.3.0"
|
||||||
|
|
||||||
"@noble/curves@^1.0.0", "@noble/curves@^1.1.0":
|
"@noble/curves@^1.0.0", "@noble/curves@^1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d"
|
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d"
|
||||||
@ -1653,7 +1660,12 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@noble/hashes" "1.3.1"
|
"@noble/hashes" "1.3.1"
|
||||||
|
|
||||||
"@noble/hashes@1.3.1", "@noble/hashes@^1.3.1":
|
"@noble/hashes@1.3.0":
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1"
|
||||||
|
integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==
|
||||||
|
|
||||||
|
"@noble/hashes@1.3.1", "@noble/hashes@^1.3.1", "@noble/hashes@~1.3.0":
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9"
|
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9"
|
||||||
integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==
|
integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==
|
||||||
@ -1741,11 +1753,28 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.3.2.tgz#31b9c510d8cada9683549e1dbb4284cca5001faf"
|
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.3.2.tgz#31b9c510d8cada9683549e1dbb4284cca5001faf"
|
||||||
integrity sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw==
|
integrity sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw==
|
||||||
|
|
||||||
"@scure/base@1.1.1", "@scure/base@^1.1.1":
|
"@scure/base@1.1.1", "@scure/base@^1.1.1", "@scure/base@~1.1.0":
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938"
|
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938"
|
||||||
integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==
|
integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==
|
||||||
|
|
||||||
|
"@scure/bip32@1.3.0":
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.0.tgz#6c8d980ef3f290987736acd0ee2e0f0d50068d87"
|
||||||
|
integrity sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q==
|
||||||
|
dependencies:
|
||||||
|
"@noble/curves" "~1.0.0"
|
||||||
|
"@noble/hashes" "~1.3.0"
|
||||||
|
"@scure/base" "~1.1.0"
|
||||||
|
|
||||||
|
"@scure/bip39@1.2.0":
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.0.tgz#a207e2ef96de354de7d0002292ba1503538fc77b"
|
||||||
|
integrity sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==
|
||||||
|
dependencies:
|
||||||
|
"@noble/hashes" "~1.3.0"
|
||||||
|
"@scure/base" "~1.1.0"
|
||||||
|
|
||||||
"@sinclair/typebox@^0.24.1":
|
"@sinclair/typebox@^0.24.1":
|
||||||
version "0.24.51"
|
version "0.24.51"
|
||||||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f"
|
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f"
|
||||||
@ -6644,6 +6673,17 @@ normalize-url@^6.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
|
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
|
||||||
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
|
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
|
||||||
|
|
||||||
|
nostr-tools@^1.12.0:
|
||||||
|
version "1.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-1.12.0.tgz#ec3618fc2298e029941b7db3bbe95187777c488f"
|
||||||
|
integrity sha512-fsIXaNJPKaSrO9MxsCEWbhI4tG4pToQK4D4sgLRD0fRDfZ6ocCg8CLlh9lcNx0o8pVErCMLVASxbJ+w4WNK0MA==
|
||||||
|
dependencies:
|
||||||
|
"@noble/curves" "1.0.0"
|
||||||
|
"@noble/hashes" "1.3.0"
|
||||||
|
"@scure/base" "1.1.1"
|
||||||
|
"@scure/bip32" "1.3.0"
|
||||||
|
"@scure/bip39" "1.2.0"
|
||||||
|
|
||||||
npm-run-path@^4.0.1:
|
npm-run-path@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
|
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
|
||||||
|
Loading…
Reference in New Issue
Block a user