Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@
<a href="https://pypi.org/project/wrtc/">PyPI</a>
</p>

<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" type="image/avif" srcset="https://github.com/MarshalX/python-webrtc/raw/main/docs/source/_static/img/demo-dark.avif">
<source type="image/avif" srcset="https://github.com/MarshalX/python-webrtc/raw/main/docs/source/_static/img/demo-light.avif">
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/MarshalX/python-webrtc/raw/main/docs/source/_static/img/demo-dark.gif">
<img src="https://github.com/MarshalX/python-webrtc/raw/main/docs/source/_static/img/demo-light.gif" alt="A browser page sends its camera to a Python server, which sends it back in grayscale and answers a chat message over a data channel">
</picture>
</p>

---

Python WebRTC binds **libwebrtc M152**, the engine inside Chrome, and exposes it through the **W3C APIs you
Expand Down Expand Up @@ -98,11 +107,13 @@ equal to the strings of the specification, dictionaries are typed dataclasses, a
| Browser API (`RTCPeerConnection`, `createOffer`, …) | ✅ | similar | ❌ rooms | ❌ GObject signals |
| Media path | native | Python | native | native |
| VP9 and AV1 | ✅ | ❌ | ✅ | ✅ |
| `pip install` on Linux, macOS and Windows | ✅ | ✅ | ✅ | ❌ system packages |
| `pip install` on Linux, macOS and Windows | ✅ | ✅ | ✅ | ❌ no Linux wheels |

Against aiortc, it opens connections 7.3–8.3x faster, moves 7.7–14.6x more data through a data channel and spends
less CPU on audio. See the [benchmarks](https://wrtc.marshal.dev/benchmarks/), which also measure Pion, libdatachannel
and webrtc-rs.

Against aiortc, it opens connections 7–8x faster, moves 7.7–14.6x more data through a data channel and spends less
CPU on audio. See the [benchmarks](https://wrtc.marshal.dev/benchmarks/), which also measure Pion, libdatachannel and
webrtc-rs.
The full comparison, and when another library is the better choice: [Choosing a Python WebRTC library](https://wrtc.marshal.dev/guides/comparison/).

## Codecs

Expand Down
45 changes: 45 additions & 0 deletions docs/source/_static/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,48 @@ dl.py.class > dd > dl.py.attribute:not(:has(> dd > *)) > dt {
padding-top: 0.35rem;
border-top: 0;
}

/* Page actions: copy the page's Markdown or ask a chat about it. */
.page-actions {
float: right;
display: flex;
gap: 0.75rem;
margin-left: 1rem;
font-size: 0.8125rem;
line-height: 1.25rem;
color: hsl(var(--muted-foreground));
}

.page-actions[hidden] {
display: none;
}

.page-actions button,
.page-actions a {
display: inline-flex;
align-items: center;
gap: 0.3rem;
color: inherit;
text-decoration: none;
white-space: nowrap;
}

.page-actions svg {
width: 0.875rem;
height: 0.875rem;
flex: none;
}

.page-actions button:hover,
.page-actions a:hover {
color: hsl(var(--foreground));
}

@media (max-width: 640px) {
.page-actions {
float: none;
flex-wrap: wrap;
row-gap: 0.25rem;
margin: 0 0 0.5rem;
}
}
Binary file added docs/source/_static/img/demo-dark.avif
Binary file not shown.
Binary file added docs/source/_static/img/demo-dark.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/img/demo-light.avif
Binary file not shown.
Binary file added docs/source/_static/img/demo-light.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions docs/source/_static/js/copy-page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Wires the page actions to the Markdown copy of the page that sphinx-llm links in <head>.
(function () {
const link = document.querySelector('link[rel="alternate"][type="text/markdown"]');
const actions = document.querySelector('.page-actions');
if (!link || !actions) {
return;
}

const markdownUrl = new URL(link.getAttribute('href'), location.href).href;
const question = encodeURIComponent(`Read ${markdownUrl} so I can ask questions about it.`);
actions.querySelector('.view-markdown').href = markdownUrl;
actions.querySelector('.open-chatgpt').href = `https://chatgpt.com/?q=${question}`;
actions.querySelector('.open-claude').href = `https://claude.ai/new?q=${question}`;

// Safari writes to the clipboard only synchronously inside the click, so the fetch is handed over as a promise.
let markdown = null;
function fetchMarkdown() {
markdown ??= fetch(markdownUrl).then((response) => (response.ok ? response.text() : Promise.reject(response.status)));
return markdown;
}

const button = actions.querySelector('.copy-page');
const label = button.querySelector('span');
button.addEventListener('click', () => {
const blob = fetchMarkdown().then((text) => new Blob([text], {type: 'text/plain'}));
navigator.clipboard.write([new ClipboardItem({'text/plain': blob})]).then(
() => {
label.textContent = 'Copied';
setTimeout(() => { label.textContent = 'Copy page'; }, 2000);
},
() => { window.open(markdownUrl, '_blank', 'noopener'); },
);
});

actions.hidden = false;
})();
23 changes: 23 additions & 0 deletions docs/source/_templates/page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{#- Adds page actions for agents: copy the page's Markdown, view it, ask a chat about it. -#}
{%- extends "!page.html" %}

{%- block body_before %}
<div class="page-actions" hidden>
<button type="button" class="copy-page">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
<span>Copy page</span>
</button>
<a class="view-markdown" target="_blank" rel="noopener">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M22.27 19.385H1.73A1.73 1.73 0 010 17.655V6.345a1.73 1.73 0 011.73-1.73h20.54A1.73 1.73 0 0124 6.345v11.308a1.73 1.73 0 01-1.73 1.731zM5.769 15.923v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.46v7.847zM21.232 12h-2.309V8.077h-2.307V12h-2.308l3.461 4.039z"/></svg>
<span>Markdown</span>
</a>
<a class="open-chatgpt" target="_blank" rel="noopener" title="Open in ChatGPT">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"/></svg>
<span>ChatGPT</span>
</a>
<a class="open-claude" target="_blank" rel="noopener" title="Open in Claude">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z"/></svg>
<span>Claude</span>
</a>
</div>
{%- endblock body_before %}
11 changes: 6 additions & 5 deletions docs/source/benchmarks.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ This chart favors aiortc, for two reasons that come from the engine rather than
controller and its transport. The Python objects on top take under 1 KB.
- **A connected data channel** is mostly ICE: the engine keeps every candidate pair it checked, about 8 KB each, to
switch networks without reconnecting. The benchmark Mac has 10 addresses (Wi-Fi, VPN, virtual machine bridges and
loopback), each over UDP and TCP, so 50 pairs a connection. aiortc gathers UDP only and skips loopback. In the same
loopback), each over UDP and TCP, so 50 pairs a connection. aiortc gathers UDP only. In the same
test on Linux with one interface, a connected pair takes a third of the memory it takes on that Mac.

Memory stays reserved after connections close, because the allocator keeps it for reuse: opening and closing 100
Expand All @@ -87,8 +87,8 @@ close. H.264 is cheaper at every resolution, decoded by VideoToolbox on macOS.

Both libraries send under the same bitrate cap. Read the two panels under the bars before the bars:

- **Bitrate over the cap.** Python WebRTC stays under the cap. aiortc's VP8 ignores it as the picture grows: twice the
cap at 1080p60 and five times at 2160p. Sent to a real network, that is packet loss.
- **Bitrate over the cap.** Python WebRTC stays under the cap. aiortc's VP8 ignores it as the picture grows: 2.4 times
the cap at 1080p60 and five times at 2160p. Sent to a real network, that is packet loss.
- **Frame rate over the source.** To fit 1080p and 4K into 1.5 Mbps, the engine keeps the resolution and drops
frames, as browsers do with that setting. It encodes fewer, much larger frames, so the CPU per frame goes up.

Expand All @@ -101,8 +101,9 @@ encoder on macOS and costs a tenth of the CPU per frame.

Two peers of the same library call each other, and each library's congestion control picks the bitrate. Python WebRTC
reaches the bitrate the video asks for, 2.5 or 5 Mbps, while aiortc stays near 1.5–2.4 Mbps. The panel under the bars
shows it: Python WebRTC receives up to three times the bits for every frame, so a better picture, and decoding more
bits costs more CPU per frame.
shows it: Python WebRTC receives up to 2.8 times the bits for every frame, and decoding more bits costs more CPU per
frame. That isn't the whole cost: at H.264 720p the bits per frame are within 6%, and Python WebRTC's receiver still
takes more CPU per frame.

![Video call latency](https://raw.githubusercontent.com/MarshalX/python-webrtc/main/benchmarks/charts/media-video-latency.png)

Expand Down
5 changes: 5 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@
html_css_files = [
'css/custom.css',
]
html_js_files = [
('js/copy-page.js', {'defer': 'defer'}),
]
templates_path = ['_templates']

html_theme_options = {
'show_prev_next': True,
Expand Down Expand Up @@ -206,6 +210,7 @@
# -- llms.txt ---------------------------------------------------
# a Markdown copy of every page, e.g. /quickstart.md, for agents
llms_txt_override_source = 'llms-txt'
llms_txt_suffix_mode = 'url-suffix'
llms_txt_nested_enabled = False
llms_txt_full_build = True
LLMS_MARKDOWN_BUILDER = 'llms-markdown'
Expand Down
Loading
Loading