WIP: Snapcast
This commit is contained in:
@ -20,7 +20,7 @@ import * as spotifyActions from '../services/spotify/actions';
|
||||
import * as lastfmActions from '../services/lastfm/actions';
|
||||
import * as geniusActions from '../services/genius/actions';
|
||||
|
||||
class Services extends React.Component {
|
||||
class Services extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@ -40,7 +40,7 @@ class Services extends React.Component {
|
||||
if (this.props.genius.authorization && (!this.props.genius.me || this.props.core.users[`genius:user:${this.props.genius.me.id}`] === undefined)) {
|
||||
this.props.geniusActions.getMe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
let changed = false;
|
||||
@ -96,10 +96,9 @@ class Services extends React.Component {
|
||||
if (!this.props.mopidy.uri_schemes || !this.props.mopidy.uri_schemes.includes('spotify:')) {
|
||||
not_installed = (
|
||||
<div>
|
||||
<p className="message warning">
|
||||
<em>Mopidy-Spotify</em>
|
||||
{' '}
|
||||
extension is not running - you will not be able to play any Spotify tracks
|
||||
<p className="message warning">
|
||||
<em>Mopidy-Spotify</em>
|
||||
{' extension is not running - you will not be able to play any Spotify tracks'}
|
||||
</p>
|
||||
<br />
|
||||
</div>
|
||||
@ -117,17 +116,14 @@ extension is not running - you will not be able to play any Spotify tracks
|
||||
onChange={(e) => this.setState({ country: e.target.value })}
|
||||
onFocus={(e) => this.setState({ input_in_focus: 'country' })}
|
||||
onBlur={(e) => this.props.spotifyActions.set({ country: e.target.value })}
|
||||
value={this.state.country}
|
||||
value={this.state.country}
|
||||
/>
|
||||
<div className="description">
|
||||
An
|
||||
{' '}
|
||||
<a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2" target="_blank">ISO 3166-1 alpha-2</a>
|
||||
{' '}
|
||||
country code (eg
|
||||
{' '}
|
||||
<em>NZ</em>
|
||||
)
|
||||
{'An '}
|
||||
<a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2" target="_blank">ISO 3166-1 alpha-2</a>
|
||||
{' country code (eg '}
|
||||
<em>NZ</em>
|
||||
{')'}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
@ -139,20 +135,16 @@ country code (eg
|
||||
onChange={(e) => this.setState({ locale: e.target.value })}
|
||||
onFocus={(e) => this.setState({ input_in_focus: 'locale' })}
|
||||
onBlur={(e) => this.props.spotifyActions.set({ locale: e.target.value })}
|
||||
value={this.state.locale}
|
||||
value={this.state.locale}
|
||||
/>
|
||||
<div className="description">
|
||||
Lowercase
|
||||
{' '}
|
||||
<a href="http://en.wikipedia.org/wiki/ISO_639" target="_blank">ISO 639 language code</a>
|
||||
{' '}
|
||||
and an uppercase
|
||||
{' '}
|
||||
<a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2" target="_blank">ISO 3166-1 alpha-2 country code</a>
|
||||
, joined by an underscore (eg
|
||||
{' '}
|
||||
<em>en_NZ</em>
|
||||
)
|
||||
{'Lowercase '}
|
||||
<a href="http://en.wikipedia.org/wiki/ISO_639" target="_blank">ISO 639 language code</a>
|
||||
{' and an uppercase '}
|
||||
<a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2" target="_blank">ISO 3166-1 alpha-2 country code</a>
|
||||
{', joined by an underscore (eg'}
|
||||
<em>en_NZ</em>
|
||||
{')'}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
@ -170,7 +162,16 @@ and an uppercase
|
||||
<div className="name">Authorization</div>
|
||||
<div className="input">
|
||||
<SpotifyAuthenticationFrame />
|
||||
{this.props.spotify.refreshing_token ? <a className="button button--working">Refreshing...</a> : <a className="button button--default" onClick={(e) => this.props.spotifyActions.refreshingToken()}>Force token refresh</a>}
|
||||
{this.props.spotify.refreshing_token ? (
|
||||
<a className="button button--working">Refreshing...</a>
|
||||
) : (
|
||||
<a
|
||||
className="button button--default"
|
||||
onClick={(e) => this.props.spotifyActions.refreshingToken()}
|
||||
>
|
||||
Force token refresh
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -201,7 +202,7 @@ and an uppercase
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.props.lastfm.authorization ? (
|
||||
{this.props.lastfm.authorization ? (
|
||||
<div className="field current-user">
|
||||
<div className="name">Current user</div>
|
||||
<div className="input">
|
||||
@ -209,7 +210,7 @@ and an uppercase
|
||||
{user}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="field">
|
||||
@ -246,7 +247,7 @@ and an uppercase
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.props.genius.authorization ? (
|
||||
{this.props.genius.authorization ? (
|
||||
<div className="field current-user">
|
||||
<div className="name">Current user</div>
|
||||
<div className="input">
|
||||
@ -254,7 +255,7 @@ and an uppercase
|
||||
{user}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="field">
|
||||
@ -278,7 +279,7 @@ and an uppercase
|
||||
type="checkbox"
|
||||
name="ssl"
|
||||
checked={this.props.core.http_streaming_enabled}
|
||||
onChange={(e) => this.props.coreActions.set({ http_streaming_enabled: !this.props.core.http_streaming_enabled })}
|
||||
onChange={(e) => this.props.coreActions.set({ http_streaming_enabled: !this.props.core.http_streaming_enabled })}
|
||||
/>
|
||||
<span className="label">
|
||||
Stream audio to this browser
|
||||
@ -292,7 +293,7 @@ and an uppercase
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) => this.props.coreActions.set({ http_streaming_url: e.target.value })}
|
||||
value={this.props.core.http_streaming_url}
|
||||
value={this.props.core.http_streaming_url}
|
||||
/>
|
||||
<div className="description">
|
||||
The full URL to your stream endpoint
|
||||
@ -303,7 +304,7 @@ and an uppercase
|
||||
);
|
||||
}
|
||||
|
||||
renderMenu() {
|
||||
renderMenu() {
|
||||
if (this.props.spotify.me && this.props.core.users[this.props.spotify.me.uri]) {
|
||||
var spotify_icon = <Thumbnail className="menu-item__thumbnail" circle size="small" images={this.props.core.users[this.props.spotify.me.uri].images} />;
|
||||
} else {
|
||||
@ -402,7 +403,7 @@ and an uppercase
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@ -2,22 +2,19 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import Link from './Link';
|
||||
|
||||
import VolumeControl from './Fields/VolumeControl';
|
||||
import MuteControl from './Fields/MuteControl';
|
||||
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';
|
||||
|
||||
class Snapcast extends React.Component {
|
||||
class Snapcast extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@ -27,21 +24,16 @@ class Snapcast extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.pusher_connected && this.props.snapcast_enabled) {
|
||||
this.props.snapcastActions.getServer();
|
||||
const { connected, actions: snapcastActions } = this.props;
|
||||
if (this.props.connected) {
|
||||
actions.getServer();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
// Just connected
|
||||
if (newProps.snapcast_enabled && !this.props.pusher_connected && newProps.pusher_connected) {
|
||||
this.props.snapcastActions.getServer();
|
||||
|
||||
// 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) {
|
||||
this.props.snapcastActions.getServer();
|
||||
componentWillReceiveProps(newProps) {
|
||||
const { connected, actions: snapcastActions } = this.props;
|
||||
if (newProps.connected && !connected) {
|
||||
actions.getServer();
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +50,11 @@ class Snapcast extends React.Component {
|
||||
this.setState({ clients_expanded });
|
||||
}
|
||||
|
||||
renderClientsList(group, groups) {
|
||||
renderClientsList(group, groups) {
|
||||
const {
|
||||
snapcastActions: actions,
|
||||
} = this.props;
|
||||
|
||||
if (!this.props.show_disconnected_clients && group.clients) {
|
||||
var clients = helpers.applyFilter('connected', true, group.clients);
|
||||
} else {
|
||||
@ -101,8 +97,8 @@ class Snapcast extends React.Component {
|
||||
</div>
|
||||
<div className="input">
|
||||
<TextField
|
||||
onChange={(value) => this.props.snapcastActions.setClientName(client.id, value)}
|
||||
value={client.name}
|
||||
onChange={(value) => actions.setClientName(client.id, value)}
|
||||
value={client.name}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
@ -111,7 +107,7 @@ class Snapcast extends React.Component {
|
||||
Group
|
||||
</div>
|
||||
<div className="input">
|
||||
<select onChange={(e) => this.props.snapcastActions.setClientGroup(client.id, e.target.value)} value={group.id}>
|
||||
<select onChange={(e) => actions.setClientGroup(client.id, e.target.value)} value={group.id}>
|
||||
{
|
||||
groups.map((group) => (
|
||||
<option value={group.id} key={group.id}>
|
||||
@ -133,12 +129,12 @@ class Snapcast extends React.Component {
|
||||
<MuteControl
|
||||
className="snapcast__client__mute-control"
|
||||
mute={client.mute}
|
||||
onMuteChange={(mute) => this.props.snapcastActions.setClientMute(client.id, mute)}
|
||||
onMuteChange={(mute) => actions.setClientMute(client.id, mute)}
|
||||
/>
|
||||
<VolumeControl
|
||||
className="snapcast__client__volume-control"
|
||||
volume={client.volume}
|
||||
onVolumeChange={(percent) => this.props.snapcastActions.setClientVolume(client.id, percent)}
|
||||
onVolumeChange={(percent) => actions.setClientVolume(client.id, percent)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -150,13 +146,13 @@ class Snapcast extends React.Component {
|
||||
<LatencyControl
|
||||
max="150"
|
||||
value={client.latency}
|
||||
onChange={(value) => this.props.snapcastActions.setClientLatency(client.id, parseInt(value))}
|
||||
onChange={(value) => actions.setClientLatency(client.id, parseInt(value))}
|
||||
/>
|
||||
<TextField
|
||||
className="tiny"
|
||||
type="number"
|
||||
onChange={(value) => this.props.snapcastActions.setClientLatency(client.id, parseInt(value))}
|
||||
value={String(client.latency)}
|
||||
onChange={(value) => actions.setClientLatency(client.id, parseInt(value))}
|
||||
value={String(client.latency)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -174,7 +170,7 @@ class Snapcast extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
@ -182,16 +178,11 @@ class Snapcast extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.snapcast_enabled) {
|
||||
return (
|
||||
<p className="message warning">
|
||||
To enable Snapcast, edit your
|
||||
<code>mopidy.conf</code>
|
||||
{' '}
|
||||
file
|
||||
</p>
|
||||
);
|
||||
}
|
||||
const {
|
||||
enabled,
|
||||
snapcastActions: actions,
|
||||
uiActions,
|
||||
} = this.props;
|
||||
|
||||
const streams = [];
|
||||
for (var id in this.props.streams) {
|
||||
@ -211,22 +202,24 @@ file
|
||||
<div className="snapcast">
|
||||
|
||||
<div className="field checkbox">
|
||||
<div className="name">
|
||||
Display
|
||||
</div>
|
||||
<div className="input">
|
||||
<button
|
||||
className="button button--default button--small"
|
||||
onClick={(e) => this.props.snapcastActions.getServer()}
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="enabled"
|
||||
checked={enabled}
|
||||
onChange={() => actions.setEnabled(!enabled)}
|
||||
/>
|
||||
<span className="label">
|
||||
Enabled
|
||||
</span>
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
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 })}
|
||||
onChange={() => uiActions.set({ snapcast_show_disconnected_clients: !this.props.show_disconnected_clients })}
|
||||
/>
|
||||
<span className="label">
|
||||
Show disconnected clients
|
||||
@ -237,7 +230,7 @@ file
|
||||
|
||||
<div className="snapcast__groups">
|
||||
{
|
||||
groups.map((group) => {
|
||||
groups.map((group) => {
|
||||
group = helpers.collate(group, { clients: this.props.clients });
|
||||
|
||||
// Average our clients' volume for an overall group volume
|
||||
@ -256,13 +249,13 @@ file
|
||||
</div>
|
||||
<div className="input">
|
||||
<div className="text">
|
||||
{group.name}
|
||||
{' '}
|
||||
{group.name}
|
||||
{' '}
|
||||
|
||||
<span className="mid_grey-text">
|
||||
(
|
||||
{group.id}
|
||||
)
|
||||
<span className="mid_grey-text">
|
||||
(
|
||||
{group.id}
|
||||
)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -272,14 +265,14 @@ file
|
||||
Stream
|
||||
</div>
|
||||
<div className="input">
|
||||
<select onChange={(e) => this.props.snapcastActions.setGroupStream(group.id, e.target.value)} value={group.stream_id}>
|
||||
<select onChange={(e) => actions.setGroupStream(group.id, e.target.value)} value={group.stream_id}>
|
||||
{
|
||||
streams.map((stream) => (
|
||||
<option value={stream.id} key={stream.id}>
|
||||
{stream.id}
|
||||
{' '}
|
||||
(
|
||||
{stream.status}
|
||||
{stream.id}
|
||||
{' '}
|
||||
(
|
||||
{stream.status}
|
||||
)
|
||||
</option>
|
||||
))
|
||||
@ -295,12 +288,12 @@ file
|
||||
<MuteControl
|
||||
className="snapcast__group__mute-control"
|
||||
mute={group.muted}
|
||||
onMuteChange={(mute) => this.props.snapcastActions.setGroupMute(group.id, mute)}
|
||||
onMuteChange={(mute) => actions.setGroupMute(group.id, mute)}
|
||||
/>
|
||||
<VolumeControl
|
||||
className="snapcast__group__volume-control"
|
||||
volume={group_volume}
|
||||
onVolumeChange={(percent, old_percent) => this.props.snapcastActions.setGroupVolume(group.id, percent, old_percent)}
|
||||
onVolumeChange={(percent, old_percent) => actions.setGroupVolume(group.id, percent, old_percent)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -323,9 +316,13 @@ file
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
snapcast_enabled: state.pusher.config.snapcast_enabled,
|
||||
pusher_connected: state.pusher.connected,
|
||||
show_disconnected_clients: (state.ui.snapcast_show_disconnected_clients !== undefined ? state.ui.snapcast_show_disconnected_clients : false),
|
||||
enabled: state.snapcast.enabled,
|
||||
connected: state.snapcast.connected,
|
||||
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),
|
||||
clients: (state.snapcast.clients ? state.snapcast.clients : null),
|
||||
@ -335,7 +332,6 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
coreActions: bindActionCreators(coreActions, dispatch),
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
snapcastActions: bindActionCreators(snapcastActions, dispatch),
|
||||
pusherActions: bindActionCreators(pusherActions, dispatch),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Snapcast);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Snapcast);
|
||||
|
||||
@ -1,4 +1,23 @@
|
||||
|
||||
export function setEnabled(enabled) {
|
||||
return {
|
||||
type: 'SNAPCAST_SET_ENABLED',
|
||||
enabled,
|
||||
};
|
||||
}
|
||||
|
||||
export function connect() {
|
||||
return {
|
||||
type: 'SNAPCAST_CONNECT',
|
||||
};
|
||||
}
|
||||
|
||||
export function disconnect() {
|
||||
return {
|
||||
type: 'SNAPCAST_DISCONNECT',
|
||||
};
|
||||
}
|
||||
|
||||
export function getServer() {
|
||||
return {
|
||||
type: 'SNAPCAST_GET_SERVER',
|
||||
@ -130,4 +149,4 @@ export function streamsLoaded(streams, flush = false) {
|
||||
streams,
|
||||
flush,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,23 +7,158 @@ const uiActions = require('../ui/actions');
|
||||
const pusherActions = require('../pusher/actions');
|
||||
const snapcastActions = require('./actions');
|
||||
|
||||
const SnapcastMiddleware = (function () {
|
||||
// A snapcast request is an alias of the Pusher request
|
||||
const request = (store, params = null, response_callback = null, error_callback = null) => {
|
||||
store.dispatch(
|
||||
pusherActions.request(
|
||||
'snapcast',
|
||||
params,
|
||||
response_callback,
|
||||
error_callback,
|
||||
),
|
||||
);
|
||||
const SnapcastMiddleware = (function () {
|
||||
let socket = null;
|
||||
|
||||
// requests pending
|
||||
const deferredRequests = [];
|
||||
|
||||
// handle all manner of socket messages
|
||||
const handleMessage = (ws, store, message) => {
|
||||
if (store.getState().ui.log_snapcast) {
|
||||
console.log('Snapcast log (incoming)', message);
|
||||
}
|
||||
|
||||
// Pull our ID. JSON-RPC nests the ID under the error object,
|
||||
// so make sure we handle that.
|
||||
// TODO: Use this as our measure of a successful response vs error
|
||||
let id = null;
|
||||
if (message.id) {
|
||||
id = message.id;
|
||||
} else if (message.error && message.error.id) {
|
||||
id = message.error.id;
|
||||
}
|
||||
|
||||
// Response with request_id
|
||||
if (id) {
|
||||
// Response matches a pending request
|
||||
if (deferredRequests[id] !== undefined) {
|
||||
store.dispatch(uiActions.stopLoading(id));
|
||||
|
||||
// Response is an error
|
||||
if (message.error !== undefined) {
|
||||
deferredRequests[id].reject(message.error);
|
||||
|
||||
// Successful response
|
||||
} else {
|
||||
deferredRequests[id].resolve(message.result);
|
||||
}
|
||||
|
||||
// Hmm, the response doesn't appear to be for us?
|
||||
} else {
|
||||
store.dispatch(coreActions.handleException(
|
||||
'Snapcast: Response received with no matching request',
|
||||
message,
|
||||
));
|
||||
}
|
||||
|
||||
// General broadcast received
|
||||
} else {
|
||||
// Broadcast of an error
|
||||
if (message.error !== undefined) {
|
||||
store.dispatch(coreActions.handleException(
|
||||
`Snapcast: ${message.error.message}`,
|
||||
message,
|
||||
(message.error.data !== undefined && message.error.data.description !== undefined ? message.error.data.description : null),
|
||||
));
|
||||
} else {
|
||||
switch (message.method) {
|
||||
case 'connection_added':
|
||||
store.dispatch(pusherActions.connectionAdded(message.params.connection));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const request = (store, method, params = null) => new Promise((resolve, reject) => {
|
||||
const id = helpers.generateGuid();
|
||||
const message = {
|
||||
jsonrpc: '2.0',
|
||||
id,
|
||||
method,
|
||||
};
|
||||
if (params) {
|
||||
message.params = params;
|
||||
}
|
||||
|
||||
if (store.getState().ui.log_pusher) {
|
||||
console.log('Snapcast log (outgoing)', message);
|
||||
}
|
||||
|
||||
socket.send(JSON.stringify(message));
|
||||
|
||||
store.dispatch(uiActions.startLoading(id, `snapcast_${method}`));
|
||||
|
||||
// Start our 30 second timeout
|
||||
const timeout = setTimeout(
|
||||
() => {
|
||||
store.dispatch(uiActions.stopLoading(id));
|
||||
reject({
|
||||
id,
|
||||
code: 32300,
|
||||
message: 'Request timed out',
|
||||
});
|
||||
},
|
||||
30000,
|
||||
);
|
||||
|
||||
// add query to our deferred responses
|
||||
deferredRequests[id] = {
|
||||
resolve,
|
||||
reject,
|
||||
};
|
||||
});
|
||||
|
||||
return (store) => (next) => (action) => {
|
||||
const { snapcast } = store.getState();
|
||||
|
||||
switch (action.type) {
|
||||
switch (action.type) {
|
||||
|
||||
case 'SNAPCAST_CONNECT':
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
}
|
||||
|
||||
store.dispatch({ type: 'SNAPCAST_CONNECTING' });
|
||||
var state = store.getState();
|
||||
|
||||
socket = new Mopidy({
|
||||
webSocketUrl: `ws${window.location.protocol === 'https:' ? 's' : ''}://${state.snapcast.host}:${state.snapcast.port}/jsonrpc/`,
|
||||
callingConvention: 'by-position-or-by-name',
|
||||
});
|
||||
|
||||
socket.on((type, data) => handleMessage(socket, store, type, data));
|
||||
break;
|
||||
|
||||
case 'SNAPCAST_CONNECTED':
|
||||
if (store.getState().ui.allow_reporting) {
|
||||
const hashed_hostname = sha256(window.location.hostname);
|
||||
ReactGA.event({ category: 'Snapcast', action: 'Connected', label: hashed_hostname });
|
||||
}
|
||||
store.dispatch(uiActions.createNotification({ content: 'Snapcast connected' }));
|
||||
next(action);
|
||||
break;
|
||||
|
||||
case 'SNAPCAST_DISCONNECT':
|
||||
if (socket != null) socket.close();
|
||||
socket = null;
|
||||
store.dispatch({ type: 'SNAPCAST_DISCONNECTED' });
|
||||
break;
|
||||
|
||||
case 'SNAPCAST_DISCONNECTED':
|
||||
store.dispatch(uiActions.createNotification({ type: 'bad', content: 'Snapcast disconnected' }));
|
||||
helpers.setFavicon('favicon_error.png');
|
||||
break;
|
||||
|
||||
case 'SNAPCAST_DEBUG':
|
||||
request(socket, store, action.call, action.value)
|
||||
.then((response) => {
|
||||
store.dispatch({ type: 'DEBUG', response });
|
||||
});
|
||||
break;
|
||||
|
||||
|
||||
case 'SNAPCAST_GET_SERVER':
|
||||
request(
|
||||
store,
|
||||
@ -358,7 +493,7 @@ const SnapcastMiddleware = (function () {
|
||||
var group = snapcast.groups[action.id];
|
||||
var change = action.percent - action.old_percent;
|
||||
|
||||
for (const client_id of group.clients_ids) {
|
||||
for (const client_id of group.clients_ids) {
|
||||
// Apply the change proportionately to each client
|
||||
var client = snapcast.clients[client_id];
|
||||
const current_volume = client.volume;
|
||||
@ -393,7 +528,7 @@ const SnapcastMiddleware = (function () {
|
||||
// Drop the prefix
|
||||
action.method = action.method.replace('snapcast_', '');
|
||||
|
||||
switch (action.method) {
|
||||
switch (action.method) {
|
||||
case 'Client.OnConnect':
|
||||
store.dispatch(snapcastActions.clientLoaded(
|
||||
{
|
||||
@ -462,7 +597,7 @@ const SnapcastMiddleware = (function () {
|
||||
default:
|
||||
return next(action);
|
||||
}
|
||||
};
|
||||
};
|
||||
}());
|
||||
|
||||
export default SnapcastMiddleware;
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
|
||||
export default function reducer(snapcast = {}, action) {
|
||||
switch (action.type) {
|
||||
console.log(action);
|
||||
switch (action.type) {
|
||||
case 'SNAPCAST_SET_ENABLED':
|
||||
return { ...snapcast, enabled: action.enabled };
|
||||
|
||||
case 'SNAPCAST_SERVER_LOADED':
|
||||
var server = { ...action.server };
|
||||
return { ...snapcast, server };
|
||||
return { ...snapcast, server: action.server };
|
||||
|
||||
case 'SNAPCAST_CLIENTS_LOADED':
|
||||
if (action.flush) {
|
||||
|
||||
@ -117,6 +117,15 @@ Back
|
||||
/>
|
||||
<span className="label">Log Pusher</span>
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="log_pusher"
|
||||
checked={this.props.log_snapcast}
|
||||
onChange={(e) => this.props.uiActions.set({ log_snapcast: !this.props.log_snapcast })}
|
||||
/>
|
||||
<span className="label">Log Snapcast</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
@ -224,6 +233,7 @@ const mapStateToProps = (state, ownProps) => ({
|
||||
log_actions: (state.ui.log_actions ? state.ui.log_actions : false),
|
||||
log_pusher: (state.ui.log_pusher ? state.ui.log_pusher : false),
|
||||
log_mopidy: (state.ui.log_mopidy ? state.ui.log_mopidy : false),
|
||||
log_snapcast: (state.ui.log_snapcast ? state.ui.log_snapcast : false),
|
||||
test_mode: (state.ui.test_mode ? state.ui.test_mode : false),
|
||||
debug_info: (state.ui.debug_info ? state.ui.debug_info : false),
|
||||
debug_response: state.ui.debug_response,
|
||||
|
||||
@ -73,7 +73,7 @@ class Settings extends React.Component {
|
||||
// This forces our SW to bugger off and a new one is registered on refresh
|
||||
navigator.serviceWorker.getRegistrations().then(
|
||||
(registrations) => {
|
||||
for (let registration of registrations) {
|
||||
for (let registration of registrations) {
|
||||
registration.unregister();
|
||||
}
|
||||
}
|
||||
@ -445,17 +445,16 @@ Advanced
|
||||
<div className="input">
|
||||
<span className="text">
|
||||
{this.props.pusher.version.current}
|
||||
{' '}
|
||||
installed
|
||||
{' installed'}
|
||||
{this.props.pusher.version.upgrade_available ? (
|
||||
<span className="flag flag--dark">
|
||||
<Icon name="cloud_download" className="blue-text" />
|
||||
Upgrade available
|
||||
{' Upgrade available'}
|
||||
</span>
|
||||
) : (
|
||||
<span className="flag flag--dark">
|
||||
<Icon name="check" className="green-text" />
|
||||
Up-to-date
|
||||
{' Up-to-date'}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user