diff --git a/lib/sonos-http-api.js b/lib/sonos-http-api.js index 5bedee51..73b4cbb5 100644 --- a/lib/sonos-http-api.js +++ b/lib/sonos-http-api.js @@ -2,11 +2,15 @@ var http = require('http'); var fs = require('fs'); +var url = require('url'); +var request = require('request'); +var paperboy = require('paperboy'); -function HttpAPI(discovery, port, presets) { +function HttpAPI(discovery, port, presets, ip, webroot) { var lockVolumes = {}; var pauseState = {}; + var saveState = {}; // This is to handle setTimeout function pauseAll() { @@ -47,14 +51,28 @@ function HttpAPI(discovery, port, presets) { // This is faulty. res.end(); return; - } else if (params.length == 2 && ["preset", "pauseall", "resumeall", "reindex"].some(function (i) { return params[0] == i; })) { + } else if (params.length == 2 && ["saveall", "restoreall", "preset", "pauseall", "resumeall", "reindex"].some(function (i) { return params[0] == i; })) { // Handle presets var opt = { action: params[0], value: params[1] }; - } else if (params.length > 1) { + } else if (params.length > 2 && params[1] == "say") { + // Handle say command + var lang = "en" // Optional + if (params.length > 3) lang = params[3]; + var opt = { + room: params[0], + action: params[1], + text: params[2], + lang: lang + }; + } else if (params.length == 2 && params[0] == "tts") { + // Handle tts file request from SONOS + streamFile(req, res); + return; + } else if (params.length > 1) { var opt = { room: params[0], @@ -86,9 +104,108 @@ function HttpAPI(discovery, port, presets) { player.setVolume(lockVolumes[info.uuid]); } + function streamFile(req, res) { + // Deliver requested mp3 file to SONOS + var ip = req.connection.remoteAddress; + paperboy + .deliver(webroot, req, res) + .addHeader('Expires', 30000) + .addHeader('Content-type:', 'audio/mpeg') + .addHeader('X-PaperRoute', 'Node') + .before(function() { + console.log('Received Request from Sonos'); + }) + .after(function(statCode) { + log(statCode, req.url, ip); + }) + .error(function(statCode, msg) { + res.writeHead(statCode, {'Content-Type': 'text/plain'}); + res.end("Error " + statCode); + log(statCode, req.url, ip, msg); + }) + .otherwise(function(err) { + console.log("404 Not Found"); + res.writeHead(404, {'Content-Type': 'text/plain'}); + res.end("Error 404: File not found"); + }) + } + + function log(statCode, url, ip, err) { + var logStr = statCode + ' - ' + url + ' - ' + ip; + if (err) logStr += ' - ' + err; + console.log(logStr); + } + function handleAction(options, callback) { console.log(options) + if (options.action == "say") { + // Use Google tts translation service to create a mp3 file + var tts_request = "http://translate.google.com/translate_tts?ie=UTF-8&q=" + options.text + "&tl=" + options.lang; + + // Construct a filesystem neutral filename + var filename = decodeURIComponent(options.text); + filename = encodeURIComponent(filename); + filename = filename.replace(/[^a-z0-9_\-]/gi, '_').toLowerCase() + '-' + options.lang + '.mp3'; + var filepath = webroot + '/tts/' + filename; + + // If not already downloaded request translation + fs.stat(filepath, function(err, stat){ + if (err){ + console.log("Downloading new tts message file: " + filepath); + request(tts_request, function(error, response, buffer) { + }).pipe(fs.createWriteStream(filepath)); + } else{ + console.log("Using cached tts message file: " + filepath); + } + }) + + var player = discovery.getPlayer(options.room); + + // Use the preset action to play the tts file + var tts_params = { + "players": [ + {"roomName": options.room, "volume": player.getState().volume} + ], + "state": "play", + "uri": "http://" + ip + ":" + port + "/tts/" + filename, + "playMode": "NORMAL" + } + discovery.applyPreset(tts_params); + + callback(); + return; + } + + if (options.action === "saveall") { + // Save the current state + saveState = {}; + discovery.getZones().forEach(function (zone) { + if (zone.coordinator.state.zoneState == "PLAYING") { + var player = discovery.getPlayerByUUID(zone.uuid); + var state = player.getState(); + saveState[zone.uuid] = { + "players": [ + {"roomName": player.roomName, "volume": state.volume} + ], + "state": "play", + "uri": state.currentTrack.uri, + "playMode": "NORMAL" + } + } + }); + callback(); + return; + } + if (options.action === "restoreall") { + for (var uuid in saveState) { + // Use the preset action to restore the saved state + discovery.applyPreset(saveState[uuid]); + } + callback(); + return; + } + if (options.action === "zones") { callback(discovery.getZones()); return; diff --git a/package.json b/package.json index dff60d8b..f1abd699 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ "url": "https://github.com/jishi/node-sonos-http-api.git" }, "dependencies": { - "sonos-discovery": "git+https://github.com/jishi/node-sonos-discovery.git#master" + "sonos-discovery": "git+https://github.com/jishi/node-sonos-discovery.git#master", + "paperboy": "*", + "request": "*" }, "engine": "node 0.8.x", "main": "lib/sonos-http-api.js" diff --git a/server.js b/server.js index 375bbe50..77c35ae6 100644 --- a/server.js +++ b/server.js @@ -3,8 +3,13 @@ var SonosDiscovery = require('sonos-discovery'); var SonosHttpAPI = require('./lib/sonos-http-api.js'); var fs = require('fs'); var discovery = new SonosDiscovery(); -var port = 5005; +var path = require('path'); +var config = require('./config.json'); +var port = config.my_port; +var ip = config.my_ip_address; + +var webroot = path.join(path.dirname(__filename), 'sonos'); var presets = {}; fs.exists('./presets.json', function (exists) { @@ -14,6 +19,6 @@ fs.exists('./presets.json', function (exists) { } else { console.log('no preset file, ignoring...'); } - new SonosHttpAPI(discovery, port, presets); + new SonosHttpAPI(discovery, port, presets, ip, webroot); });