Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

pyxpo-audio

Sound playback for pyxpo apps, written in Python: play, pause, loop, seek and set the volume of a local file or a URL. It follows the player part of expo-audio, in Python style.

The audio module on Android and iOS: a chime playing in a loop with play, restart and volume buttons

import pyxpo_audio as Audio

player = Audio.create_audio_player("https://example.com/beep.mp3", volume=0.8)
player.play()
player.pause()
player.remove()                      # frees the native player

Features

  • Files and URLs: a path, a file:// URI, an http(s):// URL or {"uri": ...}.
  • Controls: play(), pause(), seek_to(seconds), and loop and volume (0 to 1) that you can change at any time.
  • A hook for components: use_audio_player(source, loop, volume) makes the player, follows your arguments on every render and frees it when the component goes away.
  • Cheap: the native player is made on the first play() or seek_to(), so a player that never plays costs nothing.
  • Gapless loops for files on iOS (AVAudioPlayer).
  • Plays well with the phone: sound pauses while the app is in the background and plays on when it returns. iOS plays with the silent switch on and mixes with other apps' sound.
  • Works in the browser: the web page and the desktop window use an HTML audio element.
  • Typed: ships py.typed. Every name also has its expo spelling (Audio.useAudioPlayer, player.seekTo).

Contents

If you are... Start here
Playing a sound on a button Quick start
Playing a file from the web Sources
Looping music or changing the volume Controls
Playing outside a component Players you own
Hearing nothing in the browser Web and desktop
Looking for exact signatures API reference · Errors
Seeing it in a real app Recipe: a sound effect and background music
Stuck Testing · Troubleshooting · FAQ

Platform support

Target What works
Android, API 24+ MediaPlayer. A play() asked while the file is still preparing starts when it is ready. Adds the INTERNET permission (granted at install) for URLs.
iOS 15+ AVAudioPlayer for files (gapless loops), AVPlayer for URLs. Plays with the silent switch on.
Web page and desktop window (pyxpo run web / pyxpo run desktop) An HTML audio element. Browsers play sound only after the first tap on the page.

Requires Python 3.9+ and pyxpo>=0.1.0a2.

Install

pip install pyxpo-audio

Add the module to your app's pyproject.toml:

[tool.pyxpo]
modules = ["audio"]

The module has native code, so rebuild the app after adding it (pyxpo run android or pyxpo run ios). It needs no runtime permission and no prompt.

Quick start

import os

import pyxpo_audio as Audio
from pyxpo import Button, Text, View, component

CHIME = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "chime.wav")


@component
def Chime():
    player = Audio.use_audio_player(CHIME, loop=True, volume=0.8)
    return View(
        Text("Playing" if player.playing else "Paused"),
        Button("Pause" if player.playing else "Play",
               lambda: player.pause() if player.playing else player.play()),
        Button("Restart", lambda: player.seek_to(0)),
    )

Tip: player.playing is what you last asked for, not a live status from the speaker. It changes when you call play() or pause(), but a sound that does not loop stops by itself at its end while playing stays True. The next play() starts it over. Also note that setting playing does not re-render, so keep your own use_state flag when the label must change (the screenshots do this).

The audio controls on Android and iOS

Sources

Audio.use_audio_player("/data/user/0/com.example/files/song.wav")
Audio.use_audio_player("file:///path/song.wav")
Audio.use_audio_player("https://example.com/beep.mp3")
Audio.use_audio_player({"uri": "https://example.com/beep.mp3"})
Audio.use_audio_player(pathlib.Path("song.wav"))
Audio.use_audio_player(None)                      # plays nothing
Source Notes
A path, a file:// URI or a Path A Path is resolved to an absolute path. Put files you ship in your app next to your Python code, as above.
An http(s):// URL Streams on iOS with AVPlayer. Needs a network.
{"uri": ...} The expo shape.
None A player with no sound. All calls do nothing.

A source of any other type, or an empty string, raises ValueError.

Controls

player.play()                 # from where it paused, or from the start
player.pause()
player.seek_to(12.5)          # seconds, a number >= 0
player.loop = True
player.volume = 0.4           # 0 to 1
Member Meaning
play() Starts or resumes. After a non-looping sound ended, starts it over.
pause() Pauses where it is.
seek_to(seconds) (seekTo) Jumps to a time. Makes the native player if there is none yet.
loop True repeats forever. Settable.
volume A number from 0 to 1. Settable. Anything else raises ValueError.
playing What you last asked for. Read only.
uri, id The resolved source and the player's number.
remove() Stops the sound and frees the native player. The player does nothing afterwards.

In use_audio_player, loop and volume follow the arguments on every render. A new source makes a new player and removes the old one.

Players you own

Outside a component, make and free the player yourself:

player = Audio.create_audio_player("https://example.com/beep.mp3", loop=False, volume=1.0)
player.play()
...
player.remove()

Forgetting remove() leaves the native player alive until the app ends.

Errors

Error When What to do
ValueError A bad source, a volume outside 0 to 1, or a negative or non-number seek_to. Fix the argument. The message names it.
ModuleNotLinkedError (from pyxpo.module_api) The app was built without "audio" in modules, and play() was called. Add it and rebuild.

A file that cannot be found or decoded does not raise in Python. The native player cannot start, so you hear nothing. Check the path first, for example with os.path.exists.

Config

There are no module options and no config plugin. The only setup is the modules entry. On Android the module adds INTERNET to the manifest for you.

Web and desktop

pyxpo run web and pyxpo run desktop play through an HTML audio element in the page:

  • Browsers refuse sound before the first tap on the page. A play() before that is refused by the browser, and the next play() after a tap works.
  • A hidden tab pauses the sound and plays it on when it shows again, as the phone apps do.

Recipe: a sound effect and background music

Looping music that follows a "music on" switch, and a one-shot effect on a button.

import os

import pyxpo_audio as Audio
from pyxpo import Button, Text, View, component, use_state

HERE = os.path.dirname(os.path.abspath(__file__))
MUSIC = os.path.join(HERE, "assets", "music.mp3")
POP = os.path.join(HERE, "assets", "pop.wav")


@component
def Game():
    music_on, set_music = use_state(True)
    music = Audio.use_audio_player(MUSIC, loop=True, volume=0.4)
    pop = Audio.use_audio_player(POP)

    if music_on and not music.playing:
        music.play()
    if not music_on and music.playing:
        music.pause()

    def hit():
        pop.seek_to(0)
        pop.play()

    return View(
        Text("Music is %s" % ("on" if music_on else "off")),
        Button("Toggle music", lambda: set_music(not music_on)),
        Button("Pop", hit),
    )

pop.seek_to(0) before play() lets the effect play again when pressed fast.

Production checklist

  • The sound files exist on every platform and are small enough to load quickly.
  • A one-shot effect rewinds with seek_to(0) before play().
  • A player made with create_audio_player is removed with remove().
  • Music has an off switch, and starts only after a user action on the web.
  • A URL source has a network check or a fallback.
  • You tested with the silent switch on (iOS) and with the volume down.

Testing

Unit tests run on the desktop host. Calls go to a native bridge that is not there, so replace the bridge call with a fake and check what the player sent:

import pyxpo_audio as Audio


def test_play_creates_then_plays(monkeypatch):
    sent = []
    monkeypatch.setattr(Audio, "call", lambda *a, **k: sent.append(a))
    monkeypatch.setattr(Audio, "require_linked", lambda *a, **k: None)

    player = Audio.create_audio_player("song.wav", loop=True, volume=0.5)
    player.play()

    assert sent[0][:2] == ("Audio", "create") and sent[0][3:] == ("song.wav", True, 0.5)
    assert sent[1][:2] == ("Audio", "play")
    assert player.playing

Value checks need no fake: Audio.create_audio_player("x", volume=2) raises ValueError.

Troubleshooting

Symptom Cause and fix
No sound in the browser Browsers need a tap on the page first. Play after a button press.
No sound on a phone, no error The path is wrong, the format is not supported, or the volume or the phone is muted. Check os.path.exists(path) and the file type.
The label does not change when I call play() player.playing is a plain value and does not re-render. Keep a use_state flag.
A sound does not repeat loop is False. Set loop=True, or call seek_to(0) and play().
A short effect does not replay while still playing Call seek_to(0) before play().
ValueError: volume must be a number from 0 to 1 Pass a float in range. A bool is rejected too.
Sound stops when the app goes to the background By design. It plays on when the app comes back.
ModuleNotLinkedError Add "audio" to modules and rebuild.

FAQ

Can I play several sounds at once? Yes. Each player is independent.

Can I record sound? No. This module only plays.

Does it keep playing in the background or on the lock screen? No. It pauses in the background.

Which formats work? Whatever the platform's player supports, such as WAV, MP3 and AAC on both phones.

Is useAudioPlayer the same as use_audio_player? Yes. Every function has both spellings.

API reference

Functions

Function (expo name) Returns
use_audio_player(source, *, loop=False, volume=1.0) (useAudioPlayer) AudioPlayer, freed on unmount
create_audio_player(source, *, loop=False, volume=1.0) (createAudioPlayer) AudioPlayer, you call remove()

AudioPlayer

Member Description
play(), pause() Start or resume, and pause.
seek_to(seconds) (seekTo) Jump to a time in seconds.
loop, volume Settable properties.
playing What was last asked for.
uri, id The resolved source and the player number.
remove() Stop and free the native player.

Types

Name Description
Source str, Path, {"uri": ...} or None.

License

MIT

Metadata

Release files for pyxpo-audio 0.1.0a1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyxpo-audio 0.1.0a1
File Size Uploaded Provenance
pyxpo_audio-0.1.0a1.tar.gz 17.6 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pyxpo-audio 0.1.0a1
File Interpreter ABI Platform
pyxpo_audio-0.1.0a1-py3-none-any.whl Python 3 none any Details

Total release size: 32.3 kB

Release files / pyxpo_audio-0.1.0a1.tar.gz

Download URL pyxpo_audio-0.1.0a1.tar.gz
Size 17.6 kB
Tags Source
SHA-256 checksum
How to use checksums
d1b250053f928c3173779c333078376d92677ce5a72891b7eeb8f61faa86ab54
BLAKE2b-256 checksum
How to use checksums
5ec15fa0385231d0a097138229ff2469d8fb073f296e3be38c186aef6477d39a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / pyxpo_audio-0.1.0a1-py3-none-any.whl

Download URL pyxpo_audio-0.1.0a1-py3-none-any.whl
Size 14.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d31085b0fef17ae1e4fe779be6a78e9291795ed699ef83b3051ef044add376b0
BLAKE2b-256 checksum
How to use checksums
f6fd0e905c4a6df303fa2f9406cfd8b72873bd0ae6fb568210e617750cfc5311
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Security for pyxpo-audio 0.1.0a1

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pyxpo_audio-0.1.0a1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

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.

Release history Release notifications | RSS feed

This release
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page