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
18 changes: 18 additions & 0 deletions docs/source/examples/browser_server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Browser and Python server

A browser page sends your camera to Python, which sends it back in grayscale, and Python answers a chat over a data
channel. Open the printed address and allow the camera:

```bash
uv run https://raw.githubusercontent.com/MarshalX/python-webrtc/main/examples/browser_server.py
```

Python reads the video with a {obj}`~webrtc.MediaStreamTrackProcessor`, pipes it through a
{obj}`~webrtc.TransformStream` and writes it to a {obj}`~webrtc.VideoTrackGenerator`. Browsers allow the camera only on
localhost or over HTTPS, so the server listens on localhost. Behind a strict NAT, pass your own TURN server with
`--turn`.

```{literalinclude} ../../../examples/browser_server.py
:language: python
:caption: examples/browser_server.py
```
38 changes: 38 additions & 0 deletions docs/source/examples/gemini_live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Voice agent with Gemini Live

Talks to Gemini from a browser page, with live transcripts, through a Python server in the middle:

```text
browser <-- WebRTC --> Python server <-- WebSocket --> Gemini Live API
```

The server reads the microphone with a {obj}`~webrtc.MediaStreamTrackProcessor`, streams it to Gemini as 16 kHz PCM,
and writes Gemini's 24 kHz answer to a {obj}`~webrtc.MediaStreamTrackGenerator`. When you talk over Gemini, it drops
the rest of the answer.

## Why a server in the middle

Google's [Live API overview](https://ai.google.dev/gemini-api/docs/live-api) describes two ways to connect. A page can
connect to Gemini directly, with short-lived tokens in production, or go through your server. This example takes the
second way, with WebRTC to the browser, as the WebRTC partner integrations on that page do:

- The API key stays on the server, which sees every message and can add tools, logging or limits.
- The browser gets WebRTC's echo cancellation, jitter buffer and NAT traversal.
- Gemini gets the WebSocket it's specified with.

## Running it

Get a key in [Google AI Studio](https://aistudio.google.com/apikey). The free tier works, but Google uses what you send
on it to improve its products. See [Gemini API pricing](https://ai.google.dev/gemini-api/docs/pricing).

```bash
GEMINI_API_KEY=... uv run https://raw.githubusercontent.com/MarshalX/python-webrtc/main/examples/gemini_live.py
```

Open <http://localhost:8080> and press Talk. Anyone who can open the page talks on your key, so the server listens on
localhost.

```{literalinclude} ../../../examples/gemini_live.py
:language: python
:caption: examples/gemini_live.py
```
32 changes: 28 additions & 4 deletions docs/source/examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ Sends the received video back in grayscale.
Writes received video and audio to raw files.
:::

:::{grid-item-card} {octicon}`broadcast;1em;sd-mr-1` Live stream in the terminal
:link: janus_streaming
:::{grid-item-card} {octicon}`browser;1em;sd-mr-1` Browser and Python server
:link: browser_server
:link-type: doc

Watches a public stream, with the video as colored characters and the audio on your speakers.
Sends your camera back from Python in grayscale, with a chat over a data channel.
:::

:::{grid-item-card} {octicon}`unmute;1em;sd-mr-1` Voice chat with OpenAI GPT-Live
Expand All @@ -37,13 +37,37 @@ Watches a public stream, with the video as colored characters and the audio on y
Talks to the model through your microphone and speakers.
:::

:::{grid-item-card} {octicon}`comment-discussion;1em;sd-mr-1` Voice agent with Gemini Live
:link: gemini_live
:link-type: doc

Talks to Gemini from a browser page, through a Python server that keeps the API key.
:::

:::{grid-item-card} {octicon}`broadcast;1em;sd-mr-1` Live stream in the terminal
:link: janus_streaming
:link-type: doc

Watches a public stream, with the video as colored characters and the audio on your speakers.
:::

:::{grid-item-card} {octicon}`broadcast;1em;sd-mr-1` WHIP and WHEP
:link: whip_whep
:link-type: doc

Plays a WHEP stream in the terminal, or publishes one over WHIP.
:::

::::

```{toctree}
:hidden:

echo
recorder
janus_streaming
browser_server
openai_live
gemini_live
janus_streaming
whip_whep
```
27 changes: 27 additions & 0 deletions docs/source/examples/whip_whep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# WHIP and WHEP

Plays a live stream over [WHEP](https://www.rfc-editor.org/rfc/rfc9725) in the terminal, or publishes one over WHIP.
With no URL it plays the public demo stream of [Dolby OptiView](https://optiview.dolby.com/), with no account or key:

```bash
uv run https://raw.githubusercontent.com/MarshalX/python-webrtc/main/examples/whip_whep.py
```

The demo stream is H.264. Where the OS has no H.264 codec, as on Linux and Windows, the example downloads Cisco's
OpenH264 binary once, and `--no-openh264` skips it. See [H.264](../guides/h264.md).

OpenH264 Video Codec provided by Cisco Systems, Inc.

To publish with `--whip` and play it back, a local [MediaMTX](https://github.com/bluenviron/mediamtx) server works:

```bash
docker run -d --rm --name wrtc-mediamtx -p 8889:8889 -p 8189:8189/udp bluenviron/mediamtx:latest
uv run whip_whep.py --whip http://localhost:8889/wrtc/whip
uv run whip_whep.py http://localhost:8889/wrtc/whep
docker stop wrtc-mediamtx
```

```{literalinclude} ../../../examples/whip_whep.py
:language: python
:caption: examples/whip_whep.py
```
5 changes: 3 additions & 2 deletions docs/source/guides/connections.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ async def on_candidate(event):
await signaling.send(event.candidate.to_json())
```

To skip trickling, wait for `ice_gathering_state` to be `'complete'` and send `pc.local_description`, which then has
every candidate in it.
To skip trickling, send `pc.local_description` once the candidates you need have arrived: it includes them. Don't wait
for `ice_gathering_state` to be `'complete'`: with a STUN server, an address that can't reach it, like a VPN's, holds
gathering open for about 40 seconds. Stop at the first `srflx` candidate, or after a short pause with no new ones.

## Peers on one machine

Expand Down
8 changes: 7 additions & 1 deletion docs/source/llms-txt.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,15 @@ of date.

- {doc}`Echo peer <examples/echo>`: sends back the video it receives, in grayscale.
- {doc}`Recorder <examples/recorder>`: writes the video and audio a peer receives to files.
- {doc}`Live stream in the terminal <examples/janus_streaming>`: plays a stream of the public Janus demo server.
- {doc}`Browser and Python server <examples/browser_server>`: serves a page whose camera comes back from Python in
grayscale, with a chat over a data channel.
- {doc}`Voice chat with OpenAI GPT-Live <examples/openai_live>`: talks to the model through the microphone and
speakers.
- {doc}`Voice agent with Gemini Live <examples/gemini_live>`: a browser talks to Gemini over WebRTC through a Python
server, which streams the audio to the Live API over a WebSocket.
- {doc}`Live stream in the terminal <examples/janus_streaming>`: plays a stream of the public Janus demo server.
- {doc}`WHIP and WHEP <examples/whip_whep>`: plays a WHEP stream, a public demo by default, or publishes the synthetic
camera and microphone over WHIP.

## Optional

Expand Down
24 changes: 24 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ without worrying about copyrights.

**Transforming video with a processor, a TransformStream and a VideoTrackGenerator.** Sends the received video back in grayscale.

### [browser_server.py](browser_server.py)

**A Python WebRTC server for a browser page.** Your camera comes back from Python in grayscale, and Python answers a chat over a data channel. Open the printed address and allow the camera:

```bash
uv run https://raw.githubusercontent.com/MarshalX/python-webrtc/main/examples/browser_server.py
```

### [openai_live.py](openai_live.py)

**Voice chat with OpenAI GPT-Live.** Talks to the model through your microphone and speakers, with live transcripts in the terminal. No server: just an API key. Dependencies are declared inline, so [uv](https://docs.astral.sh/uv/) runs it with nothing to install:
Expand All @@ -20,10 +28,26 @@ without worrying about copyrights.
OPENAI_API_KEY=sk-... uv run https://raw.githubusercontent.com/MarshalX/python-webrtc/main/examples/openai_live.py
```

### [gemini_live.py](gemini_live.py)

**Voice agent with Gemini Live.** A browser page talks to Gemini through a Python server that keeps the API key, over WebRTC to the page and a WebSocket to the Live API:

```bash
GEMINI_API_KEY=... uv run https://raw.githubusercontent.com/MarshalX/python-webrtc/main/examples/gemini_live.py
```

### [janus_streaming.py](janus_streaming.py)

**Watching a live stream in the terminal.** Plays a stream of the public [Janus](https://janus.conf.meetecho.com/) demo server: the video drawn with colored characters, the audio on your speakers, if there are any, so it runs on a server too. No account or key:

```bash
uv run https://raw.githubusercontent.com/MarshalX/python-webrtc/main/examples/janus_streaming.py
```

### [whip_whep.py](whip_whep.py)

**Playing and publishing streams over WHIP and WHEP.** Plays the public demo stream of [Dolby OptiView](https://optiview.dolby.com/) in the terminal, or any WHEP endpoint, and `--whip` publishes instead. Where the OS has no H.264 codec, it downloads Cisco's OpenH264 once (OpenH264 Video Codec provided by Cisco Systems, Inc.). No account or key:

```bash
uv run https://raw.githubusercontent.com/MarshalX/python-webrtc/main/examples/whip_whep.py
```
Loading
Loading