Harness Web Player
Status: beta. Unofficial integration — the streaming services do not provide or support it.
A Harness Web Player is a media player that runs each streaming service's own web player in Chrome — Spotify, Apple Music, Amazon Music, YouTube Music, SiriusXM, TuneIn, Audible and Calm Radio — and exposes them through one REST / Server-Sent Events API. One player instance is one GEM device. Every service answers browse, search and play in the same shapes, so a single webplayer source page renders all of them.
Each client signs in on the player with their own service accounts; GEM stores no service credentials.
What it does
- Services menu — the source page lists the services the player offers (limited and ordered
by the
servicesattribute), then the chosen service's own menu (Playlists, Artists, Albums, Stations, Channels …). Home goes back to the services. - Browse and play — artists show as square tiles; albums, playlists, stations, channels, songs and episodes list as wide rows with their artwork. A row opens what has its own list and plays the rest; a track keeps playing through the list it was picked from. The bottom-bar play (and the media bar's play) starts the open album, playlist or Liked Songs. Once something starts, the page shows the now playing card (with Like, Favorites or an audiobook's controls); the bottom bar's menu button goes back to the list.
- Search — searches the current service. From a typed list (Artists, Albums …) it searches that kind; elsewhere it returns top results of every kind, labelled per row.
- Filter — any list longer than 20 rows offers a filter (the bottom bar's filter button;
a magnifier on SiriusXM, which has no search). It narrows the list on the panel as each key
is typed, matching every word against the name and description; digits alone match the
channel number (
2finds 2, 20–29 and the 200s). The rest of a paged list is fetched first so nothing is missed. Back clears the filter before leaving the list. - Autoplay — with the player's Autoplay setting on (its settings page, Audio tab), tapping a service starts it playing: what the service has paused, or what played last on it. Opening the source page doesn't. Needs a player from 0.1.26 on.
- Categories — SiriusXM's channels carry a category (Pop, Rock, Country, Sports Talk, News & Politics …), listed in the right pane under Channels. A category narrows the channel list to it, together with any filter; Channels shows them all again, and so does Back. The whole lineup is loaded as it opens, so every category is listed. Needs a player from 0.1.25 on.
- Now playing — title, artist, album or station, artwork and position arrive on the player's event stream and feed the media bar.
- Like and Favorites — the now playing card has ♡ Like for a song on Spotify (Liked Songs), Apple Music (Love), Amazon Music or YouTube Music, ♡ Add to Favorites for a TuneIn station or a Calm Radio channel, and + Add to Library for a SiriusXM channel; tapping it again takes it back out.
- Library — an open album, playlist, artist or podcast shows a button above its list:
+ Add to Library (✓ In Library once there), + Follow for an artist
(+ Subscribe on YouTube Music, ☆ Favorite on Apple Music). Tapping it again takes
it out. Which kinds each service keeps is its
capabilities.library; songs are liked from the now playing card. SiriusXM's right pane has Library, the channels added to it. Apple Music artists, Amazon Music and YouTube Music podcasts and SiriusXM's Library need a player from 0.1.38 on. - Audiobooks — Audible lists the library (Library, Unfinished, Finished), each book with its author and time left; a book opens to its chapters. A book plays from where the account left off (the position is shared with the account's phones); a chapter plays from its start. While a book plays, the now playing card shows the chapter and hours, with −30 s, +30 s and Chapters (the playing book's chapter list, the current one highlighted); the media bar's previous and next change chapter. Needs a player from 0.1.29 on.
- Sign-in from the panel — a signed-out service shows a Signed out badge on its button. Tapping it offers Sign in: the player shows the service's sign-in page and its screen opens over the source page so someone can sign in remotely. A GEM page served over https opens the sign-in screen in a new tab instead.
- Service health — a service the player's own checks find not working (for example after the
service changed its web app) shows Not working on its button and appears in
service_problems.
The media bar shows previous, play, pause and next for every service; SiriusXM live channels ignore skips. While Spotify, Apple Music or YouTube Music is playing it also shows shuffle and repeat, lit while on. Repeat steps off, the list, the track, as the services' own button does. Amazon Music's web player has neither, and the other services have no use for them, so the buttons stay hidden for them. Needs a player from 0.1.17 on.
Setup
- Run a Harness Web Player on the LAN — one player instance per zone, each on its own port. The player's computer must run Ubuntu 26.04 LTS: earlier releases' PipeWire can't send its AES67 output, so the player's installer and updates refuse them. That holds for a GEM server that also runs the player.
- Sign each service in once on the player (
npm run loginon the player opens the sign-in window), or later from the source page as above. - Create a device with this driver. Enter the player's IP and HTTP port (8686 unless the instance was started on another).
- Optionally set services to limit and order what the source page offers, e.g.
spotify,applemusic,amazon,youtubemusic,siriusxm,tunein,audible,calmradio. - Create an AV source for the device and set its component to
webplayer.
For Spotify on the Spotify source page instead, add a Harness Web Player - Spotify device on the same player.
Attributes
| Attribute | Purpose |
|---|---|
ip, port | Player address; default port 8686. |
services | Comma list of the services the source page offers, in order. Blank offers every service the player has. |
now_playing | Read-only JSON: service, title, artist, album, station, chapter (Audible), item (the id of what plays), liked (the song playing, where the service likes songs), shuffle and repeat (off, all or one; null where the service doesn't set them), image_url, playing, position, duration, actions, service_actions, volume, volume_mode. |
now_playing_track, now_playing_artist, now_playing_album, now_playing_station, playing, active_service | Read-only flat copies for widgets and triggers. |
shuffle, repeat_mode | Read-only flat copies of now_playing.shuffle and now_playing.repeat, written only while the playing service sets them. |
volume | Read-only. Player volume 0-100; always 100 when volume_mode is fixed. |
volume_mode | Read-only. fixed (full level, for an amplifier or processor that sets the volume; set_volume is refused) or variable. Set on the player (VOLUME_MODE). |
sign_in_needed | Read-only. True while any offered service is signed out. A service is only checked when it is used, so a sign-out shows on its next use. |
signed_out_services | Read-only. Comma list of offered services that are signed out (spotify, applemusic, amazon, youtubemusic, siriusxm, tunein, audible, calmradio). |
service_problems | Read-only. Comma list of offered services the player's checks find degraded or failing. |
service_status | Read-only JSON, per offered service: id, name, signed_in (true, false, or null when not yet known), auth_error, auth_since, shuffle_only, health, version, problems. |
sign_in_needed and service_problems are ordinary attributes, so a trigger or alarm can watch
them to tell someone a service needs attention.
Commands
| Command | Arguments | Notes |
|---|---|---|
get_services | — | The offered services with their capabilities. |
get_status | — | The player state. |
browse | service, item_id, offset, limit, cursor | Blank item_id lists the service's top level. |
search | service, query, item_type, offset, limit | item_type is one of the service's search types (artist, album, playlist, track, show, station); blank returns every kind. |
play_item | service, item_id, context, title | Plays an item, switching service if needed. context keeps a track playing through its list. |
play | service, item_id | Starts the item when both are given; otherwise resumes. |
pause, play_pause, stop, next, previous | — | Transport. |
seek | position (seconds) | |
set_volume | level (0-100) | Refused when volume_mode is fixed. |
like | state (on, off, toggle; true/1 and false/0 also work) | Likes or unlikes the song playing, on services whose capabilities have like. now_playing.liked says whether it is. |
shuffle | state (on, off, toggle; true/1 and false/0 also work) | Shuffles what plays, on services whose capabilities have shuffle (Spotify, Apple Music, YouTube Music). |
repeat | mode (off, all, one, toggle) | all repeats the list, one the track; toggle steps off, all, one. Services whose capabilities have repeat. |
add_to_library, remove_from_library | service, item_id | Saves an item to the account's library (liked, saved, followed) or takes it out; on TuneIn and Calm Radio, their Favorites; on SiriusXM, its Library; Apple Music artists, its Favorites. The item's kind must be in the service's capabilities.library. |
library_status | service, item_ids (comma list, at most 50) | Whether each item is in the library: {id: true, false or null}. |
action | action | A service action listed in now_playing.service_actions (none of the player's current services has any). |
sign_in | service | Shows the service's sign-in page on the player and returns viewer_url. Replaces what the player was playing. |
check_sign_in | service | Whether the service is signed in now. |
open_service | service | Shows a service on the player without playing anything. |
Failures come back as {error, code} (auth for a signed-out service, unreachable,
bad_request, service_error) rather than throwing.
Known quirks
- One player instance plays one stream at a time, and most services allow one stream per account: zones that play different music need their own instance and, for Spotify, their own account.
- Amazon Music on a Prime plan shuffles playlists and albums instead of playing them in order; its songs are labelled Shuffle on the source page.
- SiriusXM needs a plan that includes streaming.
- Apple Music needs an Apple Music subscription; without one it plays only previews. Players from before Apple Music was added don't offer it.
- YouTube Music needs YouTube Music Premium (or YouTube Premium, which includes it); without it songs play with ads. Needs a player from 0.1.32 on.
- TuneIn needs no account: Local Radio, iHeartRadio (its local stations and genres), Music, News & Talk (Audacy's news stations among them), Sports, By Location and By Language. Its Favorites are kept on the player, not in a TuneIn account; podcasts and Premium-only stations aren't offered. Needs a player from 0.1.33 on.
- Calm Radio plays every channel with a membership; without one only its free channels play, and the rest are labelled Members only. Audible and Calm Radio need a player from 0.1.29 on.
Troubleshooting
| Symptom | Check |
|---|---|
A service answers "signed out", or sign_in_needed is true | Tap the service on the source page and choose Sign in. signed_out_services names the services; it clears once the service answers signed in. |
| The sign-in screen stays blank or does not open | The panel must reach the player's IP and port (its LAN address, not 127.0.0.1), the player must run with npm start (screen sharing on), and x11vnc must be installed on it. |
| Device stays disconnected | Open http://<ip>:<port>/status in a browser on the GEM network; the player must answer with its state. |
| Playback starts in the wrong room | Each zone needs its own player instance; check this device's port. |
| A service button shows Not working | The service changed its web app; service_status lists the failing checks and the web app version. Update the player. |