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 (
);
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 {