diff --git a/src/js/components/Fields/OutputControl.js b/src/js/components/Fields/OutputControl.js index a45038c9..60cde690 100755 --- a/src/js/components/Fields/OutputControl.js +++ b/src/js/components/Fields/OutputControl.js @@ -13,70 +13,78 @@ import * as mopidyActions from '../../services/mopidy/actions'; import * as pusherActions from '../../services/pusher/actions'; import * as snapcastActions from '../../services/snapcast/actions'; import { sortItems, indexToArray } from '../../util/arrays'; -import { formatImages, digestMopidyImages } from '../../util/format'; import { titleCase } from '../../util/helpers'; -import { I18n } from '../../locale'; +import { I18n, i18n } from '../../locale'; import Link from '../Link'; import ErrorBoundary from '../ErrorBoundary'; -const Header = ({ stream, server }) => { +const Header = ({ + stream, + server, +}) => { const dispatch = useDispatch(); const { id, - meta: { - name, - artists, - images: rawImages, - } = {}, status, - uri: { - scheme, - query: { - control_url, + properties: { + playbackStatus, + canPlay, + canPause, + canControl, + metadata: { + title, + artist: artists = [], + artUrl, } = {}, } = {}, } = stream || {}; - const controlURL = control_url ? new URL(control_url) : null; - const controlServer = controlURL ? { - url: control_url, - ssl: controlURL.protocol === 'https:', - host: controlURL.hostname, - port: controlURL.port || (controlURL.protocol === 'https:' ? '443' : '80'), - } : null; - const images = rawImages ? formatImages(digestMopidyImages(controlServer, rawImages)) : null; - const current_server_id = useSelector((state) => state.mopidy.current_server); - const current_server = useSelector((state) => state.mopidy.servers[current_server_id]); - const isCurrentServer = control_url === current_server.url; - const isControlSwitchable = controlServer && !isCurrentServer; - - const onClick = () => { - if (isControlSwitchable) { - dispatch(mopidyActions.setCurrentServer(controlServer)); - } - }; + let onClick = null; + switch (playbackStatus) { + case 'playing': + if (canPause) onClick = () => dispatch(snapcastActions.controlStream(id, 'pause')); + break; + default: + if (canPlay) onClick = () => dispatch(snapcastActions.controlStream(id, 'play')); + break; + } return (
- +
+ +
- {isControlSwitchable ? ( - - {server?.name || id} - - ) : (server?.name || id)} - {isCurrentServer && } - {status === 'playing' && } - {control_url &&
{control_url}
} + {server?.name || id} + {!canControl && ( + + {i18n('snapcast.not_controllable').toUpperCase()} + + )}
-
    -
  • {name || scheme || 'Unknown'}
  • - {artists &&
  • } -
+ {!title && !artists?.length ? ( +
+ +
+ ) : ( +
    +
  • {title}
  • + {artists && ( +
  • + ({ name }))} type="artist" nolinks /> +
  • + )} +
+ )}
); diff --git a/src/js/components/Services.js b/src/js/components/Services.js index 20b466fd..d1b32443 100755 --- a/src/js/components/Services.js +++ b/src/js/components/Services.js @@ -334,6 +334,35 @@ class Services extends React.Component { return (
+ +
+ +
+ +
+ {!snapcastEnabled && ( + + + + )} + {snapcastEnabled && !snapcastConnected && ( + + + + )} + {snapcastEnabled && snapcastConnected && ( + + + + )} +
+ - -
- -
- -
- {!snapcastEnabled && ( - - - - )} - {snapcastEnabled && !snapcastConnected && ( - - - - )} - {snapcastEnabled && snapcastConnected && ( - - - - )} -
-
); diff --git a/src/js/locale/en.yaml b/src/js/locale/en.yaml index f402c131..f2894499 100755 --- a/src/js/locale/en.yaml +++ b/src/js/locale/en.yaml @@ -58,6 +58,11 @@ common: filter: Filter by: ' by ' loading_library: 'Loading %{provider} %{type}' + play_state: + playing: Playing + paused: Paused + stopped: Stopped + idle: Idle fields: start_typing: 'Start typing...' search: 'Search...' @@ -216,6 +221,7 @@ snapcast: latency: Latency stream: Stream volume: Volume + not_controllable: Not controllable encryption: label: Encryption sublabel: Use SSL diff --git a/src/js/services/snapcast/actions.js b/src/js/services/snapcast/actions.js index 956dddc9..cd601f2d 100755 --- a/src/js/services/snapcast/actions.js +++ b/src/js/services/snapcast/actions.js @@ -150,6 +150,14 @@ export function calculateGroupVolume(id, clients) { }; } +export function controlStream(id, command) { + return { + type: 'SNAPCAST_CONTROL_STREAM', + id, + command, + }; +} + export function serverLoaded(server) { return { type: 'SNAPCAST_SERVER_LOADED', diff --git a/src/js/services/snapcast/middleware.js b/src/js/services/snapcast/middleware.js index d4a62705..8251cbd3 100755 --- a/src/js/services/snapcast/middleware.js +++ b/src/js/services/snapcast/middleware.js @@ -100,10 +100,7 @@ const SnapcastMiddleware = (function () { break; case 'Stream.OnUpdate': - store.dispatch(snapcastActions.streamLoaded(message.params)); - break; - - case 'Stream.OnMetadata': + case 'Stream.OnProperties': store.dispatch(snapcastActions.streamLoaded(message.params)); break; @@ -651,6 +648,24 @@ const SnapcastMiddleware = (function () { })); break; + case 'SNAPCAST_CONTROL_STREAM': { + const { id, command } = action; + request(store, 'Stream.Control', { id, command }) + .then( + () => { + // No response body + }, + (error) => { + store.dispatch(coreActions.handleException( + 'Could not control stream', + error, + error.message, + )); + }, + ); + break; + } + default: return next(action); } diff --git a/src/scss/components/_output-control.scss b/src/scss/components/_output-control.scss index cfa81cd8..2a267c9b 100755 --- a/src/scss/components/_output-control.scss +++ b/src/scss/components/_output-control.scss @@ -69,8 +69,65 @@ margin-right: 0; } - &__thumbnail { + &__art { flex-shrink: 0; + width: 50px; + position: relative; + cursor: pointer; + + &:before { + @include animate(); + content: "\f04b"; + display: block; + font-family: "FontAwesome"; + position: absolute; + top: 50%; + left: 0; + bottom: 0; + right: 0; + margin-top: -9px; + text-align: center; + font-size: 18px; + color: colour('white'); + z-index: 3; + opacity: 0; + } + + &:after { + @include animate(); + content: ""; + display: block; + background: rgba(0, 0, 0, 0.75); + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + border-radius: 3px; + opacity: 0; + z-index: 2; + } + + &:hover { + &:before, + &:after { + opacity: 1; + } + } + + &--disabled { + cursor: default; + &:before, + &:after { + display: none; + } + } + + &--playing { + &:before { + content: "\f04c"; + } + } } &__title {