├── logs └── dummy ├── index.js ├── test ├── index.css ├── pretty-json │ ├── css │ │ └── pretty-json.css │ ├── pretty-json-min.js │ └── libs │ │ ├── underscore-min.js │ │ └── backbone-min.js ├── index.js └── index.html ├── del_test.js ├── lib ├── requestHandlers │ ├── lib.request.volumeUp.js │ ├── lib.request.volumeDown.js │ ├── lib.request.getMediaList.js │ ├── lib.request.next.js │ ├── lib.request.prev.js │ ├── lib.request.play.js │ ├── lib.request.stop.js │ ├── lib.request.pause.js │ ├── lib.request.getSleepTimerState.js │ ├── lib.request.getVersion.js │ ├── lib.request.loadUri.js │ ├── lib.request.loadSingle.js │ ├── lib.request.loadLineIn.js │ ├── lib.request.createZone.js │ ├── lib.request.dropFromZone.js │ ├── lib.request.loadContainer.js │ ├── lib.request.loadShuffle.js │ ├── lib.request.getZoneConfig.js │ ├── lib.request.loadPlaylist.js │ ├── lib.request.getDeviceSetting.js │ ├── lib.request.togglePlayPause.js │ ├── lib.request.setDeviceSetting.js │ ├── lib.request.seekToTrack.js │ ├── lib.request.addToZone.js │ ├── lib.request.unMute.js │ ├── lib.request.sleepTimer.js │ ├── lib.request.setPlayMode.js │ ├── lib.request.mute.js │ ├── lib.request.AddCurrentItemToPlaylist.js │ ├── lib.request.seek.js │ ├── lib.request.leaveStandby.js │ ├── lib.request.enterAutomaticStandby.js │ ├── lib.request.enterManualStandby.js │ ├── lib.request.toggleMute.js │ ├── lib.request.fadeToVolume.js │ ├── lib.request.setVolume.js │ └── lib.request.getRendererState.js ├── lib.base.requestLongPolling.js ├── lib.base.request.js ├── lib.base.requestMediaRenderer.js └── lib.raumserver.js ├── .gitignore ├── package.json ├── config └── default.json ├── .vscode └── launch.json ├── raumserver.js └── README.md /logs/dummy: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = './lib/'; 4 | 5 | module.exports = require(path + "lib.raumserver"); 6 | -------------------------------------------------------------------------------- /test/index.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | /*font-size: 100%;*/ 4 | font-family: 'Quicksand', sans-serif; 5 | } 6 | 7 | input { 8 | border: 1px solid black; 9 | padding: 3px; 10 | } -------------------------------------------------------------------------------- /del_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Raumserver = require('./lib/lib.raumserver'); 3 | 4 | var raumserver = new Raumserver(); 5 | 6 | raumserver.createLogger(4); 7 | raumserver.init(); 8 | 9 | 10 | function execute(){ 11 | 12 | } 13 | 14 | setInterval(execute,1000); -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.volumeUp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestSetVolume = require('./lib.request.setVolume'); 3 | 4 | module.exports = class Request_VolumeUp extends RequestSetVolume 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | useRelativeValue() 13 | { 14 | return true; 15 | } 16 | 17 | 18 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.volumeDown.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestSetVolume = require('./lib.request.setVolume'); 3 | 4 | module.exports = class Request_VolumeDown extends RequestSetVolume 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | useRelativeValue() 13 | { 14 | return true; 15 | } 16 | 17 | 18 | volumeMultiplier() 19 | { 20 | return -1; 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.getMediaList.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Request = require('../lib.base.request'); 3 | 4 | module.exports = class Request_GetMediaList extends Request 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | runAction(_resolve, _reject) 13 | { 14 | var containerId = this.getQueryValue("id", "0") 15 | this.raumkernel.managerDisposer.mediaListManager.getMediaList(containerId, containerId, "", true, true).then(function(_data){ 16 | _resolve(_data); 17 | }).catch(function(_data){ 18 | _reject(_data); 19 | }); 20 | } 21 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.next.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_Next extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | _mediaRendererVirtual.next().then(function(_data){ 21 | _resolve(_data); 22 | }).catch(function(_data){ 23 | _reject(_data); 24 | }); 25 | } 26 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.prev.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_Prev extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | _mediaRendererVirtual.prev().then(function(_data){ 21 | _resolve(_data); 22 | }).catch(function(_data){ 23 | _reject(_data); 24 | }); 25 | } 26 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.play.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_Play extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | _mediaRendererVirtual.play(this.waitTillConfirmed()).then(function(_data){ 21 | _resolve(_data); 22 | }).catch(function(_data){ 23 | _reject(_data); 24 | }); 25 | } 26 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.stop.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_Stop extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | _mediaRendererVirtual.stop(this.waitTillConfirmed()).then(function(_data){ 21 | _resolve(_data); 22 | }).catch(function(_data){ 23 | _reject(_data); 24 | }); 25 | } 26 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.pause.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_Pause extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | _mediaRendererVirtual.pause(this.waitTillConfirmed()).then(function(_data){ 21 | _resolve(_data); 22 | }).catch(function(_data){ 23 | _reject(_data); 24 | }); 25 | } 26 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | npm-debug.log* 4 | 5 | #binaries 6 | binaries 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # nyc test coverage 20 | .nyc_output 21 | 22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # node-waf configuration 26 | .lock-wscript 27 | 28 | # Compiled binary addons (http://nodejs.org/api/addons.html) 29 | build/Release 30 | 31 | # Dependency directories 32 | node_modules 33 | jspm_packages 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Optional REPL history 39 | .node_repl_history 40 | -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.getSleepTimerState.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_GetSleepTimerState extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | _mediaRendererVirtual.getSleepTimerState().then(function(_data){ 21 | _resolve(_data); 22 | }).catch(function(_data){ 23 | _reject(_data); 24 | }); 25 | } 26 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.getVersion.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Raumkernel = require('node-raumkernel'); 3 | var PackageJSON = require("../../package.json") 4 | var Request = require('../lib.base.request'); 5 | 6 | module.exports = class Request_GetVersion extends Request 7 | { 8 | constructor() 9 | { 10 | super(); 11 | } 12 | 13 | runAction(_resolve, _reject) 14 | { 15 | try 16 | { 17 | _resolve({ 18 | "raumkernelLib" : Raumkernel.PackageJSON.version, 19 | "raumserverLib" : PackageJSON.version 20 | }); 21 | } 22 | catch(_exception) 23 | { 24 | this.logError(_exception); 25 | _reject(_excpetion); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.loadUri.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_LoadUri extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | var value = this.getQueryValue("value", ""); 21 | 22 | _mediaRendererVirtual.loadUri(value, false, this.waitTillConfirmed()).then(function(_data){ 23 | _resolve(_data); 24 | }).catch(function(_data){ 25 | _reject(_data); 26 | }); 27 | } 28 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.loadSingle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_LoadSingle extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | var value = this.getQueryValue("value", ""); 21 | 22 | _mediaRendererVirtual.loadSingle(value, "", false, this.waitTillConfirmed()).then(function(_data){ 23 | _resolve(_data); 24 | }).catch(function(_data){ 25 | _reject(_data); 26 | }); 27 | } 28 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "engines": { 3 | "node": ">=7.6.0" 4 | }, 5 | "name": "node-raumserver", 6 | "version": "0.1.9", 7 | "description": "Server to control the Raumfeld multiroom system via requests", 8 | "main": "index.js", 9 | "scripts": { 10 | "start": "node raumserver.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/ChriD/node-raumserver.git" 15 | }, 16 | "keywords": [ 17 | "raumserver", 18 | "raumkernel", 19 | "raumfeld" 20 | ], 21 | "dependencies": { 22 | "config": "^1.31.0", 23 | "node-raumkernel": "^1.2.22" 24 | }, 25 | "author": "Christian Dürnberger", 26 | "bugs": { 27 | "url": "https://github.com/ChriD/node-raumserver/issues" 28 | }, 29 | "homepage": "https://github.com/ChriD/node-raumserver#readme" 30 | } 31 | -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "raumserver": { 3 | "port" : 8080, 4 | "loglevel" : 2, // 0=error, 1=warnings, 2=info, 3=verbose, 4=debug, 5=silly 5 | "logpath" : "./logs" 6 | }, 7 | "raumfeld": { 8 | "raumfeldHost" : "0.0.0.0", // 0.0.0.0 = autodetect 9 | "raumfeldHostRequestPort" : 47365, 10 | "raumfeldManufacturerId" : ["Raumfeld GmbH", "Lautsprecher Teufel GmbH"], 11 | "raumfeldVirtualMediaPlayerModelDescription" : "Virtual Media Player", 12 | "alivePingerIntervall" : 2500, 13 | "ssdpDiscovertimeout" : 5000, 14 | "bonjourDiscoverTimeout" : 3000, 15 | "uriMetaDataTemplateFile" : "lib/setUriMetadata.template", 16 | "rendererStateTriggerConfirmationTimout": 3500, 17 | "zoneTriggerConfirmationTimout": 6000 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.loadLineIn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_LoadLineIn extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | var value = this.getQueryValue("room", ""); 21 | if (value == "") 22 | value = this.getQueryValue("value", ""); 23 | 24 | _mediaRendererVirtual.loadLineIn(value, this.waitTillConfirmed()).then(function(_data){ 25 | _resolve(_data); 26 | }).catch(function(_data){ 27 | _reject(_data); 28 | }); 29 | } 30 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.createZone.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_CreateZone extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForVirtualRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | isRoomScope() 19 | { 20 | return true; 21 | } 22 | 23 | 24 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 25 | { 26 | this.managerDisposer.zoneManager.connectRoomToZone(_roomUdn, "", this.waitTillConfirmed()).then(function(_data){ 27 | _resolve(_data); 28 | }).catch(function(_data){ 29 | _reject(_data); 30 | }); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.dropFromZone.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_DropFromZone extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForVirtualRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | isRoomScope() 19 | { 20 | return true; 21 | } 22 | 23 | 24 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 25 | { 26 | this.managerDisposer.zoneManager.dropRoomFromZone(_roomUdn, this.waitTillConfirmed()).then(function(_data){ 27 | _resolve(_data); 28 | }).catch(function(_data){ 29 | _reject(_data); 30 | }); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.loadContainer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_LoadContainer extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | var value = this.getQueryValue("value", ""); 21 | var trackNumber = parseInt(this.getQueryValue("trackNumber", 1)); 22 | 23 | _mediaRendererVirtual.loadContainer(value, "", trackNumber, false, this.waitTillConfirmed()).then(function(_data){ 24 | _resolve(_data); 25 | }).catch(function(_data){ 26 | _reject(_data); 27 | }); 28 | } 29 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.loadShuffle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_LoadShuffle extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | var value = this.getQueryValue("value", ""); 21 | var selection = this.getQueryValue("selection", ""); 22 | 23 | _mediaRendererVirtual.loadShuffle(value, selection, false, this.waitTillConfirmed()).then(function(_data){ 24 | _resolve(_data); 25 | }).catch(function(_data){ 26 | _reject(_data); 27 | }); 28 | } 29 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "type": "node", 10 | "request": "launch", 11 | "name": "Programm starten", 12 | "program": "${workspaceRoot}\\raumserver.js" 13 | }, 14 | { 15 | "type": "node", 16 | "request": "launch", 17 | "name": "Debug on mac", 18 | "program": "${workspaceRoot}/raumserver.js" 19 | }, 20 | { 21 | "type": "node", 22 | "request": "attach", 23 | "name": "An den Prozess anfügen", 24 | "address": "localhost", 25 | "port": 5858 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.getZoneConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestLongPolling = require('../lib.base.requestLongPolling'); 3 | 4 | module.exports = class Request_GetZoneConfig extends RequestLongPolling 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | runAction(_resolve, _reject) 12 | { 13 | try 14 | { 15 | // TODO: we have to convert the zone configuration object to a readable json format. In fact it is already readable but is should 16 | // be a little bit nicer and maybe compatible with the json return of the C++ version of the raumserver 17 | _resolve(this.managerDisposer.zoneManager.zoneConfiguration); 18 | } 19 | catch(_exception) 20 | { 21 | this.logError(_exception); 22 | _reject(_exception); 23 | } 24 | } 25 | 26 | getCurrentUpdateId() 27 | { 28 | return this.managerDisposer.zoneManager.lastUpdateId; 29 | } 30 | } -------------------------------------------------------------------------------- /test/pretty-json/css/pretty-json.css: -------------------------------------------------------------------------------- 1 | /* common */ 2 | .mark-water{ 3 | color:#bbb; 4 | } 5 | /* eof common */ 6 | 7 | /* node */ 8 | .node-content-wrapper{ 9 | font-family: 'Quicksand', sans-serif; 10 | background-color:#fff; 11 | } 12 | .node-content-wrapper ul{ 13 | border-left:1px dotted #ccc; 14 | list-style:none; 15 | padding-left:25px; 16 | margin:0px; 17 | } 18 | .node-content-wrapper ul li{ 19 | list-style:none; 20 | border-bottom:0; 21 | padding-bottom:0 22 | } 23 | .node-hgl-path{ 24 | background-color:#fefbdf; 25 | } 26 | .node-bracket{ 27 | font-weight:bold; 28 | display:inline-block; 29 | cursor:pointer; 30 | } 31 | .node-bracket:hover{ 32 | color:#999; 33 | } 34 | /* eof node */ 35 | 36 | /* leaf */ 37 | .leaft-container{ 38 | width:100%; 39 | max-width:300px; 40 | height:100%; 41 | } 42 | 43 | .title{ color:#ccc;} 44 | .string{ color:#080;} 45 | .number{ color:#ccaa00;} 46 | .boolean{ color:#1979d3;} 47 | .date{ color:#aa6655;} 48 | .null{ color:#ff5050;} 49 | /* eof leaf */ -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.loadPlaylist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_LoadPlaylist extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | var value = this.getQueryValue("value", ""); 21 | var trackNumber = parseInt(this.getQueryValue("trackNumber", 1)); 22 | 23 | if(!value) 24 | { 25 | this.logError("'value' parameter has to be set"); 26 | _reject(new Error("'value' parameter has to be set")); 27 | return; 28 | } 29 | 30 | _mediaRendererVirtual.loadPlaylist(value, trackNumber, this.waitTillConfirmed()).then(function(_data){ 31 | _resolve(_data); 32 | }).catch(function(_data){ 33 | _reject(_data); 34 | }); 35 | } 36 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.getDeviceSetting.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_GetDeviceSetting extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return true; 15 | } 16 | 17 | isAllowedForVirtualRenderer() 18 | { 19 | return false; 20 | } 21 | 22 | isRoomScope() 23 | { 24 | return true; 25 | } 26 | 27 | 28 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 29 | { 30 | if(!_mediaRendererRoom) 31 | _reject(new Error("This request needs a room identifier!")); 32 | 33 | var key = this.getQueryValue("key", ""); 34 | 35 | if(!key) 36 | _reject(new Error("This request needs a key option")); 37 | 38 | _mediaRendererRoom.getDeviceSetting(key).then(function(_data){ 39 | _resolve(_data); 40 | }).catch(function(_data){ 41 | _reject(_data); 42 | }); 43 | } 44 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.togglePlayPause.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_TogglePlayPause extends RequestMediaRenderer { 5 | constructor() { 6 | super(); 7 | } 8 | 9 | 10 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) { 11 | var self = this; 12 | var mute = 0; 13 | 14 | if (_mediaRendererVirtual && _mediaRendererVirtual.rendererState) { 15 | if (_mediaRendererVirtual.rendererState.TransportState == "PLAYING") { 16 | _mediaRendererVirtual.pause(this.waitTillConfirmed()).then(function (_data) { 17 | _resolve(_data); 18 | }).catch(function (_data) { 19 | _reject(_data); 20 | }); 21 | } 22 | else { 23 | _mediaRendererVirtual.play(this.waitTillConfirmed()).then(function (_data) { 24 | _resolve(_data); 25 | }).catch(function (_data) { 26 | _reject(_data); 27 | }); 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.setDeviceSetting.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_SetDeviceSetting extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return true; 15 | } 16 | 17 | isAllowedForVirtualRenderer() 18 | { 19 | return false; 20 | } 21 | 22 | isRoomScope() 23 | { 24 | return true; 25 | } 26 | 27 | 28 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 29 | { 30 | if(!_mediaRendererRoom) 31 | _reject(new Error("This request needs a room identifier!")); 32 | 33 | var key = this.getQueryValue("key", ""); 34 | var value = this.getQueryValue("value", ""); 35 | 36 | if(!key || !value) 37 | _reject(new Error("This request needs a key and a value option")); 38 | 39 | _mediaRendererRoom.setDeviceSetting(key, value).then(function(_data){ 40 | _resolve(_data); 41 | }).catch(function(_data){ 42 | _reject(_data); 43 | }); 44 | } 45 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.seekToTrack.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_SeekToTrack extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | var trackIndex = parseInt(this.getQueryValue("trackIndex" , -1)); 21 | var trackNumber = parseInt(this.getQueryValue("trackNumber" , -1)); 22 | 23 | if(trackNumber < 1) 24 | trackNumber = trackIndex + 1; 25 | 26 | if(trackIndex < 0 && trackNumber < 1) 27 | { 28 | this.logError("'trackIndex' or 'trackNumber' parameter not ok"); 29 | _reject("'trackIndex' or 'trackNumber' parameter not ok"); 30 | return; 31 | } 32 | 33 | _mediaRendererVirtual.seek("TRACK_NR", trackNumber).then(function(_data){ 34 | _resolve(_data); 35 | }).catch(function(_data){ 36 | _reject(_data); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.addToZone.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_AddToZone extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForVirtualRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | isRoomScope() 19 | { 20 | return true; 21 | } 22 | 23 | 24 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 25 | { 26 | var zoneId = this.getQueryValue("zoneId"); 27 | var _mediaRendererZone = this.managerDisposer.deviceManager.getVirtualMediaRenderer(zoneId); 28 | 29 | if(_mediaRendererZone) 30 | { 31 | this.managerDisposer.zoneManager.connectRoomToZone(_roomUdn, _mediaRendererZone.udn(), this.waitTillConfirmed()).then(function(_data){ 32 | _resolve(_data); 33 | }).catch(function(_data){ 34 | _reject(_data); 35 | }); 36 | } 37 | else 38 | { 39 | _reject(new Error("Zone for zoneId '" + zoneId + "' not found!")); 40 | } 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.unMute.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_UnMute extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 13 | { 14 | if(_mediaRendererVirtual && _roomUdn) 15 | { 16 | _mediaRendererVirtual.setRoomMute(_roomUdn, 0).then(function(_data){ 17 | _resolve(_data); 18 | }).catch(function(_data){ 19 | _reject(_data); 20 | }); 21 | } 22 | else if (_mediaRendererVirtual) 23 | { 24 | _mediaRendererVirtual.setMute(0).then(function(_data){ 25 | _resolve(_data); 26 | }).catch(function(_data){ 27 | _reject(_data); 28 | }); 29 | } 30 | else 31 | { 32 | _mediaRendererRoom.setMute(0).then(function(_data){ 33 | _resolve(_data); 34 | }).catch(function(_data){ 35 | _reject(_data); 36 | }); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.sleepTimer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_SleepTimer extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | var secondsUntilSleep = parseInt(this.getQueryValue("secondsUntilSleep", 0)); 21 | var secondsForVolumeRamp = parseInt(this.getQueryValue("secondsForVolumeRamp", 0)); 22 | 23 | if(secondsUntilSleep > 0) 24 | { 25 | _mediaRendererVirtual.startSleepTimer(secondsUntilSleep, secondsForVolumeRamp).then(function(_data){ 26 | _resolve(_data); 27 | }).catch(function(_data){ 28 | _reject(_data); 29 | }); 30 | } 31 | else 32 | { 33 | _mediaRendererVirtual.cancelSleepTimer().then(function(_data){ 34 | _resolve(_data); 35 | }).catch(function(_data){ 36 | _reject(_data); 37 | }); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.setPlayMode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_SetPlayMode extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | var playMode = this.getQueryValue("mode" , ""); 21 | 22 | if( playMode != "NORMAL" && 23 | playMode != "SHUFFLE" && 24 | playMode != "REPEAT_ONE" && 25 | playMode != "REPEAT_ALL" && 26 | playMode != "DIRECT_1" && 27 | playMode != "RANDOM" ) 28 | { 29 | this.logError("playMode '" + playMode + "' not valid"); 30 | _reject("playMode '" + playMode + "' not valid"); 31 | return; 32 | 33 | } 34 | 35 | _mediaRendererVirtual.setPlayMode(playMode, this.waitTillConfirmed()).then(function(_data){ 36 | _resolve(_data); 37 | }).catch(function(_data){ 38 | _reject(_data); 39 | }); 40 | } 41 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.mute.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_Mute extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 13 | { 14 | var value = parseInt(this.getQueryValue("value", 1)); 15 | 16 | if(_mediaRendererVirtual && _roomUdn) 17 | { 18 | _mediaRendererVirtual.setRoomMute(_roomUdn, value).then(function(_data){ 19 | _resolve(_data); 20 | }).catch(function(_data){ 21 | _reject(_data); 22 | }); 23 | } 24 | else if(_mediaRendererVirtual) 25 | { 26 | _mediaRendererVirtual.setMute(value, this.waitTillConfirmed()).then(function(_data){ 27 | _resolve(_data); 28 | }).catch(function(_data){ 29 | _reject(_data); 30 | }); 31 | } 32 | else 33 | { 34 | _mediaRendererRoom.setMute(value, this.waitTillConfirmed()).then(function(_data){ 35 | _resolve(_data); 36 | }).catch(function(_data){ 37 | _reject(_data); 38 | }); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /raumserver.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Config = require('config'); 3 | var Raumserver = require('./lib/lib.raumserver'); 4 | 5 | var raumserver = new Raumserver(); 6 | 7 | setConfiguration("raumserver", "port"); 8 | setConfiguration("raumserver", "loglevel"); 9 | setConfiguration("raumfeld", "raumfeldHost"); 10 | setConfiguration("raumfeld", "raumfeldHostRequestPort"); 11 | setConfiguration("raumfeld", "raumfeldManufacturerId"); 12 | setConfiguration("raumfeld", "raumfeldVirtualMediaPlayerModelDescription"); 13 | setConfiguration("raumfeld", "alivePingerIntervall"); 14 | setConfiguration("raumfeld", "ssdpDiscovertimeout"); 15 | setConfiguration("raumfeld", "bonjourDiscoverTimeout"); 16 | setConfiguration("raumfeld", "uriMetaDataTemplateFile"); 17 | setConfiguration("raumfeld", "rendererStateTriggerConfirmationTimout"); 18 | setConfiguration("raumfeld", "zoneTriggerConfirmationTimout"); 19 | 20 | 21 | //raumserver.createLogger(4); 22 | raumserver.init(); 23 | 24 | 25 | function setConfiguration(_object, _key) { 26 | try 27 | { 28 | if(_object == "raumserver") 29 | raumserver.settings[_key] = Config.get(_object + '.' + _key); 30 | else 31 | raumserver.raumkernel.settings[_key] = Config.get(_object + '.' + _key); 32 | } 33 | catch(_exception) 34 | { 35 | raumserver.logError("Configuration key '" + _object + "." + _key + "' not set! Using default!", _exception); 36 | } 37 | } 38 | 39 | function execute() { 40 | } 41 | 42 | setInterval(execute,1000); 43 | 44 | -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.AddCurrentItemToPlaylist.js: -------------------------------------------------------------------------------- 1 | // contribution by davie2000 2 | // https://github.com/davie2000 3 | 4 | 'use strict'; 5 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 6 | 7 | module.exports = class Request_addCurrentItemToPlaylist extends RequestMediaRenderer 8 | { 9 | constructor() 10 | { 11 | super(); 12 | } 13 | 14 | 15 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 16 | { 17 | var _playlistName = this.getQueryValue("playlist"); 18 | // INFO: The 'refId' may not only ba a playing track. In some cases it might be a container 19 | // But for the purpose of this request it seems good ebough to assume that its a direkt song link 20 | var _mediaItemId = _mediaRendererVirtual.currentMediaItemData.refID; // actual playing track 21 | 22 | if(!_playlistName) 23 | { 24 | this.logError("'playlist' parameter has to be set"); 25 | _reject(new Error("'playlist' parameter has to be set")); 26 | return; 27 | } 28 | 29 | if(_mediaItemId) 30 | { 31 | this.raumkernel.nativePlaylistController.addItemToPlaylist(_playlistName, _mediaItemId).then(function(_data){ 32 | _resolve(_data); 33 | }).catch(function(_data){ 34 | _reject(_data); 35 | }); 36 | } 37 | else 38 | { 39 | this.logError("Currently playing track is not available!"); 40 | _reject(new Error("Currently playing track is not available!")); 41 | } 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-raumserver 2 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/ChriD/) 3 | [![npm:?](https://img.shields.io/npm/v/node-raumserver.svg?style=flat-square)](https://www.npmjs.com/packages/node-raumserver) 4 | [![dependencies:?](https://img.shields.io/npm/dm/node-raumserver.svg?style=flat-square)](https://www.npmjs.com/packages/node-raumserver) 5 | 6 | [![NPM](https://nodei.co/npm/node-raumserver.png?downloads=true&downloadRank=true)](https://nodei.co/npm/node-raumserver/) 7 | 8 | A nodejs module to control the raumfeld multiroomsystem via HTTP-Requests 9 | **Please check the [wiki](https://github.com/ChriD/node-raumserver/wiki) for install information and how to use the server** 10 | 11 | **Another wiki can be found here [gahujipo-wiki](https://github.com/gahujipo/node-raumserver-wiki). Thanks to gahujipo for creating this!** 12 | 13 | 14 | Installation 15 | ------------- 16 | 17 | ### Via NPM 18 | create a folder with a name of your choice 19 | open command line\console in the folder and do following 20 | ``` 21 | npm install node-raumserver 22 | cd node_modules/node-raumserver 23 | npm start 24 | ``` 25 | 26 | ### Via Docker image 27 | Search for **docker-raumserver** and install it. 28 | You have to use `net=--host` for the container, otherwise the raumserver will not find any devices. 29 | Docker raumserver will reside on **port 8585** 30 | **INFO: Docker image is currently outdated!** 31 | 32 | Changelog 33 | ------------- 34 | Changelog can be found [here](https://github.com/ChriD/node-raumserver/releases) 35 | 36 | 37 | Requirements 38 | ------------- 39 | Please use with node version 7.x or above 40 | For Version lower than 7.6.0 the --harmony-async-await parameter has to be used 41 | -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.seek.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_Seek extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | isAllowedForRoomRenderer() 13 | { 14 | return false; 15 | } 16 | 17 | 18 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 19 | { 20 | var value = parseInt(this.getQueryValue("value" , -1)); 21 | var relative = parseInt(this.getQueryValue("relative" , 0)); 22 | var seekType = "ABS_TIME"; 23 | 24 | if(relative) 25 | seekType = "REL_TIME"; 26 | 27 | // Convert seconds to "HH:MM:SS" 28 | this.logDebug("Seek " + seekType + " to: " + this.secondsToHHMMSS(value)); 29 | _mediaRendererVirtual.seek(seekType, this.secondsToHHMMSS(value)).then(function(_data){ 30 | _resolve(_data); 31 | }).catch(function(_data){ 32 | _reject(_data); 33 | }); 34 | } 35 | 36 | 37 | secondsToHHMMSS(_seconds) 38 | { 39 | var hours = Math.floor(_seconds / 3600); 40 | var minutes = Math.floor((_seconds - (hours * 3600)) / 60); 41 | var seconds = _seconds - (hours * 3600) - (minutes * 60); 42 | 43 | // round seconds 44 | seconds = Math.round(seconds * 100) / 100 45 | 46 | var result = (hours < 10 ? "0" + hours : hours); 47 | result += ":" + (minutes < 10 ? "0" + minutes : minutes); 48 | result += ":" + (seconds < 10 ? "0" + seconds : seconds); 49 | return result; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.leaveStandby.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_LeaveStandby extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | async runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 13 | { 14 | if(_mediaRendererRoom) 15 | { 16 | _mediaRendererRoom.leaveStandby().then(function(_data){ 17 | _resolve(_data); 18 | }).catch(function(_data){ 19 | _reject(_data); 20 | }); 21 | } 22 | else 23 | { 24 | var resultSum = {}; 25 | var rendererUdns = _mediaRendererVirtual.getRoomRendererUDNs(); 26 | for(var rendererIdx=0; rendererIdx 0) 53 | _action += "&confirm=1" 54 | else 55 | _action += "?confirm=1" 56 | } 57 | 58 | showJSONResult({ "val" : "Waiting for response..."}); 59 | setUrls(); 60 | $.ajax({ 61 | url: raumserverRequestController + _action, 62 | cache: false, 63 | async: false, 64 | success: function(res, status, xhr) 65 | { 66 | showJSONResult( res ); 67 | }, 68 | error: function(xhr, textStatus, errorThrown) 69 | { 70 | showJSONResult({ "error" : errorThrown.message }); 71 | } 72 | }); 73 | } 74 | 75 | 76 | function showJSONResult(_result) 77 | { 78 | var node = new PrettyJSON.view.Node({ 79 | el:$('#resultData'), 80 | data:_result 81 | }); 82 | node.expandAll(); 83 | } -------------------------------------------------------------------------------- /lib/lib.base.requestLongPolling.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Request = require('./lib.base.request'); 3 | 4 | module.exports = class RequestLongPolling extends Request 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | run() 12 | { 13 | var self = this; 14 | var updateIdQuery = this.getQueryValue("updateId"); 15 | return new Promise(function(_resolve, _reject){ 16 | // if there is no update id we can directly call the request run action 17 | if(!updateIdQuery || !self.getCurrentUpdateId()) 18 | { 19 | self.logDebug("No updateId given for action '" + self.action + "'. Using direct execution"); 20 | if(self.getCurrentUpdateId()) 21 | self.addReturnHeaders("updateId", self.getCurrentUpdateId()); 22 | self.runAction(_resolve, _reject); 23 | } 24 | // otherwise we have to wait until the internal update id has changed 25 | else 26 | { 27 | self.logDebug("No data has changed for long polling action '" + self.action + "'. Waiting for change"); 28 | self.checkForChangedUpdateId(updateIdQuery).then(function(){ 29 | if(self.getCurrentUpdateId()) 30 | self.addReturnHeaders("updateId", self.getCurrentUpdateId()); 31 | self.runAction(_resolve, _reject); 32 | }).catch(function(_data){ 33 | self.logError("Long Polling request for action '" + self.action + "' was rejected"); 34 | _reject(_data); 35 | }) 36 | } 37 | }); 38 | } 39 | 40 | 41 | checkForChangedUpdateId(_updateIdQuery) 42 | { 43 | var self = this; 44 | return new Promise(function(_resolve, _reject){ 45 | self.logDebug("Set interval for checking updateId on request action " + self.action); 46 | var intervalId = setInterval(function() { 47 | try 48 | { 49 | if(self.getCurrentUpdateId() != _updateIdQuery) 50 | { 51 | self.logDebug("Clear interval for checking updateId on request action " + self.action); 52 | clearInterval(intervalId); 53 | _resolve({}); 54 | } 55 | } 56 | catch(_exception) 57 | { 58 | _reject(_exception); 59 | } 60 | },150); 61 | }); 62 | } 63 | 64 | 65 | /** 66 | * this method should return the current updateId for that request 67 | * @return {String} the current update id for that request 68 | */ 69 | getCurrentUpdateId() 70 | { 71 | return ""; 72 | } 73 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.fadeToVolume.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_FadeToVolume extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | useRelativeValue() 13 | { 14 | var relative = parseInt(this.getQueryValue("relative" , 0)); 15 | if(relative) 16 | return true; 17 | return false; 18 | } 19 | 20 | 21 | volumeMultiplier() 22 | { 23 | return 1; 24 | } 25 | 26 | 27 | runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 28 | { 29 | var self = this; 30 | var value = parseInt(this.getQueryValue("value", 0)); 31 | var duration = parseInt(this.getQueryValue("duration", 0)); 32 | 33 | if(_mediaRendererVirtual && _roomUdn) 34 | { 35 | if(this.useRelativeValue()) 36 | { 37 | _mediaRendererVirtual.getRoomVolume(_roomUdn).then(function(_volumeNow){ 38 | _mediaRendererVirtual.fadeToVolumeRoom(_roomUdn, parseInt(_volumeNow) + (parseInt(value) * self.volumeMultiplier())).then(function(_data){ 39 | _resolve(_data); 40 | }).catch(function(_data){ 41 | _reject(_data); 42 | }); 43 | }).catch(function(_data){ 44 | _reject(_data); 45 | }); 46 | } 47 | else 48 | { 49 | _mediaRendererVirtual.fadeToVolumeRoom(_roomUdn, value * self.volumeMultiplier(), duration).then(function(_data){ 50 | _resolve(_data); 51 | }).catch(function(_data){ 52 | _reject(_data); 53 | }); 54 | } 55 | } 56 | else if(_mediaRendererVirtual) 57 | { 58 | if(this.useRelativeValue()) 59 | { 60 | _mediaRendererVirtual.getVolume().then(function(_volumeNow){ 61 | _mediaRendererVirtual.fadeToVolume(parseInt(_volumeNow) + (parseInt(value) * self.volumeMultiplier())).then(function(_data){ 62 | _resolve(_data); 63 | }).catch(function(_data){ 64 | _reject(_data); 65 | }); 66 | }).catch(function(data){ 67 | _reject(_data); 68 | }); 69 | } 70 | else 71 | { 72 | _mediaRendererVirtual.fadeToVolume(value * self.volumeMultiplier(), duration).then(function(_data){ 73 | _resolve(_data); 74 | }).catch(function(_data){ 75 | _reject(_data); 76 | }); 77 | } 78 | } 79 | else 80 | { 81 | if(this.useRelativeValue()) 82 | { 83 | _mediaRendererRoom.getVolume().then(function(_volumeNow){ 84 | _mediaRendererRoom.fadeToVolume(parseInt(_volumeNow) + (parseInt(value) * self.volumeMultiplier())).then(function(_data){ 85 | _resolve(_data); 86 | }).catch(function(_data){ 87 | _reject(_data); 88 | }); 89 | }).catch(function(data){ 90 | _reject(_data); 91 | }); 92 | } 93 | else 94 | { 95 | _mediaRendererRoom.fadeToVolume(value * self.volumeMultiplier(), duration).then(function(_data){ 96 | _resolve(_data); 97 | }).catch(function(_data){ 98 | _reject(_data); 99 | }); 100 | } 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /lib/lib.base.request.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Raumkernel = require('node-raumkernel'); 3 | 4 | module.exports = class Request extends Raumkernel.BaseManager 5 | { 6 | constructor() 7 | { 8 | super(); 9 | this.url = ""; 10 | this.action = ""; 11 | this.queryObject = {}; 12 | this.queryObjectLowerKeys = {}; 13 | this.returnHeaders = new Map(); 14 | this.raumkernel = null; 15 | } 16 | 17 | static newFromAction(_action) 18 | { 19 | var request = null; 20 | try 21 | { 22 | var actionClass = require('./requestHandlers/lib.request.' + _action); 23 | return new actionClass(); 24 | } 25 | catch(_exception) 26 | { 27 | // parent will handle exception, no silent exception here anymore! 28 | throw _exception 29 | } 30 | return null; 31 | } 32 | 33 | additionalLogIdentifier() 34 | { 35 | return "Request." + this.action; 36 | } 37 | 38 | 39 | parmUrl(_url = this.url) 40 | { 41 | this.url = _url; 42 | return this.url; 43 | } 44 | 45 | 46 | parmAction(_action = this.action) 47 | { 48 | //this.action = _action.toLowerCase(); 49 | this.action = _action; 50 | return this.action; 51 | } 52 | 53 | parmQueryObject(_queryObject = this.queryObject) 54 | { 55 | // convert keys to lower case for nicer handling 56 | var key, keys = Object.keys(_queryObject); 57 | var n = keys.length; 58 | this.queryObjectLowerKeys = {}; 59 | while (n--) { 60 | key = keys[n]; 61 | this.queryObjectLowerKeys[key.toLowerCase()] = _queryObject[key]; 62 | } 63 | // store original query too 64 | this.queryObject = _queryObject; 65 | return this.queryObject; 66 | } 67 | 68 | 69 | parmRaumkernel(_raumkernel = this.raumkernel) 70 | { 71 | this.raumkernel = _raumkernel; 72 | return this.raumkernel; 73 | } 74 | 75 | 76 | addReturnHeaders(_key, _value) 77 | { 78 | this.logDebug("Adding response header for request '" + this.action + "' -> " + _key + " : " + _value); 79 | this.returnHeaders[_key] = _value; 80 | } 81 | 82 | 83 | getQueryValue(_key, _noIdReturn = "") 84 | { 85 | if(this.queryObjectLowerKeys[_key.toLowerCase()]) 86 | return this.queryObjectLowerKeys[_key.toLowerCase()]; 87 | return _noIdReturn; 88 | } 89 | 90 | 91 | init() 92 | { 93 | } 94 | 95 | 96 | run() 97 | { 98 | var self = this; 99 | return new Promise(function(_resolve, _reject){ 100 | try 101 | { 102 | self.runAction(_resolve, _reject); 103 | } 104 | catch(_exception) 105 | { 106 | _reject(_exception); 107 | } 108 | }) 109 | } 110 | 111 | 112 | runAction(_resolve, _reject) 113 | { 114 | } 115 | 116 | 117 | getDevicesFromId(_id) 118 | { 119 | this.getDevicesFromIdAndScope(_id); 120 | } 121 | 122 | 123 | /** 124 | * returns a map with mediaRederers 125 | * @param {String} udn or room name 126 | * @param {String} the scope. That means which device we want to get (zone device or sub) 127 | * @return {Map} a map with key, value whereby the key is the udn and the value is the device 128 | */ 129 | getMediaRenderersFromIdAndScope(_id, _scope = "zone") 130 | { 131 | // first check if there is an id, if there is no id we should return all devices 132 | // in this case the scope does not matter because for that case we only want to 133 | // have the virtual renderers 134 | if(!_id) 135 | { 136 | return this.managerDisposer.deviceManager.mediaRenderersVirtual; 137 | } 138 | else 139 | { 140 | var mediaRenderer; 141 | if(_scope == "zone") 142 | mediaRenderer = this.managerDisposer.deviceManager.getVirtualMediaRenderer(_id); 143 | else 144 | mediaRenderer = this.managerDisposer.deviceManager.getMediaRenderer(_id); 145 | 146 | var ret = new Map(); 147 | if(mediaRenderer) 148 | ret.set(mediaRenderer.udn(), mediaRenderer); 149 | 150 | return ret; 151 | } 152 | } 153 | 154 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.setVolume.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestMediaRenderer = require('../lib.base.requestMediaRenderer'); 3 | 4 | module.exports = class Request_SetVolume extends RequestMediaRenderer 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | useRelativeValue() 13 | { 14 | var relative = parseInt(this.getQueryValue("relative" , 0)); 15 | if(relative) 16 | return true; 17 | return false; 18 | } 19 | 20 | 21 | volumeMultiplier() 22 | { 23 | return 1; 24 | } 25 | 26 | 27 | async runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, _roomUdn) 28 | { 29 | var self = this; 30 | var value = parseInt(this.getQueryValue("value", -1)); 31 | var equalRooms = parseInt(this.getQueryValue("equalRooms", 0)); 32 | 33 | // if we have the 'euqlRooms' set, we do have to set the value directly on the given rooms for the zone, so that all 34 | // rooms will have the same volume value. Equal rooms only works if not relative of course. 35 | if(_mediaRendererVirtual && equalRooms) 36 | { 37 | var resultSum = {}; 38 | var rendererUdns = _mediaRendererVirtual.getRoomRendererUDNs(); 39 | for(var rendererIdx=0; rendererIdx'+''+''+'
    '+''+''+'';PrettyJSON.tpl.Leaf=''+''+' <%-data%><%= coma %>'+'';PrettyJSON.view.Node=Backbone.View.extend({tagName:'span',data:null,level:1,path:'',type:'',size:0,isLast:true,rendered:false,events:{'click .node-bracket':'collapse','mouseover .node-container':'mouseover','mouseout .node-container':'mouseout'},initialize:function(opt){this.options=opt;this.data=this.options.data;this.level=this.options.level||this.level;this.path=this.options.path;this.isLast=_.isUndefined(this.options.isLast)?this.isLast:this.options.isLast;this.dateFormat=this.options.dateFormat;var m=this.getMeta();this.type=m.type;this.size=m.size;this.childs=[];this.render();if(this.level==1) 8 | this.show();},getMeta:function(){var val={size:_.size(this.data),type:_.isArray(this.data)?'array':'object',};return val;},elements:function(){this.els={container:$(this.el).find('.node-container'),contentWrapper:$(this.el).find('.node-content-wrapper'),top:$(this.el).find('.node-top'),ul:$(this.el).find('.node-body'),down:$(this.el).find('.node-down')};},render:function(){this.tpl=_.template(PrettyJSON.tpl.Node);$(this.el).html(this.tpl);this.elements();var b=this.getBrackets();this.els.top.html(b.top);this.els.down.html(b.bottom);this.hide();return this;},renderChilds:function(){var count=1;_.each(this.data,function(val,key){var isLast=(count==this.size);count=count+1;var path=(this.type=='array')?this.path+'['+key+']':this.path+'.'+key;var opt={key:key,data:val,parent:this,path:path,level:this.level+1,dateFormat:this.dateFormat,isLast:isLast};var child=(PrettyJSON.util.isObject(val)||_.isArray(val))?new PrettyJSON.view.Node(opt):new PrettyJSON.view.Leaf(opt);child.on('mouseover',function(e,path){this.trigger("mouseover",e,path);},this);child.on('mouseout',function(e){this.trigger("mouseout",e);},this);var li=$('
  • ');var colom=' : ';var left=$('');var right=$('').append(child.el);(this.type=='array')?left.html(''):left.html(key+colom);left.append(right);li.append(left);this.els.ul.append(li);child.parent=this;this.childs.push(child);},this);},isVisible:function(){return this.els.contentWrapper.is(":visible");},collapse:function(e){e.stopPropagation();this.isVisible()?this.hide():this.show();this.trigger("collapse",e);},show:function(){if(!this.rendered){this.renderChilds();this.rendered=true;} 9 | this.els.top.html(this.getBrackets().top);this.els.contentWrapper.show();this.els.down.show();},hide:function(){var b=this.getBrackets();this.els.top.html(b.close);this.els.contentWrapper.hide();this.els.down.hide();},getBrackets:function(){var v={top:'{',bottom:'}',close:'{ ... }'};if(this.type=='array'){v={top:'[',bottom:']',close:'[ ... ]'};};v.bottom=(this.isLast)?v.bottom:v.bottom+',';v.close=(this.isLast)?v.close:v.close+',';return v;},mouseover:function(e){e.stopPropagation();this.trigger("mouseover",e,this.path);},mouseout:function(e){e.stopPropagation();this.trigger("mouseout",e);},expandAll:function(){_.each(this.childs,function(child){if(child instanceof PrettyJSON.view.Node){child.show();child.expandAll();}},this);this.show();},collapseAll:function(){_.each(this.childs,function(child){if(child instanceof PrettyJSON.view.Node){child.hide();child.collapseAll();}},this);if(this.level!=1) 10 | this.hide();}});PrettyJSON.view.Leaf=Backbone.View.extend({tagName:'span',data:null,level:0,path:'',type:'string',isLast:true,events:{"mouseover .leaf-container":"mouseover","mouseout .leaf-container":"mouseout"},initialize:function(opt){this.options=opt;this.data=this.options.data;this.level=this.options.level;this.path=this.options.path;this.type=this.getType();this.dateFormat=this.options.dateFormat;this.isLast=_.isUndefined(this.options.isLast)?this.isLast:this.options.isLast;this.render();},getType:function(){var m='string';var d=this.data;if(_.isNumber(d))m='number';else if(_.isBoolean(d))m='boolean';else if(_.isDate(d))m='date';else if(_.isNull(d))m='null' 11 | return m;},getState:function(){var coma=this.isLast?'':',';var state={data:this.data,level:this.level,path:this.path,type:this.type,coma:coma};return state;},render:function(){var state=this.getState();if(state.type=='date'&&this.dateFormat){state.data=PrettyJSON.util.dateFormat(this.data,this.dateFormat);} 12 | if(state.type=='null'){state.data='null';} 13 | if(state.type=='string'){state.data=(state.data=='')?'""':'"'+state.data+'"';} 14 | this.tpl=_.template(PrettyJSON.tpl.Leaf);$(this.el).html(this.tpl(state));return this;},mouseover:function(e){e.stopPropagation();var path=this.path+' : '+this.data+'';this.trigger("mouseover",e,path);},mouseout:function(e){e.stopPropagation();this.trigger("mouseout",e);}}); -------------------------------------------------------------------------------- /lib/lib.base.requestMediaRenderer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Request = require('./lib.base.request'); 3 | 4 | module.exports = class RequestMediaRenderer extends Request 5 | { 6 | constructor() 7 | { 8 | super(); 9 | } 10 | 11 | 12 | useZoneRenderer() 13 | { 14 | // always try to get the zine renderer first 15 | //if(this.getQueryValue("scope") == "room") 16 | // return false; 17 | return true; 18 | } 19 | 20 | isRoomScope() 21 | { 22 | if(this.getQueryValue("scope") == "room") 23 | return true; 24 | return false; 25 | } 26 | 27 | 28 | isAllowedForVirtualRenderer() 29 | { 30 | return true; 31 | } 32 | 33 | 34 | isAllowedForRoomRenderer() 35 | { 36 | return true; 37 | } 38 | 39 | 40 | waitTillConfirmed() 41 | { 42 | var confirm = this.getQueryValue("confirm", ""); 43 | if (confirm != "" && confirm != "0" && confirm != "false") 44 | return true; 45 | return false; 46 | } 47 | 48 | 49 | async run() 50 | { 51 | var self = this; 52 | var id = this.getQueryValue("id"); 53 | var scope = this.getQueryValue("scope"); 54 | var rendererThrowedException = null; 55 | var returnData = null; 56 | var noVirtualRenderer = false; 57 | 58 | // the request itself may use a forced scope. if so we have to use it! 59 | if(this.useZoneRenderer()) 60 | scope = "zone"; 61 | else 62 | scope = "room"; 63 | 64 | try 65 | { 66 | // always get the zone renderer with the given id (no matter if this is the zone UDN or name of a room) 67 | // there may be more than one if no identifier is given! 68 | var mediaRenderers = self.getMediaRenderersFromIdAndScope(id, "zone"); 69 | var mediaRendererRoom = null; 70 | 71 | // if we have scoped the room and we do only have found one virtual renderer we can read the roomRenderer 72 | if(this.isRoomScope() && mediaRenderers.size <= 1) 73 | { 74 | mediaRendererRoom = self.managerDisposer.deviceManager.getMediaRenderer(id); 75 | // if the room is not found but we have scoped it we have to give an error! 76 | if(!mediaRendererRoom) 77 | { 78 | self.logError("Room with id '" + id + "' not found"); 79 | throw new Error("Room with id '" + id + "' not found"); 80 | } 81 | } 82 | 83 | // if no virtual devices or no room renderer was found give an error 84 | if(!mediaRenderers.size && !mediaRendererRoom) 85 | { 86 | // if room scope is enabled we do give other error message as if room scope is disabled 87 | if(self.isRoomScope()) 88 | { 89 | self.logError("MediaRenderer for id '" + id + "' not found"); 90 | throw new Error("MediaRenderer for id '" + id + "' not found"); 91 | } 92 | else 93 | { 94 | self.logError("VirtualMediaRenderer for id '" + id + "' not found"); 95 | throw new Error("VirtualMediaRenderer for id '" + id + "' not found"); 96 | } 97 | } 98 | 99 | // if there is no virtual renderer but a room renderer (this is the case when we address a room without a zone / unassigned room) 100 | // we do add the room renderer to the mediaRenderers map to be sure we run into the loop. 101 | if(!mediaRenderers.size) 102 | { 103 | noVirtualRenderer = true; 104 | mediaRenderers.set(mediaRendererRoom.udn(), mediaRendererRoom); 105 | } 106 | 107 | 108 | for (var udn of mediaRenderers.keys()) 109 | { 110 | var mediaRenderer = mediaRenderers.get(udn); 111 | if(mediaRenderer) 112 | { 113 | self.logDebug("Calling action '" + this.action + "' for renderer " + mediaRenderer.name()); 114 | try 115 | { 116 | // await the result of the media renderer action, if there is an error we will get into the 117 | // catch and we log the error but we try to execute other renderers in listStyleType 118 | // we do only provide a virtual renderer if we have found one. This is nearly always the case except 119 | // we do some requests on unassigned rooms. 120 | var data = await self.runActionForMediaRenderer(noVirtualRenderer ? null : mediaRenderer, mediaRendererRoom); 121 | // if the return value is no object, then create an object and put the result into it 122 | if(typeof data !== "object") 123 | data = { "result" : data }; 124 | // add some information fields 125 | data["udn"] = udn; 126 | // sum up data from all renderers 127 | if(mediaRenderers.size > 1) 128 | { 129 | if(returnData == null) 130 | returnData = []; 131 | returnData.push(data) 132 | } 133 | else 134 | { 135 | returnData = data; 136 | } 137 | } 138 | catch(_execption) 139 | { 140 | self.logError("Exception thrown: ", _execption); 141 | // do not stop executing the action on the other renderers, we try to do the action on the other 142 | // renderers too, even if one failed! But we store that we had an exception and throw it afterwards 143 | rendererThrowedException = _execption; 144 | } 145 | } 146 | else 147 | { 148 | // well in fact this error is not really a big error in normal cases, it may occur when creation of a 149 | // zone is triggered twice or once in a while very fast (when renderer was removed but is listed in the 150 | // zone configuration because it has not updated yet) 151 | self.logError("Media renderer object for udn " + udn + " not existent"); 152 | rendererThrowedException = new Error("Media renderer object for udn " + udn + " not existent"); 153 | } 154 | } 155 | 156 | // if we had an error in any of the renderers we throw the error to be sure the caller gets the error back 157 | if(rendererThrowedException) 158 | throw (rendererThrowedException); 159 | } 160 | catch(_exception) 161 | { 162 | self.logError("Some renderers had exceptions: ", _exception); 163 | throw _exception; 164 | } 165 | return returnData; 166 | } 167 | 168 | /** 169 | * will be called each media renderer for what we have to execute the request 170 | * @param {Object} the media renderer class 171 | * @return {Promise} a promise if ok or not 172 | */ 173 | runActionForMediaRenderer(_mediaRendererVirtual, _mediaRendererRoom) 174 | { 175 | var self = this; 176 | 177 | return new Promise(function(_resolve, _reject){ 178 | 179 | if(_mediaRendererRoom && !self.isAllowedForRoomRenderer()) 180 | { 181 | self.logError(self.action + " is not allowed to be used with room renderers!"); 182 | _reject(new Error(self.action + " is not allowed to be used with room renderers!")); 183 | } 184 | else if(_mediaRendererVirtual && !_mediaRendererRoom && !self.isAllowedForVirtualRenderer()) 185 | { 186 | self.logError(self.action + " is not allowed to be used with virtual renderers!"); 187 | _reject(new Error(self.action + " is not allowed to be used with virtual renderers!")); 188 | } 189 | else 190 | { 191 | self.runAction(_resolve, _reject, _mediaRendererVirtual, _mediaRendererRoom, (_mediaRendererRoom != null ? _mediaRendererRoom.roomUdn() : "")); 192 | } 193 | }); 194 | } 195 | } -------------------------------------------------------------------------------- /lib/requestHandlers/lib.request.getRendererState.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RequestLongPolling = require('../lib.base.requestLongPolling'); 3 | 4 | module.exports = class Request_GetRendererState extends RequestLongPolling 5 | { 6 | constructor() 7 | { 8 | super(); 9 | this.mediaRenderer = null; 10 | } 11 | 12 | 13 | isRoomScope() 14 | { 15 | if(this.getQueryValue("scope") == "room") 16 | return true; 17 | return false; 18 | } 19 | 20 | 21 | init() 22 | { 23 | var id = this.getQueryValue("id"); 24 | var scope = this.getQueryValue("scope"); 25 | 26 | if(id) 27 | { 28 | // if scope is not 'room' we try to get the zone renderer, otherwise get the room renderer 29 | if(this.isRoomScope()) 30 | this.mediaRenderer = this.managerDisposer.deviceManager.getMediaRenderer(id); 31 | else 32 | this.mediaRenderer = this.managerDisposer.deviceManager.getVirtualMediaRenderer(id); 33 | } 34 | } 35 | 36 | 37 | runAction(_resolve, _reject) 38 | { 39 | try 40 | { 41 | var id = this.getQueryValue("id"); 42 | var onlyVirtual = this.getQueryValue("onlyVirtual"); 43 | var onlyRaumfeld = this.getQueryValue("onlyRaumfeld"); 44 | 45 | var getVirtualRenderers = true 46 | var getRealRenderers = onlyVirtual ? false : true 47 | 48 | if(id) 49 | { 50 | if(!this.mediaRenderer) 51 | { 52 | this.logError("Media Renderer for id '" + id + "' not found"); 53 | _reject(new Error("Media Renderer for id '" + id + "' not found")); 54 | } 55 | else 56 | { 57 | var dataArray = []; 58 | dataArray.push(this.createReturnData(this.mediaRenderer, this.mediaRenderer.rendererState)) 59 | _resolve(dataArray); 60 | } 61 | } 62 | else 63 | { 64 | // TODO: only add renderers to list which state has been updated??? of course only when updateId is given?! 65 | // TODO: Add all other renderers too? when set? 66 | var dataArray = []; 67 | if(getVirtualRenderers) 68 | { 69 | for (var udn of this.managerDisposer.deviceManager.mediaRenderersVirtual.keys()) 70 | { 71 | var mediaRenderer = this.managerDisposer.deviceManager.mediaRenderersVirtual.get(udn); 72 | if(mediaRenderer) 73 | { 74 | dataArray.push(this.createReturnData(mediaRenderer, mediaRenderer.rendererState)) 75 | } 76 | } 77 | } 78 | 79 | if(getRealRenderers) 80 | { 81 | for (var udn of this.managerDisposer.deviceManager.mediaRenderers.keys()) 82 | { 83 | var mediaRenderer = this.managerDisposer.deviceManager.mediaRenderers.get(udn); 84 | if(mediaRenderer /*&& mediaRenderer.isRaumfeldRenderer()*/) 85 | { 86 | // skip 3rd party renderers 87 | if(onlyRaumfeld && !mediaRenderer.isRaumfeldRenderer()) 88 | continue; 89 | dataArray.push(this.createReturnData(mediaRenderer, mediaRenderer.rendererState)) 90 | } 91 | } 92 | } 93 | _resolve(dataArray); 94 | } 95 | } 96 | catch(_exception) 97 | { 98 | this.logError(_exception); 99 | _reject(_exception); 100 | } 101 | } 102 | 103 | 104 | getCurrentUpdateId() 105 | { 106 | var id = this.getQueryValue("id"); 107 | // if we have an id we should have a media renderer where we can get the updateId 108 | if(id) 109 | { 110 | if(!this.mediaRenderer) 111 | { 112 | this.logError("Media Renderer for id '" + id + "' not found"); 113 | throw(new Error("Media Renderer for id '" + id + "' not found")); 114 | } 115 | return this.mediaRenderer.lastUpdateIdRendererState; 116 | } 117 | // if we have no Id we have to check if there was an update it changed on any of the renderers 118 | else 119 | { 120 | return this.getCombinedUpdateIdforRendererState(); 121 | } 122 | } 123 | 124 | 125 | getCombinedUpdateIdforRendererState() 126 | { 127 | var combindedUpdateId = ""; 128 | 129 | for (var udn of this.managerDisposer.deviceManager.mediaRenderersVirtual.keys()) 130 | { 131 | var mediaRenderer = this.managerDisposer.deviceManager.mediaRenderersVirtual.get(udn); 132 | if(mediaRenderer) 133 | { 134 | combindedUpdateId += mediaRenderer.lastUpdateIdRendererState; 135 | } 136 | } 137 | 138 | return combindedUpdateId; 139 | } 140 | 141 | 142 | createReturnData(_mediaRenderer, _rendererState) 143 | { 144 | // create better return (rooms list in fact all others are okay!) 145 | var newStateObject = JSON.parse(JSON.stringify(_rendererState)); 146 | // remove 'named' room array and add a 'normal' one instead 147 | delete newStateObject.rooms; 148 | newStateObject.rooms = []; 149 | newStateObject.udn = _mediaRenderer.udn(); 150 | 151 | // we may get the info from a zone rendere wher the attached rooms are stored in the 'rooms' array 152 | if(_rendererState.rooms) 153 | { 154 | for(var udn in _rendererState.rooms) 155 | { 156 | var roomInfoFromZone = this.createRoomInfoFromUdn(udn); 157 | var newRoomObject = JSON.parse(JSON.stringify(_rendererState.rooms[udn])); 158 | var mergedRoomObject = Object.assign(newRoomObject, roomInfoFromZone); 159 | newStateObject.rooms.push(mergedRoomObject); 160 | } 161 | } 162 | // or we may have a 'normal' renderer which could have a relation to a room 163 | else 164 | { 165 | var roomInfoFromZone = this.createRoomInfoFromUdn(_mediaRenderer.udn()); 166 | newStateObject.rooms.push(roomInfoFromZone); 167 | } 168 | 169 | // add current media item 170 | if(Object.keys(_mediaRenderer.currentMediaItemData).length > 0) 171 | newStateObject["mediaItem"] = _mediaRenderer.currentMediaItemData; 172 | else 173 | newStateObject["mediaItem"] = null; 174 | 175 | // add some basic data from the upnp device 176 | newStateObject["friendlyName"] = _mediaRenderer.friendlyName(); 177 | newStateObject["host"] = _mediaRenderer.host(); 178 | newStateObject["manufacturer"] = _mediaRenderer.manufacturer(); 179 | newStateObject["modelNumber"] = _mediaRenderer.modelNumber(); 180 | 181 | 182 | return newStateObject; 183 | } 184 | 185 | 186 | createRoomInfoFromUdn(_udn) 187 | { 188 | var roomInfoFromZone = {}; 189 | var roomObject = this.managerDisposer.zoneManager.getRoomObjectFromMediaRendererUdnOrName(_udn); 190 | 191 | // add the room info from the zone configuration file 192 | if(roomObject) 193 | { 194 | // copy "$" into the roomInfo object 195 | if(roomObject.$) 196 | { 197 | for(var key in roomObject.$) 198 | { 199 | roomInfoFromZone[key]=roomObject.$[key]; 200 | } 201 | } 202 | 203 | // copy "renderer"into the roomInfo object 204 | if(roomObject.renderer && roomObject.renderer.length) 205 | { 206 | var rendererArray = []; 207 | for(var i=0; i 2 | 3 | 4 | 5 | Raumserver Test-Application 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
    21 | node-raumserver host 22 |
    23 |
    24 | Confirm requests 25 |
    26 | 27 |
    28 | 29 |
    30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
    44 | 45 |
    46 | 47 |
    48 | Room Id 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
    61 | 62 |
    63 | Seek value 64 | 65 | 66 | 67 |
    68 |
    69 | Play Mode 70 | 71 |
    72 | 73 |
    74 | Volume 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
    86 |
    87 | 88 | 89 | 90 |
    91 |
    92 | 93 | 94 | 95 | 96 | 97 | 98 |
    99 | 100 |
    101 | 102 |
    103 | Room Id 104 | Zone Id 105 | 106 | 107 | 108 |
    109 | 110 |
    111 | 112 |
    113 | Room Id 114 | value 115 | 116 | 117 | 118 | 119 | 120 | 121 |
    122 | 123 |
    124 | 125 | 126 |
    127 |
    128 |
    129 |
    130 | 131 | -------------------------------------------------------------------------------- /lib/lib.raumserver.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Url = require('url'); 3 | const QueryString = require('query-string'); 4 | var Http = require("http"); 5 | var Raumkernel = require('node-raumkernel'); 6 | var Request = require('./lib.base.request'); 7 | var PackageJSON = require("../package.json"); 8 | var os = require('os'); 9 | 10 | module.exports = class Raumserver extends Raumkernel.Base 11 | { 12 | constructor() 13 | { 14 | super(); 15 | this.raumkernel = new Raumkernel.Raumkernel(); 16 | this.httpServer = null; 17 | 18 | this.settings = {}; 19 | this.settings.port = 8080; 20 | } 21 | 22 | additionalLogIdentifier() 23 | { 24 | return "Raumserver"; 25 | } 26 | 27 | /** 28 | * construct and set a default logger 29 | * @param {Number} the log level which should be logged 30 | */ 31 | createLogger(_logLevel = 2, _path = "./logs") 32 | { 33 | this.parmLogger(new Raumkernel.Logger(_logLevel, _path)); 34 | } 35 | 36 | /** 37 | * should be called after the class was instanced and after an external logger was set (otherwise a standard logger will be created) 38 | * this method initializes the raumkernel,starts up the request server and does the bonjour 39 | */ 40 | init() 41 | { 42 | var self = this; 43 | 44 | // set the template file for uri set method 45 | this.raumkernel.getSettings().uriMetaDataTemplateFile = "node_modules/node-raumkernel/lib/setUriMetadata.template"; 46 | 47 | // set some other settings from the config/settings file 48 | // TODO: @@@ 49 | //this.raumkernel.getSettings(). 50 | 51 | // if there is no logger defined we do create a standard logger 52 | if(!this.parmLogger()) 53 | this.createLogger(this.settings.loglevel, this.settings.logpath); 54 | 55 | this.logInfo("Welcome to raumserver v" + PackageJSON.version +" (raumkernel v" + Raumkernel.PackageJSON.version + ")"); 56 | 57 | // log some information of the network interfaces for troubleshooting 58 | this.logNetworkInterfaces(); 59 | 60 | // Do the init of the raumkernel. This will include starting for the raumfeld multiroom system and we 61 | // do hook up on some events so the raumserver knows the status of the multiroom system 62 | this.logVerbose("Setting up raumkernel"); 63 | this.raumkernel.parmLogger(this.parmLogger()); 64 | // TODO: listen to events like hostOnline/hostOffline 65 | this.raumkernel.init(); 66 | 67 | this.logVerbose("Starting HTTP server to receive requests"); 68 | this.startHTTPServer(); 69 | 70 | this.logVerbose("Starting bonjour server for advertising"); 71 | // TODO: @@@ 72 | } 73 | 74 | /** 75 | * will start the HTTP Server if its not started 76 | */ 77 | startHTTPServer() 78 | { 79 | var self = this; 80 | if(!this.httpServer) 81 | { 82 | this.httpServer = Http.createServer(); 83 | this.httpServer.on("request", function(_request, _response){ 84 | self.requestReceived(_request, _response); 85 | }); 86 | 87 | // start the server on the port given in the settings 88 | this.httpServer.listen(this.settings.port, function() { 89 | self.logVerbose('Raumserver listening on port ' + self.settings.port.toString()); 90 | }); 91 | } 92 | } 93 | 94 | /** 95 | * will be called whenever a request is made to the raumserver and the raumnserver receives it 96 | */ 97 | requestReceived(_request, _response) 98 | { 99 | this.logVerbose("Request received: " + _request.method + " " + _request.url); 100 | var parsedUrl = Url.parse(_request.url); 101 | 102 | // simply soak up system requests of browsers or other clients 103 | if(parsedUrl.pathname.startsWith("/favicon.ico")) 104 | return 105 | 106 | // if we are on the '/raumserver/controller/' or '/raumserver/data/' path we can handle the requests 107 | // otherwise they are no requests to the raumserver. 108 | if( parsedUrl.pathname.startsWith("/raumserver/controller/") || 109 | parsedUrl.pathname.startsWith("/raumserver/data/")) 110 | { 111 | this.logDebug("Request to raumserver recognized: " + _request.url); 112 | var pathArray = parsedUrl.pathname.split("/"); 113 | var queryObject = QueryString.parse(parsedUrl.query); 114 | if(pathArray.length === 4) 115 | this.handleRequestObject(pathArray[3], queryObject, _request, _response); 116 | else 117 | this.handleUnknownPath(_request, _response); 118 | } 119 | else 120 | this.handleUnknownPath(_request, _response); 121 | } 122 | 123 | 124 | handleRequestObject(_action, _queryObject, _request, _response) 125 | { 126 | var self = this; 127 | var requestObj = null; 128 | // get request action and check if this is a valid action 129 | // if the request object which is returned is 'null', the action is not a valid one! 130 | 131 | try 132 | { 133 | requestObj = Request.newFromAction(_action); 134 | } 135 | catch(_exception) 136 | { 137 | // silent catch 138 | this.logError("Error creating request for action '" + _action + "': " + this.formatException(_exception)); 139 | } 140 | 141 | if(requestObj) 142 | { 143 | try 144 | { 145 | this.logDebug("Handle action '" + _action + "' with query: " + JSON.stringify(_queryObject)); 146 | requestObj.parmLogger(this.parmLogger()); 147 | requestObj.parmManagerDisposer(this.raumkernel.managerDisposer); 148 | requestObj.parmAction(_action); 149 | requestObj.parmUrl(_request.url); 150 | requestObj.parmQueryObject(_queryObject); 151 | requestObj.parmRaumkernel(this.raumkernel); 152 | requestObj.init(); 153 | requestObj.run().then(function(_data){ 154 | self.handleFulfilledAction(_action, _request, _response, requestObj, _data); 155 | }).catch(function(_data){ 156 | // the "_data" object we get here may be a string or even an exception or something else, so we have to 157 | // convert it to a nice error output which we will do with "convertRejectedDataToResponseData" method 158 | self.handleRejectedAction(_action, _request, _response, requestObj, self.convertRejectedDataToResponseData(_data)); 159 | }); 160 | } 161 | catch(_exeption) 162 | { 163 | self.handleRejectedAction(_action, _request, _response, requestObj, self.convertRejectedDataToResponseData(_exeption)); 164 | } 165 | } 166 | else 167 | { 168 | this.logError("Action '" + _action + "' is not a valid action!"); 169 | this.handleUnknownAction(_action, _request, _response); 170 | } 171 | } 172 | 173 | 174 | handleUnknownPath(_request, _response) 175 | { 176 | this.logError("Request was rejected because unknown path"); 177 | this.addGlobalResponseHeaders(_response); 178 | this.addAccessControlHeaders(_request, _response); 179 | _response.write(this.buildJSONResponse(_request.url, "", "Unknown url path", true, {})); 180 | _response.end(); 181 | } 182 | 183 | 184 | handleUnknownAction(_action, _request, _response) 185 | { 186 | this.logError("Request was rejected because of unknown action"); 187 | this.addGlobalResponseHeaders(_response); 188 | this.addAccessControlHeaders(_request, _response); 189 | _response.write(this.buildJSONResponse(_request.url, _action, "Unknown action", true, {})); 190 | _response.end(); 191 | } 192 | 193 | 194 | handleFulfilledAction(_action, _request, _response, _requestObj, _data) 195 | { 196 | _requestObj.logDebug("Request was accepted and was successfully executed"); 197 | this.addGlobalResponseHeaders(_response); 198 | this.addAccessControlHeaders(_request, _response); 199 | this.addResponseHeaders(_response, _requestObj.returnHeaders); 200 | _response.write(this.buildJSONResponse(_request.url, _action, "", false, _data)); 201 | _response.end(); 202 | } 203 | 204 | 205 | handleRejectedAction(_action, _request, _response, _requestObj, _data) 206 | { 207 | this.logError("Request was rejected: ", _data); 208 | this.addGlobalResponseHeaders(_response); 209 | this.addAccessControlHeaders(_request, _response); 210 | this.addResponseHeaders(_response, _requestObj.returnHeaders); 211 | _response.write(this.buildJSONResponse(_request.url, _action, "Action was rejected", true, _data)); 212 | _response.end(); 213 | } 214 | 215 | 216 | addResponseHeaders(_response, _headers) 217 | { 218 | for (var key in _headers) 219 | { 220 | _response.setHeader(key, _headers[key]); 221 | } 222 | } 223 | 224 | 225 | addGlobalResponseHeaders(_response) 226 | { 227 | _response.setHeader('Content-Type', 'application/json'); 228 | _response.status = 200; 229 | } 230 | 231 | 232 | addAccessControlHeaders(_request, _response) 233 | { 234 | _response.setHeader('Access-Control-Allow-Origin', '*'); 235 | _response.setHeader('Access-Control-Request-Method', '*'); 236 | _response.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET'); 237 | _response.setHeader('Access-Control-Allow-Headers', '*'); 238 | // ChriD 20180410 --> 239 | // until browsers support the wildcard in the access controls we do specify the exposed headers 240 | //_response.setHeader('Access-Control-Expose-Headers', '*'); 241 | _response.setHeader('Access-Control-Expose-Headers', 'updateId,updateid,updateID'); 242 | // ChriD 20180410 <-- 243 | //_response.setHeader('Access-Control-Allow-Headers', _request.header.origin); 244 | } 245 | 246 | 247 | buildJSONResponse(_requestUrl, _action, _msg, _error, _data) 248 | { 249 | return JSON.stringify({ 250 | "requestUrl" : _requestUrl, 251 | "action" : _action, 252 | "error" : _error, 253 | "msg" : _msg, 254 | "data" : _data 255 | }); 256 | } 257 | 258 | 259 | convertRejectedDataToResponseData(_rejectedData) 260 | { 261 | var errorMessage = ""; 262 | var errorData = null; 263 | 264 | try 265 | { 266 | if(_rejectedData === null) 267 | { 268 | errorMessage = "Unknown error"; 269 | } 270 | // TODO: if rejected data is of type "Error" we do return the stack trace too 271 | /*else if (_rejectedData === 'object') 272 | { 273 | errorData = JSON.stringify(_rejectedData); 274 | }*/ 275 | else 276 | { 277 | errorMessage = _rejectedData.toString(); 278 | } 279 | } 280 | catch(_exception) 281 | { 282 | errorMessage = "Unknown error!" 283 | } 284 | 285 | return { 286 | "errorMessage" : errorMessage, 287 | "errorData" : errorData 288 | }; 289 | } 290 | 291 | 292 | formatException(_exception) 293 | { 294 | var exmsg = ""; 295 | if (_exception.message) 296 | { 297 | exmsg += _exception.message; 298 | } 299 | if (_exception.stack) 300 | { 301 | exmsg += ' | stack: ' + _exception.stack; 302 | } 303 | if(!exmsg) 304 | exmsg = _exception ; 305 | return exmsg; 306 | } 307 | 308 | 309 | logNetworkInterfaces() 310 | { 311 | var self = this; 312 | var ifaces = os.networkInterfaces(); 313 | 314 | Object.keys(ifaces).forEach(function (ifname) { 315 | var alias = 0; 316 | ifaces[ifname].forEach(function (iface) 317 | { 318 | if ('IPv4' !== iface.family || iface.internal !== false) 319 | { 320 | // skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses 321 | return; 322 | } 323 | 324 | if (alias >= 1) 325 | { 326 | // this single interface has multiple ipv4 addresses 327 | self.logInfo(ifname + ':' + alias, iface.address); 328 | } 329 | else 330 | { 331 | // this interface has only one ipv4 adress 332 | self.logInfo(ifname, iface.address); 333 | } 334 | ++alias; 335 | }); 336 | }); 337 | } 338 | 339 | } 340 | -------------------------------------------------------------------------------- /test/pretty-json/libs/underscore-min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.7.0 2 | // http://underscorejs.org 3 | // (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | (function(){var n=this,t=n._,r=Array.prototype,e=Object.prototype,u=Function.prototype,i=r.push,a=r.slice,o=r.concat,l=e.toString,c=e.hasOwnProperty,f=Array.isArray,s=Object.keys,p=u.bind,h=function(n){return n instanceof h?n:this instanceof h?void(this._wrapped=n):new h(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=h),exports._=h):n._=h,h.VERSION="1.7.0";var g=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}};h.iteratee=function(n,t,r){return null==n?h.identity:h.isFunction(n)?g(n,t,r):h.isObject(n)?h.matches(n):h.property(n)},h.each=h.forEach=function(n,t,r){if(null==n)return n;t=g(t,r);var e,u=n.length;if(u===+u)for(e=0;u>e;e++)t(n[e],e,n);else{var i=h.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},h.map=h.collect=function(n,t,r){if(null==n)return[];t=h.iteratee(t,r);for(var e,u=n.length!==+n.length&&h.keys(n),i=(u||n).length,a=Array(i),o=0;i>o;o++)e=u?u[o]:o,a[o]=t(n[e],e,n);return a};var v="Reduce of empty array with no initial value";h.reduce=h.foldl=h.inject=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length,o=0;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[o++]:o++]}for(;a>o;o++)u=i?i[o]:o,r=t(r,n[u],u,n);return r},h.reduceRight=h.foldr=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[--a]:--a]}for(;a--;)u=i?i[a]:a,r=t(r,n[u],u,n);return r},h.find=h.detect=function(n,t,r){var e;return t=h.iteratee(t,r),h.some(n,function(n,r,u){return t(n,r,u)?(e=n,!0):void 0}),e},h.filter=h.select=function(n,t,r){var e=[];return null==n?e:(t=h.iteratee(t,r),h.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e)},h.reject=function(n,t,r){return h.filter(n,h.negate(h.iteratee(t)),r)},h.every=h.all=function(n,t,r){if(null==n)return!0;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,!t(n[u],u,n))return!1;return!0},h.some=h.any=function(n,t,r){if(null==n)return!1;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,t(n[u],u,n))return!0;return!1},h.contains=h.include=function(n,t){return null==n?!1:(n.length!==+n.length&&(n=h.values(n)),h.indexOf(n,t)>=0)},h.invoke=function(n,t){var r=a.call(arguments,2),e=h.isFunction(t);return h.map(n,function(n){return(e?t:n[t]).apply(n,r)})},h.pluck=function(n,t){return h.map(n,h.property(t))},h.where=function(n,t){return h.filter(n,h.matches(t))},h.findWhere=function(n,t){return h.find(n,h.matches(t))},h.max=function(n,t,r){var e,u,i=-1/0,a=-1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],e>i&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(u>a||u===-1/0&&i===-1/0)&&(i=n,a=u)});return i},h.min=function(n,t,r){var e,u,i=1/0,a=1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],i>e&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(a>u||1/0===u&&1/0===i)&&(i=n,a=u)});return i},h.shuffle=function(n){for(var t,r=n&&n.length===+n.length?n:h.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=h.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},h.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=h.values(n)),n[h.random(n.length-1)]):h.shuffle(n).slice(0,Math.max(0,t))},h.sortBy=function(n,t,r){return t=h.iteratee(t,r),h.pluck(h.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var m=function(n){return function(t,r,e){var u={};return r=h.iteratee(r,e),h.each(t,function(e,i){var a=r(e,i,t);n(u,e,a)}),u}};h.groupBy=m(function(n,t,r){h.has(n,r)?n[r].push(t):n[r]=[t]}),h.indexBy=m(function(n,t,r){n[r]=t}),h.countBy=m(function(n,t,r){h.has(n,r)?n[r]++:n[r]=1}),h.sortedIndex=function(n,t,r,e){r=h.iteratee(r,e,1);for(var u=r(t),i=0,a=n.length;a>i;){var o=i+a>>>1;r(n[o])t?[]:a.call(n,0,t)},h.initial=function(n,t,r){return a.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},h.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:a.call(n,Math.max(n.length-t,0))},h.rest=h.tail=h.drop=function(n,t,r){return a.call(n,null==t||r?1:t)},h.compact=function(n){return h.filter(n,h.identity)};var y=function(n,t,r,e){if(t&&h.every(n,h.isArray))return o.apply(e,n);for(var u=0,a=n.length;a>u;u++){var l=n[u];h.isArray(l)||h.isArguments(l)?t?i.apply(e,l):y(l,t,r,e):r||e.push(l)}return e};h.flatten=function(n,t){return y(n,t,!1,[])},h.without=function(n){return h.difference(n,a.call(arguments,1))},h.uniq=h.unique=function(n,t,r,e){if(null==n)return[];h.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=h.iteratee(r,e));for(var u=[],i=[],a=0,o=n.length;o>a;a++){var l=n[a];if(t)a&&i===l||u.push(l),i=l;else if(r){var c=r(l,a,n);h.indexOf(i,c)<0&&(i.push(c),u.push(l))}else h.indexOf(u,l)<0&&u.push(l)}return u},h.union=function(){return h.uniq(y(arguments,!0,!0,[]))},h.intersection=function(n){if(null==n)return[];for(var t=[],r=arguments.length,e=0,u=n.length;u>e;e++){var i=n[e];if(!h.contains(t,i)){for(var a=1;r>a&&h.contains(arguments[a],i);a++);a===r&&t.push(i)}}return t},h.difference=function(n){var t=y(a.call(arguments,1),!0,!0,[]);return h.filter(n,function(n){return!h.contains(t,n)})},h.zip=function(n){if(null==n)return[];for(var t=h.max(arguments,"length").length,r=Array(t),e=0;t>e;e++)r[e]=h.pluck(arguments,e);return r},h.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},h.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=h.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}for(;u>e;e++)if(n[e]===t)return e;return-1},h.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=n.length;for("number"==typeof r&&(e=0>r?e+r+1:Math.min(e,r+1));--e>=0;)if(n[e]===t)return e;return-1},h.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=r||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=Array(e),i=0;e>i;i++,n+=r)u[i]=n;return u};var d=function(){};h.bind=function(n,t){var r,e;if(p&&n.bind===p)return p.apply(n,a.call(arguments,1));if(!h.isFunction(n))throw new TypeError("Bind must be called on a function");return r=a.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(a.call(arguments)));d.prototype=n.prototype;var u=new d;d.prototype=null;var i=n.apply(u,r.concat(a.call(arguments)));return h.isObject(i)?i:u}},h.partial=function(n){var t=a.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===h&&(e[u]=arguments[r++]);for(;r=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=h.bind(n[r],n);return n},h.memoize=function(n,t){var r=function(e){var u=r.cache,i=t?t.apply(this,arguments):e;return h.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},h.delay=function(n,t){var r=a.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},h.defer=function(n){return h.delay.apply(h,[n,1].concat(a.call(arguments,1)))},h.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var l=function(){o=r.leading===!1?0:h.now(),a=null,i=n.apply(e,u),a||(e=u=null)};return function(){var c=h.now();o||r.leading!==!1||(o=c);var f=t-(c-o);return e=this,u=arguments,0>=f||f>t?(clearTimeout(a),a=null,o=c,i=n.apply(e,u),a||(e=u=null)):a||r.trailing===!1||(a=setTimeout(l,f)),i}},h.debounce=function(n,t,r){var e,u,i,a,o,l=function(){var c=h.now()-a;t>c&&c>0?e=setTimeout(l,t-c):(e=null,r||(o=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,a=h.now();var c=r&&!e;return e||(e=setTimeout(l,t)),c&&(o=n.apply(i,u),i=u=null),o}},h.wrap=function(n,t){return h.partial(t,n)},h.negate=function(n){return function(){return!n.apply(this,arguments)}},h.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},h.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},h.before=function(n,t){var r;return function(){return--n>0?r=t.apply(this,arguments):t=null,r}},h.once=h.partial(h.before,2),h.keys=function(n){if(!h.isObject(n))return[];if(s)return s(n);var t=[];for(var r in n)h.has(n,r)&&t.push(r);return t},h.values=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},h.pairs=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},h.invert=function(n){for(var t={},r=h.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},h.functions=h.methods=function(n){var t=[];for(var r in n)h.isFunction(n[r])&&t.push(r);return t.sort()},h.extend=function(n){if(!h.isObject(n))return n;for(var t,r,e=1,u=arguments.length;u>e;e++){t=arguments[e];for(r in t)c.call(t,r)&&(n[r]=t[r])}return n},h.pick=function(n,t,r){var e,u={};if(null==n)return u;if(h.isFunction(t)){t=g(t,r);for(e in n){var i=n[e];t(i,e,n)&&(u[e]=i)}}else{var l=o.apply([],a.call(arguments,1));n=new Object(n);for(var c=0,f=l.length;f>c;c++)e=l[c],e in n&&(u[e]=n[e])}return u},h.omit=function(n,t,r){if(h.isFunction(t))t=h.negate(t);else{var e=h.map(o.apply([],a.call(arguments,1)),String);t=function(n,t){return!h.contains(e,t)}}return h.pick(n,t,r)},h.defaults=function(n){if(!h.isObject(n))return n;for(var t=1,r=arguments.length;r>t;t++){var e=arguments[t];for(var u in e)n[u]===void 0&&(n[u]=e[u])}return n},h.clone=function(n){return h.isObject(n)?h.isArray(n)?n.slice():h.extend({},n):n},h.tap=function(n,t){return t(n),n};var b=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof h&&(n=n._wrapped),t instanceof h&&(t=t._wrapped);var u=l.call(n);if(u!==l.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]===n)return e[i]===t;var a=n.constructor,o=t.constructor;if(a!==o&&"constructor"in n&&"constructor"in t&&!(h.isFunction(a)&&a instanceof a&&h.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c,f;if("[object Array]"===u){if(c=n.length,f=c===t.length)for(;c--&&(f=b(n[c],t[c],r,e)););}else{var s,p=h.keys(n);if(c=p.length,f=h.keys(t).length===c)for(;c--&&(s=p[c],f=h.has(t,s)&&b(n[s],t[s],r,e)););}return r.pop(),e.pop(),f};h.isEqual=function(n,t){return b(n,t,[],[])},h.isEmpty=function(n){if(null==n)return!0;if(h.isArray(n)||h.isString(n)||h.isArguments(n))return 0===n.length;for(var t in n)if(h.has(n,t))return!1;return!0},h.isElement=function(n){return!(!n||1!==n.nodeType)},h.isArray=f||function(n){return"[object Array]"===l.call(n)},h.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},h.each(["Arguments","Function","String","Number","Date","RegExp"],function(n){h["is"+n]=function(t){return l.call(t)==="[object "+n+"]"}}),h.isArguments(arguments)||(h.isArguments=function(n){return h.has(n,"callee")}),"function"!=typeof/./&&(h.isFunction=function(n){return"function"==typeof n||!1}),h.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},h.isNaN=function(n){return h.isNumber(n)&&n!==+n},h.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===l.call(n)},h.isNull=function(n){return null===n},h.isUndefined=function(n){return n===void 0},h.has=function(n,t){return null!=n&&c.call(n,t)},h.noConflict=function(){return n._=t,this},h.identity=function(n){return n},h.constant=function(n){return function(){return n}},h.noop=function(){},h.property=function(n){return function(t){return t[n]}},h.matches=function(n){var t=h.pairs(n),r=t.length;return function(n){if(null==n)return!r;n=new Object(n);for(var e=0;r>e;e++){var u=t[e],i=u[0];if(u[1]!==n[i]||!(i in n))return!1}return!0}},h.times=function(n,t,r){var e=Array(Math.max(0,n));t=g(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},h.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},h.now=Date.now||function(){return(new Date).getTime()};var _={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},w=h.invert(_),j=function(n){var t=function(t){return n[t]},r="(?:"+h.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};h.escape=j(_),h.unescape=j(w),h.result=function(n,t){if(null==n)return void 0;var r=n[t];return h.isFunction(r)?n[t]():r};var x=0;h.uniqueId=function(n){var t=++x+"";return n?n+t:t},h.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var A=/(.)^/,k={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},O=/\\|'|\r|\n|\u2028|\u2029/g,F=function(n){return"\\"+k[n]};h.template=function(n,t,r){!t&&r&&(t=r),t=h.defaults({},t,h.templateSettings);var e=RegExp([(t.escape||A).source,(t.interpolate||A).source,(t.evaluate||A).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return i+=n.slice(u,o).replace(O,F),u=o+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":a&&(i+="';\n"+a+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var a=new Function(t.variable||"obj","_",i)}catch(o){throw o.source=i,o}var l=function(n){return a.call(this,n,h)},c=t.variable||"obj";return l.source="function("+c+"){\n"+i+"}",l},h.chain=function(n){var t=h(n);return t._chain=!0,t};var E=function(n){return this._chain?h(n).chain():n};h.mixin=function(n){h.each(h.functions(n),function(t){var r=h[t]=n[t];h.prototype[t]=function(){var n=[this._wrapped];return i.apply(n,arguments),E.call(this,r.apply(h,n))}})},h.mixin(h),h.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=r[n];h.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],E.call(this,r)}}),h.each(["concat","join","slice"],function(n){var t=r[n];h.prototype[n]=function(){return E.call(this,t.apply(this._wrapped,arguments))}}),h.prototype.value=function(){return this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return h})}).call(this); 6 | //# sourceMappingURL=underscore-min.map -------------------------------------------------------------------------------- /test/pretty-json/libs/backbone-min.js: -------------------------------------------------------------------------------- 1 | (function(t,e){if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,s){t.Backbone=e(t,s,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore");e(t,exports,i)}else{t.Backbone=e(t,{},t._,t.jQuery||t.Zepto||t.ender||t.$)}})(this,function(t,e,i,r){var s=t.Backbone;var n=[];var a=n.push;var o=n.slice;var h=n.splice;e.VERSION="1.1.2";e.$=r;e.noConflict=function(){t.Backbone=s;return this};e.emulateHTTP=false;e.emulateJSON=false;var u=e.Events={on:function(t,e,i){if(!c(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,r){if(!c(this,"once",t,[e,r])||!e)return this;var s=this;var n=i.once(function(){s.off(t,n);e.apply(this,arguments)});n._callback=e;return this.on(t,n,r)},off:function(t,e,r){var s,n,a,o,h,u,l,f;if(!this._events||!c(this,"off",t,[e,r]))return this;if(!t&&!e&&!r){this._events=void 0;return this}o=t?[t]:i.keys(this._events);for(h=0,u=o.length;h").attr(t);this.setElement(r,false)}else{this.setElement(i.result(this,"el"),false)}}});e.sync=function(t,r,s){var n=T[t];i.defaults(s||(s={}),{emulateHTTP:e.emulateHTTP,emulateJSON:e.emulateJSON});var a={type:n,dataType:"json"};if(!s.url){a.url=i.result(r,"url")||M()}if(s.data==null&&r&&(t==="create"||t==="update"||t==="patch")){a.contentType="application/json";a.data=JSON.stringify(s.attrs||r.toJSON(s))}if(s.emulateJSON){a.contentType="application/x-www-form-urlencoded";a.data=a.data?{model:a.data}:{}}if(s.emulateHTTP&&(n==="PUT"||n==="DELETE"||n==="PATCH")){a.type="POST";if(s.emulateJSON)a.data._method=n;var o=s.beforeSend;s.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",n);if(o)return o.apply(this,arguments)}}if(a.type!=="GET"&&!s.emulateJSON){a.processData=false}if(a.type==="PATCH"&&k){a.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var h=s.xhr=e.ajax(i.extend(a,s));r.trigger("request",r,h,s);return h};var k=typeof window!=="undefined"&&!!window.ActiveXObject&&!(window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent);var T={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};e.ajax=function(){return e.$.ajax.apply(e.$,arguments)};var $=e.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var S=/\((.*?)\)/g;var H=/(\(\?)?:\w+/g;var A=/\*\w+/g;var I=/[\-{}\[\]+?.,\\\^$|#\s]/g;i.extend($.prototype,u,{initialize:function(){},route:function(t,r,s){if(!i.isRegExp(t))t=this._routeToRegExp(t);if(i.isFunction(r)){s=r;r=""}if(!s)s=this[r];var n=this;e.history.route(t,function(i){var a=n._extractParameters(t,i);n.execute(s,a);n.trigger.apply(n,["route:"+r].concat(a));n.trigger("route",r,a);e.history.trigger("route",n,r,a)});return this},execute:function(t,e){if(t)t.apply(this,e)},navigate:function(t,i){e.history.navigate(t,i);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=i.result(this,"routes");var t,e=i.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(I,"\\$&").replace(S,"(?:$1)?").replace(H,function(t,e){return e?t:"([^/?]+)"}).replace(A,"([^?]*?)");return new RegExp("^"+t+"(?:\\?([\\s\\S]*))?$")},_extractParameters:function(t,e){var r=t.exec(e).slice(1);return i.map(r,function(t,e){if(e===r.length-1)return t||null;return t?decodeURIComponent(t):null})}});var N=e.History=function(){this.handlers=[];i.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var R=/^[#\/]|\s+$/g;var O=/^\/+|\/+$/g;var P=/msie [\w.]+/;var C=/\/$/;var j=/#.*$/;N.started=false;i.extend(N.prototype,u,{interval:50,atRoot:function(){return this.location.pathname.replace(/[^\/]$/,"$&/")===this.root},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=decodeURI(this.location.pathname+this.location.search);var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.slice(i.length)}else{t=this.getHash()}}return t.replace(R,"")},start:function(t){if(N.started)throw new Error("Backbone.history has already been started");N.started=true;this.options=i.extend({root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var r=this.getFragment();var s=document.documentMode;var n=P.exec(navigator.userAgent.toLowerCase())&&(!s||s<=7);this.root=("/"+this.root+"/").replace(O,"/");if(n&&this._wantsHashChange){var a=e.$('