From 97390f6ab9f2f9f53782ffef5bf18783d845b5f7 Mon Sep 17 00:00:00 2001 From: kixelated Date: Mon, 30 Oct 2023 11:00:05 +0900 Subject: [PATCH] More blog updates (#72) --- package.json | 2 +- web/astro.config.mjs | 2 + web/public/blog/kixelCat.png | Bin 0 -> 28500 bytes web/public/{ => home}/ietf.svg | 0 web/public/{logo-full.svg => home/logo.svg} | 0 web/public/{ => home}/quic.svg | 0 web/public/{ => issues}/warning.svg | 0 web/public/{ => layout}/discord.svg | 0 web/public/{ => layout}/explain.svg | 0 web/public/{ => layout}/favicon.svg | 0 web/public/{ => layout}/github.svg | 0 .../{logo-small.svg => layout/logo.svg} | 0 web/public/{ => layout}/publish.svg | 0 web/public/{ => layout}/source.svg | 0 web/public/{ => layout}/watch.svg | 0 web/public/{ => watch}/bunny.png | Bin web/src/components/issues.astro | 2 +- web/src/layouts/global.astro | 12 +-- web/src/pages/blog/replacing-webrtc.mdx | 82 +++++++++--------- web/src/pages/index.mdx | 6 +- web/src/pages/watch/index.mdx | 2 +- 21 files changed, 56 insertions(+), 52 deletions(-) create mode 100644 web/public/blog/kixelCat.png rename web/public/{ => home}/ietf.svg (100%) rename web/public/{logo-full.svg => home/logo.svg} (100%) rename web/public/{ => home}/quic.svg (100%) rename web/public/{ => issues}/warning.svg (100%) rename web/public/{ => layout}/discord.svg (100%) rename web/public/{ => layout}/explain.svg (100%) rename web/public/{ => layout}/favicon.svg (100%) rename web/public/{ => layout}/github.svg (100%) rename web/public/{logo-small.svg => layout/logo.svg} (100%) rename web/public/{ => layout}/publish.svg (100%) rename web/public/{ => layout}/source.svg (100%) rename web/public/{ => layout}/watch.svg (100%) rename web/public/{ => watch}/bunny.png (100%) diff --git a/package.json b/package.json index 5ae4d4d..06c43e5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "moq-js", - "private": true, "type": "module", + "private": true, "workspaces": [ "lib", "web" diff --git a/web/astro.config.mjs b/web/astro.config.mjs index d9e3973..ee18002 100644 --- a/web/astro.config.mjs +++ b/web/astro.config.mjs @@ -41,4 +41,6 @@ export default defineConfig({ }, }, }, + // Don't add trailing slashes to paths + trailingSlash: "never", }) diff --git a/web/public/blog/kixelCat.png b/web/public/blog/kixelCat.png new file mode 100644 index 0000000000000000000000000000000000000000..aa75c5110bf3617c3c31ea358c17c761c7e366ae GIT binary patch literal 28500 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 SB^! - Warning + Warning
This is an proof-of-concept. Check out the numerous issues.
diff --git a/web/src/layouts/global.astro b/web/src/layouts/global.astro index cf8333a..0c2c6cc 100644 --- a/web/src/layouts/global.astro +++ b/web/src/layouts/global.astro @@ -23,7 +23,7 @@ if (frontmatter && frontmatter.title) title = frontmatter.title content="A new live media protocol in development. This website is a proof-of-concept demonstrating web support, utilizing moq-rs and moq-js." /> - + {title ? `${title} - ` : ""}Media over QUIC @@ -31,20 +31,20 @@ if (frontmatter && frontmatter.title) title = frontmatter.title
diff --git a/web/src/pages/blog/replacing-webrtc.mdx b/web/src/pages/blog/replacing-webrtc.mdx index 08d8811..da91f8d 100644 --- a/web/src/pages/blog/replacing-webrtc.mdx +++ b/web/src/pages/blog/replacing-webrtc.mdx @@ -6,22 +6,20 @@ author: kixelated # Replacing WebRTC -The long and winding path, searching for _something else_. -Written by [@kixelated](https://github.com/kixelated). +The long path to use _something else_ for real-time media. ## tl;dr If you primarily use WebRTC for... -- **real-time media**: it will take a while to make something comparable; we're working on it. +- **real-time media**: it will take a while to replace WebRTC; we're working on it. - **data channels**: WebTransport is amazing and _actually_ works. - **peer-to-peer**: you're stuck with WebRTC for the forseeable future. ## Disclaimer -I spent over a year building/optimizing a full WebRTC stack @ Twitch using [pion](https://github.com/pion/webrtc). -We ultimately scrapped it and my use-case was quite custom so your millage will vary. -Also, some of these issues may have been addressed since then. +I spent almost two years building/optimizing a partial WebRTC stack @ Twitch using [pion](https://github.com/pion/webrtc). +Our use-case was quite custom and we ultimately scrapped it, but your millage may vary. ## Why WebRTC? @@ -82,7 +80,7 @@ The problems start when you try to use it for anything else. My final project at Twitch was to reduce latency by replacing HLS with WebRTC for delivery. This seems like a no-brainer at first, but it quickly turned into [death by a thousand cuts](https://docs.google.com/document/d/1OTnJunbpSJchdj8XI3GU9Fo-RUUFBqLO1AhlaKk5Alo/edit?usp=sharing). The biggest issue was that the user experience was just terrible. -Twitch doesn't need the same aggressive latency as Google Meet, but WebRTC is hard-wired to compromise on quality. +Twitch doesn't need the same aggressive latency as Google Meet, but WebRTC is hard-coded to compromise on quality. In general, it's quite difficult to customize WebRTC outside of a few configurable modes. It's a black box that you turn on, and if it works it works. @@ -93,36 +91,30 @@ But you're ultimately bound by the browser implementation, unless you don't need ### Data -WebRTC also has a data channel API, which is particularly useful because [until recently](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport), it's been the only way to send/receive unreliable messages from a browser. - +WebRTC also has a data channel API, which is particularly useful because [until recently](#webtransport), it's been the only way to send/receive "unreliable" messages from a browser. In fact, many companies use WebRTC data channels to avoid the WebRTC media stack (ex. Zoom). -I went down this path too, attempting to send each video frame as an unreliable message, but ultimately it didn't work due to fundamental flaws with [SCTP](https://www.rfc-editor.org/rfc/rfc9260.html). -I eventually hacked "datagram" support into SCTP by breaking frames into unreliable messages below the MTU size. -Finally! UDP in the browser, but at what cost: +I went down this path too, attempting to send each video frame as an unreliable message, but it didn't work due to fundamental flaws with [SCTP](https://www.rfc-editor.org/rfc/rfc9260.html). +I won't go into the detail in this post, but I eventually hacked "datagram" support into SCTP by breaking frames into unreliable messages below the MTU size. + +Finally! UDP\* in the browser, but at what cost: - a convoluted handshake that takes at least 10 (!) round trips. - 2x the packets, because libsctp immediately ACKs every "datagram". - a custom SCTP implementation, which means the browser can't send "datagrams". -Oof. Fortunately there's now a [better way](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/datagrams) to send datagrams. -Feel free to use my [Rust library](https://docs.rs/webtransport-quinn/latest/webtransport_quinn/). +Oof. ### P2P The best and worst part about WebRTC is that it supports peer-to-peer. -The ICE handshake is extremely complicated: -- You need both peers to generate a SDP offer/answer. -- You need a public server (usually HTTPS/WebSocket) to exchange the offer/answer. -- Some networks use symmetric NATs, so you need a fallback TURN server. -- Some networks block UDP, so you need a fallback TCP TURN server. -- Some networks only support IPv4 or IPv6 internally, so you should support dual-stack. -- Some clients only support mDNS, so you have to figure that out too. +The [ICE handshake](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Connectivity) is extremely complicated, even from the [application's point of view](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling). +Without going into detail, there's an explosion of permutations cases that you need to handle based on the network topology. +Some networks block P2P (ex. symmetric NATs) while others outright block UDP, forcing you to use a TURN server a [non-insignificant amount of time](https://twitter.com/HCornflower/status/894600051506515968). -Most conferencing solutions these days are actually client-server for better QoS. -However, you're still forced to perform the complicated ICE handshake. -This has major architecture ramifications, but I'll save that for another blog post. +Most conferencing solutions are client-server anyway, relying on their own private network instead of public transit (aka a CDN). +However the server is still forced to perform the complicated ICE handshake which has major architecture ramifications, but I'll save that for another blog post. Note that there are rumblings of [P2P WebTransport](https://w3c.github.io/p2p-webtransport/) and [P2P QUIC](https://datatracker.ietf.org/doc/draft-seemann-quic-nat-traversal/), but I wouldn't hold my breath. @@ -181,7 +173,7 @@ In fact, you need to choose when to render each audio _sample_ via [AudioWorklet The upside is that now your web application gets full control of how to render media. It's now possible to implement WebRTC-like behavior, like temporarily freezing video and desyncing A/V. -[caniuse](https://caniuse.com/webcodecs) +Check [caniuse](https://caniuse.com/webcodecs) for current browser support. ## WebTransport @@ -189,13 +181,15 @@ It's now possible to implement WebRTC-like behavior, like temporarily freezing v Think of it like WebSockets, but with a few key differences: - [QUIC](https://www.rfc-editor.org/rfc/rfc9000.html) not TCP. -- Provides independent streams that can be closed/prioritized. -- Provides datagrams that can be dropped. +- [Reliable streams](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API#reliable_transmission_via_streams) that are delivered in order. +- **Semi-reliable streams** by closing a stream (with an error code) to drop the tail. +- [Unreliable datagrams](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/datagrams) that may be dropped during congestion. QUIC has too many benefits to enumerate, but some highlights: - Fully encrypted - Congestion controlled (even datagrams) +- Independent streams (no head-of-line blocking) - 1-RTT handshake - Multiplexed over a single UDP port - Transparent network migration (ex. switching from Wifi to LTE) @@ -204,7 +198,8 @@ QUIC has too many benefits to enumerate, but some highlights: That last one is surprisingly important: WebTransport will share all of the optimizations that HTTP/3 receives. A HTTP/3 server can simultaneously serve multiple WebTransport sessions and HTTP requests over the same connection. -[caniuse](https://caniuse.com/webtransport) +Check [caniuse](https://caniuse.com/webtransport) for current browser support. +Use my [Rust library](https://docs.rs/webtransport-quinn/latest/webtransport_quinn/) for servers and native clients! ## But how? @@ -213,18 +208,19 @@ Okay, so we have WebCodecs and WebTransport, but are they actually useful? I alluded to the secret behind latency earlier: avoiding queues. Queuing can occur at any point in the media pipeline. -| Capture | Encode | Send | Receive | Decode | Render | -| :-----: | :----: | :--: | :-----: | :----: | :----: | -| --> | --> | --> | --> | --> | --> | +| Capture/Encode | Send/Receive | Decode/Render | +| :------------: | :----------: | :-----------: | +| --> | --> | --> | Let's start with the easy one. [WebCodecs](#webcodecs) allows you to avoid queuing almost entirely. -| Capture | Encode | Send | Receive | Decode | Render | -| :-----------: | :-----------: | :--: | :-----: | :-----------: | :-----------: | -| **WebCodecs** | **WebCodecs** | ? | ? | **WebCodecs** | **WebCodecs** | +| Capture/Encode | Send/Receive | Decode/Render | +| :------------: | :----------: | :-----------: | +| **WebCodecs** | ? | **WebCodecs** | The tricky part is the bit in the middle, the network. +It's not as simple as throwing your hands into the air and proclaiming "UDP has no queues!" ### The Internet of Queues @@ -239,6 +235,7 @@ Every packet you send will fight with other packets on the internet. - If those queues are full, **packets will be dropped**. There can be random packet loss, but 99% of the time we care about loss due to queuing. +Note that even datagrams may be queued by the network; a firehose of packets is never the answer. ### Detecting Queuing @@ -284,7 +281,7 @@ You can't put the toothpaste back in the tube. However, there are actually quite a few ways of dropping media with [WebTransport](#webtransport): 1. Use datagrams and choose which packets to transmit. (like WebRTC) -2. Use QUIC streams and reset them to stop transmitting. (like [RUSH](https://www.ietf.org/archive/id/draft-kpugin-rush-00.html)) +2. Use QUIC streams and close them to stop transmittions. (like [RUSH](https://www.ietf.org/archive/id/draft-kpugin-rush-00.html)) 3. Use QUIC streams and prioritize them. (like [Warp](https://www.youtube.com/watch?v=PncdrMPVaNc)) I'm biased because I made the 3rd one. @@ -304,13 +301,18 @@ It's going to take a lot of companies who are willing to bet on a new standard.< And there are major flaws with both **WebCodecs** and **WebTransport** that still need to be addressed before we'll ever reach WebRTC parity. To name a few: -- We need something like [transport-wide-cc](https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/transport-wide-cc-02/README.md) in QUIC. - We need better [congestion control](https://www.w3.org/TR/webtransport/#dom-webtransportoptions-congestioncontrol) in browsers. -- We need echo cancellation in WebCodecs, which might be possible already? -- We need [FEC](https://datatracker.ietf.org/doc/draft-michel-quic-fec/) in QUIC, at least to experiment. -- We need more encoding options, like non-reference frames or SVC. +- We need something like [transport-wide-cc](https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/transport-wide-cc-02/README.md) in QUIC: [like this proposal](https://www.ietf.org/archive/id/draft-smith-quic-receive-ts-00.html) +- We need echo cancellation in [WebAudio](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API), which might be possible? +- We may need [FEC](https://en.wikipedia.org/wiki/Error_correction_code#Forward_error_correction) in QUIC: [like this proposal](https://datatracker.ietf.org/doc/draft-michel-quic-fec/) +- We may need more encoding options, like non-reference frames or SVC. - Oh yeah and full browser support: [WebCodecs](https://caniuse.com/webcodecs) - [WebTransport](https://caniuse.com/webtransport) ## So yeah... -Hit us up on [Discord](https://discord.gg/FCYF3p99mr) if you want to help! +Written by [@kixelated](https://github.com/kixelated). +Hit me up on [Discord](https://discord.gg/FCYF3p99mr) if you want to help! + +Tune in for next week's episode: **Replacing HLS/DASH** and **Replacing RTMP**. + + diff --git a/web/src/pages/index.mdx b/web/src/pages/index.mdx index 7e73821..14ed1db 100644 --- a/web/src/pages/index.mdx +++ b/web/src/pages/index.mdx @@ -3,7 +3,7 @@ layout: "@/layouts/global.astro" ---
- Media over QUIC Logo + Media over QUIC Logo
**Media over QUIC** is new live media protocol powered by [QUIC](https://quicwg.org/): @@ -44,9 +44,9 @@ The standard is being produced by many individuals from companies such as Google
- IETF Logo + IETF Logo - QUIC Logo + QUIC Logo
diff --git a/web/src/pages/watch/index.mdx b/web/src/pages/watch/index.mdx index e126739..1bc7c14 100644 --- a/web/src/pages/watch/index.mdx +++ b/web/src/pages/watch/index.mdx @@ -8,7 +8,7 @@ title: Watch All **PUBLIC** broadcasts will be eventually listed here. Until then, enjoy:
- Big Buck Bunny + Big Buck Bunny

[Big Buck Bunny](/watch/bbb)

_video_: h.264 1280x720 3.0Mb/s