For as long as I can remember, I have loved music. It’s one of the boldest expressions of humanity and has a profound way of nuturing your soul. Unsurprisingly, I listen to a lot of music throughout the day when I am working. I’ve been a Spotify user for at least a decade, but I’m also a little bit of a snob when it comes to format and quality.
I don’t want to rehash the endless debate about whether or not there is a real discernable difference between uncompressed vs. compressed audio or high-resolution vs. CD-quality, but my personal stance on this is that I think if you’re listening to music and are enjoying it, that’s all that matters. However, if all other variables are equal, why not remove the question entirely and listen to the best quality version available?
Spotify has been teasing high-resolution audio for quite some time now and Tidal, the first player in this space, is a big proponent of the MQA format. Qobuz is a French music service that offers the ability to download or stream albums in either mp3 or FLAC formats at resolutions up to 24 bit/192Khz.
None of these services have good Linux support. Some have desktop apps, but are Windows and Mac only. The only official way to use the service from Linux is either through a third-party app that does or through the web site and for some the setup is overly complex when all I want is to listen to music.. This isn’t ideal for me, and I was seeking a project to expose myself to Rust and GStreamer, so I decided to write a TUI-based Qobuz player.
The first obstacle was accessing the API in order to interact with the service. Qobuz does offer a third-party API by the fact that their service is available in other products, but they do not offer any documentation to the public. In my googling there used to be a Github repo that had information, but it long since been removed.
I found a Python-based app called
qobuz-dl that obtains a token by logging into the site and retreiving it from your cookies. This seemed like a reasonable approach so I ported it over to Rust. This method is extremely error prone because if Qobuz changes their login behavior things will probably break. Thankfully, since writing the app the changes have been minimal, but it’s an issue that will always be out there and threaten the stability of the app.
Once I got that working, I was able to build the actual player.
Right or wrong, for my initial design I didn’t want a complicated database schema, but rather a key-value store I could use as a persistent state. I also wanted to stick to Rust, so I started with using Sled.
Sled offers a lot of fancy features and some caveats like “if reliability is your primary constraint, use SQLite. sled is beta.”
My first implementation with Sled worked well enough, but would sometimes crash or freeze the app. This could have very well been the result of my implementation. I stored a lot of individual values and read and wrote to the database often. This created a lot of pauses in the UI updates. I also was not nearly as knowledgeable about the intricacies of async and concurrency in Rust as I am now, having gone through this.
Sometimes constraints are good, so I decided to switch to a more robust SQLite database and it’s worked really well. It interacts with the database only when needed and offers a solid foundation for expansion.
I used the SQLx crate to create a database client and test my queries. SQLx offers compile-time checked queries which validates your queries at compile time (not just a fancy name), ensuring correctness on final build. It also offers the ability to integrate migrations into your application directly through a handy macro,
For the terminal interface (TUI) I used the appropriately named crate, tui. It’s a really useful crate, but not really designed to built multi-window apps with a lot of functionality. It really shines for things like dashboards where you have panes of information updating frequently.
I was able to hack something together and make it work, but if I plan to do any more UI work, it will be to switch to a crate like Cursive, which is more expressly made to build complete applications.
Overall the app works really well. I experience some crashes here and there I haven’t been able to pinpoint the cause of, but overall it’s perfect for what I need and I’ll continue to refine it over time.