diff --git a/web/public/bunny.png b/web/public/bunny.png new file mode 100644 index 0000000..13d44df Binary files /dev/null and b/web/public/bunny.png differ diff --git a/web/src/components/publish.tsx b/web/src/components/publish.tsx index ce51962..10c6f1c 100644 --- a/web/src/components/publish.tsx +++ b/web/src/components/publish.tsx @@ -74,16 +74,14 @@ export default function Publish() { const params = Object.fromEntries(urlSearchParams.entries()) const server = params.server ?? import.meta.env.PUBLIC_RELAY_HOST - const [connection, setConnection] = createSignal() const [device, setDevice] = createSignal() + const [deviceLoading, setDeviceLoading] = createSignal(false) const [audio, setAudio] = createSignal() const [video, setVideo] = createSignal() + const [connection, setConnection] = createSignal() const [advanced, setAdvanced] = createSignal(false) const [broadcast, setBroadcast] = createSignal() - - const [share, setShare] = createSignal() const [copied, setCopied] = createSignal() - const [active, setActive] = createSignal() const [error, setError] = createSignal() @@ -99,6 +97,28 @@ export default function Publish() { return tracks[0].getSettings() as VideoTrackSettings }) + const id = crypto.randomUUID() + let watchUrl = `/watch/${id}` + if (server != import.meta.env.PUBLIC_RELAY_HOST) { + watchUrl = `${watchUrl}?server=${server}` + } + + createEffect(() => { + const url = `https://${server}/${id}` + + // Special case localhost to fetch the TLS fingerprint from the server. + // TODO remove this when WebTransport correctly supports self-signed certificates + const fingerprint = server.startsWith("localhost") ? `https://${server}/fingerprint` : undefined + + const client = new Client({ + url, + fingerprint, + role: "publisher", + }) + + client.connect().then(setConnection).catch(setError) + }) + const createBroadcast = function () { const d = device() if (!d) { @@ -145,10 +165,7 @@ export default function Publish() { if (!conn) return onCleanup(() => conn.close()) - conn.closed() - .then(setError) - .catch(setError) - .finally(() => setConnection(undefined)) + conn.closed().then(setError, setError) }) // Close the broadcast on unload or error @@ -164,14 +181,27 @@ export default function Publish() { // Wait until the broadcast is closed. b.closed() - .then(setError) - .catch(setError) + .then(setError, setError) .finally(() => { setBroadcast(undefined) setActive(false) }) }) + // The text for the submit button + const status = createMemo(() => { + if (!device()) { + if (deviceLoading()) return "device-loading" + return "device-none" + } + + if (!active()) return "ready" + if (!connection()) return "connect" + return "live" + }) + + const isStatus = createSelector(status) + // Copy the link to the clipboard const copyShare = function (event: MouseEvent) { event.preventDefault() @@ -214,14 +244,7 @@ export default function Publish() {

- - + {(track) => ( @@ -244,19 +267,30 @@ export default function Publish() { type="submit" onClick={(e) => { e.preventDefault() - setActive(true) + + if (isStatus("ready")) { + setActive(true) + } + }} + classList={{ + "bg-yellow-500": !isStatus("ready") && !isStatus("live"), + "hover:bg-yellow-600": !isStatus("ready") && !isStatus("live"), + "bg-green-500": isStatus("ready") || isStatus("live"), + "hover:bg-green-600": isStatus("ready") || isStatus("live"), }} - class="bg-green-500 text-white hover:bg-green-600" + class="text-white" > - Live - Loading - Go Live + Select Device + Loading Device + Go Live + Connecting + Live - + Share @@ -270,68 +304,11 @@ export default function Publish() { ) } -function Connect(props: { +function Device(props: { setError: (err: Error) => void - setConnection: (conn: Connection | undefined) => void - setShare: (url: string | undefined) => void - advanced: boolean - server: string + setDevice: (input: MediaStream) => void + setDeviceLoading: (ok: boolean) => void }) { - const [name, setName] = createSignal(crypto.randomUUID()) - - createEffect(() => { - props.setConnection(undefined) - props.setShare(undefined) - - const n = name() - const server = props.server - - const url = `https://${server}/${n}` - - // Special case localhost to fetch the TLS fingerprint from the server. - // TODO remove this when WebTransport correctly supports self-signed certificates - const fingerprint = server.startsWith("localhost") ? `https://${server}/fingerprint` : undefined - - const client = new Client({ - url, - fingerprint, - role: "publisher", - }) - - let watchUrl = `/watch/${n}` - if (server != import.meta.env.PUBLIC_RELAY_HOST) { - watchUrl = `${watchUrl}?server=${server}` - } - - client - .connect() - .then(props.setConnection) - // eslint-disable-next-line solid/reactivity - .then(() => props.setShare(watchUrl)) - .catch(props.setError) - }) - - return ( - <> - -

General

- - -
- - ) -} - -function Device(props: { setError: (err: Error) => void; setDevice: (input: MediaStream) => void }) { const [mode, setMode] = createSignal<"user" | "display" | "none">("none") const [device, setDevice] = createSignal() const [videoDeviceId, setVideoDeviceId] = createSignal("") @@ -341,14 +318,20 @@ function Device(props: { setError: (err: Error) => void; setDevice: (input: Medi const loadUser = function () { setMode("user") + setDevice(undefined) + props.setDeviceLoading(true) + mediaDevices() .then(setDevice) .catch(props.setError) .catch(() => setMode("none")) + .finally(() => props.setDeviceLoading(false)) } const loadDisplay = function () { setMode("display") + setDevice(undefined) + props.setDeviceLoading(true) navigator.mediaDevices .getDisplayMedia({ @@ -368,6 +351,7 @@ function Device(props: { setError: (err: Error) => void; setDevice: (input: Medi .then(setDevice) .catch(props.setError) .catch(() => setMode("none")) + .finally(() => props.setDeviceLoading(false)) } const mediaDevices = () => { diff --git a/web/src/pages/index.mdx b/web/src/pages/index.mdx index f1b7c97..bbc2c3c 100644 --- a/web/src/pages/index.mdx +++ b/web/src/pages/index.mdx @@ -26,19 +26,22 @@ Check out the [issues](/issues) and in touch if you're interested in contributin

Visit [Github](/source) or join [Discord](https://discord.gg/FCYF3p99mr)

We are designing a protocol that is simple and yet flexible enough to support the wide range of use-cases: -from ultra-low latency to ultra-high quality, 1 viewer to 1 million viewers, and everything in -between. It's possible by evaluating the entire ecosystem and breaking the problem into layers: +from ultra-low latency to ultra-high quality, 1 viewer to 1 million viewers, contribution and distribution, and everything in +between. It's possible by carefully breaking the protocol into layers: - **QUIC**: Provides the networking: independent streams and datagrams. -- **WebTransport**: Provides web support for QUIC. -- **MoqTransport**: Designed specifically for CDNs with built-in caching and fanout. -- **Catalog**: Enumerates the available media tracks. For example, the codec, container, resolution, etc. -- **Containers**: The encoding for each track. [quic.video](https://quic.video) uses CMAF and is backwards compatible with HLS/DASH. +- **WebTransport**: (optional) Provides web support for QUIC. +- **MoqTransport**: Designed specifically for CDNs and mass fanout. +- **Catalog**: Describes the available tracks: the codec, container, resolution, etc. +- **Container**: Describes the encoding of each track. + +This website uses CMAF for backwards compatibility with HLS/DASH but it's not required. +You can use whatever encoding the encoder/decoder agree to support, including for non-media purposes like chat, input, metadata, etc. MoQ is still very early in development and many features are missing. Everything is liable to change, especially if you get involved! -

Check out the current [Issues](/issues) and **Roadmap**

+

If you want to help, check out the [major issues](/issues)

The protocol is being developed by the [IETF MoQ working group](https://datatracker.ietf.org/group/moq/about/). The diff --git a/web/src/pages/issues.mdx b/web/src/pages/issues.mdx index 81074b4..7163de6 100644 --- a/web/src/pages/issues.mdx +++ b/web/src/pages/issues.mdx @@ -57,6 +57,7 @@ See the [moq-rs](https://github.com/kixelated/moq-rs/issues) issues for a full l | issue | name | description | | -----------------------------------------------------: | ------------------- | --------------------------------------------------------------------------------------------------------------------- | +| [#112](https://github.com/kixelated/moq-rs/pull/112) | moq-pub - Audio | The ffmpeg/moq-pub integration doesn't produce audio at the moment. It's probably a simple fix. | | [#63](https://github.com/kixelated/moq-rs/issues/63) | libmoq - C Bindings | Make a C library to interface with moq-rs. Any networking/caching/async is hidden from the application. | | [#105](https://github.com/kixelated/moq-rs/issues/105) | libmoq - OBS | Develop an [OBS](https://obsproject.com/) plugin. | | [#106](https://github.com/kixelated/moq-rs/issues/106) | libmoq - ffmpeg | Develop an [ffmpeg](https://www.ffmpeg.org/) plugin. We currently pipe to stdin which works but it could be improved. | diff --git a/web/src/pages/publish.astro b/web/src/pages/publish.astro deleted file mode 100644 index 3e9f881..0000000 --- a/web/src/pages/publish.astro +++ /dev/null @@ -1,14 +0,0 @@ ---- -import Issues from "@/components/issues.astro" -import Client from "@/components/publish.tsx" -import Layout from "@/layouts/global.astro" ---- - - - - -

- Publish a PUBLIC broadcast. Pls don't abuse. -

- -
diff --git a/web/src/pages/publish.mdx b/web/src/pages/publish.mdx new file mode 100644 index 0000000..ca8d15e --- /dev/null +++ b/web/src/pages/publish.mdx @@ -0,0 +1,29 @@ +--- +layout: "@/layouts/global.astro" +title: Publish +--- + +import Issues from "@/components/issues.astro" +import Client from "@/components/publish.tsx" + + + +# Publish + +Create a **PUBLIC** broadcast using [moq-js](https://github.com/kixelated/moq-js) in your browser. + + + +### NOTE + +You can use [moq-pub](https://github.com/kixelated/moq-rs) to publish from CLI instead: + +```bash +# Must be unique +NAME="my-broadcast" + +# See the README for a full list of ffmpeg arguments required +ffmpeg -f mp4 - | moq-pub https://relay.quic.video/${NAME} + +# Watch it at: https://quic.video/watch/${NAME} +``` diff --git a/web/src/pages/watch/index.mdx b/web/src/pages/watch/index.mdx index 607b99f..e126739 100644 --- a/web/src/pages/watch/index.mdx +++ b/web/src/pages/watch/index.mdx @@ -3,15 +3,18 @@ layout: "@/layouts/global.astro" title: Watch --- -# Public Broadcasts -* [Bunny Stream](https://quic.video/watch/bbb) +# Watch -*Currently this is mock example with a working video for your enjoyment* +All **PUBLIC** broadcasts will be eventually listed here. Until then, enjoy: -All **PUBLIC** broadcasts will be eventually listed here; we're busy setting up the CDN. - -*** -### Next Steps -In the meantime, feel free to PUBLISH and share your resulting link. +
+ Big Buck Bunny +
+

[Big Buck Bunny](/watch/bbb)

+

_video_: h.264 1280x720 3.0Mb/s

+

_audio_: [none](/issues)

+
+
+Or [PUBLISH](/publish) your own broadcast and share the link.