├── .gitignore
├── htmls
├── images
│ ├── Play.jpg
│ ├── next.jpg
│ ├── pause.jpg
│ ├── previous.jpg
│ ├── roonIcon.png
│ └── defaultZone.jpg
├── player.html
├── browser.css
├── browser.html
├── timers.css
├── timers.html
├── player.js
├── browser.js
└── timers.js
├── server.js
├── package.json
├── routes.js
├── README.md
└── controllers
└── roonAPI.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | config.json
3 |
--------------------------------------------------------------------------------
/htmls/images/Play.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/st0g1e/roon-extension-http-api/HEAD/htmls/images/Play.jpg
--------------------------------------------------------------------------------
/htmls/images/next.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/st0g1e/roon-extension-http-api/HEAD/htmls/images/next.jpg
--------------------------------------------------------------------------------
/htmls/images/pause.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/st0g1e/roon-extension-http-api/HEAD/htmls/images/pause.jpg
--------------------------------------------------------------------------------
/htmls/images/previous.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/st0g1e/roon-extension-http-api/HEAD/htmls/images/previous.jpg
--------------------------------------------------------------------------------
/htmls/images/roonIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/st0g1e/roon-extension-http-api/HEAD/htmls/images/roonIcon.png
--------------------------------------------------------------------------------
/htmls/images/defaultZone.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/st0g1e/roon-extension-http-api/HEAD/htmls/images/defaultZone.jpg
--------------------------------------------------------------------------------
/htmls/player.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
roon webplayer
4 |
5 | Zones:
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/htmls/browser.css:
--------------------------------------------------------------------------------
1 | div.gallery {
2 | margin: 5px;
3 | border: 1px solid #ccc;
4 | float: left;
5 | width: 150px;
6 | height: 200px;
7 | overflow: hidden;
8 | o-text-overflow: ellipsis;
9 | text-overflow: ellipsis;
10 | }
11 |
12 | div.gallery:hover {
13 | border: 1px solid #777;
14 | background-color: lightblue;
15 | }
16 |
17 | div.gallery img {
18 | width: 150px;
19 | height: 150px;
20 | }
21 |
22 | div.desc {
23 | padding: 15px;
24 | text-align: center;
25 | }
26 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var path = require('path');
3 |
4 | var app = express();
5 | const bodyParser = require('body-parser');
6 |
7 | app.use(bodyParser.urlencoded({ extended: true }));
8 | app.use(bodyParser.json());
9 |
10 | const PORT=3001;
11 |
12 | app.use(express.static(path.join(__dirname, 'htmls')));
13 |
14 | app.use(function(req, res, next) {
15 | res.header("Access-Control-Allow-Origin", "*");
16 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
17 | next();
18 | });
19 |
20 |
21 | require('./routes')(app);
22 |
23 | app.listen(PORT);
24 |
25 | console.log('Listening on port: ' + PORT);
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "roon-http-api",
3 | "description" : "st0g1e.roon-http-api",
4 | "version" : "1.0.4",
5 | "main" : "server.js",
6 | "private" : true,
7 | "dependencies": {
8 | "node-roon-api" : "github:roonlabs/node-roon-api",
9 | "node-roon-api-image" : "github:roonlabs/node-roon-api-image",
10 | "node-roon-api-status" : "github:roonlabs/node-roon-api-status",
11 | "node-roon-api-transport" : "github:roonlabs/node-roon-api-transport",
12 | "node-roon-api-browse" : "github:roonlabs/node-roon-api-browse",
13 | "express" : "4.x"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/htmls/browser.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | roon browser
8 |
9 | Zones:
10 |
11 |
15 |
16 |
17 |
18 |
19 | Home | Up
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/htmls/timers.css:
--------------------------------------------------------------------------------
1 | form {
2 | /* Just to center the form on the page */
3 | // margin: 0 auto;
4 | width: 400px;
5 | /* To see the outline of the form */
6 | padding: 1em;
7 | border: 1px solid #CCC;
8 | border-radius: 1em;
9 | }
10 |
11 |
12 | form div + div {
13 | margin-top: 1em;
14 | }
15 |
16 | label {
17 | /* To make sure that all labels have the same size and are properly aligned */
18 | display: inline-block;
19 | width: 90px;
20 | text-align: left;
21 | }
22 |
23 | input {
24 | /* To make sure that all text fields have the same font settings
25 | By default, textareas have a monospace font */
26 | font: 1em sans-serif;
27 |
28 | /* To give the same size to all text fields */
29 | width: 300px;
30 | box-sizing: border-box;
31 |
32 | /* To harmonize the look & feel of text field border */
33 | border: 1px solid #999;
34 | text-align: center;
35 | }
36 |
37 | input:focus {
38 | /* To give a little highlight on active elements */
39 | border-color: #000;
40 | }
41 |
42 | .button {
43 | /* To position the buttons to the same position of the text fields */
44 | padding-left: 90px; /* same size as the label elements */
45 | }
46 |
47 | button {
48 | /* This extra margin represent roughly the same space as the space
49 | between the labels and their text fields */
50 | margin-left: .5em;
51 | }
52 |
53 | table {
54 | border-collapse: collapse;
55 | width: 100%;
56 | }
57 |
58 | th, td {
59 | text-align: center;
60 | padding: 8px;
61 | }
62 |
63 | tr:nth-child(even){background-color: #f2f2f2}
64 |
--------------------------------------------------------------------------------
/routes.js:
--------------------------------------------------------------------------------
1 | module.exports = function(app) {
2 | var apis = require('./controllers/roonAPI');
3 |
4 | app.get('/roonAPI/getCore', apis.getCore);
5 | app.get('/roonAPI/listZones', apis.listZones);
6 | app.get('/roonAPI/listOutputs', apis.listOutputs);
7 | app.get('/roonAPI/getZone', apis.getZone);
8 | app.get('/roonAPI/play_pause', apis.play_pause);
9 | app.get('/roonAPI/stop', apis.stop);
10 | app.get('/roonAPI/previous', apis.previous);
11 | app.get('/roonAPI/next', apis.next);
12 | app.get('/roonAPI/change_volume', apis.change_volume);
13 | app.get('/roonAPI/change_volume_relative', apis.change_volume_relative);
14 | app.get('/roonAPI/getImage', apis.getImage);
15 | app.get('/roonAPI/play', apis.play);
16 | app.get('/roonAPI/pause', apis.pause);
17 | app.get('/roonAPI/listByItemKey', apis.listByItemKey);
18 | app.get('/roonAPI/listSearch', apis.listSearch);
19 | app.get('/roonAPI/goUp', apis.goUp);
20 | app.get('/roonAPI/goHome', apis.goHome);
21 | app.get('/roonAPI/listGoPage', apis.listGoPage);
22 | app.get('/roonAPI/listRefresh', apis.listRefresh);
23 | app.get('/roonAPI/getMediumImage', apis.getMediumImage);
24 | app.get('/roonAPI/getIcon', apis.getIcon);
25 | app.get('/roonAPI/getOriginalImage', apis.getOriginalImage);
26 | app.get('/roonAPI/getTimers', apis.getTimers);
27 | app.get('/roonAPI/addTimer', apis.addTimer);
28 | app.get('/roonAPI/removeTimer', apis.removeTimer);
29 | app.post('/roonAPI/group', apis.group);
30 | app.post('/roonAPI/ungroup', apis.ungroup);
31 | app.get('/roonAPI/transferZone', apis.transferZone);
32 | };
33 |
--------------------------------------------------------------------------------
/htmls/timers.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | roon timer
8 |
9 |
10 |
41 |
42 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/htmls/player.js:
--------------------------------------------------------------------------------
1 | var topUrl = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port;
2 | var callInterval;
3 | var timeInterval = 2000;
4 |
5 | function ajax_get(url, callback) {
6 | xmlhttp = new XMLHttpRequest();
7 | xmlhttp.onreadystatechange = function() {
8 | if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
9 | try {
10 | var data = JSON.parse(xmlhttp.responseText);
11 | } catch(err) {
12 | return;
13 | }
14 | callback(data);
15 | }
16 | };
17 |
18 | xmlhttp.open("GET", url, true);
19 | xmlhttp.send();
20 | }
21 |
22 | ajax_get(topUrl + '/roonAPI/listZones', function(data) {
23 | var html = "Zone List
";
24 | html += "";
25 | for (var i in data["zones"]) {
26 | html += "\n";
27 | }
28 | html += "
";
29 | document.getElementById("zoneList").innerHTML = html;
30 |
31 | startInterval();
32 | updateSelected();
33 | });
34 |
35 | function startInterval() {
36 | callInterval = setInterval(function() {
37 | var zone_id = document.getElementById("zoneList").options[document.getElementById("zoneList").selectedIndex].value;
38 |
39 | ajax_get(topUrl + '/roonAPI/getZone?zoneId=' + zone_id, function(data) {
40 | updateSelected();
41 | });
42 |
43 | }, 5000);
44 | }
45 |
46 | function stopInterval() {
47 | clearInterval( callInterval );
48 | }
49 |
50 |
51 | function updateSelected() {
52 | var zone_id = document.getElementById("zoneList").options[document.getElementById("zoneList").selectedIndex].value;
53 |
54 | ajax_get(topUrl + '/roonAPI/getZone?zoneId=' + zone_id, function(data) {
55 | var html = "";
56 |
57 | if ( data["zone"].state == "playing" || data["zone"].state == "paused" ) {
58 | html += show_zone(data["zone"]);
59 | }
60 | document.getElementById("selectedZone").innerHTML = html;
61 | });
62 | }
63 |
64 | function show_zone(zone) {
65 | var html = "";
66 |
67 | html += "\n";
68 |
69 | html += "Artist: " + zone.now_playing.three_line.line2 + "
\n";
70 | html += "Album: " + zone.now_playing.three_line.line3 + "
\n";
71 | html += "Title: " + zone.now_playing.three_line.line1 + "
\n";
72 |
73 | html += "
\n";
74 |
75 | html += "

\n";
76 |
77 | // Volumes
78 |
79 | html += "
\n";
80 | for ( var i in zone.outputs ) {
81 | if ( zone.outputs[i].zone_id == zone.zone_id && zone.outputs[i].volume != null ) {
82 | html += "
\n";
83 | html += "\n";
89 | }
90 | }
91 |
92 | // Navigation Buttons
93 |
94 | html += "\n";
95 | html += "
\n";
96 | html += "\n";
101 | html += "\n";
102 |
103 | return html;
104 | }
105 |
106 | function changeVolume(volume, outputId) {
107 | ajax_get(topUrl + '/roonAPI/change_volume?volume=' + volume + '&outputId=' + outputId, function(data) {
108 | });
109 | }
110 |
111 | function goPrev(zone_id) {
112 | ajax_get(topUrl + '/roonAPI/previous?zoneId=' + zone_id, function(data) {
113 | updateSelected();
114 | });
115 | }
116 |
117 | function goNext(zone_id) {
118 | ajax_get(topUrl + '/roonAPI/next?zoneId=' + zone_id, function(data) {
119 | updateSelected();
120 | });
121 | }
122 |
123 | function goPlayPause(zone_id) {
124 | ajax_get(topUrl + '/roonAPI/play_pause?zoneId=' + zone_id, function(data) {
125 | updateSelected();
126 | });
127 | }
128 |
--------------------------------------------------------------------------------
/htmls/browser.js:
--------------------------------------------------------------------------------
1 | var topUrl = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port;
2 |
3 | function ajax_get(url, callback) {
4 | xmlhttp = new XMLHttpRequest();
5 | xmlhttp.onreadystatechange = function() {
6 | if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
7 | try {
8 | var data = JSON.parse(xmlhttp.responseText);
9 | } catch(err) {
10 | return;
11 | }
12 | callback(data);
13 | }
14 | };
15 |
16 | xmlhttp.open("GET", url, true);
17 | xmlhttp.send();
18 | }
19 |
20 | ajax_get(topUrl + '/roonAPI/listZones', function(data) {
21 | var html = "Zone List
";
22 | html += "";
23 |
24 | for (var i in data["zones"]) {
25 | html += "\n";
26 | }
27 |
28 | html += "
";
29 | document.getElementById("zoneList").innerHTML = html;
30 |
31 | goHome();
32 | });
33 |
34 | function goHome() {
35 | var zone_id = document.getElementById("zoneList").options[document.getElementById("zoneList").selectedIndex].value;
36 |
37 | ajax_get(topUrl + '/roonAPI/goHome?zoneId=' + zone_id + '&list_size=20', function(data) {
38 | show_data(data, zone_id, 1);
39 | });
40 | }
41 |
42 | function goUp() {
43 | var zone_id = document.getElementById("zoneList").options[document.getElementById("zoneList").selectedIndex].value;
44 |
45 | ajax_get(topUrl + '/roonAPI/goUp?zoneId=' + zone_id + '&list_size=20', function(data) {
46 | show_data(data, zone_id, 1);
47 | });
48 | }
49 |
50 | function show_data( data, zone_id, page ) {
51 | var html = "";
52 |
53 | var pageHtml = "";
54 | if ( page > 1 ) {
55 | pageHtml += "prev\n";
56 | } else {
57 | pageHtml += "prev";
58 | }
59 |
60 | pageHtml += " | Page: " + page + " | ";
61 |
62 | if ( data.list.length > 99 ) {
63 | pageHtml += "next\n";
64 | } else {
65 | pageHtml += "next";
66 | }
67 |
68 | document.getElementById("PageNumber").innerHTML = pageHtml;
69 |
70 | for ( var i in data['list'] ) {
71 | html += "\n";
83 | }
84 |
85 | document.getElementById("browseTable").innerHTML = html;
86 | }
87 |
88 | function showList(item_key, zone_id, page, listSize) {
89 | ajax_get(topUrl + '/roonAPI/listByItemKey?zoneId=' + zone_id + "&item_key=" + item_key + "&page=" + page + "&list_size=" + listSize, function(data) {
90 | show_data(data, zone_id, page);
91 | });
92 | }
93 |
94 | function goPage(zone_id, page, size) {
95 | ajax_get(topUrl + '/roonAPI/listGoPage?page=' + page + "&list_size=" + size, function(data) {
96 | show_data(data, zone_id, page);
97 | });
98 | }
99 |
100 | function search(form) {
101 | var zone_id = document.getElementById("zoneList").options[document.getElementById("zoneList").selectedIndex].value;
102 | var toSearch = form.toSearch.value;
103 |
104 | //go home first
105 | ajax_get(topUrl + '/roonAPI/goHome?zoneId=' + zone_id + '&list_size=20', function(data) {
106 | var library_item_key = data.list[0].item_key;
107 |
108 | ajax_get(topUrl + '/roonAPI/listByItemKey?zoneId=' + zone_id + "&item_key=" + library_item_key + "&page=1&list_size=20", function(data) {
109 | var search_item_key = data.list[0].item_key;
110 |
111 | ajax_get(topUrl + '/roonAPI/listSearch?zoneId=' + zone_id + "&item_key=" + search_item_key + "&toSearch=" + toSearch + "&page=1&list_size=20", function(data) {
112 | show_data(data, zone_id, 1);
113 | });
114 | });
115 | });
116 | }
117 |
--------------------------------------------------------------------------------
/htmls/timers.js:
--------------------------------------------------------------------------------
1 | var topUrl = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port;
2 |
3 | var months = ["Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sep", "Oct", "Nov", "Dec"];
4 | var zone_list = {};
5 |
6 | function ajax_get(url, callback) {
7 | xmlhttp = new XMLHttpRequest();
8 | xmlhttp.onreadystatechange = function() {
9 | if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
10 | try {
11 | var data = JSON.parse(xmlhttp.responseText);
12 | } catch(err) {
13 | return;
14 | }
15 | callback(data);
16 | }
17 | };
18 |
19 | xmlhttp.open("GET", url, true);
20 | xmlhttp.send();
21 | }
22 |
23 | ajax_get(topUrl + '/roonAPI/listZones', function(data) {
24 | for (var i in data["zones"]) {
25 | zone_list[data["zones"][i].zone_id] = data["zones"][i].display_name;
26 | }
27 |
28 | show_data();
29 | });
30 |
31 | function show_data() {
32 | var optHtml = "";
33 |
34 | // CREATE ZONE LIST
35 | for (var i in zone_list) {
36 | optHtml += "\n";
37 | }
38 |
39 | document.getElementById("zoneListByTimer").innerHTML = optHtml;
40 | document.getElementById("zoneListByDate").innerHTML = optHtml;
41 |
42 |
43 | // CREATE MONTH LIST
44 | monthHtml = "";
45 | for ( var i in months ) {
46 | monthHtml += "\n";
47 | }
48 |
49 | document.getElementById("monthList").innerHTML = monthHtml;
50 |
51 | // CREATE TIMER LIST
52 | ajax_get(topUrl + '/roonAPI/getTimers', function(data) {
53 | var html = "";
54 | var curDate = new Date();
55 | var timerDate;
56 |
57 | html += "\n";
58 | html += "| Zone | \n";
59 | html += "Command | \n";
60 | html += "Date/Time | \n";
61 | html += "Duration | \n";
62 | html += "Is Repeat | \n";
63 | html += "Remove | \n";
64 | html += "
\n";
65 |
66 | if (data != null ) {
67 | for ( var i in data.timers ) {
68 |
69 | html += "\n";
70 | html += "| " + zone_list[data.timers[i].zoneId] + " | \n";
71 | html += "" + data.timers[i].command + " | \n";
72 | html += "" + dateTimeFromDate(data.timers[i].time) + " | \n";
73 | html += "" + durationFromDate(data.timers[i].time) + " | \n";
74 | html += "";
75 | if ( data.timers[i].isRepeat == "1" ) {
76 | html += "yes";
77 | } else {
78 | html += "no";
79 | }
80 | html += " | \n";
81 | html += "remove | \n";
83 | html += "
\n";
84 | }
85 | }
86 |
87 | html += "