Playing an audio file in a browser is a small task. Building an application around that playback is where things get interesting.

A play button needs to know whether something is already playing. A progress bar needs the current position. An episode list needs to know which item is active. Then you add a queue, playback speed, and the ability to come back tomorrow without losing your place.

Those are all parts of the same player, even when they live in different parts of the interface.

I’ve been building yetric/audioplayer around that idea. It is a headless TypeScript audio player for modern browsers: the library owns playback and its state, while the application decides what the controls look like.

Start with a podcast app

The project’s first target is deliberately concrete: provide an audio layer that can replace the existing one in podcast-app.

That gives the library a useful set of requirements. Load an episode, play and pause it, seek, change the playback rate, report progress, navigate a queue, and persist enough state to resume later. Add Media Session integration so browser and system controls can participate too.

Hittapodd.se has been using the library since October 11, 2026. That gives the library a real podcast site to prove itself in, beyond the examples in this repository.

It also gives the work a boundary. The engine uses HTMLAudioElement. React Native support is later work. The immediate job is to make browser playback easier to integrate and reason about.

I like that kind of starting point for a library. An actual consumer gives you a way to judge whether an API is useful beyond how tidy it looks in a README.

One player, several views

The central API is a player instance created with createAudioPlayer(). The intended model is one active instance per application or page.

That instance can serve a compact player at the bottom of the screen, a larger episode view, and playback buttons in a list. Each view reads the same state and sends commands to the same player.

Here is a small browser-side example. The audio URL is a placeholder; replace it with a real file, and call playEpisode() from a play button’s click handler.

import { createAudioPlayer } from "@yetric/audioplayer";

const player = createAudioPlayer();

const unsubscribe = player.subscribe((state) => {
    console.log(state.status, state.progress.currentTime);
});

async function playEpisode() {
    await player.play({
        id: "episode-42",
        src: "https://cdn.example.com/episode-42.mp3",
        title: "Episode 42",
        artist: "My podcast",
    });
}

The subscription supplies the information a UI needs, including playback status, the current source, progress, queue position, and errors. There are also typed events for integrations that care about a particular action, such as seeking or changing the source.

When the owning application tears down the player, it should call unsubscribe() and player.destroy(). An individual view can stop subscribing without destroying the player that other views still use.

That ownership matters. Navigating away from an episode page should not have to mean throwing away the application’s audio session.

The queue belongs with playback

Queue management lives inside the player instance. The API includes setting a queue, appending and removing items, moving to the next or previous item, and choosing a repeat mode.

Each source has a stable, caller-provided id. The application supplies the identity of the episode or track alongside its audio URL and optional metadata.

That makes it possible for the interface to ask a straightforward question: is this the item currently playing? It does not have to infer identity from a title or from where the item happens to appear in a list.

Keeping queue position and playback state together also means the UI can read whether there is a next or previous item from the same subscription. The list and the transport controls have a shared account of what is happening.

Resume state and browser controls are opt-in

Persistence, progress reporting, and Media Session support attach to the player through separate integration functions.

The persistence integration has a localStorage adapter and stores the current source, playback position, queue, and settings such as rate and volume. Progress reporting supplies snapshots to an application callback, which can decide how to send them to a backend. Media Session integration connects metadata and playback actions to the browser’s media controls where supported.

Those are related capabilities, but an application may need different combinations of them. A short audio preview has different requirements from a podcast application with listening history.

The integrations also have explicit cleanup methods. Attaching behavior should come with a way to detach it when the owning application is done.

The integration guide shows how to wire these pieces together.

React is a consumer of the player

There is a React entry point at @yetric/audioplayer/react, with an AudioPlayerProvider, useAudioPlayer(), and useAudioPlayerState().

The useful part is the relationship between those hooks and the core. React subscribes to the player’s state instead of maintaining a second playback store. Components can render the state and invoke commands, while the browser audio engine stays behind the same API.

The repository also includes guidance for Angular and Web Components, as well as vanilla JavaScript examples. Keeping the core independent of a UI framework makes those different consumers possible without changing who owns playback.

For the React setup, the React guide covers the provider, state hooks, and integration cleanup.

What’s on the roadmap

The roadmap builds out from the browser player in three main directions. These are proposed improvements, rather than features available today:

  • Less application wiring. Let the React provider manage integration setup and cleanup, add state selectors and progress hooks, and provide helpers for common controls such as skipping forward or backward. The goal is for applications to delete playback code they currently maintain themselves.
  • Richer listening experiences. Add timeline cues and chapters so an application can show chapter artwork or relevant notes as the audio progresses. Sleep timers and remembering positions across multiple episodes are also candidates, subject to a real consumer need.
  • More resilient playback. Expose buffering and stalled playback more clearly, offer opt-in retries, and let applications resolve a source to an already-cached audio file. Caching and downloads would still belong to the application. Tested Angular and Web Component integrations are part of this direction too.

Better documentation, browser tests, and demos run alongside that work. Features should go through a prerelease and validation in a named consumer before becoming stable; anything without a concrete use case can be dropped.

I also want to keep up with new browser APIs and improvements to the ones the player already uses. Media Session is a good example of where the browser can make listening feel more integrated with the device. As those capabilities evolve, the aim is to adopt the useful parts through feature detection and progressive enhancement, while keeping basic playback dependable in browsers that do not support them yet.

Further out, the roadmap explores pluggable engines, React Native, multiple players, and richer audio processing. Those are explicitly ideas, not scheduled commitments. The library is intended to stay headless throughout.

Keeping the scope useful

That feels like a useful constraint. There are plenty of directions an audio library could grow, but the first test is whether a real application becomes simpler when it uses it.

For me, the interesting part of this project is the boundary: playback, queue, and state belong together; the interface gets to decide how to present them. If that boundary holds up, building a new player view becomes mostly a UI task instead of another round of coordinating audio behavior.

The code, documentation, and examples are on GitHub. If you’re building a browser audio interface, that is a good place to start exploring it.