viser-audio
Audio for viser scenes and timeline players: positional clips, playback control, retained streaming, and client-local timeline synchronization. Installs alongside viser.
Install
pip install viser-audio
Live scenes
import numpy as np
import viser
import viser_audio
server = viser.ViserServer()
audio = viser_audio.AudioApi(server)
time = np.arange(44100) / 44100
clip = audio.add(
"/speaker",
0.3 * np.sin(2 * np.pi * 220 * time),
44100,
loop=True,
positional=True,
)
clip.play()
clip.position = (1.0, 0.0, 0.0)
clip.volume = 0.5
server.sleep_forever()
AudioApi(server)installs the runtime once per server.add(name, samples, sample_rate=44100, *, volume=1, loop=False, positional=False, playback_rate=1, start_time=None, wxyz, position)creates a clip and returns anAudioHandle. Adding the same name replaces it.play(offset=None)starts or resumes; an explicit offset seeks in seconds.pause()retains an explicit command offset for replay to new tabs.samplesreads back a copy of normalized float32 audio. Assigning replaces the samples and restarts live clips; timeline tracks retain their anchor.append(samples)extends the clip and resumes a stream that ran out of data. It retains all samples for replay; it is not an unbounded streaming sink.volume,loop,positional,playback_rate,position, andwxyzare writable.duration,sample_rate,start_time, andnameare readable.remove()releases the clip and its node. Removed or replaced handles reject further writes.
Samples have shape (N,) or (N, C), with up to 32 channels. Integer PCM is
normalized around zero; floating-point input must be finite. Empty clips can
receive samples later. Rates must be positive; playback speed also changes pitch.
Browser-supported sample rates are required when creating audio buffers.
Each new tab receives the current clip state and latest playback command. Live commands do not synchronize browser wall clocks. Browsers require a gesture to start audio; an Enable audio button appears when playback is blocked.
Timeline players and recordings
Use a callback to record messages without creating a live server. The callback
can reject edits outside a recording context; handle state changes only after it
accepts the message. Pass scene= if the recording also owns positional nodes.
from viser_audio.messages import AudioMessage
recording: list[AudioMessage] = []
audio = viser_audio.AudioApi(recording.append)
track = audio.add("/speech", samples, 44100, start_time=step / fps)
track.volume = 0.8
track.append(more_samples)
start_time is in timeline seconds. These tracks are controlled by the browser
clock, so play() and pause() reject direct control.
The injected runtime exposes its engine at window.__VISER_AUDIO__.engine:
const engine = window.__VISER_AUDIO__.engine;
engine.setTransport(() => ({
position: player.currentTime, // seconds
playing: player.playing,
rate: player.speed, // positive, e.g. 0.5, 1, 2
}));
// Each checkpoint contains complete AudioAddMessage payloads.
engine.loadCheckpoint(checkpointTracks);
for (const message of subsequentEvents) engine.handle(message);
engine.sync(); // immediately apply a seek or other clock change
The engine handles future starts, pause/resume, scrubbing, speed changes, track
loops, and autoplay recovery. Timeline looping is a seek performed by the player.
Use reset() to clear tracks and dispose() when the player is destroyed.
Different engines and tabs have independent clocks and resources.
AudioState.apply(message) folds recorded events; snapshot() returns complete
tracks for checkpoints, and copy() shares immutable sample storage. Decode
stored payloads with viser_audio.messages.from_payload() after resolving binary
placeholders.
message.as_payload(binary_buffers) returns the public data protocol for custom
recording storage. Without the buffer argument, samples are memoryviews; with it,
arrays use viser's binary placeholders. The player must resolve placeholders
before calling handle(). The engine accepts Float32Array, Uint8Array, and
ArrayBuffer sample data, all containing little-endian float32 PCM.
message.as_serializable_dict() returns a viser JavaScript command.
viser_audio.runtime_source() supplies the runtime for live or exported players.
A standalone ES module, viser_audio/engine.js in the installed package, exports
AudioEngine; TypeScript sources and protocol types ship under viser_audio/client.
It needs no viser viewer for non-positional audio.
Run uv run python examples/03_timeline.py to create a self-contained HTML player.
Exported players must bind their playback clock explicitly. No DOM controls are
scraped, and no older protocol or viser4d compatibility adapter is provided.
See the design and viser4d feature comparison for the integration
contract and remaining constraints.
Development
uv sync --group dev
uv run ruff check .
uv run ruff format --check .
uv run pyright
uv run pytest tests --ignore=tests/e2e
uv run playwright install chromium
uv run pytest tests/e2e
npm --prefix src/viser_audio/client ci
npm --prefix src/viser_audio/client run typecheck
npm --prefix src/viser_audio/client run build
Commit both generated bundles, runtime.js and engine.js. CI checks that they
match the TypeScript sources. Live delivery uses viser's JavaScript command with
base64 PCM; custom recording players can use binary payloads directly.
Metadata
Release files for viser-audio 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| viser_audio-0.1.0.tar.gz | 34.3 kB | Without provenance | Details for viser_audio-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| viser_audio-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for viser_audio-0.1.0-py3-none-any.whl |
Total release size: 71.3 kB
Release files / viser_audio-0.1.0.tar.gz
| Download URL | viser_audio-0.1.0.tar.gz |
|---|---|
| Size | 34.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3b178c76d03df42799d8f6fb041673d5a962f9d11fd51299e31024e4a2e8a293
|
|
BLAKE2b-256 checksum How to use checksums |
b19d3e0ba0bf0143f98c7c78b1802eac82984aedce61e8d967d41f68f0d51582
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / viser_audio-0.1.0-py3-none-any.whl
| Download URL | viser_audio-0.1.0-py3-none-any.whl |
|---|---|
| Size | 37.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
20b67e441d36c3c5b6ef6c03a25061de9979d89f700748114058b125b1b77f20
|
|
BLAKE2b-256 checksum How to use checksums |
0d48aea171bdec27afa7bc9d763051264b87358ef5ef1ce376d25a0d687d4130
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for viser-audio 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.