From d555c577df752ec6864352e6bdbf1f4afc7555a4 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 14 Sep 2023 16:30:11 -0700 Subject: [PATCH] Add an /issues page and basic /explain page. --- web/explain.tsx | 17 +++ web/github.tsx | 74 +------------ web/img/warning.svg | 1 + web/index.tsx | 4 + web/issues.tsx | 248 ++++++++++++++++++++++++++++++++++++++++++++ web/publish.tsx | 12 +-- web/tsconfig.json | 2 +- web/watch.tsx | 7 +- 8 files changed, 278 insertions(+), 87 deletions(-) create mode 100644 web/explain.tsx create mode 100644 web/img/warning.svg create mode 100644 web/issues.tsx diff --git a/web/explain.tsx b/web/explain.tsx new file mode 100644 index 0000000..960f60d --- /dev/null +++ b/web/explain.tsx @@ -0,0 +1,17 @@ +export function Explain() { + return ( +
+
UNDER CONSTRUCTION
+
+ There will be dope diagrams and animations here one day. Until then, it's better to prove that the + protocol works via a demo. Dope diagrams and animations only prove that I can use a vector graphics + editor. +
+
+ Or you can watch some of my older presentations:{" "} + Video @ Scale and{" "} + Demuxed. +
+
+ ) +} diff --git a/web/github.tsx b/web/github.tsx index 605de3b..8181234 100644 --- a/web/github.tsx +++ b/web/github.tsx @@ -1,26 +1,6 @@ -import * as caniuse from "caniuse-lite" -import { For } from "solid-js" +import { A } from "@solidjs/router" export function Github() { - const features = ["webtransport", "webcodecs", "audio-api", "sharedarraybuffer"].map((id) => { - return { id, ...caniuse.feature(caniuse.features[id]) } - }) - const agents = ["chrome", "edge", "firefox", "safari", "ios_saf", "android"].map((id) => { - const agent = caniuse.agents[id]! - - // Get the date of the latest release: - const latest = - Object.values(agent.release_date).reduce((a, b) => { - if (!a) return b - if (!b) return a - return a > b ? a : b - }) ?? 0 - - return { id, latest, ...caniuse.agents[id] } - }) - - const latest = agents.reduce((a, b) => (a.latest > b.latest ? a : b)).latest - return (
So you want some source code huh? That's cool.
@@ -106,57 +86,7 @@ export function Github() {
This project is using a lot of new web APIs. Chrome support is guaranteed but other browsers are still - catching up. -
-
- Here is a non-compreshensive list of required features from the{" "} - caniuse database, based on the latest version of each browser as of:{" "} - {new Date(1000 * latest).toDateString()} -
-
-
- - {(agent) => { - return
{agent.browser}
- }} -
- - {(feature) => { - return ( - <> - - - {(agent) => { - const versions = feature.stats[agent.id] - const latest = Object.keys(versions).reduce((a, b) => - versions[a] > versions[b] ? a : b, - ) - - const supported = versions[latest] - const yes = supported.startsWith("y") - const no = supported.startsWith("n") - const maybe = !yes && !no - - return ( -
- {yes ? "yes" : no ? "no" : "maybe"} -
- ) - }} -
- - ) - }} -
+ catching up. See issues for more details.
Licensing
diff --git a/web/img/warning.svg b/web/img/warning.svg new file mode 100644 index 0000000..9a3cc96 --- /dev/null +++ b/web/img/warning.svg @@ -0,0 +1 @@ + diff --git a/web/index.tsx b/web/index.tsx index 5314f19..2a65a30 100644 --- a/web/index.tsx +++ b/web/index.tsx @@ -7,6 +7,8 @@ import { Listings } from "./listing" import { Publish } from "./publish" import { Home } from "./home" import { Github } from "./github" +import { Issues } from "./issues" +import { Explain } from "./explain" // Import the icons using this trick so Parcel can rewrite the URLs. const icons = { @@ -61,6 +63,8 @@ function Main() { + + 404 Not found

} /> diff --git a/web/issues.tsx b/web/issues.tsx new file mode 100644 index 0000000..20374e6 --- /dev/null +++ b/web/issues.tsx @@ -0,0 +1,248 @@ +import { A } from "@solidjs/router" +import * as caniuse from "caniuse-lite" +import { For } from "solid-js" + +const icons = { + // TODO draw in my dumb art style + warning: new URL("./img/warning.svg", import.meta.url), +} + +export function Issues() { + const features = ["webtransport", "webcodecs", "audio-api", "sharedarraybuffer"].map((id) => { + return { id, ...caniuse.feature(caniuse.features[id]) } + }) + + const agents = ["chrome", "edge", "firefox", "safari", "ios_saf", "android"].map((id) => { + const agent = caniuse.agents[id]! + + // Get the date of the latest release: + const latest = + Object.values(agent.release_date).reduce((a, b) => { + if (!a) return b + if (!b) return a + return a > b ? a : b + }) ?? 0 + + return { id, latest, ...caniuse.agents[id] } + }) + + const latest = agents.reduce((a, b) => (a.latest > b.latest ? a : b)).latest + + return ( +
+
Yeah so there's a lot of work to do.
+
+ This is an early PoC and hobby project, so expect nothing to work. Check out the Github issues ( + moq-rs and{" "} + moq-js) if you want to contribute or complain; + both are equally valid. +
+ +
Browser Support
+
+ We're using some pretty new web standards, so browser support is limited. Here is a non-compreshensive + list of the required features from the caniuse database as of:{" "} + {new Date(1000 * latest).toDateString()} +
+
+
+ + {(agent) => { + return
{agent.browser}
+ }} +
+ + {(feature) => { + return ( + <> + + + {(agent) => { + const versions = feature.stats[agent.id] + const latest = Object.keys(versions).reduce((a, b) => + versions[a] > versions[b] ? a : b, + ) + + const supported = versions[latest] + const yes = supported.startsWith("y") + const no = supported.startsWith("n") + const maybe = !yes && !no + + return ( +
+ {yes ? "yes" : no ? "no" : "maybe"} +
+ ) + }} +
+ + ) + }} +
+
+
+
+ The only hard requirement is{" "} + WebTransport. See the + following sections for possible alternatives for web playback and web contribution. +
+ +
Web Playback
+
+ WebCodecs decoding is a + breeze but rendering is a nightmare, as the application is responsible for everything. This includes + when to render video frames but also when to emit audio samples, which gets very complicated quickly as + it involves synchronization. +
+
+ Additionally, there's no built-in controls. Even something as trivial as changing the volume requires + building a WebAudio filter as opposed to relying on the <video> tag. I'm not a front-end developer + (no flame pls) and would love any contributions on this front. +
+
+ A much simpler rendering technology is{" "} + MSE. I had a + previous demo that used this API, but the latency is significantly higher than WebCodecs as it will + buffer during starvation. I plan on supporting it again in the future and it works great with Media over + QUIC as both use fMP4. +
+ +
Web Contribution
+
+ WebCodecs is great for + encoding in my relatively limited experience. We may need some more functionality to support + conferencing, such as like echo cancellation or advanced encodings. +
+
+ The main limitation is capturing sources from the browser, as the browser doesn't have the same + flexibility as a native program like OBS. I would also love to see + a UI that allows positioning elements or doing cool effects. If you can render to a <canvas>, then + you can encode it with WebCodecs and transmit it with WebTransport. +
+
+ Media over QUIC relies on both congestion control and prioritization to provide the best user experience + on poor networks. The WebTransport specification does provide{" "} + + prioritization + {" "} + and{" "} + + congestion control hints + + , but these have not been implemented yet. We have limited control over the browser's congestion control + which is especially important for live media. +
+ +
Congestion Control
+
+ moq-rs uses an experimental{" "} + BBR implementation.
+ moq-js uses whatever the browser implements, which + defaults to New Reno. +
+
+ The congestion control algorithm is extremely important for live media over the internet, as{" "} + bufferbloat will cause queuing on the network. + TCP-oriented congestion control are often compared by sustained throughput, but for live media we're + more interested in latency, since the encoded bitrate is the limiting factor. When latency is critical, + it's better to drop old media instead of queuing new media, and that's only possible when you can detect + queuing via congestion control. +
+
+ Loss-based congestion control like{" "} + New Reno (Windows default) and{" "} + CUBIC (Linux default) suffer from bufferbloat. + Your experience will vary based on the network, with some ISPs and parts of the world being + significantly worse than others. This is also the fundamental issue with RTMP, since it relies on the + operating system's TCP congestion control. QUIC libraries can ship their own congestion control allowing + much faster experimentation and iteration. +
+
+ Delay-based congestion control like{" "} + + BBR + {" "} + and COPA are better, but are still fundamentally designed for + TCP. They're not designed for application-limited environments like live media where we don't fully + saturate the network. Flooding the network with PADDING packets to occasionally saturate the network + makes a big difference, but is experimental and not yet implemented. +
+
+ Delayed-based and latency-sensitive congestion control like{" "} + GCC and{" "} + SCReAM are the best* for real-time media and + see wide usage in WebRTC. However, the per-packet feedback required for these algorithms are not + available in QUIC. We will need a QUIC extension in order to match WebRTC performance and latency. +
+ +
Dynamic Bitrate
+
+ An alternative to dropping media is to dynamically adjust the bitrate. The picture quality will worsen, + but more frames will be delivered, which often results in a better user experience. +
+
+ In 1:1 video conferencing, the media encoding is adjusted in response to viewer feedback. A protocol + like WebRTC will lower the media bitrate in response to minor congestion and request a new I-frame in + response to major-congestion. No such feedback exists yet in Media over QUIC, and is complicated by the + presence of relays. +
+
+ In 1:N video conferencing, the encoding is fixed, as one viewer's experience should not degrade + everybody else's experience. The common approach is to encode multiple renditions of the broadcast at + different bitrates, allowing the viewer to switch between them depending on their network. This is + called ABR in distribution + circles and simulcast in contribution circles. + This is not implemented yet either. +
+ +
CDNs
+
+ Media over QUIC is designed with relays and CDNs in mind.{" "} + MoqTransport is media agnostic + and exposes only the most critical information to relay. The design should enable world-side scale while + still supporting real-time latency budgets. +
+
+ ...but we haven't built it yet. You're currently connecting to a single server somewhere in the US, so + don't expect the best quality. I'm working on it now so expect this section to be updated soon. +
+ +
Specification
+
+ Media over QUIC is an IETF working group. + The IETF is an open organization that develops Internet standards, + including some of your favorite protocols like HTTP, TLS, and DNS. +
+
+ The standardization effort is slow and deliberate so don't expect an RFC for years.{" "} + quic.video uses a fork of the specification, allowing us to experiment + with new features without the litigation involved in a standard.{" "} + + Here's a list + {" "} + of the changes thus far, which we hope will be merged into the standard. +
+
+ ) +} + +export function Notice() { + return ( +
+ +
+ This is an early-stage proof-of-concept. Check out the current limitations. + Contributions are welcome! +
+
+ ) +} diff --git a/web/publish.tsx b/web/publish.tsx index 5790f0b..0c7190b 100644 --- a/web/publish.tsx +++ b/web/publish.tsx @@ -7,6 +7,7 @@ import { useSearchParams } from "@solidjs/router" import { Listing } from "./listing" import { createFetch } from "./common" import { connect } from "./connect" +import { Notice } from "./issues" interface GeneralConfig { server: string @@ -204,21 +205,12 @@ export function Publish() { return ( <> +
{error()!.name}: {error()!.message}
-

- - - You've made a PUBLIC broadcast. Don't abuse it pls. - - - Make a PUBLIC broadcast. Don't abuse it pls. - - -

Preview
diff --git a/web/tsconfig.json b/web/tsconfig.json index f138174..dd33c55 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -16,6 +16,6 @@ "jsxImportSource": "solid-js", "typeRoots": ["../lib/types", "../node_modules/@types"], "lib": ["esnext"], - "types": ["web", "parcel-env", "dom-webcodecs", "dom-mediacapture-transform"] + "types": ["web", "parcel-env", "dom-webcodecs", "dom-mediacapture-transform", "solid-jsx/types"] } } diff --git a/web/watch.tsx b/web/watch.tsx index f208d3a..ecf58d6 100644 --- a/web/watch.tsx +++ b/web/watch.tsx @@ -7,6 +7,7 @@ import { useParams, useSearchParams } from "@solidjs/router" import { createFetch, createPack, createSource } from "./common" import { Listing } from "./listing" import { connect } from "./connect" +import { Notice } from "./issues" export function Watch() { const params = useParams<{ name: string }>() @@ -42,16 +43,14 @@ export function Watch() { // TODO shrink it if needed via CSS return ( <> + +
{error()!.name}: {error()!.message}
-

- This is a PUBLIC broadcast. Report any abuse pls. -

-