Skip to content

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 a spotify: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.
  • 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 a color.ini palette plus a user.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; SpotifyPlaybackService mirrors 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 mediaPlayback service covers audio; there's no manual PARTIAL_WAKE_LOCK.
  • Proper WebView teardown (removeViewdestroy) to avoid native memory leaks.
  • No WorkManager abuse: foreground services only for what must run now.