04 · Guides
The player
Player owns one output stream for its whole lifetime. Every
method below acts on that stream, so starting a track never reopens the device.
import sound_rs
player = sound_rs.Player() # opens the default device
player.play_file("first.flac") # returns immediately
player.wait()
player.play_file("second.mp3") # no device setup, starts at once
player.stop()
del player # closes the streamwith sound_rs.Player() as player: closes the stream on exit, including when an exception is raised. Without it, the stream closes when the object is garbage collected, which you cannot predict.Lifecycle
| State | What it means | Leaves it |
|---|---|---|
| idle | Stream open, nothing loaded | play_file, play_array |
| playing | Callback writes audio | pause, stop, end of source |
| paused | Callback writes silence, position frozen | resume, stop |
| finished | Source exhausted, stream still open | play_*, resume (replays) |
Controls
| Call | Effect |
|---|---|
| play_file(path) | Streams a file from disk. Returns immediately. |
| play_array(arr, rate) | Plays an in-memory array; resampled to the device once, up front. |
| pause() | Stops producing sound and freezes position. |
| resume() | Continues from the paused position, or replays from the start if finished. |
| stop() | Rewinds to zero, keeps the stream open. |
| seek(seconds) | Jumps; works while paused. |
| wait() | Blocks until the source ends (GIL released). |
| del player | Stops the decoder and closes the stream (or use with). |
Seeking
Seeking a streamed file is accurate to the frame. Containers can only seek to a packet
boundary, so the player reads the packet's real timestamp and drops the difference. A seek to
0.5 in a 16 kHz file lands on frame 8000, not on the nearest packet start.
player.play_file("track.flac")
player.seek(90.5) # 1:30.5
player.wait()
print(player.position) # ~ track length, not the seek offsetposition keeps counting from the seek target, so duration and position stay in the same units.Volume
Applied in the callback, so it changes instantly and does not touch the decoded data. Range is 0 to 4; values outside are clamped. Volume 1.0 is unity.
player.volume = 0.25 # quiet
player.volume = 1.0 # unity
Position and duration
Both are seconds, read from the audio clock, not from wall time. For files the duration
comes from the container header and is available right after play_file. For
arrays it is the array length divided by the sample rate.
player.play_file("track.mp3")
print(player.duration) # 213.4
print(player.position) # 0.0
print(player.is_playing) # True
Queue
enqueue lines up the next file; when the current one ends the decoder opens it
and continues on the same stream. Rate and channel count may differ between tracks.
with sound_rs.Player() as player:
player.play_file("first.flac")
player.enqueue("second.mp3")
player.enqueue("third.wav")
print(player.queue) # ['second.mp3', 'third.wav']
player.wait() # plays all three
Pushing audio from Python
For synthesis or a network stream, feed samples instead of a file. feed blocks
while the buffer is full, so a fast producer cannot exhaust memory. If the buffer cannot drain
for five seconds — a paused player, for example — it raises
SoundRsError with code="stalled" instead of hanging.
import numpy as np
import sound_rs
player = sound_rs.Player()
player.begin_feed(48000)
for _ in range(100):
block = (0.2 * np.sin(np.arange(4800) * 0.057)).astype(np.float32)
player.feed(block) # returns when the buffer has room
player.end_feed()
player.wait()player.sample_rate and feed that.Threading and the GIL
Playback and decoding run on Rust threads, and every blocking call releases the GIL.
player.wait() in the main thread does not block an asyncio loop
running in another thread, and threading.Thread(target=sound_rs.play_audio, ...)
works as expected.
One player is one stream. Do not call methods on the same player from several threads at once; create one player per thread instead.
Device selection
import sound_rs
print(sound_rs.list_devices())
player = sound_rs.Player(device="MacBook Pro Speakers")
print(player.device, player.sample_rate, player.channels)
The device is opened once, at construction. If a device disappears mid-playback, the
player raises SoundRsError instead of hanging: playback stalls for two seconds
without progress, then fails.