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
3 changes: 2 additions & 1 deletion docs/source/examples/janus_streaming.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Live stream in the terminal

Plays a stream of the public [Janus](https://janus.conf.meetecho.com/) demo server. The video is drawn with colored
characters and the audio plays on your speakers. It needs no account or key:
characters and the audio plays on your speakers. Without speakers, as on a server, it plays the video only. It needs no
account or key:

```bash
uv run https://raw.githubusercontent.com/MarshalX/python-webrtc/main/examples/janus_streaming.py
Expand Down
6 changes: 3 additions & 3 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ without worrying about copyrights.

**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:

```
```bash
OPENAI_API_KEY=sk-... uv run https://raw.githubusercontent.com/MarshalX/python-webrtc/main/examples/openai_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. No account or key:
**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
```
27 changes: 24 additions & 3 deletions examples/janus_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
uv run janus_streaming.py --id 1
uv run janus_streaming.py --id 3 # stereo music

The video is drawn with colored half blocks, so the terminal needs 24-bit color. Press Ctrl+C to stop.
The video is drawn with colored half blocks, so the terminal needs 24-bit color. Without an audio output, as on
a server, it plays the video only. Press Ctrl+C to stop.
"""

from __future__ import annotations
Expand All @@ -31,11 +32,17 @@
from typing import TYPE_CHECKING, TypedDict

import httpx
import sounddevice
from typing_extensions import NotRequired, Required

import webrtc

try:
import sounddevice
except OSError:
PORTAUDIO = False
else:
PORTAUDIO = True

if TYPE_CHECKING:
from collections.abc import Generator
from types import TracebackType
Expand Down Expand Up @@ -168,6 +175,17 @@ def close(self) -> None:
self.stream.close()


def speakers_found() -> bool:
"""Whether audio can be played: servers often lack the PortAudio library or an output device."""
if not PORTAUDIO:
return False
try:
sounddevice.query_devices(kind='output')
except sounddevice.PortAudioError:
return False
return True


def draw(rgbx: bytes | bytearray, width: int, height: int, *, slot: int, slots: int) -> None:
"""Draws a frame in its slot of the screen with ▀, its foreground is the upper pixel, background the lower one."""
total, rows = shutil.get_terminal_size()
Expand Down Expand Up @@ -289,13 +307,16 @@ async def main(stream_id: int | None) -> None:
pc = webrtc.RTCPeerConnection()
tasks: list[asyncio.Future[None]] = []
videos: list[webrtc.MediaStreamTrack] = []
speakers = speakers_found()
if not speakers:
print('No audio output, playing the video only')

@pc.on('track')
def on_track(event: webrtc.RTCTrackEvent) -> None:
if event.track.kind == 'video':
videos.append(event.track)
tasks.append(asyncio.ensure_future(watch(event.track, videos)))
else:
elif speakers:
tasks.append(asyncio.ensure_future(listen(event.track)))

async with Janus() as janus:
Expand Down
Loading