This repo adds a slick “Now Playing” chip to HomePageDev: cover art, title/artist, progress, and quick controls (prev / play‑pause / next / seek).
It’s intentionally split into two layers:
- NowPlaying Server (Node/Express) — a tiny API that stores the latest now‑playing state and exposes a command channel.
- HomePageDev Custom UI (JS + CSS) — the chip UI that lives inside HomePageDev’s
custom.js/custom.css.
Optional (Linux only): the server can also bridge MPRIS (desktop media control via D‑Bus).
- ✅ Top‑bar Now Playing chip (cover + title/artist + progress)
- ✅ Controls: previous / play‑pause / next
- ✅ Seek by clicking the progress bar
- ✅ “Raise” behavior: try to focus/raise the active player window (when supported)
- ✅ Multi‑source state (Spotify / YouTube / YouTube Music / MPRIS)
- ✅ Cover proxy (
/cover) to safely serve localfile://album art and allow‑listed remote covers
- YouTube (
www.youtube.com) - YouTube Music (
music.youtube.com) - Spotify Web (
open.spotify.com)
For browser sources, you’ll typically use the Tampermonkey userscript to:
- read what’s currently playing in the tab
- send it to the server
- poll commands so the HomePageDev chip can control the tab
MPRIS is a Linux desktop standard for media player control over D‑Bus.
- ✅ Reads now playing from desktop players
- ✅ Sends play/pause/next/prev/seek/raise
MPRIS only works on Linux. Windows/macOS don’t expose Linux session D‑Bus + MPRIS in the same way.
git clone https://github.com/G-grbz/Homepagedev-NowPlaying
cd Homepagedev-NowPlayingYou need a secret key to protect write/control endpoints (POST /nowplaying and POST /command).
Easiest (Linux/macOS):
# 32 bytes hex (64 chars)
openssl rand -hex 32Node one‑liner (works anywhere Node is installed):
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Copy the output — you’ll use it in:
- server
.env(WRITE_KEY=...) - HomePageDev env (
HOMEPAGE_VAR_NOWPLAYING_WRITE_KEY=...) and/or Tampermonkey script
Create a .env file next to docker-compose.yml.
Minimum example:
# Server
PORT=8787
WRITE_KEY=PASTE_YOUR_RANDOM_KEY_HERE
SEED_TOKEN=change-me
# App paths
NOWPLAYING_APP_DIR=/home/username/nowplaying
APP_WORKDIR=/app
# Linux / MPRIS (optional)
ENABLE_MPRIS=1
XDG_RUNTIME_DIR=/run/user/1000
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus-
WRITE_KEY— set to the random key you generated. -
NOWPLAYING_APP_DIR— must point to thenowplaying/folder on your host. -
If using MPRIS on Linux:
XDG_RUNTIME_DIRandDBUS_SESSION_BUS_ADDRESSmust match your actual UID.
docker compose up -dhttp://your-nowplaying-host:8787/→nowplaying ok (multi-source + mpris)http://your-nowplaying-host:8787/nowplaying→ JSON payload
This repo also ships with a fully automated installer script that sets everything up for you in one go.
If you don’t want to manually:
- generate keys
- edit
.env - patch
homepagedev-custom.js - patch
tampermonkey.script.txt - copy JS/CSS into Homepage
…then this script is for you.
The goal of install.sh is simple:
One command → working Now Playing widget 🚀
When you run install.sh, it will:
-
Detect your LAN IP automatically
-
Generate secure secrets
WRITE_KEYSEED_TOKEN
-
Create a fresh
.envfile for Docker Compose -
Patch project files
homepagedev-custom.jstampermonkey.script.txt(if present)
-
Optionally modify Homepage
- Append or update the Now Playing JS/CSS blocks
- Automatically back up existing files
-
Optionally start Docker Compose
Everything is reversible:
- Homepage files are backed up
- Blocks are wrapped in
NOWPLAYING BEGIN / ENDmarkers
Make sure these are installed before running the script:
-
bash
-
Docker
-
Docker Compose (
docker composeordocker-compose) -
One of:
opensslorpython3
On Linux, MPRIS is supported by default (can be disabled).
From the project root:
chmod +x install.sh
./install.shThis runs in safe default mode:
- Homepage is patched interactively
- Docker is not started
- Existing Homepage blocks are not overwritten
./install.sh --upWhat happens:
- LAN IP is auto-detected
.envis generated- Homepage JS/CSS is appended
- Containers are built and started
If you use a domain like nowplaying.example.com:
./install.sh \
--domain nowplaying.example.com \
--upEffects:
- JS endpoints use
https://nowplaying.example.com - Tampermonkey uses HTTPS
.envincludesNOWPLAYING_DOMAIN
If you already ran the installer before and want to refresh everything:
./install.sh --mode updateThis will:
- Remove old NOWPLAYING blocks
- Write clean, fresh JS/CSS
- Keep unrelated Homepage code untouched
Useful for servers / SSH installs:
./install.sh \
--homepage-dir /home/username/.homepage/config \
--upNo prompts — fully automated.
If you only want the server + Tampermonkey:
./install.sh --skip-homepage --upYou can paste JS/CSS manually later.
./install.sh --no-mprisResult:
ENABLE_MPRIS=0- No D-Bus or session bus is used
Perfect for non-Linux systems or headless servers.
./install.sh --up --recreateEquivalent to:
docker compose up -d --build --force-recreate --remove-orphans--up Start Docker Compose after setup
--no-up Do not start Docker Compose (default)
--recreate Force container recreation (with --up)
--domain <domain> HTTPS domain for endpoints
--port <port> Override server port (default: 8787)
--mode append Append blocks if missing (default)
--mode update Remove old blocks and write fresh ones
--homepage-dir Homepage config directory path
--skip-homepage Do not touch Homepage files
--mpris Enable MPRIS (default)
--no-mpris Disable MPRIS
--help Show help
.env(Docker environment)
homepagedev-custom.jstampermonkey.script.txtHomepage/custom.jsHomepage/custom.css
Backups look like:
custom.js.bak.20260123-235959
The script prints everything you need at the end:
LAN_IP: 192.168.1.29
PORT: 8787
DOMAIN: nowplaying.example.com
WRITE_KEY: <generated>
SEED_TOKEN: <generated>
If Homepage uses environment variables, set this:
HOMEPAGE_VAR_NOWPLAYING_WRITE_KEY=<WRITE_KEY>Without it:
- Widget still renders
- Controls will not work
- If you want full manual control
- If you are debugging custom JS logic
- If you intentionally don’t want automatic patching
Otherwise: use the installer — it’s fast, safe, and repeatable.
If you used
install.shwith Homepage enabled, you can skip most of this section.
Take the contents of:
homepagedev-custom.js
…and paste it into HomePageDev’s custom.js.
Then edit these lines in the script:
const NOWPLAYING_URL =
(location.protocol === "https:")
? "https://nowplaying.example.com/nowplaying"
: "http://your-nowplaying-host:8787/nowplaying";
const COMMAND_URL =
(location.protocol === "https:")
? "https://nowplaying.example.com/command"
: "http://your-nowplaying-host:8787/command";Replace with your real endpoints.
Examples:
-
LAN / HTTP
http://<server-ip>/nowplayinghttp://<server-ip>/command
-
Reverse proxy / HTTPS
https://nowplaying.example.com/nowplayinghttps://nowplaying.example.com/command
const ENV_RAW = "{{HOMEPAGE_VAR_NOWPLAYING_WRITE_KEY}}";This placeholder is used to provide the write key required by the NowPlaying service.
If you are using environment variables with Homepage.dev, add your generated write key:
HOMEPAGE_VAR_NOWPLAYING_WRITE_KEY=your-write-key-hereHomepage will inject this value into custom.js at runtime.
If you’re not using Homepage env vars, replace the placeholder manually:
const ENV_RAW = "your-write-key-here";To automatically store HOMEPAGE_VAR_NOWPLAYING_WRITE_KEY in the browser’s localStorage, the SEED_TOKEN defined on the server must match the token sent from the client.
On the client side, a seed token is sent via request headers:
const headers = { "X-Seed-Token": "change-me" };On the server side, define the same value in your .env file:
SEED_TOKEN=change-meIf these two values are identical:
- The server accepts the initial handshake
HOMEPAGE_VAR_NOWPLAYING_WRITE_KEYis written intolocalStorage- Subsequent requests can use this key automatically
If the values do not match, the request is rejected and no data is written.
Take the contents of:
homepagedev-custom.css
…and paste it into HomePageDev’s custom.css.
The widget reads the key from:
{{HOMEPAGE_VAR_NOWPLAYING_WRITE_KEY}}
So you should set this in your HomePageDev environment (Docker compose example):
environment:
- HOMEPAGE_VAR_NOWPLAYING_WRITE_KEY=PASTE_YOUR_RANDOM_KEY_HEREIf the key is missing, the chip still renders, but controls won’t work.
If you don’t want MPRIS:
- set
ENABLE_MPRIS=0 - use the Tampermonkey userscript below
This is the cleanest cross‑platform setup: it works on any OS because the browser tab itself reads/controls playback.
This repository includes the Tampermonkey script as:
tampermonkey.script.txt
You do not need to copy it from the README.
This script:
- reads Now Playing info from the active browser tab
- sends it to the NowPlaying server (
POST /nowplaying) - polls commands from the server (
GET /command) and executes them inside the tab
- Install the Tampermonkey browser extension.
- Open the file
tampermonkey.script.txt. - Copy its full contents.
- In Tampermonkey, create a new userscript and paste the contents.
- Save the script.
Inside tampermonkey.script.txt, update:
const BASE = "http://NOWPLAYING_IP_OR_DOMAIN:8787";
const WRITE_KEY = "YOUR_NOWPLAYING_WRITE_KEY";Examples:
-
LAN only
BASE = "http://192.168.1.29:8787"
-
HTTPS / reverse proxy
BASE = "https://nowplaying.example.com"
Tampermonkey blocks network requests unless the destination host is explicitly allowed.
In the userscript header, make sure these lines match your server:
// @connect nowplaying.example.com
// @connect 192.168.1.29Add/remove @connect entries so they match the hostname used in BASE.
⚠️ Important: For playback control to work, the browser tab must remain open. The userscript is what actually clicks buttons and seeks inside the page.
In .env:
ENABLE_MPRIS=1
XDG_RUNTIME_DIR=/run/user/1000
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/busIn Docker, you must mount the session runtime directory (already in the provided compose example).
ENABLE_MPRIS=0MPRIS_PREFER=spotify,firefox,chromeIf HomePageDev is served via HTTPS, your browser will block mixed content if the NowPlaying API is HTTP.
Minimal Nginx example:
server {
listen 443 ssl;
server_name nowplaying.example.com;
location / {
proxy_pass http://127.0.0.1:8787;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}
}WRITE_KEYprotects write/control endpoints. Treat it like a password.- The cover proxy is restricted by default using
COVER_PROXY_ALLOW.
If you really want to allow any remote host:
ALLOW_ALL_COVER_PROXY=1…not recommended on a public server.
| Variable | Default | Notes |
|---|---|---|
PORT |
8787 |
Server listen port |
WRITE_KEY |
change-me |
Required secret key |
SEED_TOKEN |
empty | Optional, used by /widget-key |
STALE_MS |
15000 |
State staleness window |
ENABLE_MPRIS |
1 |
Linux only |
MPRIS_TICK_MS |
1000 |
MPRIS sync tick |
MPRIS_PREFER |
empty | Player preference substrings |
COVER_PROXY_ALLOW |
i.scdn.co,i.ytimg.com |
Allow‑listed remote cover hosts |
ALLOW_ALL_COVER_PROXY |
0 |
Allow all remote hosts (unsafe) |
DBUS_SESSION_BUS_ADDRESS |
(varies) | Linux session D‑Bus |
Returns the active state plus all per‑source states.
Header:
X-Widget-Key: <WRITE_KEY>
Body (example):
{
"source": "spotify",
"title": "Track",
"artist": "Artist",
"url": "https://...",
"cover": "https://...",
"playing": true,
"positionMs": 12000,
"durationMs": 180000
}Header:
X-Widget-Key: <WRITE_KEY>
Body:
{ "action": "toggle" }Actions:
togglenextprevseek(value = ms)raise(value = optional hint likespotify/youtube)
-
Check
GET /nowplaying -
If using browser mode:
- confirm Tampermonkey is running on the site
- confirm
BASEand@connect
- Confirm HomePageDev has
HOMEPAGE_VAR_NOWPLAYING_WRITE_KEY - Confirm the server
WRITE_KEYmatches - In browser mode, keep the player tab open
- Confirm
/run/user/<uid>/busexists on the host - Confirm the container mounts
XDG_RUNTIME_DIR - Confirm
ENABLE_MPRIS=1
MIT