Features¶
Ad handling — quiet by default¶
The ad engine is a pure-JS layer injected into the page. It does no network interception and no packet-level filtering — it observes the player and reacts.
- Mute is the default. When an ad starts, the engine mutes the WebView's audio (recording the previous volume so it can restore it), and unmutes when the ad ends.
-
Skip is opt-in. An explicit toggle in settings arms skip:
- only when the player reports an ad (
contentType === "ad"or aspotify:ad:URI), - only if the "track" is under 90 seconds,
- with a 5-second cell cooldown per ad,
- rate-limited to 3 skips per 10 minutes.
- only when the player reports an ad (
-
Two detection layers. The engine scans the webpack module registry (
ListPlayer) for a clean playback-state signal, and falls back to DOM probing ([aria-label~="Advertisement"]) when the registry isn't reachable. - Modes are runtime-switchable (off / mute / mute+skip) from the when ad settings screen.
Themes¶
Spicetify-style theming, fully offline:
themes/win95,themes/spotify-dark,themes/retro-player— each acolor.inipalette plus auser.css.- The theme runs through the Theming skill pipeline: palette → CSS custom properties → injected stylesheet, applied and animated back out live.
- Switch themes any time from settings without reloading the player state.
Native settings screen¶
A programmatic UIKit settings screen with a single source of truth (SettingsStore on
DataStore preferences):
| Setting | Effect |
|---|---|
| Ad mode | off / mute / mute + skip |
| Theme | active bundled theme |
| Background playback | starts/stops the foreground MediaSession service |
| Desktop user-agent | reloads the player with desktop UA |
| DNS preconnect | warms Spotify origin DNS at app start |
| Renderer prewarm | constructs an early WebView to warm the renderer process |
Every change is wired end-to-end: the settings button inside the app (injected by
core-spooty.js) calls SpootyBridge.openSettings() → SettingsActivity.
Background playback & lockscreen controls¶
- The activity stays keyed to the WebView's
<audio>element;SpotifyPlaybackServicemirrors track/title/play-pause state to a MediaSession. - Foregrounding is deterministic (
FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK), so background audio keeps playing and the lockscreen shows the artwork/track metadata without the app needing to be on screen. - Native ExoPlayer is only present as a silent, placeholder player so the Media3 session framework has something to wrap — the stream itself never passes through it.
Connectivity-aware caching¶
A NetworkMonitor flips the WebView's cacheMode:
- online →
LOAD_DEFAULT - offline →
LOAD_CACHE_ELSE_NETWORK
so the app keeps working (as much as the web player will allow) when you lose the network.
Performance & battery¶
- Startup prewarm: DNS preconnect to Spotify's origins and renderer warm-up, both feature-guarded via settings.
- No unbounded wake locks: the foreground
mediaPlaybackservice covers audio; there's no manualPARTIAL_WAKE_LOCK. - Proper WebView teardown (
removeView→destroy) to avoid native memory leaks. - No WorkManager abuse: foreground services only for what must run now.