One of my best mates, Matt, once gave me a hard time for listening to internet radio and somehow likened it to me being middle aged… Well, he was (is!) kind of right. That said, having some kind of background music while I’m coding, riding to work or vacuuming the house is not abnormal. Some people turn on the TV, some people turn to Alexa “Hey Alexa, play the ‘Cleaning the house’ playlist”, for me…I do internet radio, and for the most part, it will be one of great stations on SomaFM.
I hadn’t touched PyQT in some time. I wrote some very hacked together desktop applications for work that integrate with RFID readers, but aside from that, not much. I certainly wasn’t adept with any best practices, so when I decided I wanted a simple desktop app that could live in the systray and play whatever stations I add to it, I thought this would be a great opportunity to crack open the old PyQT chest, specifically PyQT5.
The app must have
- Basic CRUD functionality for managing stations
- Grab song meta for currently playing song
- Uninstrusive. Need to be able to minimise to systray
- Easily favorite/love a song aka Spotify, Apple Music, etc
Nice to haves
- CR(u)D functionality for managing favourites
- Well formatted preferences
- Tooltips with currently playing song
- Cross platform (I do use MacOS on occasion)
Before I even began, I need to suss what would be the best library to actually handle our stream.
For years I had (and still do) use mpd and ncmpcpp to handle playback of streams (or music in general) – I figured that since this had been my tool of choice, why not see if I could make use of some python bindings, all that it would need to do it
- Add the station to the playlist
- Read song meta
A quick search on pypi leads to a plethora of libraries that got me thinking, not everywhere has mpd installed, and whats more, I don’t want it impeded on my current mpd ncmpcpp experience (since I still use it for playback of FLAC’s, MP3, etc)
The search continues…
This was far by the simplest and most reliable way of implementing playback. As simple as:
self.instance = vlc.Instance() self.mediaplayer = self.instance.media_player_new() self.media = self.instance.media_new(some_shoutcast_url) self.media.parse() self.mediaplayer.set_media(self.media) self.mediaplayer.play()
And we’re off to the races!
However, there was one small issue, vlc does not parse meta stream information.
Getting stream meta
One of the downsides of using VLC bindings is that there is no native way of obtaining stream information. Not wanting to re-invent the wheel, I did some research that led me down the path of a few stackoverflow questions and finally stumbled across StreamScrobbler, however the project is quite old (last commit is some 7 years ago) and is written before everyone started wrapping their print statements in parathensis, let alone using fstrings!
Cloning the repo and doing some porting to Python3, I had a working StreamScrobbler library! (Being an MIT license, I will fork this in the coming days)
Buiding the GUI
This is the part I hate. I’m a backend guy, I really hate thinking about UI and making GUI’s, hence why I spend the majority of my days living inside of a zsh terminal. That said, I had a stroke of inspiration and decided to give it a crack. The end result:
Putting it all together
I gave myself a solid day or 2 to refresh myself with PyQT, QThread’s and Signal’s and this was the end result:
For the most part, I am happy. Everything is working as expected, even a working install script that will install a precompiled version (using Pyinstaller) that compiles down to a ~55mb binary that includes all required libraries sans any QT bindings that the users DE requires.
- Bug fixes
- Precompiled binaries for other OS’s
Can be found on here on github.