"use strict";
class Host {
constructor(json) {
this.arch = "";
this.ip = "";
this.mac = "";
this.name = "";
this.os = "";
this.fromJson(json);
}
fromJson(json) {
this.arch = json.arch;
this.ip = json.ip;
this.mac = json.mac;
this.name = json.name;
this.os = json.os;
}
}
class Client {
constructor(json) {
this.id = "";
this.connected = false;
this.fromJson(json);
}
fromJson(json) {
this.id = json.id;
this.host = new Host(json.host);
let jsnapclient = json.snapclient;
this.snapclient = { name: jsnapclient.name, protocolVersion: jsnapclient.protocolVersion, version: jsnapclient.version };
let jconfig = json.config;
this.config = { instance: jconfig.instance, latency: jconfig.latency, name: jconfig.name, volume: { muted: jconfig.volume.muted, percent: jconfig.volume.percent } };
this.lastSeen = { sec: json.lastSeen.sec, usec: json.lastSeen.usec };
this.connected = Boolean(json.connected);
}
}
class Group {
constructor(json) {
this.name = "";
this.id = "";
this.stream_id = "";
this.muted = false;
this.clients = [];
this.fromJson(json);
}
fromJson(json) {
this.name = json.name;
this.id = json.id;
this.stream_id = json.stream_id;
this.muted = Boolean(json.muted);
for (let client of json.clients)
this.clients.push(new Client(client));
}
getClient(id) {
for (let client of this.clients) {
if (client.id == id)
return client;
}
return null;
}
}
class Stream {
constructor(json) {
this.id = "";
this.status = "";
this.fromJson(json);
}
fromJson(json) {
this.id = json.id;
this.status = json.status;
let juri = json.uri;
this.uri = { raw: juri.raw, scheme: juri.scheme, host: juri.host, path: juri.path, fragment: juri.fragment, query: juri.query };
}
}
class Server {
constructor(json) {
this.groups = [];
this.streams = [];
if (json)
this.fromJson(json);
}
fromJson(json) {
this.groups = [];
for (let jgroup of json.groups)
this.groups.push(new Group(jgroup));
let jsnapserver = json.server.snapserver;
this.server = { host: new Host(json.server.host), snapserver: { controlProtocolVersion: jsnapserver.controlProtocolVersion, name: jsnapserver.name, protocolVersion: jsnapserver.protocolVersion, version: jsnapserver.version } };
this.streams = [];
for (let jstream of json.streams) {
this.streams.push(new Stream(jstream));
}
}
getClient(id) {
for (let group of this.groups) {
let client = group.getClient(id);
if (client)
return client;
}
return null;
}
getGroup(id) {
for (let group of this.groups) {
if (group.id == id)
return group;
}
return null;
}
getStream(id) {
for (let stream of this.streams) {
if (stream.id == id)
return stream;
}
return null;
}
}
class SnapControl {
constructor(host, port) {
this.server = new Server();
this.connection = new WebSocket('ws://' + host + ':' + port + '/jsonrpc');
this.msg_id = 0;
this.status_req_id = -1;
// console.log(navigator);
this.connection.onmessage = (msg) => this.onMessage(msg.data);
this.connection.onopen = (ev) => { this.status_req_id = this.sendRequest('Server.GetStatus'); };
this.connection.onerror = (ev) => { alert("error: " + ev.type); }; //this.onError(ev);
}
action(answer) {
switch (answer.method) {
case 'Client.OnVolumeChanged':
let client = this.getClient(answer.params.id);
client.config.volume = answer.params.volume;
updateGroupVolume(this.getGroupFromClient(client.id));
break;
case 'Client.OnLatencyChanged':
this.getClient(answer.params.id).config.latency = answer.params.latency;
break;
case 'Client.OnNameChanged':
this.getClient(answer.params.id).config.name = answer.params.name;
break;
case 'Client.OnConnect':
case 'Client.OnDisconnect':
this.getClient(answer.params.client.id).fromJson(answer.params.client);
break;
case 'Group.OnMute':
this.getGroup(answer.params.id).muted = Boolean(answer.params.mute);
break;
case 'Group.OnStreamChanged':
this.getGroup(answer.params.id).stream_id = answer.params.stream_id;
break;
case 'Stream.OnUpdate':
this.getStream(answer.params.id).fromJson(answer.params.stream);
break;
case 'Server.OnUpdate':
this.server.fromJson(answer.params.server);
break;
default:
break;
}
}
getClient(client_id) {
return this.server.getClient(client_id);
}
getGroup(group_id) {
return this.server.getGroup(group_id);
}
getGroupVolume(group, online) {
if (group.clients.length == 0)
return 0;
let group_vol = 0;
let client_count = 0;
for (let client of group.clients) {
if (online && !client.connected)
continue;
group_vol += client.config.volume.percent;
++client_count;
}
if (client_count == 0)
return 0;
return group_vol / client_count;
}
getGroupFromClient(client_id) {
for (let group of this.server.groups)
for (let client of group.clients)
if (client.id == client_id)
return group;
return null;
}
getStream(stream_id) {
return this.server.getStream(stream_id);
}
setVolume(client_id, percent, mute) {
percent = Math.max(0, Math.min(100, percent));
let client = this.getClient(client_id);
client.config.volume.percent = percent;
if (mute != undefined)
client.config.volume.muted = mute;
this.sendRequest('Client.SetVolume', '{"id":"' + client_id + '","volume":{"muted":' + (client.config.volume.muted ? "true" : "false") + ',"percent":' + client.config.volume.percent + '}}');
}
setClientName(client_id, name) {
let client = this.getClient(client_id);
let current_name = (client.config.name != "") ? client.config.name : client.host.name;
if (name != current_name) {
this.sendRequest('Client.SetName', '{"id":"' + client_id + '","name":"' + name + '"}');
client.config.name = name;
}
}
setClientLatency(client_id, latency) {
let client = this.getClient(client_id);
let current_latency = client.config.latency;
if (latency != current_latency) {
this.sendRequest('Client.SetLatency', '{"id":"' + client_id + '","latency":' + latency + '}');
client.config.latency = latency;
}
}
deleteClient(client_id) {
let client = this.getClient(client_id);
this.sendRequest('Server.DeleteClient', '{"id": "' + client_id + '"}');
this.server.groups.forEach((g, gi) => {
g.clients.forEach((c, ci) => {
if (c.id == client_id) {
this.server.groups[gi].clients.splice(ci, 1);
}
});
});
this.server.groups.forEach((g, gi) => {
if (g.clients.length == 0) {
this.server.groups.splice(gi, 1);
}
});
show();
}
setStream(group_id, stream_id) {
this.getGroup(group_id).stream_id = stream_id;
this.sendRequest('Group.SetStream', '{"id":"' + group_id + '","stream_id":"' + stream_id + '"}');
}
setClients(group_id, clients) {
this.status_req_id = this.sendRequest('Group.SetClients', '{"clients":' + JSON.stringify(clients) + ',"id":"' + group_id + '"}');
}
muteGroup(group_id, mute) {
this.getGroup(group_id).muted = mute;
this.sendRequest('Group.SetMute', '{"id":"' + group_id + '","mute":' + (mute ? "true" : "false") + '}');
}
sendRequest(method, params) {
let msg = '{"id": ' + (++this.msg_id) + ',"jsonrpc":"2.0","method":"' + method + '"';
if (params)
msg += ',"params": ' + params;
msg += '}';
console.log("Sending: " + msg);
this.connection.send(msg);
return this.msg_id;
}
onMessage(msg) {
let answer = JSON.parse(msg);
let is_response = (answer.id != undefined);
console.log("Received " + (is_response ? "response" : "notification") + ", json: " + JSON.stringify(answer));
if (is_response) {
if (answer.id == this.status_req_id) {
this.server = new Server(answer.result.server);
show();
}
}
else {
if (Array.isArray(answer)) {
for (let a of answer) {
this.action(a);
}
}
else {
this.action(answer);
}
// TODO: don't update everything, but only the changed,
// e.g. update the values for the volume sliders
show();
}
}
}
let snapcontrol;
let snapstream = null;
let hide_offline = true;
let autoplay_done = false;
function autoplayRequested() {
return document.location.hash.match(/autoplay/) !== null;
}
function show() {
// Render the page
let play_img;
if (snapstream) {
play_img = 'stop.png';
}
else {
play_img = 'play.png';
}
let content = "";
content += "
Snapcast";
let serverVersion = snapcontrol.server.server.snapserver.version.split('.');
if ((serverVersion.length >= 2) && (+serverVersion[1] >= 21)) {
content += " ";
// Stream became ready and was not playing. If autoplay is requested, start playing.
if (!snapstream && !autoplay_done && autoplayRequested()) {
autoplay_done = true;
play();
}
}
content += "
";
content += "
";
let server = snapcontrol.server;
for (let group of server.groups) {
if (hide_offline) {
let groupActive = false;
for (let client of group.clients) {
if (client.connected) {
groupActive = true;
break;
}
}
if (!groupActive)
continue;
}
// Set mute variables
let classgroup;
let muted;
let mute_img;
if (group.muted == true) {
classgroup = 'group muted';
muted = true;
mute_img = 'mute_icon.png';
}
else {
classgroup = 'group';
muted = false;
mute_img = 'speaker_icon.png';
}
// Start group div
content += "
";
// Create stream selection dropdown
let streamselect = "";
// Group mute and refresh button
content += "
";
content += streamselect;
let clientCount = 0;
for (let client of group.clients)
if (!hide_offline || client.connected)
clientCount++;
if (clientCount > 1) {
let volume = snapcontrol.getGroupVolume(group, hide_offline);
content += "";
content += "
";
content += "";
// Create clients in group
for (let client of group.clients) {
if (!client.connected && hide_offline)
continue;
// Set name and connection state vars, start client div
let name;
let clas = 'client';
if (client.config.name != "") {
name = client.config.name;
}
else {
name = client.host.name;
}
if (client.connected == false) {
clas = 'client disconnected';
}
content += "
";
// Client mute status vars
let muted;
let mute_img;
let sliderclass;
if (client.config.volume.muted == true) {
muted = true;
sliderclass = 'slider muted';
mute_img = 'mute_icon.png';
}
else {
sliderclass = 'slider';
muted = false;
mute_img = 'speaker_icon.png';
}
// Populate client div
content += "";
content += "