
The Sonos Javascript Control API sample app demonstrates how easy it is to connect to a Sonos player using WebSockets and shows best practices for handling Sonos system events.
Because of planned security restrictions, browser-based apps will not be able to connect to websockets on Sonos players. Therefore, this sample consists of a Node.js/Electron app to find Sonos players and groups on the local network, with a user interface that displays album art, metadata, playback and volume controls for a selected group.
This is an early release version of the sample app and may be lacking features or may have unresolved bugs. See the README.md for details.
Installing and running the sample app
Click the button above to download the JavaScript Sample App Source Code Package. The app requires a Node.js runtime environment and NPM, the Node Package Manager. Follow the instructions in the README.md to install and run the app.
In this overview, we’ll be focusing on using WebSockets and handling events. See the Discover (LAN) overview for details about player and group discovery.
Connect using WebSockets
The sample app listens for players on the network and displays the list of groups when you click the group selector area:
Select a group to control playback and volume. When you select a group, the sample app creates a WebSocket connection to the group coordinator, as shown in the connect
method of the SonosConnector
object in connector.js:
1 2 3 4 5 6 7 8 9 10 11 12 |
this.connect = function(hid, gid, url) { this.household = hid; this.group = gid; var socketUrl = url + '?key=' + this.ApiKey; this.websocket = new WebSocket(socketUrl, [this.Proto]); this.websocket.onopen = onOpen; this.websocket.onclose = onClose; this.websocket.onmessage = onMessage; this.websocket.onerror = onError; }; |
The WebSocket
constructor creates a WebSocket to the URL (with your API key appended) using the v1.api.smartspeaker.audio
sub-protocol. The connect
function also stores the household and group for later use when constructing messages to send to the group coordinator. It also registers callbacks on the WebSocket. We use the onMessage
callback to separate messages by Control API namespace to pass to a user interface function to handle. For example, to display the state of the play/pause button. We’ll go over this in more detail in the section below.
Note that your app should not send Origin headers in WebSocket connections to Sonos players on local networks. This is to prevent unauthorized websites from opening WebSocket connections to your players. See Connect (LAN) for details.
Handling events
The Sonos system may include multiple apps making changes to the system. Therefore, your app must handle events to show the current state of the system, so that changes made by different apps are reflected in your app. For example, you might use your app to start music in the living room, but your great grandmother, resting on the couch, may not want to hear it, so she may use her Sonos Controller app for Android to stop it. In this case, your app would have to handle the playbackStatus
event to show that the music has been paused.
You can see an example of this in the sample app in the code snippet below from index.jade:
1 2 3 4 5 6 7 8 |
playback.subscribe(function(msg) { if (msg.header.type == 'playbackStatus') { var isPlaying = ['PLAYBACK_STATE_PLAYING', 'PLAYBACK_STATE_BUFFERING'].indexOf( msg.body.playbackState) > -1; $('#play-pause-stop').toggleClass('playing', isPlaying); } }); |
The sample app registers a callback function to receive playback namespace events using the subscribe
method. When your great grandmother presses pause on her Sonos app, your sample app receives a playbackStatus
event from the group coordinator with the playback state value PLAYBACK_STATE_PAUSED
. This event invokes the callback function, which verifies the message type as a playbackStatus
event, determines that the player is no longer playing using the playbackState value in the message body, and changes the control to the pause button.
By tying the UI display state to the playbackStatus
event, your app stays in sync with changes.
Sending commands
When you click a playback button to make a change, for example, such as pressing the play button to play music, the app builds the Control API play
command (in playback.js):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
SonosPlayback.prototype.command = function(command) { var header = { namespace: this.namespace, householdId: this.connector.household, groupId: this.connector.group, command: command }; this.connector.send(JSON.stringify([header, {}])); }; SonosPlayback.prototype.play = function() { this.command('play'); }; SonosPlayback.prototype.pause = function() { this.command('pause'); }; |
and sends it through the WebSocket (in connector.js) to the group coordinator:
1 |
this.send = function(msg) { this.websocket.send(msg); } |
All commands require a target ID, such as the householdId
and groupId
for the play
command above. These IDs come from discovery. See the Discover (LAN) overview for details.
Stay tuned
We’ll update this sample with more guidance on key features and best practices, about:
- The volume control slider – since players event back volume changes after you send them, there are some best practices for improving the user experience of the volume slider so that it doesn’t appear jumpy when you use it.
- SSDP in the Node Server – we’ll add some details about how the sample app discovers groups using SSDP.