From e35fa1053164b24d6ca27659abe949e0f604d308 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 17 Aug 2023 14:26:30 -0700 Subject: [PATCH 1/3] Split the site into separate pages. --- .postcssrc | 8 +-- .posthtmlrc | 7 ++ package-lock.json | 58 ++++++++++++++++ package.json | 10 ++- src/app.tsx | 84 ----------------------- src/{index.css => common/base.css} | 0 src/{img => common}/logo.svg | 0 src/explained.html | 14 ++++ src/header.html | 12 ++-- src/index.html | 29 ++------ src/index.tsx | 21 ------ src/publish.html | 24 +++++++ src/publish/index.tsx | 72 ++++++++++++++++++++ src/publish/preview.tsx | 24 +++++++ src/{publish.tsx => publish/setup.tsx} | 21 ------ src/watch.html | 24 +++++++ src/{watch.tsx => watch/controls.tsx} | 94 ++------------------------ src/watch/index.tsx | 71 +++++++++++++++++++ src/watch/setup.tsx | 90 ++++++++++++++++++++++++ 19 files changed, 410 insertions(+), 253 deletions(-) create mode 100644 .posthtmlrc delete mode 100644 src/app.tsx rename src/{index.css => common/base.css} (100%) rename src/{img => common}/logo.svg (100%) create mode 100644 src/explained.html delete mode 100644 src/index.tsx create mode 100644 src/publish.html create mode 100644 src/publish/index.tsx create mode 100644 src/publish/preview.tsx rename src/{publish.tsx => publish/setup.tsx} (96%) create mode 100644 src/watch.html rename src/{watch.tsx => watch/controls.tsx} (57%) create mode 100644 src/watch/index.tsx create mode 100644 src/watch/setup.tsx diff --git a/.postcssrc b/.postcssrc index 0985cb2..1e9edb2 100644 --- a/.postcssrc +++ b/.postcssrc @@ -1,5 +1,5 @@ { - "plugins": { - "tailwindcss": {} - } -} + "plugins": { + "tailwindcss": {} + } +} \ No newline at end of file diff --git a/.posthtmlrc b/.posthtmlrc new file mode 100644 index 0000000..9cd9705 --- /dev/null +++ b/.posthtmlrc @@ -0,0 +1,7 @@ +{ + "plugins": { + "posthtml-include": { + "root": "./src" + } + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e856451..857d6b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "eslint-plugin-prettier": "^5.0.0", "parcel": "^2.9.2", "postcss": "^8.4.24", + "posthtml-include": "^1.7.4", "prettier": "^3.0.1", "prettier-plugin-tailwindcss": "^0.5.2", "tailwindcss": "^3.3.2", @@ -4486,6 +4487,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fclone": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", + "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", + "dev": true + }, "node_modules/file-entry-cache": { "version": "6.0.1", "dev": true, @@ -5966,6 +5973,57 @@ "node": ">=12.0.0" } }, + "node_modules/posthtml-expressions": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/posthtml-expressions/-/posthtml-expressions-1.11.1.tgz", + "integrity": "sha512-2zA5SRM7quupTGa422xH72T0n3tF5quZeZ66czmMa/4QAj8HFzCTlN5l42wWVjLCxj7XOmMmQJEeK0+p3AgH+w==", + "dev": true, + "dependencies": { + "fclone": "^1.0.11", + "posthtml": "^0.16.5", + "posthtml-match-helper": "^1.0.1", + "posthtml-parser": "^0.10.0", + "posthtml-render": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/posthtml-include": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/posthtml-include/-/posthtml-include-1.7.4.tgz", + "integrity": "sha512-GO5QzHiM6/fXq8DxLoLN+jEW4sH/6nuGF9z+NJmP1qi1A3J2zCC7WwXrEwaPL3T8LrH+FL4IedK+mIJHbn5ZEA==", + "dev": true, + "dependencies": { + "posthtml": "^0.16.6", + "posthtml-expressions": "^1.7.1", + "posthtml-parser": "^0.11.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/posthtml-include/node_modules/posthtml-parser": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", + "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", + "dev": true, + "dependencies": { + "htmlparser2": "^7.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/posthtml-match-helper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/posthtml-match-helper/-/posthtml-match-helper-1.0.3.tgz", + "integrity": "sha512-aeRAPvok2Fs6uzSm85665jdAk5UOd8US2QCkWtGU6yLPlKSwzWTSgZZuABc3UeNy3K1lVk/HV9bRkWJYN05Ymw==", + "dev": true, + "peerDependencies": { + "posthtml": ">=0.5.0" + } + }, "node_modules/posthtml-parser": { "version": "0.10.2", "dev": true, diff --git a/package.json b/package.json index 824eaf7..10578e4 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,12 @@ "version": "0.0.1", "description": "Website for quic.video", "license": "(MIT OR Apache-2.0)", - "source": "src/index.html", + "source": [ + "src/index.html", + "src/explained.html", + "src/publish.html", + "src/watch.html" + ], "scripts": { "serve": "parcel serve --https --cert cert/localhost.crt --key cert/localhost.key --port 4444 --open", "build": "parcel build", @@ -34,6 +39,7 @@ "eslint-plugin-prettier": "^5.0.0", "parcel": "^2.9.2", "postcss": "^8.4.24", + "posthtml-include": "^1.7.4", "prettier": "^3.0.1", "prettier-plugin-tailwindcss": "^0.5.2", "tailwindcss": "^3.3.2", @@ -43,4 +49,4 @@ "@kixelated/moq": "^0.0.7", "solid-js": "^1.7.7" } -} +} \ No newline at end of file diff --git a/src/app.tsx b/src/app.tsx deleted file mode 100644 index f1d1d76..0000000 --- a/src/app.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Client } from "@kixelated/moq/transport" -import { asError } from "@kixelated/moq/common" - -import { createSignal, createEffect, Show, createMemo } from "solid-js" - -import * as Watch from "./watch" -import * as Publish from "./publish" - -import { Player } from "@kixelated/moq/playback" -import { Broadcast } from "@kixelated/moq/contribute" -import { Connection } from "@kixelated/moq/transport" - -export function App(props: { url: string }) { - const [error, setError] = createSignal() - const [connection, setConnection] = createSignal() - - const fingerprint = process.env.NODE_ENV !== "production" ? props.url + "/fingerprint" : undefined - - createEffect(async () => { - try { - const client = new Client({ - url: props.url, - role: "both", - fingerprint, - }) - - const conn = await client.connect() - - setConnection(conn) - await conn.run() - } catch (e) { - setError(asError(e)) - } finally { - setConnection() - } - }) - - createEffect(() => { - const err = error() - if (err) console.error(err) - }) - - const [player, setPlayer] = createSignal() - const [broadcast, setBroadcast] = createSignal() - const setup = createMemo(() => !player() && !broadcast()) - - return ( -
- -
- {error()?.name}: {error()?.message} -
-
-
- - - -
- -
- - - -
- -
-
-

Watch

- - - -
-
-
-

Publish

- -
-
-
- ) -} diff --git a/src/index.css b/src/common/base.css similarity index 100% rename from src/index.css rename to src/common/base.css diff --git a/src/img/logo.svg b/src/common/logo.svg similarity index 100% rename from src/img/logo.svg rename to src/common/logo.svg diff --git a/src/explained.html b/src/explained.html new file mode 100644 index 0000000..e42671b --- /dev/null +++ b/src/explained.html @@ -0,0 +1,14 @@ + + + + + quic.video + + + + + + +
+ + diff --git a/src/header.html b/src/header.html index f136864..c62e8d5 100644 --- a/src/header.html +++ b/src/header.html @@ -1,14 +1,14 @@
-
- Media over QUIC +
+ Media over QUIC
- + - - -
- -
- - + + +
diff --git a/src/index.tsx b/src/index.tsx deleted file mode 100644 index 355337d..0000000 --- a/src/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { App } from "./app" - -import { render } from "solid-js/web" - -const params = new URLSearchParams(window.location.search) - -let url = params.get("url") - -// Change the default URL based on the environment. -if (process.env.NODE_ENV === "production") { - url ??= "https://moq-demo.englishm.net:4443" -} else { - url ??= "https://localhost:4443" -} - -const app = document.getElementById("app") -if (!app) { - throw new Error("no container") -} - -render(() => , app) diff --git a/src/publish.html b/src/publish.html new file mode 100644 index 0000000..22637db --- /dev/null +++ b/src/publish.html @@ -0,0 +1,24 @@ + + + + + quic.video + + + + + + +
+ + + + diff --git a/src/publish/index.tsx b/src/publish/index.tsx new file mode 100644 index 0000000..c0eaec6 --- /dev/null +++ b/src/publish/index.tsx @@ -0,0 +1,72 @@ +import { Broadcast } from "@kixelated/moq/contribute" +import { Connection, Client } from "@kixelated/moq/transport" +import { asError } from "@kixelated/moq/common" + +import { createEffect, createSignal, Match, Show, Switch } from "solid-js" + +import { Preview } from "./preview" +import { Setup } from "./setup" + +export function Main() { + const [error, setError] = createSignal() + const [connection, setConnection] = createSignal() + + const params = new URLSearchParams(window.location.search) + + let url = params.get("url") ?? undefined + let fingerprint = params.get("fingerprint") ?? undefined + + // Change the default URL based on the environment. + if (process.env.NODE_ENV === "production") { + url ??= "https://moq-demo.englishm.net:4443" + } else { + url ??= "https://localhost:4443" + fingerprint ??= url + "/fingerprint" + } + + const client = new Client({ + url, + role: "both", + fingerprint, + }) + + createEffect(async () => { + try { + const conn = await client.connect() + setConnection(conn) + await conn.run() + } catch (e) { + setError(asError(e)) + } finally { + setConnection() + } + }) + + createEffect(() => { + const err = error() + if (err) console.error(err) + }) + + const [broadcast, setBroadcast] = createSignal() + + return ( +
+ +
+ {error()?.name}: {error()?.message} +
+
+ +
+ + + + + + + + +
+
+ ) +} diff --git a/src/publish/preview.tsx b/src/publish/preview.tsx new file mode 100644 index 0000000..823942a --- /dev/null +++ b/src/publish/preview.tsx @@ -0,0 +1,24 @@ +import { Broadcast } from "@kixelated/moq/contribute" +import { asError } from "@kixelated/moq/common" + +import { createEffect, onMount } from "solid-js" + +export function Preview(props: { broadcast: Broadcast; setBroadcast(): void; setError(e: Error): void }) { + let preview: HTMLVideoElement + + onMount(() => { + props.broadcast.preview(preview) + }) + + createEffect(async () => { + try { + await props.broadcast.run() + } catch (e) { + props.setError(asError(e)) + } finally { + props.setBroadcast() + } + }) + + return +} diff --git a/src/publish.tsx b/src/publish/setup.tsx similarity index 96% rename from src/publish.tsx rename to src/publish/setup.tsx index ed9af75..06b2eac 100644 --- a/src/publish.tsx +++ b/src/publish/setup.tsx @@ -10,7 +10,6 @@ import { createSignal, For, createResource, - onMount, createSelector, Show, } from "solid-js" @@ -110,26 +109,6 @@ const VIDEO_CODECS: VideoCodec[] = [ { name: "h.264", profile: "baseline", value: "avc1.420034" }, ] -export function Main(props: { broadcast: Broadcast; setBroadcast(): void; setError(e: Error): void }) { - let preview: HTMLVideoElement - - onMount(() => { - props.broadcast.preview(preview) - }) - - createEffect(async () => { - try { - await props.broadcast.run() - } catch (e) { - props.setError(asError(e)) - } finally { - props.setBroadcast() - } - }) - - return -} - export function Setup(props: { connection: Connection | undefined setBroadcast(v: Broadcast | undefined): void diff --git a/src/watch.html b/src/watch.html new file mode 100644 index 0000000..bddc5f0 --- /dev/null +++ b/src/watch.html @@ -0,0 +1,24 @@ + + + + + quic.video + + + + + + +
+ + + + diff --git a/src/watch.tsx b/src/watch/controls.tsx similarity index 57% rename from src/watch.tsx rename to src/watch/controls.tsx index 4722bcd..27adefc 100644 --- a/src/watch.tsx +++ b/src/watch/controls.tsx @@ -1,10 +1,9 @@ -import { Connection } from "@kixelated/moq/transport" -import { Player, Broadcast, Broadcasts } from "@kixelated/moq/playback" -import { CatalogTrack, isAudioCatalogTrack, isVideoCatalogTrack, asError } from "@kixelated/moq/common" +import { Player } from "@kixelated/moq/playback" +import { asError } from "@kixelated/moq/common" -import { createSignal, onMount, For, createEffect, Show } from "solid-js" +import { onMount, createEffect } from "solid-js" -export function Main(props: { player: Player; setError(e: Error): void; setPlayer(): void }) { +export function Controls(props: { player: Player; setError(e: Error): void; setPlayer(): void }) { let canvas: HTMLCanvasElement onMount(() => { @@ -27,91 +26,6 @@ export function Main(props: { player: Player; setError(e: Error): void; setPlaye return } -export function Setup(props: { connection: Connection; setPlayer(v: Player): void; setError(e: Error): void }) { - // Create an object that we'll use to list all of the broadcasts - const announced = new Broadcasts(props.connection) - - const [broadcast, setBroadcast] = createSignal() - const [broadcasts, setBroadcasts] = createSignal([]) - - createEffect(async () => { - try { - for (;;) { - const broadcast = await announced.next() - setBroadcasts((prev) => prev.concat(broadcast)) - } - } catch (e) { - props.setError(asError(e)) - } - }) - - createEffect(() => { - const selected = broadcast() - if (!selected) return - - const player = new Player(props.connection, selected) - props.setPlayer(player) - }) - - return ( -
    - - {(broadcast) => { - const select = () => { - setBroadcast(broadcast) - } - return ( -
  • - -
  • - ) - }} -
    -
- ) -} - -function Available(props: { broadcast: Broadcast; select: () => void }) { - // A function because Match doesn't work with Typescript type guards - const trackInfo = (track: CatalogTrack) => { - if (isVideoCatalogTrack(track)) { - return ( - <> - video: {track.codec} {track.width}x{track.height} - {track.bit_rate} b/s - - ) - } else if (isAudioCatalogTrack(track)) { - return ( - <> - audio: {track.codec} {track.sample_rate}Hz {track.channel_count}ch - {track.bit_rate} b/s - - ) - } else { - return "unknown track type" - } - } - - const watch = (e: MouseEvent) => { - e.preventDefault() - props.select() - } - - return ( - <> - {props.broadcast.name.replace(/\//, " / ")} -
- - {(track) => { - return
{trackInfo(track)}
- }} -
-
- - ) -} - /* function Buffer(props: { player: Player }) { const [timeline, setTimeline] = createSignal({ audio: { buffer: [] }, video: { buffer: [] } }) diff --git a/src/watch/index.tsx b/src/watch/index.tsx new file mode 100644 index 0000000..999aff9 --- /dev/null +++ b/src/watch/index.tsx @@ -0,0 +1,71 @@ +import { Connection, Client } from "@kixelated/moq/transport" +import { Player } from "@kixelated/moq/playback" +import { asError } from "@kixelated/moq/common" + +import { createSignal, createEffect, Show, Switch, Match } from "solid-js" +import { Setup } from "./setup" +import { Controls } from "./controls" + +export function Main() { + const [error, setError] = createSignal() + const [connection, setConnection] = createSignal() + + const params = new URLSearchParams(window.location.search) + + let url = params.get("url") ?? undefined + let fingerprint = params.get("fingerprint") ?? undefined + + // Change the default URL based on the environment. + if (process.env.NODE_ENV === "production") { + url ??= "https://moq-demo.englishm.net:4443" + } else { + url ??= "https://localhost:4443" + fingerprint ??= url + "/fingerprint" + } + + const client = new Client({ + url, + role: "both", + fingerprint, + }) + + createEffect(async () => { + try { + const conn = await client.connect() + setConnection(conn) + await conn.run() + } catch (e) { + setError(asError(e)) + } finally { + setConnection() + } + }) + + createEffect(() => { + const err = error() + if (err) console.error(err) + }) + + const [player, setPlayer] = createSignal() + + return ( +
+ +
+ {error()?.name}: {error()?.message} +
+
+ +
+ + + + + + + + +
+
+ ) +} diff --git a/src/watch/setup.tsx b/src/watch/setup.tsx new file mode 100644 index 0000000..c71311b --- /dev/null +++ b/src/watch/setup.tsx @@ -0,0 +1,90 @@ +import { Connection } from "@kixelated/moq/transport" +import { Player, Broadcast, Broadcasts } from "@kixelated/moq/playback" +import { CatalogTrack, isAudioCatalogTrack, isVideoCatalogTrack, asError } from "@kixelated/moq/common" + +import { For, createSignal, createEffect, Show } from "solid-js" + +export function Setup(props: { connection: Connection; setPlayer(v: Player): void; setError(e: Error): void }) { + // Create an object that we'll use to list all of the broadcasts + const announced = new Broadcasts(props.connection) + + const [broadcast, setBroadcast] = createSignal() + const [broadcasts, setBroadcasts] = createSignal([]) + + createEffect(async () => { + try { + for (;;) { + const broadcast = await announced.next() + setBroadcasts((prev) => prev.concat(broadcast)) + } + } catch (e) { + props.setError(asError(e)) + } + }) + + createEffect(() => { + const selected = broadcast() + if (!selected) return + + const player = new Player(props.connection, selected) + props.setPlayer(player) + }) + + return ( +
    + + {(broadcast) => { + const select = () => { + setBroadcast(broadcast) + } + return ( +
  • + +
  • + ) + }} +
    +
+ ) +} + +function Available(props: { broadcast: Broadcast; select: () => void }) { + // A function because Match doesn't work with Typescript type guards + const trackInfo = (track: CatalogTrack) => { + if (isVideoCatalogTrack(track)) { + return ( + <> + video: {track.codec} {track.width}x{track.height} + {track.bit_rate} b/s + + ) + } else if (isAudioCatalogTrack(track)) { + return ( + <> + audio: {track.codec} {track.sample_rate}Hz {track.channel_count}ch + {track.bit_rate} b/s + + ) + } else { + return "unknown track type" + } + } + + const watch = (e: MouseEvent) => { + e.preventDefault() + props.select() + } + + return ( + <> + {props.broadcast.name.replace(/\//, " / ")} +
+ + {(track) => { + return
{trackInfo(track)}
+ }} +
+
+ + ) +} From 9616fa48a06dff808f12d0290d74dcdcc2b15d5c Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 17 Aug 2023 15:58:59 -0700 Subject: [PATCH 2/3] Slightly better UI. Still a lot of work to do. --- src/common/base.css | 6 +- src/common/github.svg | 1 + src/common/logo.svg | 984 ++++++++++++++---------------------------- src/explained.html | 7 +- src/header.html | 56 --- src/index.html | 7 +- src/nav.html | 13 + src/publish.html | 5 +- src/publish/index.tsx | 4 +- src/publish/setup.tsx | 81 ++-- src/watch.html | 5 +- src/watch/index.tsx | 4 +- src/watch/setup.tsx | 2 +- 13 files changed, 389 insertions(+), 786 deletions(-) create mode 100644 src/common/github.svg delete mode 100644 src/header.html create mode 100644 src/nav.html diff --git a/src/common/base.css b/src/common/base.css index 369b575..afe9700 100644 --- a/src/common/base.css +++ b/src/common/base.css @@ -3,5 +3,9 @@ @tailwind utilities; a { - @apply text-indigo-600 hover:cursor-pointer hover:text-indigo-800 hover:underline; + @apply text-green-400 hover:cursor-pointer hover:text-green-500; +} + +html { + @apply bg-slate-900 text-slate-100; } diff --git a/src/common/github.svg b/src/common/github.svg new file mode 100644 index 0000000..b85c9f7 --- /dev/null +++ b/src/common/github.svg @@ -0,0 +1 @@ + diff --git a/src/common/logo.svg b/src/common/logo.svg index 8298b8d..90f0f53 100644 --- a/src/common/logo.svg +++ b/src/common/logo.svg @@ -1,700 +1,362 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + diff --git a/src/explained.html b/src/explained.html index e42671b..b4961b0 100644 --- a/src/explained.html +++ b/src/explained.html @@ -8,7 +8,10 @@ - -
+ + +
+

// TODO explain how MoQ works

+
diff --git a/src/header.html b/src/header.html deleted file mode 100644 index c62e8d5..0000000 --- a/src/header.html +++ /dev/null @@ -1,56 +0,0 @@ -
-
- Media over QUIC -
- -
- - - - - -
-
diff --git a/src/index.html b/src/index.html index e42671b..c889f2b 100644 --- a/src/index.html +++ b/src/index.html @@ -8,7 +8,10 @@ - -
+ + +
+

// TODO splash page

+
diff --git a/src/nav.html b/src/nav.html new file mode 100644 index 0000000..b32ee73 --- /dev/null +++ b/src/nav.html @@ -0,0 +1,13 @@ + diff --git a/src/publish.html b/src/publish.html index 22637db..9bc48a6 100644 --- a/src/publish.html +++ b/src/publish.html @@ -8,8 +8,9 @@ - -
+ + +