SideB is a retro cassette-style music player for TrimUI Brick with Spotify Connect, offline favorites, local MP3 playback, and USB DAC output.
Latest release: v1.2.2
![]() Spotify connecting |
![]() FAV LIST |
![]() Offline playback |
![]() Waiting screen |
- Turns the TrimUI Brick into a Spotify Connect receiver on your local network
- Appears in Spotify Connect as TrimUI Brick
- Shows cover art, playback state, and cassette animation directly on
/dev/fb0 - Supports hardware controls for play, pause, skip, volume, favorites, and list navigation
- Keeps Spotify playback state synchronized with the fullscreen cassette UI
- Uses SideB's SDL audio path instead of a separate system playback command
- Save the current track with
X - Browse and manage favorites in the fullscreen
FAV LIST - Play downloaded favorites locally with shuffle, previous, and next track controls
- Download favorite tracks for personal offline playback
- Resume pending and unfinished favorite downloads automatically after restart
- Retry an undownloaded
FAV LISTtrack manually and move it to the front of the waiting queue - Remember the last local track across app restarts and after Spotify disconnects
- Show download progress and short failure notices for network, storage, cookie, YouTube challenge, and missing-tool errors
- Validate downloaded audio duration against Spotify metadata to reduce mismatched tracks
- Outputs both Spotify streaming audio and local playback through SideB's SDL audio callback
- Supports USB DAC output when the OS exposes the DAC as an ALSA card (
v1.2.1+) - Prefers a connected USB DAC automatically, then falls back to internal audio when the DAC is removed
- Switches back to the DAC after it is reinserted and enumerated by the OS
- Keeps SideB volume controls consistent across internal audio and USB DAC output
- Uses bundled
ffmpeg-litefor local PCM decoding so playback does not depend on systemffmpegoraplay
- Drop MP3 files into
data/imports/ - SideB imports tags and cover art automatically
- Imported tracks are added to
FAV LISTand behave like local favorites - Supports embedded cover art plus same-folder
cover.*andfolder.*image files - Ignores macOS hidden metadata files during import
- Renders directly to the TrimUI framebuffer at
/dev/fb0 - Reads hardware controls from
/dev/input - Keeps the device awake while playback, imports, or downloads are active
- Restores CPU scaling state and exits cleanly back to the OS frontend
- Bundles runtime helpers for reproducible release packages
- Stable packages are available for NextUI, Stock OS, and CrossMix OS
- KNULLI and muOS packages are Candidate-only test builds until real-device evidence promotes them
The app consists of two components:
- go-librespot: Spotify Connect backend โ handles authentication, audio streaming, and exposes a local HTTP/WebSocket API on port
3678 - spotify-ui-rs (this repo): Rust framebuffer UI โ reads input from
/dev/input, renders the cassette scene to/dev/fb0, and communicates withgo-librespotvia its local API
When a user marks a track as a favorite, the app searches for multiple matching candidates on YouTube using yt-dlp, scores them by duration match against Spotify metadata, title similarity, and channel quality, then downloads the best match as an MP3 file on the SD card. After download, the actual file duration is validated against the Spotify track length to reject mismatched results. Downloads use a bundled FFmpeg-compatible audio transcoder with MP3 encoder support. Cached audio is decoded by bundled ffmpeg-lite into PCM and played through SideB's SDL audio callback. Cover art is fetched from the Spotify CDN or copied from the local cover cache. Pending downloads and unfinished Spotify favorites are automatically resumed on the next app launch. Pressing A on an undownloaded FAV LIST item retries that track at the front of the waiting queue; the current active download is allowed to finish first.
SideB outputs both Spotify streaming audio and local playback through its SDL audio callback. If a USB DAC is connected and the OS exposes it as an ALSA card, SideB prefers that DAC automatically. During playback, unplugging the DAC falls back to internal audio; reinserting it switches playback back to the DAC after the OS enumerates it again. SideB's volume controls apply consistently across internal audio and USB DAC output.
For manual local playback, users can also drop MP3 files into data/imports/. SideB scans that folder automatically, reads MP3 metadata, moves the file into data/music/, extracts embedded cover art with the device's system ffmpeg when available, and adds the track to FAV LIST as a managed local item.
Important: The app does not intercept, decrypt, or extract audio from Spotify streams. Spotify playback and offline caching use entirely separate audio paths.
This project provides an offline caching mechanism strictly for personal, non-commercial use. By using this feature, you acknowledge and agree to the following:
- No content is hosted or distributed by this project. All audio files are cached locally on the user's own device and are never uploaded, shared, or made available to third parties.
- The project does not circumvent any DRM or copy protection. Spotify audio streams are not intercepted or recorded. Offline caching relies on publicly available content retrieved via yt-dlp from YouTube.
- Users are solely responsible for ensuring their use complies with applicable copyright laws and the terms of service of any third-party platform (including YouTube and Spotify). The authors and contributors of this project assume no liability for how the software is used.
- If you are a rights holder and believe this project facilitates infringement of your rights, please open an issue and we will address it promptly.
This software is provided as-is for educational and personal use. It is not intended to promote or facilitate unauthorized copying or distribution of copyrighted material.
- TrimUI Brick
- NextUI, Stock OS, or CrossMix OS
1.1.1+ - Spotify Premium account
- Wi-Fi on the same network as your Spotify client (for streaming mode)
Stable builds are available for NextUI, Stock OS, and CrossMix OS.
KNULLI and muOS builds are Candidate test packages. Use them only if you can try SideB on real hardware and share logs or screenshots. They are not official support yet.
| Button | Action |
|---|---|
| A | Play / Pause |
| โ / โ | Previous / Next track |
| โ / โ | Volume up / down |
| X | Favorite, or press twice to remove a favorite |
| Y | Open / close playlist |
| B / MENU | Exit app |
- Launch SideB from the TrimUI app menu.
- Open Spotify on your phone, desktop, or tablet.
- Pick TrimUI Brick from Spotify Connect.
- Use the hardware buttons on the device for playback, volume, favorites, and list access.
- Start playback from Spotify Connect.
- Press X to add the current track to favorites.
- Wait for the background download to finish.
- Press A on the device to start local playback from the downloaded library, or press Y to pick a downloaded track from
FAV LIST. - If a favorite is not downloaded yet, highlight it in
FAV LISTand press A to retry it next.
Copy .mp3 files into the data/imports/ folder inside the app directory, either directly or inside subfolders:
NextUI -> /mnt/SDCARD/Tools/tg5040/SideB.pak/data/imports/
Stock -> /mnt/SDCARD/Apps/SideB/data/imports/
CrossMix -> /mnt/SDCARD/Apps/SideB/data/imports/
SideB scans that folder recursively at startup and while running. Imported files are moved into data/music/, then added to FAV LIST and treated like downloaded local tracks. macOS metadata such as __MACOSX/, hidden folders, and ._*.mp3 files is ignored.
For best results, embed cover art in the MP3 before copying it into data/imports/. SideB uses embedded cover art first.
If the MP3 has no embedded cover, place a same-name .jpg, .jpeg, or .png image next to the MP3 in data/imports/, or in the same import subfolder:
data/imports/My Album/
Artist - Song.mp3
Artist - Song.jpg
For album subfolders, cover.jpg, cover.jpeg, cover.png, folder.jpg, folder.jpeg, and folder.png are also supported as folder-level covers:
data/imports/My Album/
01 First Song.mp3
02 Second Song.mp3
cover.jpg
Folder-level covers only apply inside import subfolders such as data/imports/My Album/. data/imports/cover.jpg is not treated as a root import default, and data/music/cover.jpg is not treated as a global library cover.
If a track was already imported without cover art, place the same-name image next to the managed MP3 in data/music/:
data/music/
Artist - Song.mp3
Artist - Song.jpg
SideB scans for these existing-library covers at startup and while running. In data/music/, the image filename still needs to match the MP3 filename stem.
Notes:
- Current manual import support is
MP3only. - Embedded cover art is preferred. If no embedded cover exists, SideB tries a same-name sidecar image, then an album-subfolder
cover.*, thenfolder.*. - Press X once to show the remove confirmation, then press X again to actually remove the track from favorites.
- If you remove the track that is currently playing locally, SideB keeps the managed file until playback moves away from that track. If you favorite it again before switching tracks, the cached file is preserved.
When a background download finally fails, SideB shows a short notice on the device and writes details to /tmp/sideb.log.
| Notice | Common log pattern | Meaning | Action |
|---|---|---|---|
Cookie check failed |
Sign in to confirm you're not a bot, Use --cookies |
YouTube wants valid account cookies | Export Firefox cookies to data/yt-dlp-cookies.txt |
YouTube challenge |
Signature solving failed, n challenge, GVS PO Token, PO Token |
YouTube changed or gated this format/client path | Retry later, update SideB/yt-dlp in the next package, or try another track |
Network error |
DNS, timeout, TLS, SSL, Network is unreachable |
Device network, Wi-Fi, certificate, proxy, or routing issue | Check device Wi-Fi/router/proxy; the device does not inherit your computer proxy automatically |
Storage full |
No space left on device, PyInstaller, failed to extract, /tmp |
Temporary extraction or SD-card storage failed | Free space, restart SideB to clear /tmp/sideb-tmp, then retry |
Missing yt-dlp |
yt-dlp missing or permission denied |
Runtime payload is missing or not executable | Reinstall the full release zip |
Audio tool failed |
ffmpeg, ffprobe, ffmpeg-location, transcoder, libmp3lame, s16le |
Bundled audio transcoder path or codec support is missing or broken | Reinstall the full release zip and verify ffmpeg-lite is present |
No audio match |
Requested format is not available, duration mismatch |
Candidate audio did not match the Spotify track | Try another track or retry later |
Download failed |
Other extractor failure | Unknown download failure | Check /tmp/sideb.log and open an issue with the log snippet |
For cookie-related failures:
Use Firefox (recommended โ Chrome rotates cookies on export, making them expire immediately):
- Install Firefox and log into YouTube
- Install yt-dlp (
brew install yt-dlp/winget install yt-dlp) - Export cookies and copy to the device:
yt-dlp --cookies-from-browser firefox --cookies cookies.txt -s "https://www.youtube.com/watch?v=dQw4w9WgXcQ" - Copy
cookies.txtto the device SD card asdata/yt-dlp-cookies.txt
Restart SideB and pending downloads will resume automatically. If a favorited Spotify track is not downloaded and no longer appears in the queue, SideB restores it to the queue on startup. To retry a specific undownloaded favorite sooner, highlight it in FAV LIST and press A; it moves to the front of the waiting queue without interrupting the active download.
Why not Chrome? Since late 2024, Chrome automatically invalidates exported cookies as a security measure. Firefox does not have this limitation.
git clone https://github.com/CharlexH/SideB.git
cd SideB
rustup target add aarch64-unknown-linux-gnu
brew install zig
cargo install cargo-zigbuild --locked
./scripts/package.shpackage/SideB.pak/go-librespotโ Spotify Connect backend binarypackage/SideB.pak/ffmpeg-liteโ bundled FFmpeg-compatible audio transcoder with MP3 encoding and local playback PCM output support; keep it minimal and audio-focusedpackage/SideB.pak/yt-dlpโ YouTube audio downloader (aarch64 binary)package/SideB.pak/resources/ca-certificates.crtโ TLS root certificates
package/SideB.pak/resources/font_mono.ttf is tracked and should already be present after checkout.
Build Stable packages:
./scripts/package.shThis produces:
dist/SideB-<version>-nextui.zipdist/SideB-<version>-stock.zipdist/SideB-<version>-crossmix.zip
Build Candidate test packages:
./scripts/package_candidates.shThis produces:
dist/SideB-<version>-knulli-candidate.zipdist/SideB-<version>-muos-candidate.muxapp
If you test KNULLI or muOS, include firmware version, install path, logs, and
the package hash. The short checklist is in
packaging/candidate-test-checklist.md.
Manual installation:
NextUI -> extract the nextui archive into /mnt/SDCARD/Tools/tg5040/SideB.pak/
Stock -> extract the stock archive at the SD-card root so it creates /mnt/SDCARD/Apps/SideB/
CrossMix -> extract the crossmix archive at the SD-card root so it creates /mnt/SDCARD/Apps/SideB/
KNULLI -> Candidate only: extract the knulli archive into the KNULLI roms/ports folder
muOS -> Candidate only: copy the .muxapp archive to ARCHIVE and install it with Archive Manager
On Stable platforms, launch SideB from the TrimUI app menu, then select TrimUI Brick from Spotify Connect on another device.
On Candidate builds, launch SideB Candidate from Ports/PortMaster on KNULLI or from Applications on muOS.
Public releases attach the three Stable archives:
SideB-<version>-nextui.zipSideB-<version>-stock.zipSideB-<version>-crossmix.zip
They may also include Candidate test packages:
SideB-<version>-knulli-candidate.zipSideB-<version>-muos-candidate.muxappcandidate-test-checklist.md
KNULLI and muOS need real-device reports before they move to Beta or Stable.
The NextUI Pak Store consumes the nextui archive via pak.json.
Before publishing, verify the GitHub release assets after upload:
./scripts/check_github_release_assets.sh v<version>Current release tag: v1.2.2
spotify-ui-rs/ Rust UI source
package/SideB.pak/ Local runtime staging folder and source assets
package/SideB.pak/data/ Runtime config copied into release packages
package/SideB.pak/resources/ UI images, icons, and fonts
packaging/ Platform wrappers and release metadata
scripts/package.sh Multi-platform release packager
scripts/check_release_metadata.sh Release metadata consistency gate
scripts/check_github_release_assets.sh GitHub release asset gate
Main config: package/SideB.pak/data/config.yml
device_name: "TrimUI Brick"
device_type: "speaker"
audio_backend: "pipe"
audio_output_pipe: "/tmp/sideb-spotify.pcm"
audio_output_pipe_format: "s16le"
external_volume: true
bitrate: 160
volume_steps: 100
initial_volume: 80
zeroconf_enabled: trueThe UI communicates with go-librespot at http://127.0.0.1:3678 and consumes
Spotify PCM from the pipe backend through SideB's SDL audio output.
This project builds upon the following open-source projects:
| Project | License | Usage |
|---|---|---|
| go-librespot | GPL-3.0 | Spotify Connect backend |
| yt-dlp | Unlicense | YouTube audio search and download for offline caching |
| FFmpeg | LGPL-2.1+ / GPL-2.0+ | Audio decoding and playback pipeline |
| CrossMix OS | โ | Firmware base for TrimUI devices |
Hardware: TrimUI Brick
Release archives also include:
LICENSES/NOTICE.mdLICENSES/THIRD_PARTY_SOURCES.md- Full upstream license texts for bundled binaries
Before publishing a GitHub release, record the exact upstream versions and checksums of the bundled third-party binaries in LICENSES/THIRD_PARTY_SOURCES.md.
Apache-2.0. See LICENSE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. THE AUTHORS ARE NOT RESPONSIBLE FOR ANY MISUSE OF THIS SOFTWARE OR FOR ANY VIOLATION OF THIRD-PARTY TERMS OF SERVICE OR APPLICABLE LAWS. USE AT YOUR OWN RISK.



