500 lines
13 KiB
Vue
500 lines
13 KiB
Vue
<template>
|
|
<div class="music-snapcast-container">
|
|
<Loading v-if="loading" />
|
|
|
|
<div class="info">
|
|
<Modal title="Server info" ref="modalHost">
|
|
<ModalHost :info="hosts[selectedHost]" v-if="selectedHost" />
|
|
</Modal>
|
|
</div>
|
|
|
|
<div class="info">
|
|
<Modal title="Group info" ref="modalGroup">
|
|
<ModalGroup :group="hosts[selectedHost].groups[selectedGroup]" :streams="hosts[selectedHost].streams"
|
|
:clients="clientsByHost[selectedHost]" :loading="loading" @add-client="addClientToGroup"
|
|
@remove-client="removeClientFromGroup" @stream-change="streamChange"
|
|
@rename-group="renameGroup($event)" v-if="selectedGroup" />
|
|
</Modal>
|
|
</div>
|
|
|
|
<div class="info">
|
|
<Modal title="Client info" ref="modalClient">
|
|
<ModalClient :client="hosts[selectedHost].groups[selectedGroup].clients[selectedClient]" :loading="loading"
|
|
@remove-client="removeClient" @rename-client="renameClient($event)" v-if="selectedClient" />
|
|
</Modal>
|
|
</div>
|
|
|
|
<Host v-for="(host, id) in hosts" :key="id"
|
|
:server="host.server"
|
|
:streams="host.streams"
|
|
:groups="host.groups"
|
|
@group-mute-toggle="groupMute($event)"
|
|
@client-mute-toggle="clientMute($event)"
|
|
@client-volume-change="clientSetVolume($event)"
|
|
@modal-show="onModalShow($event)" />
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import Modal from "@/components/Modal";
|
|
import Utils from "@/Utils";
|
|
import Host from "@/components/panels/MusicSnapcast/Host";
|
|
import ModalHost from "@/components/panels/MusicSnapcast/modals/Host";
|
|
import ModalGroup from "@/components/panels/MusicSnapcast/modals/Group";
|
|
import ModalClient from "@/components/panels/MusicSnapcast/modals/Client";
|
|
import Loading from "@/components/Loading";
|
|
|
|
export default {
|
|
name: "MusicSnapcast",
|
|
mixins: [Utils],
|
|
components: {Loading, Modal, Host, ModalHost, ModalGroup, ModalClient},
|
|
|
|
data: function() {
|
|
return {
|
|
loading: false,
|
|
hosts: {},
|
|
ports: {},
|
|
selectedHost: null,
|
|
selectedGroup: null,
|
|
selectedClient: null,
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
clientsByHost() {
|
|
return Object.entries(this.hosts).reduce((hosts, [name, info]) => {
|
|
hosts[name] = {}
|
|
|
|
Object.values(info.groups).forEach((group) => {
|
|
Object.entries(group.clients).forEach(([clientId, client]) => {
|
|
hosts[name][clientId] = client
|
|
})
|
|
})
|
|
|
|
return hosts
|
|
}, {})
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
parseServerStatus(status) {
|
|
status.server.host.port = this.ports[status.server.host.name]
|
|
this.hosts[status.server.host.name] = {
|
|
...status,
|
|
groups: status.groups.map((group) => {
|
|
return {
|
|
...group,
|
|
clients: group.clients.reduce((clients, client) => {
|
|
clients[client.id] = client
|
|
return clients
|
|
}, {}),
|
|
}
|
|
}).reduce((groups, group) => {
|
|
groups[group.id] = group
|
|
return groups
|
|
}, {}),
|
|
|
|
streams: status.streams.reduce((streams, stream) => {
|
|
streams[stream.id] = stream
|
|
return streams
|
|
}, {}),
|
|
}
|
|
},
|
|
|
|
async refresh() {
|
|
this.loading = true
|
|
|
|
try {
|
|
const hosts = await this.request('music.snapcast.get_backend_hosts')
|
|
const statuses = await Promise.all(Object.keys(hosts).map(
|
|
async (host) => this.request('music.snapcast.status', {host: host, port: hosts[host]})
|
|
))
|
|
|
|
this.hosts = {}
|
|
statuses.forEach((status) => {
|
|
this.ports[status.server.host.name] = hosts[status.server.host.name]
|
|
this.parseServerStatus(status)
|
|
})
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
},
|
|
|
|
async refreshHost(host) {
|
|
if (!(host in this.hosts))
|
|
return
|
|
|
|
this.parseServerStatus(await this.request('music.snapcast.status', {
|
|
host: host,
|
|
port: this.ports[host]
|
|
}))
|
|
},
|
|
|
|
async addClientToGroup(clientId) {
|
|
this.loading = true
|
|
|
|
try {
|
|
if (!this.selectedHost || !this.selectedGroup || !(clientId in this.clientsByHost[this.selectedHost]))
|
|
return
|
|
|
|
const clients = [...new Set([clientId,
|
|
...Object.keys(this.hosts[this.selectedHost].groups[this.selectedGroup].clients)])]
|
|
|
|
await this.request('music.snapcast.group_set_clients', {
|
|
host: this.selectedHost,
|
|
port: this.ports[this.selectedHost],
|
|
group: this.selectedGroup,
|
|
clients: clients,
|
|
})
|
|
|
|
await this.refreshHost(this.selectedHost)
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
},
|
|
|
|
async removeClientFromGroup(clientId) {
|
|
this.loading = true
|
|
|
|
try {
|
|
if (!this.selectedHost || !this.selectedGroup || !(clientId in this.clientsByHost[this.selectedHost]))
|
|
return
|
|
|
|
const clients = new Set([...Object.keys(this.hosts[this.selectedHost].groups[this.selectedGroup].clients)])
|
|
if (!clients.has(clientId))
|
|
return
|
|
|
|
clients.delete(clientId)
|
|
|
|
await this.request('music.snapcast.group_set_clients', {
|
|
host: this.selectedHost,
|
|
port: this.ports[this.selectedHost],
|
|
group: this.selectedGroup,
|
|
clients: [...clients],
|
|
})
|
|
|
|
await this.refreshHost(this.selectedHost)
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
},
|
|
|
|
async renameGroup(name) {
|
|
this.loading = true
|
|
|
|
try {
|
|
if (!this.selectedHost || !this.selectedGroup)
|
|
return
|
|
|
|
await this.request('music.snapcast.set_group_name', {
|
|
host: this.selectedHost,
|
|
port: this.ports[this.selectedHost],
|
|
group: this.selectedGroup,
|
|
name: name,
|
|
})
|
|
|
|
await this.refreshHost(this.selectedHost)
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
},
|
|
|
|
async renameClient(name) {
|
|
this.loading = true
|
|
|
|
try {
|
|
if (!this.selectedHost || !this.selectedClient)
|
|
return
|
|
|
|
await this.request('music.snapcast.set_client_name', {
|
|
host: this.selectedHost,
|
|
port: this.ports[this.selectedHost],
|
|
client: this.selectedClient,
|
|
name: name,
|
|
})
|
|
|
|
await this.refreshHost(this.selectedHost)
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
},
|
|
|
|
async removeClient() {
|
|
this.loading = true
|
|
|
|
try {
|
|
if (!(this.selectedHost && this.selectedClient))
|
|
return
|
|
|
|
await this.request('music.snapcast.delete_client', {
|
|
host: this.selectedHost,
|
|
port: this.ports[this.selectedHost],
|
|
client: this.selectedClient,
|
|
})
|
|
|
|
this.$refs.modalClient.close()
|
|
await this.refreshHost(this.selectedHost)
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
},
|
|
|
|
async streamChange(streamId) {
|
|
this.loading = true
|
|
|
|
try {
|
|
await this.request('music.snapcast.group_set_stream', {
|
|
host: this.selectedHost,
|
|
port: this.ports[this.selectedHost],
|
|
group: this.selectedGroup,
|
|
stream_id: streamId,
|
|
})
|
|
|
|
await this.refreshHost(this.selectedHost)
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
},
|
|
|
|
onClientUpdate(event) {
|
|
Object.keys(this.hosts[event.host].groups).forEach((groupId) => {
|
|
if (event.client.id in this.hosts[event.host].groups[groupId].clients) {
|
|
this.hosts[event.host].groups[groupId].clients[event.client.id] = event.client
|
|
}
|
|
})
|
|
},
|
|
|
|
onGroupStreamChange(event) {
|
|
this.hosts[event.host].groups[event.group].stream_id = event.stream
|
|
},
|
|
|
|
onServerUpdate(event) {
|
|
this.parseServerStatus(event.server)
|
|
},
|
|
|
|
onStreamUpdate(event) {
|
|
this.hosts[event.host].streams[event.stream.id] = event.stream
|
|
},
|
|
|
|
onClientVolumeChange(event) {
|
|
Object.keys(this.hosts[event.host].groups).forEach((groupId) => {
|
|
if (!(event.client in this.hosts[event.host].groups[groupId].clients))
|
|
return
|
|
|
|
if (event.volume != null)
|
|
this.hosts[event.host].groups[groupId].clients[event.client].config.volume.percent = event.volume
|
|
|
|
if (event.muted != null)
|
|
this.hosts[event.host].groups[groupId].clients[event.client].config.volume.muted = event.muted
|
|
})
|
|
},
|
|
|
|
onGroupMuteChange(event) {
|
|
this.hosts[event.host].groups[event.group].muted = event.muted
|
|
},
|
|
|
|
modalShow(event) {
|
|
switch(event.type) {
|
|
case 'host':
|
|
this.modal[event.type].info = this.hosts[event.host]
|
|
break
|
|
case 'group':
|
|
this.modal[event.type].info.server = this.hosts[event.host].server
|
|
this.modal[event.type].info.group = this.hosts[event.host].groups[event.group]
|
|
this.modal[event.type].info.streams = this.hosts[event.host].streams
|
|
this.modal[event.type].info.clients = {}
|
|
|
|
for (const group of Object.values(this.hosts[event.host].groups)) {
|
|
for (const client of Object.values(group.clients)) {
|
|
this.modal[event.type].info.clients[client.id] = client
|
|
}
|
|
}
|
|
|
|
break
|
|
case 'client':
|
|
this.modal[event.type].info = this.hosts[event.host].groups[event.group].clients[event.client]
|
|
this.modal[event.type].info.server = this.hosts[event.host].server
|
|
break
|
|
}
|
|
|
|
this.modal[event.type].visible = true
|
|
},
|
|
|
|
async groupMute(event) {
|
|
await this.request('music.snapcast.mute', {
|
|
group: event.group,
|
|
host: event.host,
|
|
port: this.ports[event.host],
|
|
mute: event.muted,
|
|
})
|
|
|
|
await this.refreshHost(event.host)
|
|
},
|
|
|
|
async clientMute(event) {
|
|
await this.request('music.snapcast.mute', {
|
|
client: event.client,
|
|
host: event.host,
|
|
port: this.ports[event.host],
|
|
mute: event.muted,
|
|
})
|
|
|
|
await this.refreshHost(event.host)
|
|
},
|
|
|
|
async clientSetVolume(event) {
|
|
await this.request('music.snapcast.volume', {
|
|
client: event.client,
|
|
host: event.host,
|
|
port: this.ports[event.host],
|
|
volume: event.volume,
|
|
})
|
|
|
|
await this.refreshHost(event.host)
|
|
},
|
|
|
|
onModalShow(event) {
|
|
switch (event.type) {
|
|
case 'host':
|
|
this.selectedHost = event.host
|
|
this.$refs.modalHost.show()
|
|
break
|
|
|
|
case 'group':
|
|
this.selectedHost = event.host
|
|
this.selectedGroup = event.group
|
|
this.$refs.modalGroup.show()
|
|
break
|
|
|
|
case 'client':
|
|
this.selectedHost = event.host
|
|
this.selectedGroup = event.group
|
|
this.selectedClient = event.client
|
|
this.$refs.modalClient.show()
|
|
break
|
|
}
|
|
}
|
|
},
|
|
|
|
mounted() {
|
|
this.refresh()
|
|
|
|
this.subscribe(this.onClientUpdate, null,
|
|
'platypush.message.event.music.snapcast.ClientConnectedEvent',
|
|
'platypush.message.event.music.snapcast.ClientDisconnectedEvent',
|
|
'platypush.message.event.music.snapcast.ClientNameChangeEvent')
|
|
|
|
this.subscribe(this.onGroupStreamChange, null, 'platypush.message.event.music.snapcast.GroupStreamChangeEvent')
|
|
this.subscribe(this.onServerUpdate, null, 'platypush.message.event.music.snapcast.ServerUpdateEvent')
|
|
this.subscribe(this.onStreamUpdate, null, 'platypush.message.event.music.snapcast.StreamUpdateEvent')
|
|
this.subscribe(this.onClientVolumeChange, null, 'platypush.message.event.music.snapcast.ClientVolumeChangeEvent')
|
|
this.subscribe(this.onGroupMuteChange, null, 'platypush.message.event.music.snapcast.GroupMuteChangeEvent')
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.music-snapcast-container {
|
|
width: 100%;
|
|
overflow: auto;
|
|
background: $background-color;
|
|
}
|
|
|
|
::v-deep(.info) {
|
|
.modal {
|
|
.content {
|
|
width: 90%;
|
|
max-width: 800px;
|
|
}
|
|
|
|
.body {
|
|
padding: 0;
|
|
}
|
|
}
|
|
|
|
.row {
|
|
display: flex;
|
|
align-items: center;
|
|
border-radius: .75em;
|
|
padding: 1em;
|
|
|
|
@include until($tablet) {
|
|
flex-direction: column;
|
|
border-bottom: $default-border;
|
|
}
|
|
|
|
@include from($desktop) {
|
|
padding: 1em 2em;
|
|
}
|
|
|
|
.label {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.value {
|
|
display: flex;
|
|
|
|
@include from($tablet) {
|
|
justify-content: right;
|
|
}
|
|
|
|
@include until($tablet) {
|
|
width: 100%;
|
|
margin-left: 0;
|
|
}
|
|
}
|
|
|
|
@include until($tablet) {
|
|
.label {
|
|
width: 100%;
|
|
display: flex;
|
|
}
|
|
}
|
|
|
|
&:nth-child(odd) {
|
|
background: $background-color;
|
|
}
|
|
|
|
&:nth-child(even) {
|
|
background: $default-bg-3;
|
|
}
|
|
|
|
&:hover {
|
|
background: $hover-bg;
|
|
}
|
|
}
|
|
|
|
.buttons {
|
|
background: initial;
|
|
margin-top: 1.5em;
|
|
padding-top: 1.5em;
|
|
border-top: $default-border-2;
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
}
|
|
|
|
@media screen and (max-width: calc(#{$tablet} - 1)) {
|
|
.music-snapcast-container {
|
|
.modal {
|
|
width: 95vw;
|
|
}
|
|
}
|
|
}
|
|
|
|
@media screen and (min-width: $tablet) {
|
|
.music-snapcast-container {
|
|
.modal {
|
|
width: 70vw;
|
|
}
|
|
}
|
|
}
|
|
|
|
@media screen and (min-width: $desktop) {
|
|
.music-snapcast-container {
|
|
.modal {
|
|
width: 45vw;
|
|
}
|
|
}
|
|
}
|
|
</style> |