Skip to content

Commit

Permalink
Last touches (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
kixelated authored Oct 25, 2023
1 parent 1998123 commit e89f292
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 113 deletions.
Binary file added web/public/bunny.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
152 changes: 68 additions & 84 deletions web/src/components/publish.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Connection | undefined>()
const [device, setDevice] = createSignal<MediaStream | undefined>()
const [deviceLoading, setDeviceLoading] = createSignal(false)
const [audio, setAudio] = createSignal<AudioEncoderConfig | undefined>()
const [video, setVideo] = createSignal<VideoEncoderConfig | undefined>()
const [connection, setConnection] = createSignal<Connection | undefined>()
const [advanced, setAdvanced] = createSignal(false)
const [broadcast, setBroadcast] = createSignal<Broadcast | undefined>()

const [share, setShare] = createSignal<string>()
const [copied, setCopied] = createSignal<boolean>()

const [active, setActive] = createSignal<boolean>()
const [error, setError] = createSignal<Error | undefined>()

Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -214,14 +244,7 @@ export default function Publish() {
</button>
</p>

<Device setError={setError} setDevice={setDevice} />
<Connect
setError={setError}
setShare={setShare}
setConnection={setConnection}
advanced={advanced()}
server={server}
/>
<Device setError={setError} setDevice={setDevice} setDeviceLoading={setDeviceLoading} />

<Show when={videoTrack()}>
{(track) => (
Expand All @@ -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"
>
<Switch>
<Match when={broadcast()}>Live</Match>
<Match when={active()}>Loading</Match>
<Match when={!active()}>Go Live</Match>
<Match when={isStatus("device-none")}>Select Device</Match>
<Match when={isStatus("device-loading")}>Loading Device</Match>
<Match when={isStatus("ready")}>Go Live</Match>
<Match when={isStatus("connect")}>Connecting</Match>
<Match when={isStatus("live")}>Live</Match>
</Switch>
</button>

<Show when={broadcast()}>
<a href={share()} onClick={copyShare} class="form-button">
<a href={watchUrl} onClick={copyShare} class="form-button">
Share
</a>
</Show>
Expand All @@ -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<string>(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 (
<>
<Show when={props.advanced}>
<h2>General</h2>

<label>
Broadcast Name
<input
type="text"
name="name"
placeholder="random"
class="block w-96"
onChange={(e) => setName(e.target.value)}
/>
</label>
</Show>
</>
)
}

function Device(props: { setError: (err: Error) => void; setDevice: (input: MediaStream) => void }) {
const [mode, setMode] = createSignal<"user" | "display" | "none">("none")
const [device, setDevice] = createSignal<MediaStream | undefined>()
const [videoDeviceId, setVideoDeviceId] = createSignal<string>("")
Expand All @@ -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({
Expand All @@ -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 = () => {
Expand Down
17 changes: 10 additions & 7 deletions web/src/pages/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,22 @@ Check out the [issues](/issues) and in touch if you're interested in contributin
<p class="tagline">Visit [Github](/source) or join [Discord](https://discord.gg/FCYF3p99mr)</p>

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!

<p class="tagline">Check out the current [Issues](/issues) and **Roadmap**</p>
<p class="tagline">If you want to help, check out the [major issues](/issues)</p>

The protocol is being developed by the
[IETF MoQ working group](https://datatracker.ietf.org/group/moq/about/). The
Expand Down
1 change: 1 addition & 0 deletions web/src/pages/issues.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
14 changes: 0 additions & 14 deletions web/src/pages/publish.astro

This file was deleted.

29 changes: 29 additions & 0 deletions web/src/pages/publish.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
layout: "@/layouts/global.astro"
title: Publish
---

import Issues from "@/components/issues.astro"
import Client from "@/components/publish.tsx"

<Issues />

# Publish

Create a **PUBLIC** broadcast using [moq-js](https://github.com/kixelated/moq-js) in your browser.

<Client client:only />

### 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}
```
19 changes: 11 additions & 8 deletions web/src/pages/watch/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="/issues">busy</a> setting up the CDN.

***
### Next Steps
In the meantime, feel free to <a href="/publish">PUBLISH</a> and share your resulting link.
<div class="grid grid-cols-[200px_1fr] items-center gap-6">
<a href="/watch/bbb"><img src="/bunny.png" alt="Big Buck Bunny" /></a>
<div>
<h3 class="m-0 mb-4">[Big Buck Bunny](/watch/bbb)</h3>
<p class="m-0 text-sm">_video_: h.264 1280x720 3.0Mb/s</p>
<p class="m-0 text-sm">_audio_: [none](/issues)</p>
</div>

</div>

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

0 comments on commit e89f292

Please sign in to comment.