// node-sonos-http-api client for Parse Cloud Code // Adjust these to match your node-sonos-http-api service var sonosHost = "yourSonosHost.com"; var sonosPort = 5005; var baseUrl = "http://" + sonosHost + ":" + sonosPort var _ = require("underscore"); // List available zones and their status exports.zones = function() { var path='/zones'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ var zones = response.data; prettyPrintZones(zones); return Parse.Promise.as(zones); }, function(error) { return Parse.Promise.error('Could not play.'); }); }; // Ask zone to start playing exports.play = function(zone) { var path = '/' + encodeURIComponent(zone) + '/play'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Playing.'); }, function(error) { return Parse.Promise.error('Could not play.'); }); }; // Start playing playlist exports.playlist = function(zone, playlist) { var path = '/' + encodeURIComponent(zone) + '/playlist/' + encodeURIComponent(playlist); return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Playlist requested.'); }, function(error) { return Parse.Promise.error('Could not play.'); }); }; // Play favorite exports.favorite = function(zone, favorite) { var path = '/' + encodeURIComponent(zone) + '/favorite/' + encodeURIComponent(favorite); return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Favorite requested.'); }, function(error) { return Parse.Promise.error('Could not play.'); }); }; // Pause exports.pause = function(zone) { var path = '/' + encodeURIComponent(zone) + '/pause'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Paused.'); }, function(error) { return Parse.Promise.error('Could not pause.'); }); }; // Adjust volume. Value can be absolute (0 to 100) or relative (-15 or +15 percent) exports.volume = function(zone, value) { var path = '/' + encodeURIComponent(zone) + '/volume/' + value; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Volume adjusted.'); }, function(error) { return Parse.Promise.error('Could not adjust volume.'); }); }; // Same as volume, but for the entire group exports.groupVolume = function(value) { var path = '/groupVolume/' + value; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Group volume adjusted.'); }, function(error) { return Parse.Promise.error('Could not adjust group volume.'); }); }; // pause all zones exports.pauseAll = function(timeout) { var path = '/pauseall'; if (timeout) { path += '/' + timeout; } return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Paused all.'); }, function(error) { return Parse.Promise.error('Could not pause all.'); }); }; // resume all zones exports.resumeAll = function(timeout) { var path = '/resumeall'; if (timeout) { path += '/' + timeout; } return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Resumed all.'); }, function(error) { return Parse.Promise.error('Could not resume all.'); }); }; // clear Sonos queue exports.clearQueue = function() { var path = '/clearqueue'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Cleared queue.'); }, function(error) { return Parse.Promise.error('Could not clear queue.'); }); }; // timeout in seconds, timestamp, or off exports.sleep = function(timeout) { var path = '/sleep/' + timeout; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ var status = 'Sleeping'; if (timeout === 'off') { status = 'Not sleeping.'; } return Parse.Promise.as(status); }, function(error) { return Parse.Promise.error('Could not adjust sleep.'); }); }; // Skip to a track at queue index exports.seek = function(zone, queueIndex) { var path = '/' + encodeURIComponent(zone) + '/seek/' + queueIndex; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Seeking.'); }, function(error) { return Parse.Promise.error('Could not seek.'); }); }; // Seek forward or backwards within the current track exports.trackSeek = function(zone, seconds) { var path = '/' + encodeURIComponent(zone) + '/trackseek/' + seconds; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Seeking.'); }, function(error) { return Parse.Promise.error('Could not seek.'); }); }; // Mute exports.mute = function(zone) { var path = '/' + encodeURIComponent(zone) + '/mute'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Volume muted.'); }, function(error) { return Parse.Promise.error('Could not mute.'); }); }; // Unmute exports.unmute = function(zone) { var path = '/' + encodeURIComponent(zone) + '/unmute'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Volume unmuted.'); }, function(error) { return Parse.Promise.error('Could not unmute.'); }); }; // Repeat current track exports.repeatOn = function(zone) { var path = '/' + encodeURIComponent(zone) + '/repeat/on'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Repeat on.'); }, function(error) { return Parse.Promise.error('Could not adjust.'); }); }; // Repeat current track exports.repeatOff = function(zone) { var path = '/' + encodeURIComponent(zone) + '/repeat/off'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Repeat off.'); }, function(error) { return Parse.Promise.error('Could not adjust.'); }); }; // Shuffle queue exports.shuffleOn = function(zone) { var path = '/' + encodeURIComponent(zone) + '/shuffle/on'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Shuffle on.'); }, function(error) { return Parse.Promise.error('Could not adjust.'); }); }; // Shuffle queue exports.shuffleOff = function(zone) { var path = '/' + encodeURIComponent(zone) + '/shuffle/off'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Shuffle off.'); }, function(error) { return Parse.Promise.error('Could not adjust.'); }); }; // Crossfrade tracks exports.crossfadeOn = function(zone) { var path = '/' + encodeURIComponent(zone) + '/crossfade/on'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Crossfade on.'); }, function(error) { return Parse.Promise.error('Could not adjust.'); }); }; // Crossfade tracks exports.crossfadeOff = function(zone) { var path = '/' + encodeURIComponent(zone) + '/crossfade/off'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Crossfade off.'); }, function(error) { return Parse.Promise.error('Could not adjust.'); }); }; // Play next track exports.next = function(zone) { var path = '/' + encodeURIComponent(zone) + '/next'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Playing next.'); }, function(error) { return Parse.Promise.error('Could not play next.'); }); }; // Play previous track exports.previous = function(zone) { var path = '/' + encodeURIComponent(zone) + '/previous'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Playing previous.'); }, function(error) { return Parse.Promise.error('Could not play previous.'); }); }; // Get current state exports.state = function(zone) { var path = '/' + encodeURIComponent(zone) + '/state'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ var data = response.data; return Parse.Promise.as(response.data); }, function(error) { return Parse.Promise.error('Could not unmute.'); }); }; // experimental exports.lockVolumes = function() { var path = '/lockvolumes'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Volumes locked.'); }, function(error) { return Parse.Promise.error('Could not lock volumes.'); }); }; exports.unlockVolumes = function() { var path = '/unlockvolumes'; return Parse.Cloud.httpRequest({ url: baseUrl + path }).then(function(response){ return Parse.Promise.as('Volumes unlocked.'); }, function(error) { return Parse.Promise.error('Could not unlock volumes.'); }); }; var prettyPrintZones = function(zones) { var pp = ""; _.each(zones, function(zone) { pp += 'uuid: ' + zone.uuid + '\n'; pp += 'coordinator:\n'; pp += ' uuid: ' + zone.coordinator.uuid + '\n'; pp += ' roomName: ' + zone.coordinator.roomName + '\n'; pp += 'members: \n'; _.each(zone.members, function(member) { pp += ' uuid:' + member.uuid + '\n'; pp += ' roomName:' + member.roomName + '\n'; pp += ' playMode:\n'; pp += ' ├── shuffle:' + member.playMode.shuffle + '\n'; pp += ' ├── repeat:' + member.playMode.repeat + '\n'; pp += ' └── crossfade:' + member.playMode.crossfade + '\n'; pp += ' groupState:\n'; pp += ' ├── volume:' + member.groupState.volume + '\n'; pp += ' └── mute:' + member.groupState.mute + '\n'; pp += ' state:\n'; pp += ' ├── currentTrack:\n'; pp += ' | ├── artist:' + member.state.currentTrack.artist + '\n'; pp += ' | ├── title:' + member.state.currentTrack.title + '\n'; pp += ' | └── album:' + member.state.currentTrack.album + '\n'; pp += ' ├── nextTrack:\n'; pp += ' | ├── artist:' + member.state.nextTrack.artist + '\n'; pp += ' | ├── title:' + member.state.nextTrack.title + '\n'; pp += ' | └── album:' + member.state.nextTrack.album + '\n'; pp += ' ├── volume:' + member.state.volume + '\n'; pp += ' ├── mute:' + member.state.mute + '\n'; pp += ' ├── trackNo:' + member.state.trackNo + '\n'; pp += ' ├── elapsedTime:' + member.state.elapsedTime + '\n'; pp += ' ├── elapsedTimeFormatted:' + member.state.elapsedTimeFormatted + '\n'; pp += ' ├── zoneState:' + member.state.zoneState + '\n'; pp += ' ├── playerState:' + member.state.playerState + '\n'; pp += ' └── zonePlayMode:\n'; pp += ' ├── shuffle:' + member.state.zonePlayMode.shuffle + '\n'; pp += ' ├── repeat:' + member.state.zonePlayMode.repeat + '\n'; pp += ' └── crossfade:' + member.state.zonePlayMode.crossfade + '\n'; }); }); console.log(pp); };