jupyterlab_voice_capture_extension
Capture microphone audio in the JupyterLab browser tab and stream it to a server-side FIFO, so terminal applications running inside the container - notably Claude Code voice mode - can record from a microphone the container itself has no access to.
The container has no capture device; the browser does. This extension bridges that gap: the browser captures the mic, ships the audio over an authenticated websocket to a Jupyter server handler, and the handler writes raw PCM to a named pipe. A separate, out-of-scope plumbing layer (PulseAudio module-pipe-source + SoX) turns that pipe into the system default audio source.
How it works
- Capture - a microphone toggle in the status bar calls
getUserMedia; the AudioContext resamples to 16 kHz; an AudioWorklet downmixes to mono and encodes signed 16-bit little-endian PCM off the UI thread - Transport - 20 ms PCM frames (640 bytes) are sent as binary websocket messages to
…/jupyterlab-voice-capture-extension/stream, which lives under the Jupyter base URL and inherits Jupyter token auth - no new port is opened - Sink - the server handler writes each frame, in order, to a FIFO (default
/run/voice/pulseaudio.fifo); the PulseAudio reader creates the pipe (module-pipe-sourcerefuses a pre-existing one), so the handler attaches as writer, waits for the pipe to appear, and tolerates a not-yet-attached reader without blocking the server
Chain: browser mic → AudioWorklet (16 kHz mono s16le) → websocket → server handler → FIFO → (PulseAudio + SoX, out of scope) → terminal app.
Requirements
- JupyterLab >= 4.0.0
- A secure context (https or
localhost) - browsers only expose the microphone over a secure origin
Install
pip install jupyterlab-voice-capture-extension
Dependencies
- Python:
jupyter_serverandtraitlets, installed automatically with the package - System (only for the full voice chain into a terminal app): PulseAudio + SoX. Provision and verify them with the bundled CLI:
jupyterlab_voice_capture install # apt packages + /run/voice dir + client.conf + Jupyter config line (does NOT start the daemon)
jupyterlab_voice_capture start -d # start the PulseAudio daemon + pipe-source + voicedrain loopback that keeps it reading (run after install and each restart)
jupyterlab_voice_capture validate # check every component, print what to fix (--json for machine output)
jupyterlab_voice_capture stop # kill the PulseAudio daemon
See docs/jupyterlab-enable-claude-voice.md for the full setup and troubleshooting.
Usage
- Click the microphone icon in the status bar (or run Toggle Voice Capture from the command palette) to start capture
- On the first start the browser asks for microphone permission; the status label moves Disconnected → Connecting → Connected, the icon glows green while streaming, and the browser shows its active-microphone indicator
- Click again to stop - capture tracks are released and the browser indicator clears
- In the Error state (blinking icon) a click disconnects and returns the control to Disconnected; click once more to start capture again
- Only one tab streams at a time: starting capture in a second tab takes over and stops the first
Configuration
The sink FIFO path defaults to /run/voice/pulseaudio.fifo and is overridable via Jupyter server config:
c.VoiceCapture.sink_path = "/run/voice/pulseaudio.fifo"
Settings → Voice Capture has one option, Auto-connect on startup (autoConnect, default off): when enabled, capture starts automatically as JupyterLab loads instead of waiting for a click.
Uninstall
pip uninstall jupyterlab-voice-capture-extension
Troubleshoot
If you see the frontend extension but it is not working, check that the server extension is enabled:
jupyter server extension list
If the server extension is installed and enabled but you do not see the frontend extension, check the frontend extension is installed:
jupyter labextension list
Contributing
If you would like to contribute to this extension, please refer to the Contributing Guide.
Metadata
Release files for jupyterlab-voice-capture-extension 1.0.13
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jupyterlab_voice_capture_extension-1.0.13.tar.gz | 401.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jupyterlab_voice_capture_extension-1.0.13-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 520.2 kB
Release files / jupyterlab_voice_capture_extension-1.0.13.tar.gz
| Download URL | jupyterlab_voice_capture_extension-1.0.13.tar.gz |
|---|---|
| Size | 401.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bfdc0057013eb5230357bedc3ded2e4a9be33722b30ce4733e5aa1dce9aa141e
|
|
BLAKE2b-256 checksum How to use checksums |
369390635b8159ea2f695befaf722327cdfc7abc21cd206e564d35a29386e3aa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|
Release files / jupyterlab_voice_capture_extension-1.0.13-py3-none-any.whl
| Download URL | jupyterlab_voice_capture_extension-1.0.13-py3-none-any.whl |
|---|---|
| Size | 118.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
59ac1df1b05d97ccace432904341302a935b87e3c09896636709600c77c78bd3
|
|
BLAKE2b-256 checksum How to use checksums |
56fe7a1fb84ba6faa359747d119e2981d12bae3f5299df4c9f140afbe792c242
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|