Storing next track and pre-fetching artwork for snappier UI

This commit is contained in:
James Barnsley
2018-04-12 15:55:39 +12:00
parent 4736f85ff2
commit cd4451f69c
13 changed files with 220 additions and 68 deletions

View File

@ -2598,6 +2598,14 @@ h4 {
h4 .dropdown-field { h4 .dropdown-field {
margin-left: 10px; } margin-left: 10px; }
.hide {
width: 0px;
height: 0px;
position: fixed;
z-index: -99;
top: -99px;
left: -99px; }
.no-results { .no-results {
text-align: center; text-align: center;
padding: 40px; padding: 40px;

View File

@ -612,6 +612,9 @@ var formatTracks = exports.formatTracks = function formatTracks(tracks) {
* @param uri = string * @param uri = string
**/ **/
var uriSource = exports.uriSource = function uriSource(uri) { var uriSource = exports.uriSource = function uriSource(uri) {
if (!uri) {
return false;
}
var exploded = uri.split(':'); var exploded = uri.split(':');
return exploded[0]; return exploded[0];
}; };
@ -848,14 +851,21 @@ var removeDuplicates = exports.removeDuplicates = function removeDuplicates(arra
* @param field = string (the field we're to search) * @param field = string (the field we're to search)
* @param value = string (the value to find) * @param value = string (the value to find)
* @param array = array of objects to search * @param array = array of objects to search
* @param singular = boolean (just return the first result)
* @return array * @return array
**/ **/
var applyFilter = exports.applyFilter = function applyFilter(field, value, array) { var applyFilter = exports.applyFilter = function applyFilter(field, value, array) {
var singular = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
var results = []; var results = [];
for (var i = 0; i < array.length; i++) { for (var i = 0; i < array.length; i++) {
if (array[i][field] && array[i][field].toLowerCase().includes(value.toLowerCase())) { if (array[i][field] && String(array[i][field]).toLowerCase().includes(String(value).toLowerCase())) {
results.push(array[i]); if (singular) {
return array[i];
} else {
results.push(array[i]);
}
} }
} }
@ -3613,8 +3623,9 @@ exports.setTimePosition = setTimePosition;
exports.timePosition = timePosition; exports.timePosition = timePosition;
exports.getUriSchemes = getUriSchemes; exports.getUriSchemes = getUriSchemes;
exports.getCurrentTrack = getCurrentTrack; exports.getCurrentTrack = getCurrentTrack;
exports.clearCurrentTrack = clearCurrentTrack;
exports.currentTrackLoaded = currentTrackLoaded; exports.currentTrackLoaded = currentTrackLoaded;
exports.getNextTrack = getNextTrack;
exports.clearCurrentTrack = clearCurrentTrack;
exports.getQueue = getQueue; exports.getQueue = getQueue;
exports.changeTrack = changeTrack; exports.changeTrack = changeTrack;
exports.playURIs = playURIs; exports.playURIs = playURIs;
@ -3819,14 +3830,6 @@ function getCurrentTrack() {
}; };
} }
function clearCurrentTrack() {
return {
type: 'CURRENT_TRACK_LOADED',
current_track: null,
current_track_uri: null
};
}
function currentTrackLoaded(tl_track) { function currentTrackLoaded(tl_track) {
return { return {
type: 'MOPIDY_CURRENT_TRACK_LOADED', type: 'MOPIDY_CURRENT_TRACK_LOADED',
@ -3834,6 +3837,20 @@ function currentTrackLoaded(tl_track) {
}; };
} }
function getNextTrack() {
return {
type: 'MOPIDY_GET_NEXT_TRACK'
};
}
function clearCurrentTrack() {
return {
type: 'CURRENT_TRACK_LOADED',
track: null,
uri: null
};
}
function getQueue() { function getQueue() {
return { return {
type: 'MOPIDY_GET_QUEUE' type: 'MOPIDY_GET_QUEUE'
@ -4330,8 +4347,13 @@ var Thumbnail = function (_React$Component) {
// multiple images // multiple images
} else if (this.props.images && this.props.images.length > 0) { } else if (this.props.images && this.props.images.length > 0) {
var images = helpers.sizedImages(this.props.images); var images = helpers.sizedImages(this.props.images);
// Default to medium-sized image, but accept size property as override
var size = 'medium'; var size = 'medium';
if (this.props.size) size = this.props.size; if (this.props.size) {
size = this.props.size;
}
return images[size]; return images[size];
} }
} }
@ -16759,7 +16781,7 @@ var ArtistSentence = function (_React$Component) {
if (!this.props.artists) { if (!this.props.artists) {
return _react2.default.createElement( return _react2.default.createElement(
'span', 'span',
null, { className: this.props.className ? this.props.className + " artist-sentence" : "artist-sentence" },
'-' '-'
); );
} }
@ -50784,8 +50806,13 @@ function reducer() {
case 'CURRENT_TRACK_LOADED': case 'CURRENT_TRACK_LOADED':
return Object.assign({}, core, { return Object.assign({}, core, {
current_track: action.current_track, current_track: action.track,
current_track_uri: action.current_track_uri current_track_uri: action.uri
});
case 'NEXT_TRACK_LOADED':
return Object.assign({}, core, {
next_track_uri: action.uri
}); });
case 'QUEUE_LOADED': case 'QUEUE_LOADED':
@ -52235,7 +52262,7 @@ var CoreMiddleware = function () {
case 'CURRENT_TRACK_LOADED': case 'CURRENT_TRACK_LOADED':
store.dispatch({ store.dispatch({
type: 'TRACKS_LOADED', type: 'TRACKS_LOADED',
tracks: [action.current_track] tracks: [action.track]
}); });
next(action); next(action);
@ -53436,6 +53463,7 @@ var MopidyMiddleware = function () {
case 'event:tracklistChanged': case 'event:tracklistChanged':
store.dispatch(mopidyActions.getQueue()); store.dispatch(mopidyActions.getQueue());
store.dispatch(mopidyActions.getNextTrack());
break; break;
case 'event:playbackStateChanged': case 'event:playbackStateChanged':
@ -53460,6 +53488,7 @@ var MopidyMiddleware = function () {
case 'event:trackPlaybackStarted': case 'event:trackPlaybackStarted':
store.dispatch(mopidyActions.currentTrackLoaded(data.tl_track)); store.dispatch(mopidyActions.currentTrackLoaded(data.tl_track));
store.dispatch(mopidyActions.getNextTrack());
break; break;
case 'event:volumeChanged': case 'event:volumeChanged':
@ -55069,20 +55098,54 @@ var MopidyMiddleware = function () {
case 'MOPIDY_CURRENT_TRACK_LOADED': case 'MOPIDY_CURRENT_TRACK_LOADED':
var track = helpers.formatTracks(action.tl_track); var track = helpers.formatTracks(action.tl_track);
// We've got Spotify running, and it's a spotify track - go straight to the source! // We don't have the track already in our index
if (store.getState().spotify.enabled && helpers.uriSource(track.uri) == 'spotify') { if (store.getState().core.tracks[track.uri] === undefined || store.getState().core.tracks[track.uri].images === undefined) {
store.dispatch(spotifyActions.getTrack(track.uri));
// Some other source, rely on Mopidy backends to do their work // We've got Spotify running, and it's a spotify track - go straight to the source!
} else { if (store.getState().spotify.enabled && helpers.uriSource(track.uri) == 'spotify') {
store.dispatch(mopidyActions.getImages('tracks', [track.uri])); store.dispatch(spotifyActions.getTrack(track.uri));
// Some other source, rely on Mopidy backends to do their work
} else {
store.dispatch(mopidyActions.getImages('tracks', [track.uri]));
}
} }
// Set our window title to the track title // Set our window title to the track title
helpers.setWindowTitle(track, store.getState().mopidy.play_state); helpers.setWindowTitle(track, store.getState().mopidy.play_state);
store.dispatch({ store.dispatch({
type: 'CURRENT_TRACK_LOADED', type: 'CURRENT_TRACK_LOADED',
current_track: track track: track
});
break;
case 'MOPIDY_GET_NEXT_TRACK':
request(socket, store, 'tracklist.getNextTlid').then(function (response) {
if (response && response >= 0) {
var track = helpers.applyFilter('tlid', response, store.getState().core.queue, true);
if (track) {
store.dispatch({
type: 'NEXT_TRACK_LOADED',
uri: track.uri
});
console.log(track.name);
// We don't have the track already in our index
if (store.getState().core.tracks[track.uri] === undefined || store.getState().core.tracks[track.uri].images === undefined) {
// We've got Spotify running, and it's a spotify track - go straight to the source!
if (store.getState().spotify.enabled && helpers.uriSource(track.uri) == 'spotify') {
store.dispatch(spotifyActions.getTrack(track.uri));
// Some other source, rely on Mopidy backends to do their work
} else {
store.dispatch(mopidyActions.getImages('tracks', [track.uri]));
}
}
}
}
}); });
break; break;
@ -59907,6 +59970,10 @@ var _Icon = __webpack_require__(17);
var _Icon2 = _interopRequireDefault(_Icon); var _Icon2 = _interopRequireDefault(_Icon);
var _helpers = __webpack_require__(2);
var helpers = _interopRequireWildcard(_helpers);
var _actions = __webpack_require__(5); var _actions = __webpack_require__(5);
var uiActions = _interopRequireWildcard(_actions); var uiActions = _interopRequireWildcard(_actions);
@ -60089,6 +60156,7 @@ var PlaybackControls = function (_React$Component) {
{ id: 'http-streamer', autoPlay: true, preload: 'none' }, { id: 'http-streamer', autoPlay: true, preload: 'none' },
_react2.default.createElement('source', { src: this.props.http_streaming_url, type: "audio/" + this.props.http_streaming_encoding }) _react2.default.createElement('source', { src: this.props.http_streaming_url, type: "audio/" + this.props.http_streaming_encoding })
) : null, ) : null,
this.props.next_track && this.props.next_track.images ? _react2.default.createElement(_Thumbnail2.default, { className: 'hide', size: 'large', images: this.props.next_track.images }) : null,
_react2.default.createElement( _react2.default.createElement(
'div', 'div',
{ className: 'current-track' }, { className: 'current-track' },
@ -60214,6 +60282,7 @@ var mapStateToProps = function mapStateToProps(state, ownProps) {
http_streaming_encoding: state.core.http_streaming_encoding, http_streaming_encoding: state.core.http_streaming_encoding,
http_streaming_url: state.core.http_streaming_url, http_streaming_url: state.core.http_streaming_url,
current_track: state.core.current_track && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null, current_track: state.core.current_track && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null,
next_track: state.core.next_track_uri && state.core.tracks[state.core.next_track_uri] !== undefined ? state.core.tracks[state.core.next_track_uri] : null,
radio_enabled: state.ui.radio && state.ui.radio.enabled ? true : false, radio_enabled: state.ui.radio && state.ui.radio.enabled ? true : false,
play_state: state.mopidy.play_state, play_state: state.mopidy.play_state,
time_position: state.mopidy.time_position, time_position: state.mopidy.time_position,
@ -68661,6 +68730,7 @@ var Queue = function (_React$Component) {
var current_track = null; var current_track = null;
var tracks = []; var tracks = [];
if (this.props.queue && this.props.tracks) { if (this.props.queue && this.props.tracks) {
for (var i = 0; i < this.props.queue.length; i++) { for (var i = 0; i < this.props.queue.length; i++) {
var track = this.props.queue[i]; var track = this.props.queue[i];
@ -68689,11 +68759,10 @@ var Queue = function (_React$Component) {
} }
} }
var image = null; var current_track_image = null;
if (current_track) { if (current_track) {
if (current_track.images !== undefined && current_track.images) { if (current_track.images !== undefined && current_track.images) {
image = helpers.sizedImages(current_track.images); current_track_image = helpers.sizedImages(current_track.images).large;
image = image.large;
} }
} }
@ -68743,14 +68812,14 @@ var Queue = function (_React$Component) {
'div', 'div',
{ className: 'view queue-view' }, { className: 'view queue-view' },
_react2.default.createElement(_Header2.default, { icon: 'play', className: 'overlay', title: 'Now playing', options: options, uiActions: this.props.uiActions }), _react2.default.createElement(_Header2.default, { icon: 'play', className: 'overlay', title: 'Now playing', options: options, uiActions: this.props.uiActions }),
_react2.default.createElement(_Parallax2.default, { blur: true, image: image }), _react2.default.createElement(_Parallax2.default, { blur: true, image: current_track_image }),
_react2.default.createElement( _react2.default.createElement(
'div', 'div',
{ className: 'content-wrapper' }, { className: 'content-wrapper' },
_react2.default.createElement( _react2.default.createElement(
'div', 'div',
{ className: 'current-track' }, { className: 'current-track' },
this.renderArtwork(image), this.renderArtwork(current_track_image),
_react2.default.createElement( _react2.default.createElement(
'div', 'div',
{ className: 'title' }, { className: 'title' },

File diff suppressed because one or more lines are too long

View File

@ -11,7 +11,7 @@ export default class ArtistSentence extends React.Component{
render(){ render(){
if (!this.props.artists){ if (!this.props.artists){
return <span>-</span>; return <span className={this.props.className ? this.props.className+" artist-sentence" : "artist-sentence" }>-</span>;
} }
return ( return (

View File

@ -12,6 +12,7 @@ import ArtistSentence from './ArtistSentence'
import Thumbnail from './Thumbnail' import Thumbnail from './Thumbnail'
import Icon from './Icon' import Icon from './Icon'
import * as helpers from '../helpers'
import * as uiActions from '../services/ui/actions' import * as uiActions from '../services/ui/actions'
import * as coreActions from '../services/core/actions' import * as coreActions from '../services/core/actions'
import * as mopidyActions from '../services/mopidy/actions' import * as mopidyActions from '../services/mopidy/actions'
@ -75,6 +76,8 @@ class PlaybackControls extends React.Component{
{this.props.http_streaming_enabled && this.props.http_streaming_active && this.props.play_state == 'playing' ? <audio id="http-streamer" autoPlay preload="none"> {this.props.http_streaming_enabled && this.props.http_streaming_active && this.props.play_state == 'playing' ? <audio id="http-streamer" autoPlay preload="none">
<source src={this.props.http_streaming_url} type={"audio/"+this.props.http_streaming_encoding} /> <source src={this.props.http_streaming_url} type={"audio/"+this.props.http_streaming_encoding} />
</audio> : null} </audio> : null}
{this.props.next_track && this.props.next_track.images ? <Thumbnail className="hide" size="large" images={this.props.next_track.images} /> : null}
<div className="current-track"> <div className="current-track">
<div className="thumbnail-wrapper" onClick={e => this.handleThumbnailClick(e)}> <div className="thumbnail-wrapper" onClick={e => this.handleThumbnailClick(e)}>
@ -150,6 +153,7 @@ const mapStateToProps = (state, ownProps) => {
http_streaming_encoding: state.core.http_streaming_encoding, http_streaming_encoding: state.core.http_streaming_encoding,
http_streaming_url: state.core.http_streaming_url, http_streaming_url: state.core.http_streaming_url,
current_track: (state.core.current_track && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null), current_track: (state.core.current_track && state.core.tracks[state.core.current_track.uri] !== undefined ? state.core.tracks[state.core.current_track.uri] : null),
next_track: (state.core.next_track_uri && state.core.tracks[state.core.next_track_uri] !== undefined ? state.core.tracks[state.core.next_track_uri] : null),
radio_enabled: (state.ui.radio && state.ui.radio.enabled ? true : false), radio_enabled: (state.ui.radio && state.ui.radio.enabled ? true : false),
play_state: state.mopidy.play_state, play_state: state.mopidy.play_state,
time_position: state.mopidy.time_position, time_position: state.mopidy.time_position,

View File

@ -44,24 +44,29 @@ class Thumbnail extends React.Component{
// no images // no images
if (!this.props.image && (!this.props.images || this.props.images.length <= 0)){ if (!this.props.image && (!this.props.images || this.props.images.length <= 0)){
return require('../../assets/no-image.svg') return require('../../assets/no-image.svg');
// single image // single image
} else if (this.props.image){ } else if (this.props.image){
return this.props.image return this.props.image;
// multiple images // multiple images
} else if (this.props.images && this.props.images.length > 0){ } else if (this.props.images && this.props.images.length > 0){
var images = helpers.sizedImages(this.props.images ) var images = helpers.sizedImages(this.props.images);
var size = 'medium'
if (this.props.size ) size = this.props.size // Default to medium-sized image, but accept size property as override
return images[size] var size = 'medium';
if (this.props.size){
size = this.props.size;
}
return images[size];
} }
} }
zoom(e, image){ zoom(e, image){
e.preventDefault() e.preventDefault();
this.props.uiActions.openModal('image_zoom', {url: image} ) this.props.uiActions.openModal('image_zoom', {url: image});
} }
render(){ render(){

View File

@ -347,6 +347,9 @@ export let formatTracks = function(tracks){
* @param uri = string * @param uri = string
**/ **/
export let uriSource = function(uri){ export let uriSource = function(uri){
if (!uri){
return false;
}
var exploded = uri.split(':'); var exploded = uri.split(':');
return exploded[0] return exploded[0]
} }
@ -586,18 +589,23 @@ export let removeDuplicates = function(array){
* @param field = string (the field we're to search) * @param field = string (the field we're to search)
* @param value = string (the value to find) * @param value = string (the value to find)
* @param array = array of objects to search * @param array = array of objects to search
* @param singular = boolean (just return the first result)
* @return array * @return array
**/ **/
export let applyFilter = function(field, value, array){ export let applyFilter = function(field, value, array, singular = false){
var results = [] var results = [];
for (var i = 0; i < array.length; i++){ for (var i = 0; i < array.length; i++){
if (array[i][field] && array[i][field].toLowerCase().includes(value.toLowerCase())){ if (array[i][field] && String(array[i][field]).toLowerCase().includes(String(value).toLowerCase())){
results.push(array[i]) if (singular){
return array[i];
} else {
results.push(array[i]);
}
} }
} }
return results return results;
} }

View File

@ -316,7 +316,7 @@ const CoreMiddleware = (function(){
case 'CURRENT_TRACK_LOADED': case 'CURRENT_TRACK_LOADED':
store.dispatch({ store.dispatch({
type: 'TRACKS_LOADED', type: 'TRACKS_LOADED',
tracks: [action.current_track] tracks: [action.track]
}); });
next(action); next(action);

View File

@ -13,8 +13,13 @@ export default function reducer(core = {}, action){
case 'CURRENT_TRACK_LOADED': case 'CURRENT_TRACK_LOADED':
return Object.assign({}, core, { return Object.assign({}, core, {
current_track: action.current_track, current_track: action.track,
current_track_uri: action.current_track_uri current_track_uri: action.uri
});
case 'NEXT_TRACK_LOADED':
return Object.assign({}, core, {
next_track_uri: action.uri
}); });
case 'QUEUE_LOADED': case 'QUEUE_LOADED':

View File

@ -171,14 +171,6 @@ export function getCurrentTrack(){
} }
} }
export function clearCurrentTrack(){
return {
type: 'CURRENT_TRACK_LOADED',
current_track: null,
current_track_uri: null
}
}
export function currentTrackLoaded(tl_track){ export function currentTrackLoaded(tl_track){
return { return {
type: 'MOPIDY_CURRENT_TRACK_LOADED', type: 'MOPIDY_CURRENT_TRACK_LOADED',
@ -186,6 +178,20 @@ export function currentTrackLoaded(tl_track){
} }
} }
export function getNextTrack(){
return {
type: 'MOPIDY_GET_NEXT_TRACK'
}
}
export function clearCurrentTrack(){
return {
type: 'CURRENT_TRACK_LOADED',
track: null,
uri: null
}
}
export function getQueue(){ export function getQueue(){
return { return {
type: 'MOPIDY_GET_QUEUE' type: 'MOPIDY_GET_QUEUE'

View File

@ -72,7 +72,8 @@ const MopidyMiddleware = (function(){
break; break;
case 'event:tracklistChanged': case 'event:tracklistChanged':
store.dispatch(mopidyActions.getQueue()) store.dispatch(mopidyActions.getQueue());
store.dispatch(mopidyActions.getNextTrack());
break; break;
case 'event:playbackStateChanged': case 'event:playbackStateChanged':
@ -97,6 +98,7 @@ const MopidyMiddleware = (function(){
case 'event:trackPlaybackStarted': case 'event:trackPlaybackStarted':
store.dispatch(mopidyActions.currentTrackLoaded(data.tl_track)); store.dispatch(mopidyActions.currentTrackLoaded(data.tl_track));
store.dispatch(mopidyActions.getNextTrack());
break; break;
case 'event:volumeChanged': case 'event:volumeChanged':
@ -2016,23 +2018,60 @@ const MopidyMiddleware = (function(){
case 'MOPIDY_CURRENT_TRACK_LOADED': case 'MOPIDY_CURRENT_TRACK_LOADED':
var track = helpers.formatTracks(action.tl_track); var track = helpers.formatTracks(action.tl_track);
// We've got Spotify running, and it's a spotify track - go straight to the source! // We don't have the track already in our index
if (store.getState().spotify.enabled && helpers.uriSource(track.uri) == 'spotify'){ if (store.getState().core.tracks[track.uri] === undefined || store.getState().core.tracks[track.uri].images === undefined){
store.dispatch(spotifyActions.getTrack(track.uri))
// Some other source, rely on Mopidy backends to do their work // We've got Spotify running, and it's a spotify track - go straight to the source!
} else { if (store.getState().spotify.enabled && helpers.uriSource(track.uri) == 'spotify'){
store.dispatch(mopidyActions.getImages('tracks',[track.uri])) store.dispatch(spotifyActions.getTrack(track.uri));
// Some other source, rely on Mopidy backends to do their work
} else {
store.dispatch(mopidyActions.getImages('tracks',[track.uri]));
}
} }
// Set our window title to the track title // Set our window title to the track title
helpers.setWindowTitle(track, store.getState().mopidy.play_state); helpers.setWindowTitle(track, store.getState().mopidy.play_state);
store.dispatch({ store.dispatch({
type: 'CURRENT_TRACK_LOADED', type: 'CURRENT_TRACK_LOADED',
current_track: track track: track
}); });
break; break;
case 'MOPIDY_GET_NEXT_TRACK':
request(socket, store, 'tracklist.getNextTlid')
.then(
response => {
if (response && response >= 0){
var track = helpers.applyFilter('tlid', response, store.getState().core.queue, true);
if (track){
store.dispatch({
type: 'NEXT_TRACK_LOADED',
uri: track.uri
});
console.log(track.name);
// We don't have the track already in our index
if (store.getState().core.tracks[track.uri] === undefined || store.getState().core.tracks[track.uri].images === undefined){
// We've got Spotify running, and it's a spotify track - go straight to the source!
if (store.getState().spotify.enabled && helpers.uriSource(track.uri) == 'spotify'){
store.dispatch(spotifyActions.getTrack(track.uri));
// Some other source, rely on Mopidy backends to do their work
} else {
store.dispatch(mopidyActions.getImages('tracks',[track.uri]));
}
}
}
}
}
);
break;
case 'MOPIDY_GET_TRACK': case 'MOPIDY_GET_TRACK':
request(socket, store, 'library.lookup', action.data ) request(socket, store, 'library.lookup', action.data )
.then( .then(

View File

@ -91,9 +91,9 @@ class Queue extends React.Component{
} }
render(){ render(){
var current_track = null; var current_track = null;
var tracks = []; var tracks = [];
if (this.props.queue && this.props.tracks){ if (this.props.queue && this.props.tracks){
for (var i = 0; i < this.props.queue.length; i++){ for (var i = 0; i < this.props.queue.length; i++){
var track = this.props.queue[i]; var track = this.props.queue[i];
@ -131,11 +131,10 @@ class Queue extends React.Component{
} }
} }
var image = null var current_track_image = null;
if (current_track){ if (current_track){
if (current_track.images !== undefined && current_track.images){ if (current_track.images !== undefined && current_track.images){
image = helpers.sizedImages(current_track.images) current_track_image = helpers.sizedImages(current_track.images).large;
image = image.large
} }
} }
@ -164,11 +163,11 @@ class Queue extends React.Component{
return ( return (
<div className="view queue-view"> <div className="view queue-view">
<Header icon="play" className="overlay" title="Now playing" options={options} uiActions={this.props.uiActions} /> <Header icon="play" className="overlay" title="Now playing" options={options} uiActions={this.props.uiActions} />
<Parallax blur image={image} /> <Parallax blur image={current_track_image} />
<div className="content-wrapper"> <div className="content-wrapper">
<div className="current-track"> <div className="current-track">
{this.renderArtwork(image)} {this.renderArtwork(current_track_image)}
<div className="title"> <div className="title">
{current_track ? <URILink type="track" uri={current_track.uri}>{current_track.name}</URILink> : <span>-</span>} {current_track ? <URILink type="track" uri={current_track.uri}>{current_track.name}</URILink> : <span>-</span>}
</div> </div>

View File

@ -197,6 +197,15 @@ h4 {
} }
} }
.hide {
width: 0px;
height: 0px;
position: fixed;
z-index: -99;
top: -99px;
left: -99px;
}
.no-results { .no-results {
text-align: center; text-align: center;
padding: 40px; padding: 40px;