Check out my personal blogsVisit maxrave.dev
SimpMusicSimpMusic Docs
For Developers

Playback engine

How the player is built — one ExoPlayer per track, crossfade, DJ filters, and the Cast handoff.

Playback is the least conventional part of the codebase. SimpMusic does not hand a playlist to the platform player and let it run — it manages the queue itself and drives one player instance per track.

Two engines, one interface

PlatformAdapterBackend
AndroidCrossfadeExoPlayerAdapterMedia3 / ExoPlayer
DesktopVlcPlayerAdapterVLC via vlcj

Both implement MediaPlayerInterface, so everything above them — ViewModels, the media session, the UI — is platform-agnostic.

A stale comment in CrossfadeExoPlayerAdapter still refers to a GstreamerPlayerAdapter. The desktop backend is VLC; the GStreamer path is gone.

Why one player per track

Crossfading requires two tracks producing audio at the same time, which a single player instance cannot do. So the adapter:

  • keeps its own internal playlist rather than ExoPlayer's
  • gives each track its own ExoPlayer, holding a single MediaItem
  • precaches the upcoming track so the transition doesn't stall
  • swaps listeners between instances as the active track changes
  • exposes the whole thing to MediaSession through a DelegatingForwardingPlayer

Stream URLs are resolved lazily by MergingMediaSourceFactory plus a ResolvingDataSource, so the adapter never has to extract URLs up front.

DJ-style transitions

CrossfadeFilterAudioProcessor is a Media3 AudioProcessor applying a real-time Biquad filter during a crossfade:

  • Low-pass on the outgoing track
  • High-pass on the incoming track
  • Cutoff frequency is animated across the transition

Two details worth knowing when working on it: the processor reports isActive = false when disabled, so ExoPlayer skips it entirely and costs nothing during normal playback; and filter coefficients are recomputed lazily, only when the cutoff or filter type actually changes. It handles 16-bit PCM, mono and stereo.

Cast handoff

Because the adapter simulates the playlist itself, every ExoPlayer sits on a single-item timeline — which means Media3's built-in state transfer cannot populate a Cast receiver's queue. CastHandoffManager fills that gap by watching the session player's DeviceInfo:

TransitionWhat the manager does
Local → remoteSnapshots index and position, silences local playback, pushes a resolved-URL window to the receiver
While remoteRoutes transport calls to the session player, polls remote position
Remote → localRestores the adapter at the last known remote position

Resolve failures on the current track are counted, and auto-skip is capped, so a network outage cannot walk the whole queue.

Community

Get involved in our community. Everyone is welcome!

SimpMusic is sponsored by:

Get free $200 credit over 60 days on DigitalOcean: GET NOW

Crowdin and Sentry both have a free enterprise plan for Open-source projects. Follow the URLs:

Check out the Vercel open-source program

SimpMusic app is FOSS and under GPL-3.0 license.

© 2023-2026 SimpMusic - @maxrave-dev

We are not affiliated with or endorsed by Google, YouTube, Spotify, or any other third-party brands or applications.