2018-03-12 21:39:27 +13:00
|
|
|
|
2019-02-10 19:51:33 +13:00
|
|
|
import React from 'react';
|
2018-09-24 21:37:59 +12:00
|
|
|
import { connect } from 'react-redux';
|
|
|
|
|
import { bindActionCreators } from 'redux';
|
2018-10-24 21:34:41 +13:00
|
|
|
import Link from './Link';
|
2018-09-24 21:37:59 +12:00
|
|
|
|
|
|
|
|
import VolumeControl from './Fields/VolumeControl';
|
2018-10-11 08:25:58 +13:00
|
|
|
import MuteControl from './Fields/MuteControl';
|
2018-09-24 21:37:59 +12:00
|
|
|
import LatencyControl from './Fields/LatencyControl';
|
|
|
|
|
import TextField from './Fields/TextField';
|
|
|
|
|
import DropdownField from './Fields/DropdownField';
|
|
|
|
|
import Icon from './Icon';
|
|
|
|
|
|
|
|
|
|
import * as helpers from '../helpers';
|
|
|
|
|
import * as coreActions from '../services/core/actions';
|
|
|
|
|
import * as uiActions from '../services/ui/actions';
|
|
|
|
|
import * as pusherActions from '../services/pusher/actions';
|
|
|
|
|
import * as snapcastActions from '../services/snapcast/actions';
|
2018-03-12 21:39:27 +13:00
|
|
|
|
|
|
|
|
class Snapcast extends React.Component{
|
|
|
|
|
|
|
|
|
|
constructor(props){
|
2018-03-23 16:54:56 +13:00
|
|
|
super(props);
|
2018-09-24 21:37:59 +12:00
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
|
clients_expanded: []
|
|
|
|
|
}
|
2018-03-12 21:39:27 +13:00
|
|
|
}
|
|
|
|
|
|
2018-03-21 21:40:47 +13:00
|
|
|
componentDidMount(){
|
2018-03-26 10:36:13 +13:00
|
|
|
if (this.props.pusher_connected && this.props.snapcast_enabled){
|
2018-09-24 13:21:08 +12:00
|
|
|
this.props.snapcastActions.getServer();
|
2018-03-21 21:40:47 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentWillReceiveProps(newProps){
|
2018-03-26 10:36:13 +13:00
|
|
|
|
|
|
|
|
// Just connected
|
2018-04-03 17:01:40 +12:00
|
|
|
if (newProps.snapcast_enabled && !this.props.pusher_connected && newProps.pusher_connected){
|
2018-09-24 13:21:08 +12:00
|
|
|
this.props.snapcastActions.getServer();
|
2018-04-19 06:20:54 +12:00
|
|
|
|
|
|
|
|
// Just enabled
|
|
|
|
|
// This is the more probable scenario as we don't know if we're enabled until pusher connects
|
|
|
|
|
// and then gets the config from the server
|
|
|
|
|
} else if (!this.props.snapcast_enabled && newProps.snapcast_enabled && newProps.pusher_connected){
|
2018-09-24 13:21:08 +12:00
|
|
|
this.props.snapcastActions.getServer();
|
2018-03-21 21:40:47 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-24 21:37:59 +12:00
|
|
|
toggleClientExpanded(client_id){
|
|
|
|
|
var clients_expanded = this.state.clients_expanded;
|
|
|
|
|
var index = clients_expanded.indexOf(client_id);
|
|
|
|
|
|
|
|
|
|
if (index >= 0){
|
|
|
|
|
clients_expanded.splice(index, 1);
|
|
|
|
|
} else {
|
|
|
|
|
clients_expanded.push(client_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.setState({clients_expanded: clients_expanded});
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-01 08:57:09 +12:00
|
|
|
renderClientsList(group, groups){
|
|
|
|
|
|
2018-09-27 20:43:42 +12:00
|
|
|
if (!this.props.show_disconnected_clients && group.clients){
|
|
|
|
|
var clients = helpers.applyFilter('connected', true, group.clients);
|
|
|
|
|
} else {
|
|
|
|
|
var clients = group.clients;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!clients || clients.length <= 0){
|
2018-05-01 08:57:09 +12:00
|
|
|
return (
|
2018-10-13 15:57:01 +13:00
|
|
|
<div className="text mid_grey-text">
|
2018-05-01 08:57:09 +12:00
|
|
|
No clients
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
2018-09-24 16:10:42 +12:00
|
|
|
<div className="list snapcast__clients">
|
2018-05-01 08:57:09 +12:00
|
|
|
{
|
2018-09-27 20:43:42 +12:00
|
|
|
clients.map(client => {
|
2019-02-12 20:20:25 +13:00
|
|
|
var class_name = "list__item list__item--no-interaction snapcast__client";
|
2018-09-24 21:37:59 +12:00
|
|
|
if (client.connected){
|
|
|
|
|
class_name += " snapcast__client--connected";
|
|
|
|
|
} else {
|
|
|
|
|
class_name += " snapcast__client--disconnected";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.state.clients_expanded.includes(client.id)){
|
|
|
|
|
return (
|
|
|
|
|
<div className={class_name+" snapcast__client--expanded"} key={client.id}>
|
2018-09-25 17:00:04 +12:00
|
|
|
<div className="snapcast__client__header" onClick={e => this.toggleClientExpanded(client.id)}>
|
2018-09-27 20:43:42 +12:00
|
|
|
{client.name}
|
2018-09-25 17:00:04 +12:00
|
|
|
<div className="snapcast__client__header__icons">
|
|
|
|
|
{!client.connected ? <Icon name="power_off" className="disconnected" /> : null}
|
|
|
|
|
<Icon name="expand_less" />
|
|
|
|
|
</div>
|
2018-09-24 21:37:59 +12:00
|
|
|
</div>
|
2018-09-25 17:00:04 +12:00
|
|
|
<div className="snapcast__client__details">
|
2019-02-12 20:20:25 +13:00
|
|
|
<label className="field">
|
2018-09-25 17:00:04 +12:00
|
|
|
<div className="name">
|
|
|
|
|
Name
|
|
|
|
|
</div>
|
|
|
|
|
<div className="input">
|
|
|
|
|
<TextField
|
|
|
|
|
onChange={value => this.props.snapcastActions.setClientName(client.id, value)}
|
2018-09-27 20:43:42 +12:00
|
|
|
value={client.name}
|
2018-09-25 17:00:04 +12:00
|
|
|
/>
|
|
|
|
|
</div>
|
2019-02-12 20:20:25 +13:00
|
|
|
</label>
|
|
|
|
|
<label className="field dropdown">
|
2018-09-25 17:00:04 +12:00
|
|
|
<div className="name">
|
|
|
|
|
Group
|
|
|
|
|
</div>
|
|
|
|
|
<div className="input">
|
|
|
|
|
<select onChange={e => this.props.snapcastActions.setClientGroup(client.id, e.target.value)} value={group.id}>
|
|
|
|
|
{
|
|
|
|
|
groups.map(group => {
|
|
|
|
|
return (
|
|
|
|
|
<option value={group.id} key={group.id}>
|
|
|
|
|
{group.name ? group.name : 'Group '+group.id.substring(0,3)}
|
|
|
|
|
</option>
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
<option value={group.id}>
|
|
|
|
|
New group
|
|
|
|
|
</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
2019-02-12 20:20:25 +13:00
|
|
|
</label>
|
2018-09-25 17:00:04 +12:00
|
|
|
<div className="snapcast__client__volume field">
|
|
|
|
|
<div className="name">
|
|
|
|
|
Volume
|
|
|
|
|
</div>
|
|
|
|
|
<div className="input">
|
2018-10-11 08:25:58 +13:00
|
|
|
<MuteControl
|
|
|
|
|
className="snapcast__client__mute-control"
|
|
|
|
|
mute={client.mute}
|
|
|
|
|
onMuteChange={mute => this.props.snapcastActions.setClientMute(client.id, mute)}
|
|
|
|
|
/>
|
2018-09-25 17:00:04 +12:00
|
|
|
<VolumeControl
|
|
|
|
|
className="snapcast__client__volume-control"
|
2018-09-27 20:43:42 +12:00
|
|
|
volume={client.volume}
|
2018-09-25 17:00:04 +12:00
|
|
|
onVolumeChange={percent => this.props.snapcastActions.setClientVolume(client.id, percent)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="snapcast__client__latency field">
|
|
|
|
|
<div className="name">
|
|
|
|
|
Latency
|
|
|
|
|
</div>
|
|
|
|
|
<div className="input">
|
|
|
|
|
<LatencyControl
|
2019-02-12 20:20:25 +13:00
|
|
|
max="150"
|
2018-09-27 20:43:42 +12:00
|
|
|
value={client.latency}
|
2018-09-25 17:00:04 +12:00
|
|
|
onChange={value => this.props.snapcastActions.setClientLatency(client.id, parseInt(value))}
|
|
|
|
|
/>
|
|
|
|
|
<TextField
|
|
|
|
|
className="tiny"
|
|
|
|
|
type="number"
|
|
|
|
|
onChange={value => this.props.snapcastActions.setClientLatency(client.id, parseInt(value))}
|
2018-09-27 20:43:42 +12:00
|
|
|
value={String(client.latency)}
|
2018-09-25 17:00:04 +12:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2018-09-24 21:37:59 +12:00
|
|
|
</div>
|
2018-05-01 08:57:09 +12:00
|
|
|
</div>
|
2018-09-24 21:37:59 +12:00
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
return (
|
2018-09-25 17:00:04 +12:00
|
|
|
<div className={class_name+" snapcast__client--collapsed"} key={client.id}>
|
|
|
|
|
<div className="snapcast__client__header" onClick={e => this.toggleClientExpanded(client.id)}>
|
2018-09-27 20:43:42 +12:00
|
|
|
{client.name}
|
2018-09-25 17:00:04 +12:00
|
|
|
<div className="snapcast__client__header__icons">
|
|
|
|
|
{!client.connected ? <Icon name="power_off" className="disconnected" /> : null}
|
|
|
|
|
<Icon name="expand_more" />
|
|
|
|
|
</div>
|
2018-09-24 21:37:59 +12:00
|
|
|
</div>
|
2018-05-01 08:57:09 +12:00
|
|
|
</div>
|
2018-09-24 21:37:59 +12:00
|
|
|
);
|
|
|
|
|
}
|
2018-05-01 08:57:09 +12:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-12 21:39:27 +13:00
|
|
|
render(){
|
2018-04-03 17:01:40 +12:00
|
|
|
if (!this.props.snapcast_enabled){
|
|
|
|
|
return (
|
2018-09-18 16:59:10 +12:00
|
|
|
<p className="message warning">To enable Snapcast, edit your <code>mopidy.conf</code> file</p>
|
2018-04-03 17:01:40 +12:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-29 21:10:57 +12:00
|
|
|
var streams = [];
|
2018-09-27 20:43:42 +12:00
|
|
|
for (var id in this.props.streams){
|
|
|
|
|
if (this.props.streams.hasOwnProperty(id)){
|
|
|
|
|
streams.push(this.props.streams[id]);
|
2018-04-29 21:10:57 +12:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-22 16:59:04 +13:00
|
|
|
var groups = [];
|
2018-09-27 20:43:42 +12:00
|
|
|
for (var id in this.props.groups){
|
|
|
|
|
if (this.props.groups.hasOwnProperty(id)){
|
|
|
|
|
groups.push(this.props.groups[id]);
|
2018-03-21 22:08:53 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-12 21:39:27 +13:00
|
|
|
return (
|
|
|
|
|
<div className="snapcast">
|
2018-04-29 10:03:19 +12:00
|
|
|
|
|
|
|
|
<div className="field checkbox">
|
2018-07-01 21:08:35 +12:00
|
|
|
<div className="name">
|
|
|
|
|
Display
|
|
|
|
|
</div>
|
2018-04-29 10:03:19 +12:00
|
|
|
<div className="input">
|
2018-07-01 21:08:35 +12:00
|
|
|
<button
|
2018-12-10 21:27:22 +13:00
|
|
|
className="button button--default button--small"
|
2018-09-24 13:21:08 +12:00
|
|
|
onClick={e => this.props.snapcastActions.getServer()}>
|
2018-07-01 21:08:35 +12:00
|
|
|
Refresh
|
|
|
|
|
</button>
|
2018-04-29 10:03:19 +12:00
|
|
|
<label>
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
2018-09-24 13:21:08 +12:00
|
|
|
name="show_disconnected_clients"
|
|
|
|
|
checked={this.props.show_disconnected_clients}
|
|
|
|
|
onChange={e => this.props.uiActions.set({ snapcast_show_disconnected_clients: !this.props.show_disconnected_clients })} />
|
2018-04-29 10:03:19 +12:00
|
|
|
<span className="label">
|
|
|
|
|
Show disconnected clients
|
|
|
|
|
</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2018-09-24 16:10:42 +12:00
|
|
|
<div className="snapcast__groups">
|
2018-07-01 21:08:35 +12:00
|
|
|
{
|
|
|
|
|
groups.map(group => {
|
2018-05-02 16:14:53 +12:00
|
|
|
|
2018-09-27 20:43:42 +12:00
|
|
|
group = helpers.collate(group, {clients: this.props.clients});
|
|
|
|
|
|
2018-07-01 21:08:35 +12:00
|
|
|
// Average our clients' volume for an overall group volume
|
|
|
|
|
var group_volume = 0;
|
|
|
|
|
for (var i = 0; i < group.clients.length; i++){
|
|
|
|
|
var client = group.clients[i];
|
2018-09-27 20:43:42 +12:00
|
|
|
group_volume += client.volume;
|
2018-07-01 21:08:35 +12:00
|
|
|
}
|
|
|
|
|
group_volume = group_volume / group.clients.length;
|
2018-05-02 16:14:53 +12:00
|
|
|
|
2018-07-01 21:08:35 +12:00
|
|
|
return (
|
2018-09-24 16:10:42 +12:00
|
|
|
<div className="snapcast__group" key={group.id}>
|
2018-07-01 21:08:35 +12:00
|
|
|
<div className="field">
|
|
|
|
|
<div className="name">
|
|
|
|
|
Name
|
|
|
|
|
</div>
|
|
|
|
|
<div className="input">
|
|
|
|
|
<div className="text">
|
2018-09-27 20:43:42 +12:00
|
|
|
{group.name}
|
2018-10-13 15:57:01 +13:00
|
|
|
<span className="mid_grey-text">({group.id})</span>
|
2018-07-01 21:08:35 +12:00
|
|
|
</div>
|
2018-04-30 20:34:15 +12:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2018-07-01 21:08:35 +12:00
|
|
|
<div className="field dropdown">
|
|
|
|
|
<div className="name">
|
|
|
|
|
Stream
|
|
|
|
|
</div>
|
|
|
|
|
<div className="input">
|
2018-09-24 13:21:08 +12:00
|
|
|
<select onChange={e => this.props.snapcastActions.setGroupStream(group.id, e.target.value)} value={group.stream_id}>
|
2018-07-01 21:08:35 +12:00
|
|
|
{
|
|
|
|
|
streams.map(stream => {
|
|
|
|
|
return (
|
|
|
|
|
<option value={stream.id} key={stream.id}>
|
|
|
|
|
{stream.id} ({stream.status})
|
|
|
|
|
</option>
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
2018-05-01 21:03:05 +12:00
|
|
|
</div>
|
2018-07-01 21:08:35 +12:00
|
|
|
<div className="field">
|
|
|
|
|
<div className="name">
|
|
|
|
|
Volume
|
|
|
|
|
</div>
|
|
|
|
|
<div className="input">
|
2018-10-11 08:25:58 +13:00
|
|
|
<MuteControl
|
|
|
|
|
className="snapcast__group__mute-control"
|
|
|
|
|
mute={group.muted}
|
|
|
|
|
onMuteChange={mute => this.props.snapcastActions.setGroupMute(group.id, mute)}
|
|
|
|
|
/>
|
2018-07-01 21:08:35 +12:00
|
|
|
<VolumeControl
|
2018-09-24 16:10:42 +12:00
|
|
|
className="snapcast__group__volume-control"
|
2018-07-01 21:08:35 +12:00
|
|
|
volume={group_volume}
|
2018-09-24 13:21:08 +12:00
|
|
|
onVolumeChange={(percent, old_percent) => this.props.snapcastActions.setGroupVolume(group.id, percent, old_percent)}
|
2018-07-01 21:08:35 +12:00
|
|
|
/>
|
|
|
|
|
</div>
|
2018-04-18 17:01:57 +12:00
|
|
|
</div>
|
2018-07-01 21:08:35 +12:00
|
|
|
<div className="field">
|
|
|
|
|
<div className="name">
|
|
|
|
|
Clients
|
|
|
|
|
</div>
|
|
|
|
|
<div className="input">
|
|
|
|
|
{this.renderClientsList(group, groups)}
|
|
|
|
|
</div>
|
2018-04-18 17:01:57 +12:00
|
|
|
</div>
|
2018-03-23 16:54:56 +13:00
|
|
|
</div>
|
2018-07-01 21:08:35 +12:00
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
</div>
|
2018-03-12 21:39:27 +13:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mapStateToProps = (state, ownProps) => {
|
|
|
|
|
return {
|
2018-03-26 10:36:13 +13:00
|
|
|
snapcast_enabled: state.pusher.config.snapcast_enabled,
|
2018-03-21 21:40:47 +13:00
|
|
|
pusher_connected: state.pusher.connected,
|
2018-09-24 13:21:08 +12:00
|
|
|
show_disconnected_clients: (state.ui.snapcast_show_disconnected_clients !== undefined ? state.ui.snapcast_show_disconnected_clients : false),
|
|
|
|
|
streams: (state.snapcast.streams ? state.snapcast.streams : null),
|
|
|
|
|
groups: (state.snapcast.groups ? state.snapcast.groups : null),
|
2018-10-09 21:39:38 +13:00
|
|
|
clients: (state.snapcast.clients ? state.snapcast.clients : null)
|
2018-03-12 21:39:27 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mapDispatchToProps = (dispatch) => {
|
|
|
|
|
return {
|
|
|
|
|
coreActions: bindActionCreators(coreActions, dispatch),
|
|
|
|
|
uiActions: bindActionCreators(uiActions, dispatch),
|
2018-09-24 13:21:08 +12:00
|
|
|
snapcastActions: bindActionCreators(snapcastActions, dispatch),
|
2018-03-12 21:39:27 +13:00
|
|
|
pusherActions: bindActionCreators(pusherActions, dispatch)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default connect(mapStateToProps, mapDispatchToProps)(Snapcast)
|