Saturday, 6 January 2024

Sonos : Web Page Control

Rationale

We have made excellent progress controlling Denon and Heos functions through python and web pages.  Playing albums using my web page only requires 3 clicks and is much easier than navigating through the NAS folder structure using Heos.  

I previously thought that Sonos was mainly limited to a closed eco-system of Amazon Music, Spotify etc but as I begin to look in detail I see that it can do a lot more, allowing you to play music from your own devices or a DLNA server.  The Sonos app, although it is slightly better than Heos, requires you to navigate through a menu structure to play albums.  Sonos actually provide an excellent in depth developers guide, if I want to develop from scratch.

My first objective was to see whether I could play RPI/mpd output on Sonos and I found that I could do so using TuneIn (Classic).  This was my fall back plan when my Sony amplifier was misbehaving, before I bought the Denon receiver.  It turned out I didn’t need to use Sonos for that purpose but the Sonos Beam is still available to me.  My temporary solution was somewhat limited as Sonos treats RPI/mpd input as a stream, whereas I really want to play an album using Sonos controls for viewing playlists, skipping tracks etc.

Investigation

Previously the usage of python for back-end programming and javascript / web sockets in the web page worked really well for the Denon  / Heos control page so I will use this approach again.
Sonos is more widely used than Heos and, searching on github,  SoCo is amongst the most popular Sonos projects.  It is a general purpose client written in python, currently maintained and includes excellent documentation so it seems perfectly suited for my needs.

 

Installation is trivial ("pip install soco").  Browsing the documentation I could see that the music_library submodule will be most useful to me. I was quickly able to check using just three statements in interactive python that I can use SoCo in conjunction with my Sonos Beam:

Music Storage

I have recently installed a rather excellent TP-link ax5400 router which has a USB port.  It has the capability to share music / video / data held on a USB flash drive.  I actually attach an unpowered 4-port USB hub which allows me to utilise multiple drives.  This gives me a free NAS capability and easily allows me to keep all music in one place.

Sonos likes good quality mp3 tags to scan so that it can scan music in an orderly manner.  In particular, my charts downloads often have the artists name both in the "album" tag and the "album artist" tag, which causes Sonos to think I have hundreds of albums each with one track.  In addition Sonos plays tracks in filename order if there are no track numbers.  I fixed these problems using the wonderful windows mp3tag utility by setting "album artists" to "various artists" for chart downloads and by adding a track number.  Sonos is now pretty good if I navigate through the music library by artist or album and play all tracks in an album.

Unfortunately Heos displays the "album artist" field to display tracks so lots of my tracks now appear as "Various Artists".  The only solution I can think of is to have separate charts folders for Heos and Sonos.  Ah well, it is not a perfect world.

 Soco python programs

SoCo is an excellent python module, which is enhanced by the comprehensive Sonos API and quality documentation.  It proved to be straightforward to set up my own functions to choose and play albums.

Although it is easy to choose an album by artist I prefer to use the folder structure to define my album playlists.  This allows me to navigate to an album and play it even if the mp3 tags for album, artist are imperfect.  

The following interactive python example shows how to navigate the router music flash drive folders to choose an album to play.  The  "root" level is a shared folder "//RouterShare/H" which is obtained using a command "get_music_library_information('share'). The music at each successive level in the folder structure is achieved by selecting an array element from root, level1, level2, level3 by browsing the folder object retrieved at the next level up.

Having chosen a level3 item, which is an album, it can be added to the Sonos music queue and played as shown below.


These commands can be integrated into our command line python program which can return a list of groups, artists or albums.  The list is typically passed to the web page which returns with a choice indicating the next level. Once the group/artist/album selection has been made their values are passed to the play option and python makes a Denon request to add the tracks to the queue and play the album.

Web page


Our web page simply has to display available groups, artists and albums and allow the user to choose what they want.
Initially the "Show Groups" button is used to make a python request to Sonos which returns the list of groups that are inserted into the page.  In due course this will be done automatically on loading the page.  In the HTML you see that the web socket command is still denon, as we are using the framework setup for Denon and Heos.  The "-l groups" parameter causes python to return a list of the groups.  For convenience all the commands include "-g" (group chosen) and "-a" (artist chosen) parameters as well.  

This web socket command  causes the python command "sonos.py -l groups -g 0 -a 0" to be executed and the list of groups "A-B", "C-F", ... is displayed.








Clicking on a group causes a Sonos request which fetches and displays an artist list.  Click again and a list of albums from that artist is displayed.  A third click puts the album on the Sonos play queue and starts the music.





The Chrome console log is very helpful when testing the web page.  In the example to the right you can see information returned to the web page after clicking the "show groups" button, then clicking "C-F" then "Coldplay".  At each stage the next level of information is returned and displayed on the web page.  The information from Sonos is returned in JSON format so that it can be easily interpreted in javascript.

My python script adds some extra fields at the end of JSON returned by Sonos.  In the example on the right the second JSON response has
 "Sonos" : "albums", "group":"1", "artist":"4"
added to show that the information is a list of albums relating to group1 (C-F) and artist4 (coldplay).

When a Sonos response is received by the web page web socket ws.onmessage function the javascript code looks at the "Sonos":"albums" field and can see that the list of albums should be placed in field #sonos7 which is below the group / artist fields.
makeSonosList formats the json as html fields and buttons so that it looks correct and reacts correctly.


If the level is groups or artists the html button causes a request for the next level of information.
If the level is album, the button submits a request to play the appropriate album.



No comments:

Post a Comment