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

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

View File

@ -12,6 +12,7 @@ import ArtistSentence from './ArtistSentence'
import Thumbnail from './Thumbnail'
import Icon from './Icon'
import * as helpers from '../helpers'
import * as uiActions from '../services/ui/actions'
import * as coreActions from '../services/core/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">
<source src={this.props.http_streaming_url} type={"audio/"+this.props.http_streaming_encoding} />
</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="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_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),
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),
play_state: state.mopidy.play_state,
time_position: state.mopidy.time_position,

View File

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

View File

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

View File

@ -13,8 +13,13 @@ export default function reducer(core = {}, action){
case 'CURRENT_TRACK_LOADED':
return Object.assign({}, core, {
current_track: action.current_track,
current_track_uri: action.current_track_uri
current_track: action.track,
current_track_uri: action.uri
});
case 'NEXT_TRACK_LOADED':
return Object.assign({}, core, {
next_track_uri: action.uri
});
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){
return {
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(){
return {
type: 'MOPIDY_GET_QUEUE'

View File

@ -72,7 +72,8 @@ const MopidyMiddleware = (function(){
break;
case 'event:tracklistChanged':
store.dispatch(mopidyActions.getQueue())
store.dispatch(mopidyActions.getQueue());
store.dispatch(mopidyActions.getNextTrack());
break;
case 'event:playbackStateChanged':
@ -97,6 +98,7 @@ const MopidyMiddleware = (function(){
case 'event:trackPlaybackStarted':
store.dispatch(mopidyActions.currentTrackLoaded(data.tl_track));
store.dispatch(mopidyActions.getNextTrack());
break;
case 'event:volumeChanged':
@ -2016,23 +2018,60 @@ const MopidyMiddleware = (function(){
case 'MOPIDY_CURRENT_TRACK_LOADED':
var track = helpers.formatTracks(action.tl_track);
// 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))
// 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){
// Some other source, rely on Mopidy backends to do their work
} else {
store.dispatch(mopidyActions.getImages('tracks',[track.uri]))
// 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]));
}
}
// Set our window title to the track title
helpers.setWindowTitle(track, store.getState().mopidy.play_state);
store.dispatch({
type: 'CURRENT_TRACK_LOADED',
current_track: track
track: track
});
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':
request(socket, store, 'library.lookup', action.data )
.then(

View File

@ -91,9 +91,9 @@ class Queue extends React.Component{
}
render(){
var current_track = null;
var tracks = [];
if (this.props.queue && this.props.tracks){
for (var i = 0; i < this.props.queue.length; 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.images !== undefined && current_track.images){
image = helpers.sizedImages(current_track.images)
image = image.large
current_track_image = helpers.sizedImages(current_track.images).large;
}
}
@ -164,11 +163,11 @@ class Queue extends React.Component{
return (
<div className="view queue-view">
<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="current-track">
{this.renderArtwork(image)}
{this.renderArtwork(current_track_image)}
<div className="title">
{current_track ? <URILink type="track" uri={current_track.uri}>{current_track.name}</URILink> : <span>-</span>}
</div>