Last Music Bot
A self-hosted Discord music bot with a React dashboard. Slash commands drive the same player the web UI does, with Lavalink handling audio and Spotify supplying autoplay recommendations.
Overview
Last Music Bot is two halves of one product: a Discord bot and a React dashboard that control the same player. Whether you type /play in a channel or click a track in the browser, the request lands in the same player service. The dashboard is not a read-only mirror of the bot; it is a second front end to it.
Audio is delegated to Lavalink, so the bot process never touches an audio stream; it sends the node a track and receives events back. That is what lets a single instance serve several guilds without turning into an encoding server.
It is designed to be self-hosted. You run it, you own the data, and the whole stack comes up under Docker.
Key Features
- Two Front Ends Slash commands and a React dashboard drive the same player service, so the two never disagree about what is playing.
- Lavalink Audio Playback is handled by a Lavalink node over the standard client, keeping the bot process free of audio work.
- Spotify Autoplay When the queue runs dry the bot keeps going, choosing related tracks rather than repeating the last one.
- Playlist Import Pull existing playlists in from Spotify and YouTube instead of rebuilding them by hand.
- Persistent Library Playlists, likes, history, and per-guild settings live in SQLite and survive restarts.
- Drag-and-Drop Queue Reorder the queue from the dashboard, on desktop or touch.
- Audited Actions Actions are written to an append-only JSONL log, so who-did-what is answerable after the fact.
- Scheduled Backups A backup service snapshots the databases on a loop, with a restore path that does not involve improvising.
- One-Command Stack Bot, dashboard, Lavalink, and the cipher helper all come up together under Docker Compose.
- Tested Unit, smoke, and end-to-end suites cover the player, the services, and the command handlers.
Slash Commands
The command surface is deliberately small. Anything beyond transport control belongs in the dashboard, where there is room for it.
| Command | What it does |
|---|---|
| /play | Queue a track, playlist, or search. Joins your voice channel if the bot is not already in one. |
| /pause · /resume | Hold and release playback without losing the queue. |
| /skip | Advance to the next track. With autoplay on, an empty queue is refilled rather than stopped. |
| /connect · /disconnect | Move the bot into or out of a voice channel explicitly. |
| /control | Bring up the interactive player controls in-channel. |
| /admin | Owner-side maintenance. Not for general use. |
Architecture & Stack
Discord and the dashboard are two doors into one player service. Below it, the work fans out: Lavalink for audio, the recommendation engine for autoplay, and SQLite for anything that must survive a restart. Click a node to jump to the section that documents it.
The Containers
Four services come up together. Splitting them apart is what allows a redeploy of the bot to leave audio playback untouched.
| Service | Role |
|---|---|
| bot | discord.js client, the REST API, and the player service. |
| ui | The React dashboard, built by Vite. |
| lavalink | The audio node. Does the actual streaming and encoding. |
| yt-cipher | Signature helper Lavalink needs to keep resolving YouTube sources. |
.
├── apps/bot/ # discord.js client, REST API, player, services, tests
│ └── src/
│ ├── api/ # Express route registration
│ ├── discord/ # Slash commands
│ ├── player/ # PlayerService - the one source of truth
│ ├── lavalink/ # Audio node client
│ ├── recommendation/# Autoplay + ANN index
│ ├── spotify/ # Auth, catalog, playlist import
│ ├── youtube/ # Playlist import
│ ├── store/ # SQLite stores + repositories
│ ├── audit/ # Append-only JSONL logger
│ └── backup/ # Scheduled snapshots
├── src/ # React dashboard (production UI)
├── lavalink/ # Lavalink node config
└── docker-compose.local.yml
Lavalink & the Audio Pipeline
The bot never touches an audio stream. It sends a Lavalink node a track and gets events back, and the node does the streaming, decoding, and mixing. That separation is the reason a redeploy of the bot does not interrupt playback: the audio container keeps running.
Almost every built-in source is switched off
Lavalink ships with source managers for YouTube, Bandcamp, Twitch, Vimeo and more. Nearly all of them are disabled here, and only http is left on. That looks like a mistake until you see the plugin list: the built-ins are turned off because the plugins replace them, and running both would mean two code paths competing to resolve the same URL.
| Plugin | What it is for |
|---|---|
| youtube-plugin | Handles YouTube resolution, in place of the built-in source manager. |
| LavaSrc | Adds Spotify as a catalog. See below: it does not do what most people assume. |
Spotify is a catalog, not a stream
This is the part worth understanding. Spotify audio is never played. Spotify is used to identify the music; the audio is then sourced from YouTube. LavaSrc resolves each Spotify track by trying, in order:
providers:
- "ytsearch:%ISRC%" # 1. match the exact recording
- "ytsearch:%QUERY%" # 2. fall back to artist + title text
The ISRC is the recording's global identifier, so searching by it finds that recording, not a cover, a live take, or a remaster that happens to share the title. Only when a track has no usable ISRC does it fall back to a plain text search, which is the path that occasionally lands on the wrong version. That two-step order is the whole reason Spotify-sourced playback usually gets the right take.
Surviving YouTube
YouTube is a moving target, and two pieces of the config exist purely to cope with that.
The YouTube plugin is configured to rotate through several client identities: MUSIC, ANDROID_VR, WEB, WEBEMBEDDED, and TV. They break at different times and under different conditions, so if one client stops resolving, another is likely to still work. It is redundancy, not cleverness.
The second piece is yt-cipher, which is why there is a fourth container in the stack that otherwise looks unnecessary. YouTube protects stream URLs with a signature computed by JavaScript that changes regularly. Rather than making the audio node run that JavaScript itself, the node delegates signature work to a small dedicated service over the local network. Keeping it separate means the thing that breaks most often is also the thing you can update or restart on its own.
Tuning
| Setting | Value | Why |
|---|---|---|
| Frame buffer | 10 s, non-allocating | Absorbs network jitter without churning memory per player. |
| Buffer duration | 1 s | Keeps the gap between a command and hearing it short. |
| Playlist load limit | 250 tracks | A ceiling on how much one /play of a huge playlist can queue. |
| Spotify page limit | 6 pages | Bounds how far an album or playlist import will page through the API. |
Dashboard & REST API
The dashboard is a Vite + React 18 app. It holds no player logic of its own; it calls the same API the bot exposes, which is why the queue you drag in the browser is the queue Discord is playing.
The API is Express, authenticated with JWT, and validates request bodies with zod so a malformed payload is rejected at the edge rather than halfway through a state change. Routes are grouped by concern:
| Route group | Covers |
|---|---|
| Auth | Sign-in and session issuing for the dashboard. |
| Playback | Play, pause, skip, seek, volume, queue order. |
| Library | Playlists, likes, and listening history. |
| Guild | Per-server settings and state. |
| Spotify | Account linking, catalog lookups, playlist import. |
| Admin | Owner-only maintenance operations. |
Library, Storage & Backups
State is split across two SQLite databases, and the split is deliberate: library.db holds what you want to keep (playlists, likes, history, settings, linked accounts), while player.db holds what is merely true right now. Losing the second is an inconvenience; losing the first is a loss.
Each concern gets its own repository rather than sharing one god-object over the connection, and schema changes run through an explicit migration path instead of being applied by hand.
Two safety nets sit underneath. Actions are appended to a JSONL audit log, which is append-only and therefore still trustworthy after something has gone wrong. And a backup service snapshots the databases on a schedule, with a restore command as a first-class script rather than an improvised copy.
Autoplay & Recommendations
An empty queue is where most music bots stop. This one keeps going: when the last track ends, the recommendation service picks what follows.
Rather than asking an API for "related tracks" every time, the bot maintains its own approximate nearest-neighbour index over a track index it builds up. Candidates are drawn from that index and enriched through the Spotify catalog, which keeps suggestions relevant to what has actually been playing instead of drifting into whatever is globally popular.
Playlists can be imported wholesale from Spotify (private, public, or embedded) and from YouTube, so an existing collection does not have to be rebuilt track by track.
Self-Hosting
The whole stack is Docker Compose. Node 24 or newer is required if you intend to run the bot outside a container.
# Fill in your own configuration first
cp .env.example .env
npm run dev:up:build # first run: builds the bot and UI images
npm run dev:up # thereafter
npm run dev:logs # tail the bot
npm run dev:down # tear it down
Configuration is entirely environment-driven: the same image runs locally and in production, with no code branching between them. .env.example documents every key the stack reads. Your credentials stay yours: they are gitignored, and the deploy path is built so that they are never uploaded from a developer machine.
Terms & License
Last Music Bot is open source under the GNU Affero General Public License v3.0. The Affero clause matters here: if you run a modified version as a network service that other people use, you must offer those users the source of your modifications. Running it unmodified for your own server carries no such obligation.
Lavalink, discord.js, and the other dependencies remain under their own licenses. Discord, Spotify, and YouTube are trademarks of their respective owners; this project is not affiliated with any of them, and you are responsible for complying with their terms of service.
Last Download Manager
A modern, feature-rich download manager built with C++ and wxWidgets. Accelerated multi-segment downloads, video site support via yt-dlp, browser extension integration, and robust crash recovery - all powered by native Windows APIs.
Overview
Last Download Manager is a native Windows download manager built on C++ and wxWidgets. It splits a file into segments and pulls them in parallel over WinINet, which is where the speed comes from, and it treats interruption as normal rather than exceptional: chunks retry, downloads resume, and progress is checkpointed to disk so a crash costs you seconds instead of a whole file.
Video sites are handled by shipping the problem out to yt-dlp, with ffmpeg and Deno fetched and updated automatically. A browser extension intercepts downloads and hands them to the running app over a local HTTP server.
Key Features
- Multi-Segment Engine Splits each file across parallel WinINet connections, which is where the throughput gain comes from.
- Resume & Auto-Retry Two-level retry with exponential backoff; part files survive failure, so an interrupted download continues rather than restarting.
- Crash Recovery Progress is written to the database periodically, so an unexpected shutdown does not cost the transfer.
- Checksum Verification MD5 and SHA256 hashes verified against the finished file.
- Video Sites yt-dlp integration covering YouTube, Vimeo, and 1000+ sites, with resolution and format chosen before the download starts.
- Managed Toolchain yt-dlp, ffmpeg, and the Deno runtime are downloaded and kept current automatically. Nothing to install by hand.
- Browser Extension Chrome, Edge, Brave, and Firefox: auto-intercept, right-click download, page video scan, and batch grabbing.
- Speed Graph Live throughput plotted over a rolling window, so a stalling connection is visible rather than inferred.
- Categories & Scheduler Automatic sorting into Documents, Video, Music, Images, Programs, and Compressed, plus start and stop times for queues.
- Tray & Dark Mode Minimise to tray with notifications, and a dark theme throughout.
Download Engine
The engine assumes networks fail. Retries are layered so that a brief blip is absorbed at the chunk level without disturbing the download, while a genuine outage backs off instead of hammering the server.
| Level | Attempts | Backoff |
|---|---|---|
| Chunk: one segment fails | 3 | 500 ms base delay |
| Download: the whole transfer fails | 5 | Exponential, 2 s → 32 s |
Resume
Segments are written as .part0, .part1, and so on, and are kept when a download fails, and that is what makes resume possible after the automatic retries are exhausted. On restart the app checks the existing size on disk, and validates Content-Range responses before appending, so a server that ignores the range request cannot silently corrupt the file.
Thread Safety
Status, sizes, and speed are atomics; metadata, chunk lists, and database writes are mutex-guarded. WinINet handles are owned by RAII session objects, and async tasks are tracked so they cannot outlive the download that spawned them.
Video & Tool Management
Video extraction is delegated to yt-dlp rather than reimplemented. The app fetches the toolchain itself on first use and keeps it updated, so there is no manual setup step.
| Tool | Why it is there |
|---|---|
| yt-dlp | Extracts and downloads from YouTube, Vimeo, and 1000+ other sites. |
| ffmpeg | Merges the separate audio and video streams these sites serve. |
| deno | JavaScript runtime some extractors need, YouTube in particular. |
All three live in %APPDATA%\LDM\tools\. Delete that folder and the app will simply fetch them again, which is the quickest fix when an extractor starts misbehaving after a site change.
Streams behind a CDN that checks where the request came from are handled by propagating the Referer header from the originating page.
Installation & Setup
Click the Download button above for the latest release. Requires Windows 10 or 11. Nothing else needs installing: the video toolchain fetches itself on first use.
Building from Source
Requires Visual Studio 2022 with the Desktop development with C++ workload and wxWidgets 3.2 or newer. Networking uses the native WinINet APIs, so there is no libcurl dependency to satisfy.
- Build wxWidgets from wxwidgets.org.
- Set WXWIN to your wxWidgets directory; the project expects libraries at $(WXWIN)\lib\vc_x64_lib.
- Open LDM.sln, select Release and x64, and build.
# Either pass the flag...
LDM.exe --debug
# ...or drop an empty debug.txt next to the executable
type nul > debug.txt
Browser Extension & Local API
The extension does not download anything itself. It intercepts the browser's download, cancels it, and hands the URL to the running app, which means the app must already be running, since the extension will not start it for you.
- Start LDM.
- Open
chrome://extensions,edge://extensions,brave://extensions, orabout:debugging#/runtime/this-firefox. - Enable Developer mode.
- Click Load unpacked (Chrome, Edge, Brave) or Load Temporary Add-on (Firefox).
- Select the BrowserExtension folder from your LDM installation.
Beyond auto-intercept it adds a right-click Download with LDM entry, a video tab that scans the page for media and offers quality selection, batch Download All Links and Download All Media actions, a manual URL box, a live connection indicator, configurable hotkeys, and filters for file type, domain, and minimum size.
Local HTTP API
The two halves talk over a small HTTP server bound to loopback. It is worth understanding if you are debugging a connection problem, or building against it.
| Endpoint | Method | Purpose |
|---|---|---|
| /token | GET | Fetch the session auth token. |
| /ping | GET | Liveness check behind the extension's connection indicator. |
| /download | POST | Queue a URL, optionally with the page it came from. |
Content-Type: application/json
X-Auth-Token: <token from GET /token>
{
"url": "https://example.com/Win11_25H2_English_x64.iso",
"referer": "https://example.com/downloads"
}
referer is optional and only sent when the page supplied one; it is what allows downloads from CDNs that reject requests arriving without an origin.
Architecture & Component Layout
Two front doors, the wxWidgets window and the extension's HTTP server, feed one queue. DownloadManager owns that queue and dispatches to either the segmented HTTP engine or yt-dlp, depending on what the URL turns out to be. Click a node to jump to the section that documents it.
Last-Download-Manager/
├── LDM.sln
├── BrowserExtension/ # MV3 extension: background, content, popup, options
└── LDM/
├── main.cpp
├── core/ # Download · DownloadEngine · DownloadManager · YtDlpManager
├── ui/ # MainWindow · DownloadsTable · CategoriesPanel · SpeedGraphPanel
├── database/ # DatabaseManager - XML persistence
├── utils/ # HttpServer · Settings · ThemeManager · HashUtils
└── resources/ # Icons, manifests, assets
Troubleshooting & FAQ
The issues that actually come up, and what is really going on underneath each.
Terms & License
Last Download Manager is open source under the GNU General Public License v3.0. You may use, study, modify, and redistribute it, provided derivative works stay under the same license and the source remains available.
yt-dlp, ffmpeg, and Deno are separate projects under their own licenses; LDM downloads and invokes them but does not bundle or modify them. You are responsible for respecting the terms of service of whatever site you download from.
Last Rich Presence
Windows app for accurate, polished Discord Rich Presence for media, creative, and productivity workflows. Built with C++20 and WinUI 3.
Overview
Last Rich Presence keeps Discord aligned with what you are actually doing on Windows, across three independent activity lanes: media playback read from Windows media sessions, creative work detected from Adobe-family desktop apps, and productivity work detected from Office-family apps. Each lane owns a separate Discord application, so a track, a Photoshop session, and a Word document can appear as distinct cards rather than fighting over one status.
The app is single-instance and tray-first: it can start minimized, close to tray, and redirect a second launch back to the running instance. An optional Chromium extension improves timeline accuracy for web players that Windows reports poorly.
Key Features
- Media Lane Reads active Windows media sessions (GSMTC) and builds presence with title, artist, album, playback state, and timeline.
- Creativity Lane Detects 23 Adobe-family desktop apps, from Photoshop and Premiere Pro through the Substance 3D suite, with project-name and window-title display.
- Productivity Lane Detects Word, Excel, PowerPoint, OneNote, Access, Publisher, Visio, Project, and Codex, with project or file-name display.
- Three Discord Apps Each lane connects under its own application ID, so lanes coexist as separate Discord cards instead of overwriting one another.
- Browser Companion Optional MV3 extension supplies high-confidence hints for 14 web players over Chromium native messaging.
- Hint Arbitration Browser hints are scored on playback state, tab visibility, confidence, and age, so a stale background tab cannot hijack the timeline.
- Privacy Modes Per-lane Normal, App Only, and Private modes, plus a sensitive-keyword filter and a blocked app and site term list.
- Detection Modes Choose whether a lane follows the foreground window, any visible window, or foreground with visible fallback.
- Tray-First Workflow Single instance, close to tray, launch on startup, start minimized, and a global Rich Presence master toggle.
- Diagnostics Export and import settings, inspect detection state, and enable source debug mode when a lane misbehaves.
Activity Lanes
The three lanes run independently. Each has its own detector, its own presence manager, and its own Discord application ID, and each can be disabled without affecting the others.
| Lane | Source | Discord Card |
|---|---|---|
| Media | Windows media sessions (GSMTC), optionally refined by browser hints | Dedicated application |
| Creativity | Adobe-family desktop windows | Dedicated application |
| Productivity | Office-family desktop windows and Codex | Dedicated application |
Detection Modes
The Creativity and Productivity lanes decide which app to report using one of three strategies.
| Mode | Behaviour |
|---|---|
| ForegroundPreferredVisibleFallback | Default. Reports the focused app; falls back to any visible supported window when focus moves elsewhere. |
| ForegroundOnly | Reports only the app you are actively focused on. Presence clears the moment you alt-tab away. |
| VisibleWindowOnly | Reports any open supported window, whether or not it holds focus. |
Privacy Modes
| Mode | What Discord sees |
|---|---|
| Normal | App name plus the project or document name. |
| AppOnly | App name only. File and project names are withheld. |
| Private | A generic card. Neither the app nor the document is named. |
Conflict & Idle Behaviour
When media and creative activity are live at once, CreativePriorityMode decides which wins: Auto, PreferMedia, or PreferCreative. When an app closes, CreativeIdleBehavior either holds the last card for five seconds (HoldLast5Seconds) or clears it at once (ClearImmediately), which avoids flicker when you are switching between documents.
Installation & Setup
Click the Download button above for the latest installer. Requires Windows 10 or 11 and the Discord desktop client, running. The web client does not expose the local IPC socket that Rich Presence needs.
Building from Source
Requires Visual Studio 2026 with the Desktop development for C++ workload, the MSVC v145 toolset, Node.js on PATH for the extension syntax checks, and Inno Setup 6 if you intend to produce the unpackaged installer.
| Configuration | Purpose |
|---|---|
| Debug / Release | Standard development builds. |
| Release-Inno | Unpackaged, self-contained build for Inno installer releases. |
| Release-MSIX | Packaged MSIX build path. |
The verification script restores NuGet packages, builds the solution, runs the extension's JavaScript syntax checks, executes the native guard tests, validates release artifacts, and performs a launch smoke test.
.\scripts\verify.ps1 -Configuration Debug -Platform x64
# Non-interactive runners have no desktop session for the launch smoke test
.\scripts\verify.ps1 -Configuration Debug -Platform x64 -SkipLaunchSmoke
# The MSIX path installs the package, launches it by identity, then uninstalls
.\scripts\verify.ps1 -Configuration Release-MSIX -Platform x64
Browser Extension & Hint Contract
Windows reports browser playback poorly: the timeline is often missing or stale. The optional MV3 extension fills that gap by reading the page directly and sending the desktop app a structured hint.
- Open
chrome://extensionsoredge://extensionsand enable Developer mode. - Click Load unpacked and select the
browser-extension/folder. - Open the extension popup to choose whether hints are enabled and which sites may send them.
- Launch the desktop app once afterwards so it can refresh the per-user native-host registration.
key without also updating the app-side allow-list will silently break every hint.
Transport
The extension never talks to the app directly. It posts to a Chromium native-messaging host, which the app itself provides when launched with --browser-native-host; that host then forwards the payload into the running instance over a local named pipe. The app verifies the calling origin and the parent process before accepting anything, and it will not auto-launch if it is closed.
| Channel | Identifier |
|---|---|
| Native messaging host | com.lastprojects.lastrichpresence |
| Named pipe | \\.\pipe\LastRichPresence.BrowserHints |
| Accepted parents | chrome.exe, msedge.exe |
| Max message size | 64 KB |
Hint Payload
Every tab reports its own hint. The background worker scores them and forwards only the winner, so a paused background tab cannot override the video you are actually watching. Playback state is worth the most, then tab visibility, then the scraper's own confidence, with older hints decaying.
{
"schemaVersion": 1,
"kind": "hint",
"source": "lrp-browser-extension",
"timestamp": 1749650412233,
"data": {
"service": "YouTube Music",
"siteKey": "youtube_music",
"mediaKind": "music",
"title": "Time",
"artist": "Hans Zimmer",
"album": "Inception OST",
"isPlaying": true,
"positionSeconds": 191,
"durationSeconds": 348,
"pageHost": "music.youtube.com",
"pageVisible": true,
"tabId": 42,
"sequence": 17,
"confidence": 92,
"rule": "media-session"
}
}
When no tab qualifies, the extension sends { "kind": "clear" } instead, and the app falls back to whatever GSMTC reports on its own.
Supported Web Sources
Fourteen players ship with high-confidence scrapers: YouTube, YouTube Music, Spotify Web, SoundCloud, Apple Music, Amazon Music, Deezer, TIDAL, JioSaavn, Gaana, Wynk Music, Bandcamp, Mixcloud, and Twitch. Each can be toggled individually from the extension popup.
Architecture & Component Layout
Detection, presence building, and Discord IPC live in src/core/; the WinUI 3 shell, pages, and tray controller live in src/ui/. Each lane is a vertical slice (detector, presence manager, Discord application), and the browser chain feeds only the media lane. Click a node to jump to the section that documents it.
Last Rich Presence/
├── src/
│ ├── core/ # Detection, presence building, settings, Discord IPC
│ └── ui/ # WinUI shell, pages, settings, tray, animations
├── browser-extension/ # Optional MV3 companion extension
├── installer/ # Inno Setup script and installer guide
├── scripts/ # Build and verification scripts
├── tests/ # Native guard tests
└── Assets/ # Branding and curated fallback app logos
Settings & Persistence
Settings are stored in the WinRT ApplicationData local settings container, not a config file you edit by hand. Use the Settings page, or the diagnostics export and import if you want to move a configuration between machines.
| Group | Controls |
|---|---|
| General | Rich Presence master toggle, close to tray, launch on startup, start minimized, tray left-click behaviour. |
| Media | Timestamps, source label, paused state, album art, idle card, activity type override. |
| Privacy | Sensitive keyword filter, strict browser privacy, browser album-art suppression, blocked app and site terms. |
| Creativity / Productivity | Per-lane enable, detection mode, privacy mode, project-name display, per-app filters, activity type override. |
| Appearance | Theme: FollowSystem, Light, or Dark. |
Privacy & Network
The app is local-first, but two features reach the network and are worth knowing about before you enable them. Album-art fallback may query itunes.apple.com; if no direct artwork exists and only a thumbnail is available, the app can upload those image bytes to Imgur to obtain a hostable asset URL. Both are governed by the browser privacy and album-art settings, and turning album art off stops them entirely.
Troubleshooting & FAQ
The most common issues seen during install, build, and daily use.
key is unchanged: a new key means a new ID, and the app's allow-list will reject it. Note also that the extension will not launch the app for you; it must already be running.
Terms & License
Last Rich Presence is open source under the GNU General Public License v3.0. You may use, study, modify, and redistribute it, provided derivative works remain under the same license and the source stays available.
Adobe, Microsoft Office, Discord, and the streaming services named above are trademarks of their respective owners. This project is not affiliated with, endorsed by, or sponsored by any of them; app detection relies only on publicly visible window metadata.
Last Music Player
A fast native Windows music player for local libraries, with optional remote playback through your own Music API. Offline playback comes first. Remote search, radio, lyrics, and imports stay behind a small provider contract you control.
Overview
Last Music Player is a high-performance, native Windows music player built specifically for local music enthusiasts. It features native integration with the Windows Shell, seamless playback management, SQLite-backed library indexing, and a contract-driven structure allowing users to hook up external audio streaming servers (Music APIs) via plain HTTP endpoints.
Key Features
- Local-First Play standard local formats (MP3, FLAC, M4A, WAV, Opus) with a blazing-fast startup time.
- Gapless Engine Multi-source native queue manager fetches the next song block in advance for zero gap clicks.
- Library Browser Browse by songs, albums, artists, and genres with SQLite-backed indexing.
- Playlists & Mixes Build manual playlists and use auto-generated mixes.
- Synced Lyrics Time-synced lyrics display when a Music API provider is configured.
- Windows Shell Sync Integrates natively with SMTC (System Media Transport Controls), media keys, and system flyouts.
- Google Cast Optional Cast output engine for streaming to Chromecast and Google Nest speakers.
- Discord Rich Presence Optional now-playing status on Discord.
- Music API Provider User-supplied remote catalog for search, streaming, radio, lyrics, and link imports via a simple HTTP contract.
Installation & Setup
Click the Download button above to get the latest release installer. Run the installer to configure the environment. Requires Windows 10 (2004+) or Windows 11.
Building from Visual Studio
To compile and build the client application locally:
- Clone the repository recursively to fetch submodules:
git clone --recursive - Open
Last Music Player.slnxin Visual Studio 2022. - NuGet packages should restore automatically. If not, right-click the solution and choose Restore NuGet Packages.
- Set the active architecture configuration to x64 (Debug or Release).
- Build and run. The output binaries will write to the build directories.
Building from Command Line
Use the MSBuild toolchain from the Visual Studio Developer Command Prompt to compile the project headless:
MSBuild.exe "Last Music Player.vcxproj" /t:Build /p:Configuration=Release /p:Platform=x64
The release binaries will write directly to: x64/Release/Last_Music_Player.exe
Custom Secrets (AppSecrets)
For optional features like Discord Rich Presence, the developer API secrets are excluded from Git to prevent leakage. You can configure them locally for custom builds.
AppSecrets.local.h is absent during compilation, the client falls back to AppSecrets.example.h and automatically disables Discord integrations.
Configuration Steps
- Navigate to the
Backend/directory. - Copy the template file
AppSecrets.example.hand name the new fileAppSecrets.local.h. - Open
AppSecrets.local.hand insert your custom API client IDs.
#pragma once
// Discord Application Client ID for Rich Presence integrations.
// Replace with your custom Discord Developer Portal Application ID.
#define LMP_DISCORD_CLIENT_ID "your-discord-app-id-here"
Architecture & Component Layout
Last Music Player separates components into distinct native layers. The Frontend manages the WinUI 3 presentation structure, while the Backend handles state, cache, hardware playback queues, and SQLite interactions.
Core Code Directory Structure
| Directory | Responsibility & Module Info |
|---|---|
App.xaml* |
Main entry-point, WinUI life-cycle management, shell configuration, and application boot. |
MainWindow/ |
Views, library browsers, playlists, now playing controls, queue visuals, and settings UI panels. |
Backend/ |
Audio playback engine, SQLite integration, Google Cast client, Discord RPC client, settings, and lyrics services. |
Frontend/ |
Navigation logic wrappers and converters, visual and layout helpers. |
ThirdParty/sqlite/ |
Vendored SQLite database engine (amalgamated source). |
installer/ |
Setup packaging configuration written for Inno Setup. |
Gapless Playback Engine Flow
The AudioPlayer.cpp class manages smooth transitions by utilizing the native Windows.Media.Playback.MediaPlaybackList API. When gapless playback is enabled:
- The currently playing track sits at index 0 of the playlist queue.
- A lookahead worker pre-loads the next song, configuring the audio stream into index 1 of the list in advance.
- When the current song terminates, the player transitions to the next track instantly without closing and reopening the audio channel.
SQLite Database Schema
Last Music Player manages the library metadata and user interactions locally using SQLite. The database stores local tracks scanned from disk, remote tracks resolving from the Provider API, local playlists, and configuration logs.
| Column Name | Data Type | Constraints & Metadata | Description |
|---|
-- SQL query will display here
Database Versions & Schema Migration
The DB engine implements a version-gate check using PRAGMA user_version. The schema is currently at Version 6.
| DB Version | Migration Change Log |
|---|---|
| v2 | Added column helpers to support remote media integrations (SourceKey, SourceKind, Provider, SourceUrl, etc.). Added playlist schema, and unique indices on source tracking components. |
| v4 | Dropped columns supporting volume normalizations (ReplayGainDb) since that feature was deprecated, optimization clean-up. |
| v5 | Rebranded default labels for remote imports: modified DateAddedText values matching remote source indicators to say "Music API" to prevent UI leakage. |
| v6 | Schema version baseline optimization to prevent repetitive migration iterations, improving initialization speed. |
Music API Provider Contract
If you configure a custom Music API Provider under settings, the player offloads catalog searching, album indexing, streaming, related radio, and synchronized lyrics requests to your provider base URL via HTTP.
Endpoint description details.
| Parameter | Type | Status | Description |
|---|
-- Code snippet loader
Authentication Standard
The client app communicates the API key configured in settings in two ways:
- JSON Endpoints: Rest query API calls from the client pass the key as an HTTP Authorization Bearer token header:
Authorization: Bearer <api_key> - Media Streams & Artworks: Since Windows Media and image rendering pipelines perform native queries direct from the OS stack where custom headers cannot be attached, keys are passed via query strings:
?access_token=<api_key>
Troubleshooting & FAQ
Common issues encountered during development, installation, or configuration of Last Music Player client and provider stacks.
MediaPlayer instance inside AudioPlayer.cpp is not initialized during static/global variable setup. The constructor runs before init_apartment() has been called, causing ABI proxy failures. Ensure the client player instantiation is called lazily (e.g. on UI thread initialization).
EqualizerEffect.cpp, but the UI is deactivated pending fine-tuning of the biquad audio filter coefficients to avoid crackling noise during playback adjustments.
/v1/stream) which does not enforce this timeout constraint.
MainWindow.LibraryScan.cpp) runs background tasks that recursively crawl configured directories. It parses audio file headers for embedded tags (ID3, Vorbis, MP4 metadata) and saves them to the local SQLite catalog. Track cover artwork is extracted, written to the local app data folder, and referenced as the track's ArtworkUrl.
StreamCache.cpp) which writes remote audio stream segments to temporary disk storage during active playback. If a track's sourceUrl is requested again, the client redirects the media pipeline to fetch bytes directly from local cache, saving bandwidth and preventing network latency gaps.
MediaPlaybackList, the AudioPlayer listens to item transitions. When the current track approaches its end, a lookahead thread resolves the stream URL of the next queue item, pre-loads it at index 1 of the playback list, and switches streams seamlessly when index 0 finishes.
Licensing & Contribution Terms
Last Music Player is distributed as a source-available project, not open-source. Using, modifying, or redistributing the application codebase is governed by specific licensing guidelines.
License Summary
The code is subject to the custom Last Projects License 1.0. Key tenets include:
- Personal Use: Free for natural persons studying, building, or modifying the software for strictly personal and non-commercial purposes.
- Modified Source: If you modify the codebase, you are required to publish the modified source code in a qualifying public Git repository within seven calendar days.
- Organizational Restrictions: Non-profits, companies, government institutions, and schools require a separately negotiated commercial license.
- Dataset Constraints: Using the source code or binary outputs for AI training datasets is strictly prohibited without explicit written consent.
Contributor Agreement (CLA)
By submitting code patches or pull requests to the project, you agree to grant ownership and redistribution rights to Last Projects. You can review the details in CLA.md.
For organization license requests or questions, reach out to: info@lastprojects.com.
RainFrames
Live weather, clear at a glance. Search any place on Earth for forecasts, radar, wind, temperature, air quality, and alerts in one calm view. Free to use, and an account is optional.
Overview
RainFrames is a hosted weather app you use in the browser. There is nothing to install and nothing to configure: open rainframes.com, search for a place, and the forecast is there.
This page is a guide to using the service. RainFrames is a closed-source product, so it documents what the app does and how to read it, not how it is built.
The Home Dashboard
Everything about one place, on one screen. Search a city at the top, or press Use location and let the browser tell it where you are. The four icons down the left edge are the whole app: Home, Map, Favourites, and Settings at the bottom.
The current-conditions card
The big card is the answer to "what is it like right now". Two parts of it are easy to miss and worth knowing.
Feels like is the number that actually matters. In the example above it is 31°C but feels like 40°, because humidity is at 78% and sweat cannot evaporate. Dress for the second number, not the first.
The small badge in the corner (Google, in that screenshot) names the service that produced this forecast. It changes depending on whether you are signed in and where you are asking about, which is explained in Weather Sources below.
The metric grid
Six numbers, each with the context needed to read it rather than just the raw figure.
| Tile | How to read it |
|---|---|
| Precipitation | The chance of rain, and how much. "65% · 5.7 mm" means rain is likely, and enough of it to be worth an umbrella. A high percentage with a low millimetre figure means drizzle, not a downpour. |
| Wind | Current speed. The direction and the wider pattern live on the wind map. |
| Humidity | Labelled, not just numbered. "78% · Muggy" is why the feels-like temperature is nine degrees above the real one. |
| UV index | Sunburn risk, with a word attached. "3 · moderate" is fine; a high reading is the app telling you to cover up. |
| Local time | A live clock in that city's timezone. Useful when the place you are checking is not the place you are standing. |
| Air quality | The AQI with a plain-language band. "142 · Moderate" is worse than it sounds if you have asthma. |
Hourly and seven-day
The hourly strip scrolls sideways through the coming hours, each with a temperature and a chance of rain, and it marks sunset in place so you can see it coming rather than calculate it.
The seven-day list gives each day a condition, a precipitation chance, and a coloured bar spanning its low to its high. The bar is the quickest read on the page: a long bar is a day that swings, a short one is a day that does not.
Sun, moon, and air quality
The Sun card draws an arc with the sun's current position on it, so a glance tells you how much daylight is left, with sunrise, sunset, and total daylight spelled out. The Moon card shows the phase and illumination, plus moonrise and moonset.
The Air quality card is the one worth expanding. The headline AQI sits on a 0 to 300+ scale so you can see where it falls, and it breaks out the individual pollutants underneath: PM2.5, PM10, O₃, NO₂, and CO. The card also names the main pollutant, which is what turns "the air is bad" into "the air is bad because of ozone".
Conditions and alerts
Visibility and pressure come with a sentence rather than being left as raw figures. "998 hPa · Low pressure, unsettled weather likely" is a forecast in itself; falling pressure is what precedes bad weather, and the card says so instead of assuming you know.
The Weather alerts card lists official warnings for the place you are viewing. Quiet is the normal state, and it says 0 active rather than showing nothing, so you can tell the difference between "no alerts" and "not loaded".
Maps, Layers, and the Globe
The map is where the app stops answering "what is it like" and starts answering "what is coming". Pick a layer from the dropdown at the top left, and scrub the timeline at the bottom to move through time. The play button animates it.
The timeline runs from recent observations forward through the forecast, which is what makes it possible to see where a band of rain is heading, not merely where it is now. Hovering anywhere on the map reads the value out at that point.
The globe
The button beside the layer dropdown swaps the flat map for an interactive globe. It is not decoration. A flat projection stretches everything near the poles and cuts the planet at an arbitrary line, so weather systems large enough to wrap around the Earth get distorted or split in half. On a globe they stay whole.
Favourites and Settings
Press Save in the header to keep the place you are looking at. The Favourites tab then shows them all at once, each with its current temperature, condition, and the day's high and low, so you can compare a handful of cities without searching them one at a time. Open dashboard jumps any of them back to the full view.
| Setting | What it does |
|---|---|
| Forecast provider | Signed in only. Choose whether Google Weather or Apple Weather powers your forecasts. |
| Temperature unit | Celsius or Fahrenheit, also togglable from the header. |
| Theme | Auto follows the active weather and local time, so a stormy night does not look like a clear afternoon. Dark stays put if you would rather it did not move. |
| Performance | Auto adapts to your device. Lite turns off the heavy effects for smoothness; High keeps everything. This is the fix if the maps feel sluggish. |
| Saved cities | How many you have kept, and whether they are synced to your account. Clear them all from here. |
Free Use and Signing In
The distinction is worth being precise about, because it is not the usual one. The free tier is not a trial or a crippled preview. Anonymous visitors get the whole weather app. What an account changes is the forecast source and whether your settings follow you between devices.
| Signed out | Signed in | |
|---|---|---|
| Forecasts, maps, radar, air quality, alerts | Yes | Yes |
| Saved cities | On this device | Synced across devices |
| Units, theme, quality, last location | On this device | Synced across devices |
| Premium forecast sources | No | Yes, and selectable |
Sign-in uses a MagnetoFX account. There is no separate RainFrames password to create or forget.
Where the Weather Comes From
RainFrames does not measure the weather; it presents it. The badge on the conditions card always names whichever service actually answered, and which one that is depends on whether you are signed in and where you are asking about.
| You are | Forecast source |
|---|---|
| Signed in, default | Google Weather |
| Signed in, Apple selected | Apple Weather |
| Signed out | WeatherAPI |
| Signed out, in India, Pakistan, Bangladesh, or Indonesia | Open-Meteo forecast, with air quality and alerts from WeatherAPI |
| Anyone, when a source is having a bad day | Open-Meteo, as a fallback |
That last row is the one people rarely think about. Weather APIs go down, and a weather app that goes down with them is not much use, so RainFrames falls back rather than showing you an error. If the badge is not the source you expected, this is usually why.
Maps, radar, and the weather layers are rendered from MapTiler with MapLibre GL, over basemaps built from OpenStreetMap data.
Privacy and Your Data
A weather app has to know where before it can tell you anything, and how it learns that is the whole privacy story.
If you grant location access, the browser tells RainFrames where you are. If you do not, it falls back to an approximate location derived from your IP address, which is enough for a city-level forecast and no more precise than that. Granting location access is always your choice, and declining it does not lock you out of anything: searching for a place works just as well.
Beyond location, the service processes what it needs to answer your request: the places you search, and, if you are signed in, your account identity as returned by the MagnetoFX account service. Cookies and browser storage hold your session and your preferences.
Signing in means your settings are stored server-side, which is precisely what allows them to follow you to another device. That is the trade: sync requires the service to keep saved cities, last used location, units, theme, forecast source, and quality settings against your account. Staying signed out keeps all of it on your own device.
FAQ
Terms & Licensing
RainFrames is proprietary, closed-source software, provided as a service rather than sold. Using it grants no ownership of the service, its code, design, branding, or data.
The hosted app is free for a personal, noncommercial purpose. A separate written license is required for commercial or organisational use, for integrating the service or its data into another product, for source access, for use of the branding, and for AI-related uses such as training on the service or its content.
Weather, map, and location data come from third-party providers, each retaining its own licenses and terms. Basemaps include OpenStreetMap contributor data.
Licensing enquiries: info@lastprojects.com