Intro
In the previous two Denon posts I have described setting up a web page which duplicates most of the functions available in the Denon AVR App, tailored to my own needs. I now want to see what the Heos app can do for me and which functions I can access through my own web page.
Heos is a brand associated with smart speakers, I believe it is owned by Denon/Marantz. When you switch the amplifier to Heos input it functions like a Smart Speaker. Heos, like Sonos, needs you to choose what to play and to control the speakers. Heos has Android and Apple apps which allow you to choose music and change volume, pause, skip tracks etc. Heos and Sonos focus on Streaming Services such as Spotify and Amazon for their music input but they additionally allow you to add music from local files or shared folders. I am most interested in playing my own music, more specifically I want to easily select and play albums.
What can Heos do?
Heos can select the standard Denon inputs for you:
- AMUSE
- CD
- JONGO
- Media Player (USB)
- Tuner.
In addition it can play:
- Local music from the iPad / phone running the Heos App
- Music from a network DLNA shared folder. For me this is principally my Router USB Media Server (which is called RouterShare).
- USB (which is a stick plugged into Denon)
- Heos playlists which you can define (by saving a list of items off the playing queue). These playlists are stored on your cloud HEOS account, so they available on all apps.
- TuneIn radio stations, including a list of favourites
- Music Services such as Spotify or Amazon Music
- Recently played tracks can be selected.
When music is playing you have standard options to control:
- volume/mute
- pause/resume
- next/previous
- shuffle/repeat
- save current music queue as a playlist
When a track is playing you can see time elapsed, time remaining, a list of items on the queue
My particular objective is to be able to select and play albums from the RouterShare DLNA server.
Development Approach
Of course I would prefer to build on someone elses solution for my Heos web page. The approach used to develop Denon functions entailed providing a python client as a back-end and invoking those functions from a web page using javascript and web sockets. Separation of front-end and back-end programming was very successful, making testing and fault-finding comparatively straigh-forward, so I followed the same approach for Heos.
Github is now the principal repository for open-source projects. Searching for "Heos" on github yielded
heospy as the most appropriate python project. However I found that the
Heos CLI protocol specification gives me the information I need. It explains that you telnet into Denon port 1255 and use commands in the format
heos://player/get_players to communicate with Heos as shown below
As an aside, my development environment is Windows and there is no official Heos "app" for Windows. Some forum posts recommended "Heos Remote" which is a very simple client available from the Windows Store for £5. I bought a copy which allowed me to quickly see what was happening inside the Heos environment.
Heos Web Page PoC
When programming Denon XML back-end I tried out the python Telnet package interactively. It is quite simple to adapt this to my needs so that the function telnet_client writes a request to the Denon Heos port and reads the response.
There were a few formatting wrinkles to resolve to make the data format acceptable for input to javascript on the web page. Firstly the response above includes plenty of newline characters so it is readable. We use the command
heos://system/prettify_json_response?enable=offto remove them. Secondly our JSON conversion alters double quotes to single and we have to put them back to doubles. Once this has been done we have our JSON ready to pass to the web page.
Our mechanism for invoking python scripts from a web page using web sockets is now well established.
Step 1: On the web page make a web socket request using the javascript we.send command.
Step 2: Web socket processing sees the denon command and calls the python denon script. Within this script the "H" parameter causes our heos.py script to be executed.
Step 3: Output from heos.py is returned to the web socket and is captured into the web page within the ws.onmessage function as event.data. ws.onmessage parses event.data input into a JSON structure myobj
Step 4: javascript jquery function inserts appropriate output from myobj into the web page.
In this PoC example there is a top level json field payload which is an array. We take the first element of the array and insert the name field into html field "heos1". In fact this displays the name of the Denon receiver "Bruce" in the web page. I called the receiver Bruce as Google home had a problem understanding the (rather dull) name Denon.
We have now successfully demonstrated that we can execute Heos commands and utilise output in our web page.
I found a useful summary of Heos commands we can use in a github project
heospy
Now we have a working mechanism we can start adding useful stuff.
We add buttons for play, pause, stop, next, previous and status. The status button will update information on the current song, artist and track.
In python we send appropriate commands to Denon for the corresponding functions:
The "status" parameter causes Denon to provide song, artist and track info which is received by ws.onmessage and added to the web page.
The console shows information received and the web page is updated:
Playlists
Heos allows you to setup your own playlists. These are stored in the cloud under you Heos user name so that they can be selected from any device. To put an album or a selection of tracks on a playlist, first place the tracks on the queue and then save the queue to a named playlist. I dont expect to use them much but it will be helpful to be able play predefined playlists.
As usual we ask Denon to provide the information to us. The system identifier for playlists is sid=1025.
We issue a browse command for sid=1025 and a list of playlists is returned. We can then play that list by quoting the container id e.g. cid=555631
I set up two sample playlists and hard-coded their cids so that I could use a python command to start playing them:
Heos Commands to play an album
The main use of Heos is to play albums. I have migrated my commonly used albums to a DLNA shared folder on the router called RouterShare. I have defined this folder to Denon so that we can access an play those albums. My albums on RouterShare are ordered on three levels of folders, at the top level are alphabetical groups of artists, the middle level has a list of the artists in that group and the bottom level has albums by the artist. The files in bottom level folders are the album tracks.
For example "Taylor Swift" is a folder in the T-Z group and includes the album "evermore (2020)".
The Heos Browse command is used to determine the content of DLNA shares, which are defined as system identifier sid=1024. The telnet output below shows that there are two DLNA shares and RouterShare has sid= -1779289667. Browsing sid=-177928667 shows that "Browse Folders" has a container id cid=64.
If we look at container cid=64 we see that cid=64$2 contains our music and cid=64$2$5 is the folder
T-Z
Going down a couple more levels shows us that "evermore (2020)" has cid=64$2$5$0$0 and browsing this cid shows us the identifiers of individual tracks, for example the first track "willow" has mid=64$2$5$0$0$0.
We can play the album by adding cid=64$2$5$0$0 to the Heos with the Heos Command
where pid is the physical id of the smart speaker, sid is the device containing the music and cid is the container whose contents we want to add. The last parameter aid=4 says that we want to replace whatever is on the queue with our new content. Optionally this track list could be saved as a playlist.
A summary of these commands is shown below.
Album Play Code
Python code is quite straightforward, we add two new functions album_list (show contents of a container) and album_list (add a container contents to a queue).
The "-g" command displays the contents of a container and "-a" adds the contents of a container to the play queue. "-g root" displays contents of the top level container "64$2".
We add some extra fields to the web page "heos5" through "heos9" so we can display album and track playing information.
When the page is loaded the top level folder structure is shown as buttons in cell in heos5. Clicking on one of these buttons causes a list of artists to be displayed in cell heos6 and clicking on an artist displays their albums in cell 7. Clicking on the album causes it to be placed on to the Heos queue and start playing.
The web page below shows part of the html when the "T-Z" button has been pressed. It shows that if the "Taylor Swift" button is pressed a web socket command is made to obtain the contents of container 64$2$5$0, which comprises a list of Taylor Swift albums.
A javascript loop is used to format each array element to a button for the next level. At this stage the javascript becomes somewhat incomprehensible so I wont include the details.
Current track and Queue
The final piece of the picture for a Heos Music Player is to display the track which is currently playing and a list of tracks in the queue.
The appropriate Heos commands to display the current queue and display the current track are shown below.
In fact, at the time of writing, the queue command only passes the first six tracks to display on the web page. I will fix the problem in due course. Current track is stored in heos9 and the queue is inserted as field heos8.
We now have a fully functional Heos music player which is tailored to my requirements. You are quickly able to select and play any album from the shared folder on the router. This has greatly exceeded my expectations. It provides the functionality of my existing AMUSE music player, it uses the Router "NAS" folder and can be integrated with Denon controls on a single page. In fact, once I have tidied it up it will probably replace the RPI mpd solution completely.