Current track images to use Mopidy-Spotify so no immediate dependency on Spotify HTTP API

This commit is contained in:
James Barnsley
2017-08-03 15:26:54 +12:00
parent 48c37b3ddf
commit 5184d06f55
11 changed files with 166 additions and 77 deletions

View File

@ -101,7 +101,6 @@ class App extends React.Component{
// Listen for standalone key codes
let keyCodes = [27,32,191]
if (keyCodes.indexOf(e.keyCode) > -1){
if (keyCode == 37)
e.preventDefault()
return true
}

View File

@ -24,8 +24,8 @@ export default class KioskModeModal extends React.Component{
}
render(){
if (this.props.current_track && this.props.current_track.album && this.props.current_track.album.images){
var images = this.props.current_track.album.images
if (this.props.current_track && this.props.current_track.images){
var images = this.props.current_track.images
} else {
var images = []
}

View File

@ -75,8 +75,8 @@ class PlaybackControls extends React.Component{
render(){
var images = false
if (this.props.current_track && this.props.current_track.album && this.props.current_track.album.images){
images = this.props.current_track.album.images
if (this.props.current_track && this.props.current_track.images){
images = this.props.current_track.images
}
return (

View File

@ -13,50 +13,67 @@ export let sizedImages = function( images ){
huge: false
}
if( images.length <= 0 ) return sizes;
if (images.length <= 0) return sizes;
for (var i = 0; i < images.length; i++){
let image = images[i]
for( var i = 0; i < images.length; i++ ){
// Mopidy image object
if (typeof(images[i].__model__) !== 'undefined' && images[i].__model__ == 'Image'){
sizes.small = images[i].uri
if (image.__model__ && image.__model__ == 'Image'){
if (image.width < 400){
sizes.small = image.url;
}else if (image.width < 800){
sizes.medium = image.url;
}else if (image.width < 1000){
sizes.large = image.url;
}else{
sizes.huge = image.url;
}
// Mopidy image string
} else if (typeof(images[i]) == 'string'){
sizes.small = images[i]
} else if (typeof(image) == 'string'){
sizes.small = image
// spotify-styled images
} else if (typeof(images[i].width) !== 'undefined'){
if( images[i].width < 400 ){
sizes.small = images[i].url;
}else if( images[i].width < 800 ){
sizes.medium = images[i].url;
}else if( images[i].width < 1000 ){
sizes.large = images[i].url;
} else if (image.width !== undefined){
if (image.width < 400){
sizes.small = image.url;
}else if (image.width < 800){
sizes.medium = image.url;
}else if (image.width < 1000){
sizes.large = image.url;
}else{
sizes.huge = images[i].url;
sizes.huge = image.url;
}
// lastfm-styled images
} else if (typeof(images[i].size) !== 'undefined'){
switch( images[i].size ){
} else if (image.size !== undefined){
switch( image.size ){
case 'mega':
case 'extralarge':
sizes.huge = images[i]['#text']
sizes.huge = image['#text']
break
case 'large':
sizes.large = images[i]['#text']
sizes.large = image['#text']
break
case 'medium':
sizes.medium = images[i]['#text']
sizes.medium = image['#text']
break
case 'small':
sizes.small = images[i]['#text']
sizes.small = image['#text']
break
}
}
}
if (!sizes.small){
if (sizes.medium) sizes.small = sizes.medium
else if (sizes.large) sizes.small = sizes.large
else if (sizes.huge) sizes.small = sizes.huge
else sizes.small = null
}
if (!sizes.medium){
if (sizes.large) sizes.medium = sizes.large
else if (sizes.huge) sizes.medium = sizes.huge
@ -68,6 +85,41 @@ export let sizedImages = function( images ){
return sizes;
}
/**
* Digest an array of Mopidy image objects into a universal format
*
* @param mopidy = obj (mopidy store object)
* @param images = array
* @return array
**/
export let digestMopidyImages = function(mopidy, images){
let digested = []
for (let i = 0; i < images.length; i++){
// Accommodate backends that provide URIs vs URLs
let url = images[i].url
if (!url && images[i].uri){
url = images[i].uri
}
/*
// Replace local images to point directly to our Mopidy server
if (url.startsWith('/images/')){
url = '//'+mopidy.host+':'+mopidy.port+url
}
*/
// Amend our URL
images[i].url = url
digested.push(images[i])
}
return digested
}
export let generateGuid = function(format = 'xxxxxxxxxxxx'){
return format.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
@ -75,7 +127,6 @@ export let generateGuid = function(format = 'xxxxxxxxxxxx'){
});
}
export let getCurrentPusherConnection = function( connections, connectionid ){
function isCurrentConnection(connection){
return connection.connectionid == newProps.pusher.connectionid;
@ -93,15 +144,14 @@ export let getCurrentPusherConnection = function( connections, connectionid ){
* @param track object
* @return string
**/
export let getTrackIcon = function(current_track = false, ui = false){
if (!ui) return false
export let getTrackIcon = function(current_track = false, core = false){
if (!core) return false
if (!current_track) return false
if (typeof(current_track.uri) == 'undefined') return false
if (typeof(core.tracks[current_track.uri]) === 'undefined') return false
var track = core.tracks[current_track.uri]
if (!track.album) return false
if (!track.album.images) return false
return sizedImages(track.album.images).small
if (!track.images) return false
return sizedImages(track.images).small
}

View File

@ -24,16 +24,6 @@ const CoreMiddleware = (function(){
next(action)
break
case 'MOPIDY_CONNECTED':
ReactGA.event({ category: 'Mopidy', action: 'Connected', label: window.location.hostname })
next(action)
break
case 'PUSHER_CONNECTED':
ReactGA.event({ category: 'Pusher', action: 'Connected', label: action.username })
next(action)
break
case 'ALBUM_LOADED':
if (action.data) ReactGA.event({ category: 'Album', action: 'Load', label: action.album.uri })
@ -75,11 +65,6 @@ const CoreMiddleware = (function(){
next(action)
break
case 'MOPIDY_DIRECTORY':
if (action.data) ReactGA.event({ category: 'Directory', action: 'Load', label: action.data.uri })
next(action)
break
case 'PLAY_PLAYLIST':
ReactGA.event({ category: 'Playlist', action: 'Play', label: action.uri })
next(action)

View File

@ -164,6 +164,14 @@ export function setTimePosition(time_position){
* Asset-oriented actions
**/
export function getImages( context, uris ){
return {
type: 'MOPIDY_GET_IMAGES',
context: context,
uris: uris
}
}
export function deletePlaylist( uri ){
return {
type: 'MOPIDY_DELETE_PLAYLIST',

View File

@ -1,4 +1,5 @@
import ReactGA from 'react-ga'
import Mopidy from 'mopidy'
import { hashHistory } from 'react-router'
import * as helpers from '../../helpers'
@ -192,6 +193,11 @@ const MopidyMiddleware = (function(){
socket.on( (type, data) => handleMessage( socket, store, type, data ) )
break
case 'MOPIDY_CONNECTED':
ReactGA.event({ category: 'Mopidy', action: 'Connected', label: window.location.hostname })
next(action)
break
case 'MOPIDY_DISCONNECT':
if(socket != null) socket.close()
socket = null
@ -238,7 +244,7 @@ const MopidyMiddleware = (function(){
type: 'browser_notification',
title: 'Track skipped',
body: store.getState().pusher.username +' skipped this track',
icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().ui) : false)
icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false)
}
store.dispatch( pusherActions.deliverBroadcast(data) )
break
@ -248,7 +254,7 @@ const MopidyMiddleware = (function(){
type: 'browser_notification',
title: 'Playback stopped',
body: store.getState().pusher.username +' stopped playback',
icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().ui) : false)
icon: (store.getState().core.current_track ? helpers.getTrackIcon(store.getState().core.current_track, store.getState().core) : false)
}
store.dispatch( pusherActions.deliverBroadcast(data) )
break
@ -400,13 +406,6 @@ const MopidyMiddleware = (function(){
break
case 'CANCEL_PROCESS':
if (action.key == 'MOPIDY_ENQUEUE_URIS'){
store.dispatch(mopidyActions.enqueueURIsCancel())
}
next(action)
break
case 'MOPIDY_PLAY_URIS':
// Stop the radio
@ -1033,17 +1032,49 @@ const MopidyMiddleware = (function(){
// Fire off our universal track index loader
store.dispatch({ type: 'TRACK_LOADED', key: action.data.track.uri, track: action.data.track })
// When current track is Spotify track, go get the full object
// This is because Mopidy doesn't give us full artist/album objects, without artwork
if (action.data.track.uri.substring(0,14) == 'spotify:track:'){
store.dispatch( spotifyActions.getTrack( action.data.track.uri ) )
}
// Get me some images
store.dispatch(mopidyActions.getImages('tracks',[action.data.track.uri]))
}
next(action)
break
/**
* =============================================================== IMAGES ===============
* ======================================================================================
**/
case 'MOPIDY_GET_IMAGES':
instruct( socket, store, 'library.getImages', {uris: action.uris})
.then( response => {
let records = []
for (var uri in response){
if (response.hasOwnProperty(uri)){
let images = response[uri]
images = helpers.digestMopidyImages(store.getState().mopidy, images)
records.push({
uri: uri,
images: images
})
}
}
let action_data = {
type: (action.context+'_LOADED').toUpperCase()
}
action_data[action.context] = records
store.dispatch(action_data)
})
next(action)
break
/**
* =============================================================== LOCAL ================
* ======================================================================================
@ -1060,6 +1091,11 @@ const MopidyMiddleware = (function(){
})
break
case 'MOPIDY_DIRECTORY':
if (action.data) ReactGA.event({ category: 'Directory', action: 'Load', label: action.data.uri })
next(action)
break
// This action is irrelevant to us, pass it on to the next middleware
default:
return next(action)

View File

@ -98,6 +98,7 @@ const PusherMiddleware = (function(){
break;
case 'PUSHER_CONNECTED':
ReactGA.event({ category: 'Pusher', action: 'Connected', label: action.username })
request(store, 'get_config')
.then(
response => {

View File

@ -123,6 +123,13 @@ const UIMiddleware = (function(){
next(action)
break
case 'CANCEL_PROCESS':
if (action.key == 'MOPIDY_ENQUEUE_URIS'){
store.dispatch(mopidyActions.enqueueURIsCancel())
}
next(action)
break
// This action is irrelevant to us, pass it on to the next middleware
default:
return next(action)

View File

@ -64,7 +64,7 @@ class Queue extends React.Component{
return (
<span className={this.props.radio_enabled ? 'artwork radio-enabled' : 'artwork'}>
{this.props.radio_enabled ? <img className="radio-overlay" src="assets/radio-overlay.png" /> : null}
<Thumbnail size="huge" />
<Thumbnail />
</span>
)
}
@ -74,15 +74,16 @@ class Queue extends React.Component{
return (
<Link className={this.props.radio_enabled ? 'artwork radio-enabled' : 'artwork'} to={link}>
{this.props.radio_enabled ? <img className="radio-overlay" src="assets/radio-overlay.png" /> : null}
<Thumbnail size="huge" image={image} />
<Thumbnail image={image} />
</Link>
)
}
render(){
var image = null
if (this.props.current_track && this.props.current_track.album !== undefined && this.props.current_track.album.images){
image = helpers.sizedImages(this.props.current_track.album.images).huge
if (this.props.current_track && this.props.current_track.images !== undefined){
image = helpers.sizedImages(this.props.current_track.images)
image = image.large
}
var options = (

View File

@ -16,6 +16,7 @@ import LazyLoadListener from '../components/LazyLoadListener'
import SearchForm from '../components/SearchForm'
import * as helpers from '../helpers'
import * as coreActions from '../services/core/actions'
import * as uiActions from '../services/ui/actions'
import * as mopidyActions from '../services/mopidy/actions'
import * as spotifyActions from '../services/spotify/actions'
@ -30,7 +31,7 @@ class Search extends React.Component{
// Make sure we have search parameters to start with
if (this.props.params && this.props.params.type && this.props.params.query){
this.props.uiActions.startSearch(this.props.params.type, this.props.params.query)
this.props.coreActions.startSearch(this.props.params.type, this.props.params.query)
}
// Auto-focus on the input field
@ -43,12 +44,12 @@ class Search extends React.Component{
if (newProps.params && newProps.params.type && newProps.params.query){
if (this.props.params.query != newProps.params.query || this.props.params.type != newProps.params.type){
this.props.uiActions.startSearch(newProps.params.type, newProps.params.query)
this.props.coreActions.startSearch(newProps.params.type, newProps.params.query)
}
// mopidy comes online
if (!this.props.mopidy_connected && newProps.mopidy_connected){
this.props.uiActions.startSearch(newProps.params.type, newProps.params.query, true)
this.props.coreActions.startSearch(newProps.params.type, newProps.params.query, true)
}
}
}
@ -260,22 +261,23 @@ const mapStateToProps = (state, ownProps) => {
return {
mopidy_connected: state.mopidy.connected,
search_settings: (state.ui.search_settings ? state.ui.search_settings : null),
tracks: (state.ui.search_results ? state.ui.search_results.tracks : []),
tracks_more: (state.ui.search_results && state.ui.search_results.tracks_more ? state.ui.search_results.tracks_more : null),
tracks: (state.core.search_results ? state.core.search_results.tracks : []),
tracks_more: (state.core.search_results && state.core.search_results.tracks_more ? state.core.search_results.tracks_more : null),
artists: (state.core.artists ? state.core.artists : []),
artists_uris: (state.ui.search_results ? state.ui.search_results.artists_uris : []),
artists_more: (state.ui.search_results ? state.ui.search_results.artists_more : null),
artists_uris: (state.core.search_results ? state.core.search_results.artists_uris : []),
artists_more: (state.core.search_results ? state.core.search_results.artists_more : null),
albums: (state.core.albums ? state.core.albums : []),
albums_uris: (state.ui.search_results ? state.ui.search_results.albums_uris : []),
albums_more: (state.ui.search_results ? state.ui.search_results.albums_more : null),
albums_uris: (state.core.search_results ? state.core.search_results.albums_uris : []),
albums_more: (state.core.search_results ? state.core.search_results.albums_more : null),
playlists: (state.core.playlists ? state.core.playlists : []),
playlists_uris: (state.ui.search_results ? state.ui.search_results.playlists_uris : []),
playlists_more: (state.ui.search_results ? state.ui.search_results.playlists_more : null)
playlists_uris: (state.core.search_results ? state.core.search_results.playlists_uris : []),
playlists_more: (state.core.search_results ? state.core.search_results.playlists_more : null)
}
}
const mapDispatchToProps = (dispatch) => {
return {
coreActions: bindActionCreators(coreActions, dispatch),
uiActions: bindActionCreators(uiActions, dispatch),
mopidyActions: bindActionCreators(mopidyActions, dispatch),
spotifyActions: bindActionCreators(spotifyActions, dispatch)