Node.js (CommonJS) bridge that replicates josephdadams/spotify-controller's Socket.io and REST API so bitfocus/companion-module-techministry-spotifycontroller works without AppleScript. Control is done via the Spotify Web API using a Refresh Token.
- Express + Socket.io on port 8801 by default
- allowEIO3: true for Companion compatibility
- Requires Node 18+ (uses global
fetch)
- Go to Spotify Developer Dashboard.
- Create an app (or use an existing one).
- Note the Client ID and Client Secret.
- In the app settings, add Redirect URI:
http://127.0.0.1:8888/callback(must be exactly this for the auth helper).
-
Copy
.env.exampleto.env. -
Set
SPOTIFY_CLIENT_IDandSPOTIFY_CLIENT_SECRETin.env. -
Run the auth helper (starts a loopback server on 127.0.0.1:8888 and prints the auth URL):
npm run auth
-
Open the printed URL in your browser, log in with Spotify, and approve the scopes.
-
Copy the refresh token printed in the terminal and set it in
.envasSPOTIFY_REFRESH_TOKEN.
npm install
npm startThe server listens on http://0.0.0.0:8801 (HTTP + Socket.io). Configure Companion to use host localhost (or your machine IP) and port 8801.
A simple “now playing” UI is available at http://localhost:8801/ui (or /ui.html). It shows track title, artist, album, album art, progress bar, connection status, and device name, and updates live via Socket.io.
The server is created via createServer(); it does not call server.listen() when the module is required.
const { createServer } = require('./server.js');
const { app, server, io, start, stop } = createServer({ spotifyClientOverride: null });
await start(8801); // optional port, defaults to PORT env or 8801
// ...
await stop(); // stops polling, ramp timer, and closes the HTTP server- createServer({ spotifyClientOverride })
Returns{ app, server, io, start, stop }. IfspotifyClientOverrideis provided, it is used instead of building aSpotifyWebAPIfrom env (useful for tests or custom clients). - start(port)
Starts polling, optional device transfer, and listens onport. Returns a Promise that resolves when listening (or rejects on listen error). - stop()
Stops the poll interval, clears any volume-ramp timer, and closes the HTTP server. Returns a Promise that resolves when the server is closed.
| Variable | Default | Description |
|---|---|---|
PORT |
8801 |
HTTP and Socket.io port |
POLL_INTERVAL_MS |
1000 |
Playback state poll interval (ms) |
ALLOW_CONTROL |
true |
If false or 0, control commands are rejected; state still emitted |
SPOTIFY_CLIENT_ID |
— | Required. Spotify app Client ID |
SPOTIFY_CLIENT_SECRET |
— | Required. Spotify app Client Secret |
SPOTIFY_REFRESH_TOKEN |
— | Required. From npm run auth |
SPOTIFY_DEVICE_NAME |
— | Optional. Target device by name |
SPOTIFY_DEVICE_ID |
— | Optional. Target device by ID |
SPOTIFY_AUTO_TRANSFER_ON_START |
true |
Transfer playback to configured device on startup |
version,control_status,stateplay,pause,playTogglemovePlayerPosition(seconds),setPlayerPosition(seconds)playtrack(trackUriOrId),playtrackincontext(trackUriOrId, contextUriOrId)next,previousvolumeUp,volumeDown,setVolume(volume 0–100)rampVolume(targetVolume, changePercent, rampTimeSeconds)mute,unmuterepeatOn,repeatOff,shuffleOn,shuffleOff
version(string)control_status(boolean)state_change({ playbackInfo, state })ramping_state(boolean)
/version,/control_status,/state/play,/pause,/playToggle,/next,/previous/playTrack/:track,/playTrackInContext/:track/:context/movePlayerPosition/:seconds,/setPlayerPosition/:seconds/volumeUp,/volumeDown,/setVolume/:volume/rampVolume/:volume/:changePercent/:rampTime/mute,/unmute,/repeatOn,/repeatOff,/shuffleOn,/shuffleOff
- playbackInfo:
{ name, artist, album, duration (ms), playbackPosition (seconds), trackId (spotify:track:...), playerState ('Playing'|'Paused'|'Stopped'), albumArtUrl, deviceName, deviceIsActive } - state:
{ track_id, volume (0–100), position (seconds), state ('playing'|'paused'|'stopped'), isRepeating, isShuffling }
-
Unit/integration tests (Node built-in test runner):
npm test- test/fake-spotify.js – Fake Spotify client implementing
getPlaybackState,play,pause,next,previous,seek,setVolume,setRepeat,setShuffle,getDevices,transferPlaybackfor use in tests. - test/http.test.js – Uses supertest to assert
/version,/control_status,/stateresponse shape, and/playToggletoggling playback. - test/socket.test.js – Uses socket.io-client to assert
versionandcontrol_statuson connect, and that emittingplayToggleresults in astate_changeevent.
- test/fake-spotify.js – Fake Spotify client implementing
-
Smoke test (real bridge) – Connects to a running bridge at
http://127.0.0.1:8801, logsstate_changeevents, and emitsstate,playToggle,next,volumeDown,pausein sequence. Exits 0 if at least onestate_changewas received and no socket errors; otherwise exits 1.npm start # in one terminal npm run smoke:real # in another
- Clone / upload: You can upload this folder to GitHub manually. Do not commit
node_modules/or.env(see.gitignore). - License: MIT (see LICENSE).
MIT