Turning LG TVs into Music Assistant Speakers with a Visualizer
I have three LG TVs, and I wanted each one to work as a Music Assistant speaker. They also had to keep doing their normal TV job while the music played. Two apps now handle that, and both are open source:
- Sendspin WebOS plays Music Assistant audio from a background service. The music mixes with live TV or HDMI audio and keeps going when the app is closed or the panel is off.
- projectM for webOS is a native build of the projectM MilkDrop visualizer. It reacts to anything the TV plays, and Sendspin can open it in place of the screensaver.
How it works
Sendspin started as a fork of Sendspin Cinema, a single web page that talked to Music Assistant from the TV's browser engine. That page only played while it was in the foreground. My fork, Sendspin WebOS, splits it in two:
- A webOS service,
com.sendspin.webos.service, holds an authenticated WebSocket to Music Assistant and receives the Sendspin stream as FLAC. - The service pipes the stream into
gst-launch-1.0 ... ! pulsesink. pulseaudio is the TV's own mixer, so the music plays on top of whatever input is on screen instead of taking over audio focus. - The app you see on the TV is a thin remote for that service. It shows the track, the album art, and a progress bar tinted with colors pulled from the art.
projectM reads the pulseaudio monitor source (pcm_output.monitor), so it reacts to whatever comes out of the speakers, from any app. The visualizer in the video above is an animated stand-in. The real app runs MilkDrop presets.
The screensaver hook ties the two apps together. The Sendspin service subscribes to the TV's screensaver requests. When Visualizer screensaver is on and music is playing, it blocks the screensaver and opens projectM in screensaver mode. Any key other than the arrows closes it and puts back whatever was on screen.
Preset performance
The cream-of-the-crop pack has 9,795 presets, and plenty of them are too heavy for a TV GPU. The webOS build of projectM has a benchmark mode that runs every preset against a generated drum loop and records the frame rate. I ran it on a webOS 6 TV (Mali-G52) and a webOS 7 TV (PowerVR) at half render resolution. The build drops every preset that came in under 30 fps on either one, which leaves 4,753. During playback, any preset that drops below 30 fps after its blend-in is skipped, unless you've favorited it.
| Remote key | In projectM |
|---|---|
| Left, Right | Previous or next preset |
| OK | Add or remove a favorite |
| Up | Change presets on its own every 25 seconds |
| Down | Favorites only |
| Back | Exit |
Setting it up
You need:
- An LG webOS TV that is rooted and has Homebrew Channel. Sendspin's Homebrew Channel package is marked as needing root. I've run both apps on webOS 6, 7 and 8.
- A Music Assistant server on the same network.
Steps:
- In Homebrew Channel, open Settings and add these two repositories:
https://github.com/daredoes/sendspin-webos/releases/latest/download/apps.jsonhttps://github.com/daredoes/projectm-webos/releases/latest/download/apps.json
- Install Sendspin WebOS and projectM from the app list. Future releases show up there as updates.
- Open Sendspin WebOS. Its setup screen shows a LAN address like
http://192.168.1.50:3917and a four-digit PIN. Open that address on your phone, type the PIN, and tap Scan to find Music Assistant. Pick your server, add a username and password if your server has login turned on, name the player, and save. You can do all of this with the remote too, but typing on a phone is much faster. - In Music Assistant, go to Settings → Players. If the new player is hidden, open its settings and turn off Hide this player in the user interface.
- Back on the TV, open the gear in Sendspin and turn on Visualizer screensaver.
The videos were made with Playwright and Remotion. Playwright loads the real Sendspin TV app and LAN setup page from the repo, backed by a fake service with a placeholder track, and records each one. Remotion draws the diagrams and frames the recordings. The source is in the videos/ folder of this site's repo.
Opening the visualizer on demand
- Use projectM's tile on the home screen.
- Over SSH, run
luna-send -n 1 luna://com.sendspin.webos.service/showVisualizer '{}'. Pass{"screensaver": false}to open it as a normal app. - Map it to a remote button with lginputhook. Its
launchaction can't pass parameters, so bind anexecaction that runs theluna-sendcommand above. - From Home Assistant, the LG webOS TV integration's
webostv.commandaction can callsystem.launcher/launchwith the payload{"id": "com.daredoes.projectm", "params": {"screensaver": true}}.
Using the TV as a speaker with the screen off
The service keeps playing with the panel off. To blank the screen and keep the audio, call com.webos.service.tvpower/power/turnOffScreen (through luna-send, or webostv.command from Home Assistant). Any remote button turns the panel back on. Turn off Keep TV awake first, because with it on the service turns the panel back on every time webOS blanks it.
Standby is different. It suspends the TV and drops the network, so the player goes offline. To play from standby, turn on the TV's wake-over-network setting and send a Wake-on-LAN packet before you start the music. The service reconnects on its own after the TV wakes. My Home Assistant script does exactly that: it sends the wake packet, blanks the panel, and then starts playback.
Source and licenses
Both apps are GPL-3.0. projectM itself is LGPL-2.1 and linked statically, and the presets keep their authors' terms. The code is at sendspin-webos and projectm-webos.
Two things I haven't confirmed on a TV yet. I've launched the visualizer screensaver by hand plenty of times, but I haven't actually watched a TV sit idle with music playing and open projectM on its own. Favorites are also untested, because I don't know yet whether the remote's OK button reaches the app as Enter. projectM logs every key it doesn't use to /tmp/projectm-webos.log, so if OK does nothing for you, that log will say what it sent.

