Moving tracks into items (need to consider performance knock-ons)

This commit is contained in:
James Barnsley
2020-09-06 21:13:53 +12:00
parent 1bc7f2b372
commit 60326d9de6
13 changed files with 739 additions and 876 deletions

View File

@ -107,71 +107,74 @@ export function updateColdStore(items) {
* relevant service to load the record - all from one neat package.
* */
export function loadItems(uris, force_reload = false) {
export function loadItems(uris, forceReload = false) {
return {
type: 'LOAD_ITEMS',
uris,
force_reload,
forceReload,
};
}
export function loadItem(uri, force_reload = false) {
return loadItems([uri], force_reload);
export function loadItem(uri, forceReload = false) {
return loadItems([uri], forceReload);
}
export function loadTrack(uri, force_reload = false) {
export function loadTrack(uri, forceReload = false) {
return {
type: 'LOAD_TRACK',
uri,
force_reload,
forceReload,
};
}
export function loadAlbum(uri, force_reload = false) {
export function loadAlbum(uri, forceReload = false, callbackAction) {
return {
type: 'LOAD_ALBUM',
uri,
force_reload,
forceReload,
callbackAction,
};
}
export function loadArtist(uri, force_reload = false) {
export function loadArtist(uri, forceReload = false, callbackAction) {
return {
type: 'LOAD_ARTIST',
uri,
force_reload,
forceReload,
callbackAction,
};
}
export function loadPlaylist(uri, force_reload = false) {
export function loadPlaylist(uri, forceReload = false, callbackAction) {
return {
type: 'LOAD_PLAYLIST',
uri,
force_reload,
forceReload,
callbackAction,
};
}
export function loadUser(uri, force_reload = false) {
export function loadUser(uri, forceReload = false) {
return {
type: 'LOAD_USER',
uri,
force_reload,
forceReload,
};
}
export function loadUserPlaylists(uri, force_reload = false) {
export function loadUserPlaylists(uri, forceReload = false) {
return {
type: 'LOAD_USER_PLAYLISTS',
uri,
force_reload,
forceReload,
};
}
export function loadLibrary(uri, force_reload = false) {
export function loadLibrary(uri, forceReload = false) {
return {
type: 'LOAD_LIBRARY',
uri,
force_reload,
forceReload,
};
}

View File

@ -318,35 +318,50 @@ const CoreMiddleware = (function () {
store.dispatch({
type: `LOAD_${uriType(uri).toUpperCase()}`,
uri,
force_reload: action.force_reload,
forceReload: action.forceReload,
});
});
break;
case 'LOAD_TRACK':
if (
!action.force_reload
&& store.getState().core.tracks[action.uri]) {
console.info(`Loading "${action.uri}" from index`);
case 'LOAD_TRACK': {
const fetchTrack = () => {
switch (uriSource(action.uri)) {
case 'spotify':
store.dispatch(spotifyActions.getTrack(action.uri));
if (spotify.me) {
store.dispatch(spotifyActions.following(action.uri));
}
break;
default:
store.dispatch(mopidyActions.getTrack(action.uri));
break;
}
};
if (action.forceReload) {
console.info(`Force-reloading "${action.uri}"`);
fetchTrack();
break;
}
if (store.getState().core.items[action.uri]) {
console.info(`Using "${action.uri}" from index`);
break;
}
switch (uriSource(action.uri)) {
case 'spotify':
store.dispatch(spotifyActions.getTrack(action.uri));
if (spotify.me) {
store.dispatch(spotifyActions.following(action.uri));
}
break;
default:
store.dispatch(mopidyActions.getTrack(action.uri));
break;
}
localForage.getItem(action.uri).then((result) => {
if (result) {
console.info(`Loading "${action.uri}" from database`);
store.dispatch(coreActions.restoreItemsFromColdStore([result]));
} else {
fetchTrack();
}
});
next(action);
break;
}
case 'LOAD_ALBUM':
const fetchAlbum = () => {
@ -365,7 +380,7 @@ const CoreMiddleware = (function () {
};
};
if (action.force_reload) {
if (action.forceReload) {
console.info(`Force-reloading "${action.uri}"`);
fetchAlbum();
break;
@ -412,7 +427,7 @@ const CoreMiddleware = (function () {
}
};
if (action.force_reload) {
if (action.forceReload) {
console.info(`Force-reloading "${action.uri}"`);
fetchArtist();
break;
@ -460,7 +475,7 @@ const CoreMiddleware = (function () {
}
};
if (action.force_reload) {
if (action.forceReload) {
console.info(`Force-reloading "${action.uri}"`);
fetchPlaylist();
break;
@ -477,7 +492,6 @@ const CoreMiddleware = (function () {
if (result) {
console.info(`Restoring "${action.uri}" from database`);
store.dispatch(coreActions.restoreItemsFromColdStore([result]));
if (!result.tracks) {
fetchPlaylist();
}
@ -491,7 +505,7 @@ const CoreMiddleware = (function () {
case 'LOAD_USER':
if (
!action.force_reload
!action.forceReload
&& store.getState().core.users[action.uri]
&& store.getState().core.users[action.uri].playlists_uris) {
console.info(`Loading "${action.uri}" from index`);
@ -517,7 +531,7 @@ const CoreMiddleware = (function () {
case 'LOAD_USER_PLAYLISTS':
if (
!action.force_reload
!action.forceReload
&& store.getState().core.users[action.uri]
&& store.getState().core.users[action.uri].playlists_uris) {
console.info(`Loading "${action.uri}" playlists from index`);
@ -558,7 +572,7 @@ const CoreMiddleware = (function () {
}
};
if (action.force_reload) {
if (action.forceReload) {
console.info(`Force-reloading "${action.uri}"`);
fetchLibrary();
break;
@ -592,11 +606,15 @@ const CoreMiddleware = (function () {
* These modify our asset indexes, which are used globally
* */
case 'CURRENT_TRACK_LOADED':
store.dispatch(coreActions.trackLoaded(action.track));
action.track = formatTrack(action.track);
next(action);
case 'CURRENT_TRACK_LOADED': {
const track = formatTrack(action.track);
store.dispatch(coreActions.itemLoaded(track));
next({
...action,
track,
});
break;
}
case 'QUEUE_LOADED':
store.dispatch(coreActions.tracksLoaded(action.tracks));

View File

@ -651,109 +651,65 @@ const MopidyMiddleware = (function () {
* Advanced playback events
* */
case 'MOPIDY_PLAY_PLAYLIST':
var playlist = store.getState().core.items[action.uri];
// We have the playlist loaded already, and we've got at least 1 track to start playing
if (playlist && playlist.tracks_uris && playlist.tracks_uris.length > 0) {
// We've got all of the tracks, so just play those; no further action required
if (playlist.tracks_total == playlist.tracks_uris.length) {
store.dispatch(mopidyActions.playURIs(playlist.tracks_uris, action.uri, action.shuffle));
break;
}
// Spotify-provided playlists need to be handled by the Spotify service
if (playlist.provider == 'spotify') {
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'play'));
break;
}
// It's a Spotify playlist that we haven't loaded yet, so Spotify HTTP API needs to go get it
} else if (uriSource(action.uri) == 'spotify' && store.getState().spotify.enabled) {
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'play'));
case 'MOPIDY_PLAY_PLAYLIST': {
const playlist = store.getState().core.items[action.uri];
if (playlist && playlist.tracks) {
store.dispatch(
mopidyActions.playURIs(
arrayOf('uri', playlist.tracks),
action.uri,
action.shuffle,
),
);
break;
}
// Not in index, and Spotify HTTP not enabled, so just play it as-is
// Fetch the playlist tracks via backend and add each track by URI
request(store, 'playlists.lookup', { uri: action.uri })
.then(
(response) => {
if (!response || response.tracks === undefined || !response.tracks) {
store.dispatch(uiActions.createNotification({ content: 'Failed to load playlist tracks', level: 'error' }));
} else {
let tracks_uris = arrayOf('uri', response.tracks);
if (action.shuffle) {
tracks_uris = shuffle(tracks_uris);
}
store.dispatch(mopidyActions.playURIs(tracks_uris, action.uri));
}
store.dispatch(
coreActions.loadPlaylist(
action.uri,
false,
{
name: 'play',
shuffle: action.shuffle,
},
(error) => {
store.dispatch(coreActions.handleException(
`Mopidy: ${error.message ? error.message : 'Lookup failed'}`,
error,
));
},
);
),
);
break;
}
case 'MOPIDY_ENQUEUE_PLAYLIST':
var playlist = store.getState().core.items[action.uri];
// We have the playlist loaded already, and we've got at least 1 track to start playing
if (playlist && playlist.tracks_uris && playlist.tracks_uris.length > 0) {
// We've got all of the tracks, so just play those; no further action required
if (playlist.tracks_total == playlist.tracks_uris.length) {
let tracks_uris = Object.assign([], playlist.tracks_uris);
if (action.shuffle) {
tracks_uris = shuffle(tracks_uris);
}
store.dispatch(mopidyActions.enqueueURIs(tracks_uris, action.uri, action.play_next, action.at_position, action.offset));
break;
}
// Spotify-provided playlists need to be handled by the Spotify service
if (playlist.provider == 'spotify') {
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'enqueue', action.play_next));
break;
}
// It's a Spotify playlist that we haven't loaded yet, so Spotify HTTP API needs to go get it
} else if (uriSource(action.uri) == 'spotify' && store.getState().spotify.enabled) {
store.dispatch(spotifyActions.getAllPlaylistTracks(action.uri, action.shuffle, 'enqueue', action.play_next));
case 'MOPIDY_ENQUEUE_PLAYLIST': {
const playlist = store.getState().core.items[action.uri];
if (playlist && playlist.tracks) {
store.dispatch(
mopidyActions.enqueueURIs(
arrayOf('uri', playlist.tracks),
action.uri,
action.shuffle,
),
);
break;
}
// Not in index, and Spotify HTTP not enabled, so just play it as-is
// Fetch the playlist tracks via backend and add each track by URI
request(store, 'playlists.lookup', { uri: action.uri })
.then(
(response) => {
if (response.tracks === undefined) {
store.dispatch(uiActions.createNotification({ content: 'Failed to load playlist tracks', level: 'error' }));
} else {
let tracks_uris = arrayOf('uri', response.tracks);
if (action.shuffle) {
tracks_uris = shuffle(tracks_uris);
}
store.dispatch(mopidyActions.enqueueURIs(tracks_uris, action.uri, action.play_next, action.at_position, action.offset));
}
store.dispatch(
coreActions.loadPlaylist(
action.uri,
false,
{
name: 'enqueue',
shuffle: action.shuffle,
play_next: action.play_next,
at_position: action.at_position,
offset: action.offset,
},
(error) => {
store.dispatch(coreActions.handleException(
`Mopidy: ${error.message ? error.message : 'Lookup failed'}`,
error,
));
},
);
),
);
break;
}
case 'MOPIDY_ENQUEUE_URIS':
case 'MOPIDY_ENQUEUE_URIS': {
if (!action.uris || action.uris.length <= 0) {
this.props.uiActions.createNotification({ content: 'No URIs to enqueue', level: 'warning' });
this.props.uiActions.createNotification({
content: 'No URIs to enqueue',
level: 'warning',
});
break;
}
@ -762,15 +718,18 @@ const MopidyMiddleware = (function () {
{
notification: {
content: `${store.getState().pusher.username} is adding ${action.uris.length} URIs to queue`,
icon: (store.getState().core.current_track ? getTrackIcon(store.getState().core.current_track, store.getState().core) : false),
icon: (
store.getState().core.current_track
? getTrackIcon(store.getState().core.current_track, store.getState().core)
: false
),
},
},
));
// split into batches
var uris = Object.assign([], action.uris);
var batches = [];
var batch_size = 5;
const uris = Object.assign([], action.uris);
const batches = [];
const batch_size = 5;
while (uris.length > 0) {
batches.push({
uris: uris.splice(0, batch_size),
@ -781,11 +740,11 @@ const MopidyMiddleware = (function () {
});
}
// pass this modified action to the reducer (and other middleware)
action.batches = batches;
next(action);
next({
...action,
batches,
});
// start our processor
store.dispatch(uiActions.startProcess(
'MOPIDY_ENQUEUE_URIS_PROCESSOR',
i18n('services.mopidy.adding_uris', { count: action.uris.length }),
@ -796,17 +755,14 @@ const MopidyMiddleware = (function () {
},
));
break;
}
case 'MOPIDY_ENQUEUE_URIS_PROCESSOR':
case 'MOPIDY_ENQUEUE_URIS_PROCESSOR': {
const last_run = store.getState().ui.processes.MOPIDY_ENQUEUE_URIS_PROCESSOR;
var last_run = store.getState().ui.processes.MOPIDY_ENQUEUE_URIS_PROCESSOR;
// Cancelling
if (last_run && last_run.status == 'cancelling') {
store.dispatch(uiActions.processCancelled('MOPIDY_ENQUEUE_URIS_PROCESSOR'));
return;
// make sure we have some uris in the queue
} if (action.data.batches && action.data.batches.length > 0) {
var batches = Object.assign([], action.data.batches);
var batch = batches[0];
@ -829,9 +785,11 @@ const MopidyMiddleware = (function () {
break;
}
var { current_track } = store.getState().core;
var { queue } = store.getState().core;
var current_track_index = -1;
const {
current_track,
queue,
} = store.getState().core;
let current_track_index = -1;
if (current_track) {
for (var i = 0; i < queue.length; i++) {
@ -888,8 +846,8 @@ const MopidyMiddleware = (function () {
));
},
);
break;
}
case 'MOPIDY_PLAY_URIS':
const { from_uri } = action;
@ -2039,7 +1997,7 @@ const MopidyMiddleware = (function () {
// Some other source, rely on Mopidy backends to do their work
} else {
store.dispatch(mopidyActions.getImages('tracks', [track.uri]));
store.dispatch(mopidyActions.getImages([track.uri]));
}
}
}
@ -2060,17 +2018,7 @@ const MopidyMiddleware = (function () {
uri: track.uri,
});
// We don't have the track (including images) 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 && 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]));
}
}
store.dispatch(coreActions.loadItem(track.uri));
}
}
},
@ -2088,7 +2036,7 @@ const MopidyMiddleware = (function () {
store.dispatch(coreActions.tracksLoaded(tracks));
if (action.get_images) {
store.dispatch(mopidyActions.getImages('tracks', arrayOf('uri', tracks)));
store.dispatch(mopidyActions.getImages(arrayOf('uri', tracks)));
}
},
(error) => {

View File

@ -946,7 +946,7 @@ export function getRecommendations(uris = [], limit = 20, tunabilities = null) {
request(dispatch, getState, endpoint)
.then(
(response) => {
const tracks = Object.assign([], response.tracks);
const tracks = Object.assign([], formatTracks(response.tracks));
// We only get simple artist objects, so we need to
// get the full object. We'll add URIs to our recommendations
@ -983,11 +983,11 @@ export function getRecommendations(uris = [], limit = 20, tunabilities = null) {
}
if (albums.length > 0) {
dispatch(coreActions.albumsLoaded(albums));
dispatch(coreActions.itemsLoaded(albums));
}
if (tracks.length > 0) {
dispatch(coreActions.tracksLoaded(tracks));
dispatch(coreActions.itemsLoaded(tracks));
}
dispatch({
@ -1104,7 +1104,6 @@ export function getArtistImages(artist) {
return (dispatch, getState) => {
request(dispatch, getState, `search?q=${artist.name}&type=artist`)
.then(response => {
console.log('images', response);
if (response.artists.items.length > 0) {
dispatch(coreActions.itemLoaded({
uri: artist.uri,
@ -1117,22 +1116,27 @@ export function getArtistImages(artist) {
export function playArtistTopTracks(uri) {
return (dispatch, getState) => {
const { artists } = getState().core;
const {
items: {
[uri]: artist,
},
} = getState().core;
// Do we have this artist (and their tracks) in our index already?
if (typeof (artists[uri]) !== 'undefined' && typeof (artists[uri].tracks) !== 'undefined') {
const uris = arrayOf('uri', artists[uri].tracks);
if (artist && artist.tracks) {
const uris = arrayOf('uri', artist.tracks);
dispatch(mopidyActions.playURIs(uris, uri));
// We need to load the artist's top tracks first
} else {
request(dispatch, getState, `artists/${getFromUri('artistid', uri)}/top-tracks?country=${getState().spotify.country}`)
.then(
(response) => {
const uris = arrayOf('uri', response.tracks);
dispatch(mopidyActions.playURIs(uris, uri));
},
);
request(
dispatch,
getState,
`artists/${getFromUri('artistid', uri)}/top-tracks?country=${getState().spotify.country}`,
).then(
(response) => {
const uris = arrayOf('uri', response.tracks);
dispatch(mopidyActions.playURIs(uris, uri));
},
);
}
};
}
@ -1359,9 +1363,8 @@ export function savePlaylist(uri, name, description, is_public, is_collaborative
};
}
export function getPlaylist(uri) {
export function getPlaylist(uri, callbackAction = null) {
return (dispatch, getState) => {
// get the main playlist object
request(dispatch, getState, `playlists/${getFromUri('playlistid', uri)}?market=${getState().spotify.country}`)
.then(
(response) => {
@ -1393,6 +1396,29 @@ export function getPlaylist(uri) {
uri,
tracks,
}));
if (callbackAction) {
switch (callbackAction.name) {
case 'enqueue':
dispatch(mopidyActions.enqueueURIs(
arrayOf('uri', tracks),
uri,
callbackAction.play_next,
callbackAction.at_position,
callbackAction.offset,
));
break;
case 'play':
dispatch(mopidyActions.playURIs(
arrayOf('uri', tracks),
uri,
callbackAction.shuffle,
));
break;
default:
break;
}
}
}
});
@ -1498,8 +1524,26 @@ export function getLibraryTracksAndPlayProcessor(data) {
* Recursively get .next until we have all tracks
* */
export function getAllPlaylistTracks(uri, shuffle = false, callback_action = null, play_next = false, at_position = null, offset = 0) {
export function getAllPlaylistTracks(
uri,
shuffle = false,
callback_action = null,
play_next = false,
at_position = null,
offset = 0,
) {
return (dispatch, getState) => {
if (data.callback_action == 'enqueue') {
dispatch(mopidyActions.enqueueURIs(uris, data.uri, data.play_next, data.at_position, data.offset));
} else {
dispatch(mopidyActions.playURIs(uris, data.uri));
}
dispatch(uiActions.startProcess(
'SPOTIFY_GET_ALL_PLAYLIST_TRACKS_PROCESSOR',
'Loading playlist tracks',
@ -1690,7 +1734,6 @@ export function getLibraryPlaylists() {
let libraryPlaylists = [];
const fetchLibraryPlaylists = (endpoint) => request(dispatch, getState, endpoint)
.then((response) => {
console.log('spot', response);
libraryPlaylists = [...libraryPlaylists, ...formatPlaylists(response.items)];
if (response.next) {
fetchLibraryPlaylists(response.next);