diff --git a/js/actionCreators.js b/js/actionCreators.js index 85259a09..64a5576a 100644 --- a/js/actionCreators.js +++ b/js/actionCreators.js @@ -6,14 +6,20 @@ import { promptForFileReferences } from "./fileUtils"; import skinParser from "./skinParser"; -import { BANDS, TRACK_HEIGHT, LOAD_STYLE } from "./constants"; +import { + BANDS, + TRACK_HEIGHT, + LOAD_STYLE, + MEDIA_TAG_REQUEST_STATUS +} from "./constants"; import { getEqfData, nextTrack, getScrollOffset, getOverflowTrackCount, getPlaylistURL, - getSelectedTrackObjects + getSelectedTrackObjects, + getVisibleTracks } from "./selectors"; import { @@ -53,7 +59,9 @@ import { SET_MEDIA_TAGS, SET_MEDIA_DURATION, TOGGLE_SHADE_MODE, - TOGGLE_PLAYLIST_SHADE_MODE + TOGGLE_PLAYLIST_SHADE_MODE, + MEDIA_TAG_REQUEST_INITIALIZED, + MEDIA_TAG_REQUEST_FAILED } from "./actionTypes"; function playRandomTrack() { @@ -311,12 +319,27 @@ export function loadMediaFile(track, priority = null, atIndex = 0) { }; } +export function fetchMediaTagsForVisibleTracks() { + return (dispatch, getState) => { + getVisibleTracks(getState()) + .filter( + track => + track.mediaTagsRequestStatus === + MEDIA_TAG_REQUEST_STATUS.NOT_REQUESTED + ) + .forEach(track => { + dispatch(fetchMediaTags(track.url, track.id)); + }); + }; +} + export function fetchMediaTags(file, id) { // Workaround https://github.com/aadsm/jsmediatags/issues/83 if (typeof file === "string" && !/^[a-z]+:\/\//i.test(file)) { file = `${location.protocol}//${location.host}${location.pathname}${file}`; } return dispatch => { + dispatch({ type: MEDIA_TAG_REQUEST_INITIALIZED, id }); try { jsmediatags.read(file, { onSuccess: data => { @@ -326,6 +349,7 @@ export function fetchMediaTags(file, id) { dispatch({ type: SET_MEDIA_TAGS, artist, title, id }); }, onError: () => { + dispatch({ type: MEDIA_TAG_REQUEST_FAILED, id }); // Nothing to do. The filename will have to suffice. } }); @@ -333,6 +357,7 @@ export function fetchMediaTags(file, id) { // Possibly jsmediatags could not find a parser for this file? // Nothing to do. // Consider removing this after https://github.com/aadsm/jsmediatags/issues/83 is resolved. + dispatch({ type: MEDIA_TAG_REQUEST_FAILED, id }); } }; } @@ -390,6 +415,35 @@ export function openSkinFileDialog() { return _openFileDialog(".zip, .wsz"); } +// Requires Dropbox's Chooser to be loaded on the page +function genAudioFileUrlsFromDropbox() { + return new Promise((resolve, reject) => { + if (window.Dropbox == null) { + reject(); + } + window.Dropbox.choose({ + success: resolve, + error: reject, + linkType: "direct", + folderselect: false, + multiselect: true, + extensions: ["video", "audio"] + }); + }); +} + +export function openDropboxFileDialog() { + return async dispatch => { + const files = await genAudioFileUrlsFromDropbox(); + dispatch( + loadMediaFiles( + files.map(file => ({ url: file.link, defaultName: file.name })), + LOAD_STYLE.PLAY + ) + ); + }; +} + export function setEqBand(band, value) { return { type: SET_BAND_VALUE, band, value }; } diff --git a/js/actionTypes.js b/js/actionTypes.js index 92d52697..5add53b5 100644 --- a/js/actionTypes.js +++ b/js/actionTypes.js @@ -60,3 +60,5 @@ export const SET_MEDIA_TAGS = "SET_MEDIA_TAGS"; export const SET_MEDIA_DURATION = "SET_MEDIA_DURATION"; export const OPEN_GEN_WINDOW = "OPEN_GEN_WINDOW"; export const CLOSE_GEN_WINDOW = "CLOSE_GEN_WINDOW"; +export const MEDIA_TAG_REQUEST_INITIALIZED = "MEDIA_TAG_REQUEST_INITIALIZED"; +export const MEDIA_TAG_REQUEST_FAILED = "MEDIA_TAG_REQUEST_FAILED"; diff --git a/js/constants.js b/js/constants.js index 309fe8e1..802e17d2 100644 --- a/js/constants.js +++ b/js/constants.js @@ -11,6 +11,13 @@ export const LOAD_STYLE = { PLAY: "PLAY" }; +export const MEDIA_TAG_REQUEST_STATUS = { + INITIALIZED: "INITIALIZED", + FAILED: "FAILED", + COMPLETE: "COMPLETE", + NOT_REQUESTED: "NOT_REQUESTED" +}; + export const UTF8_ELLIPSIS = "\u2026"; export const CHARACTER_WIDTH = 5; export const PLAYLIST_RESIZE_SEGMENT_WIDTH = 25; diff --git a/js/reducers/playlist.js b/js/reducers/playlist.js index 918f7ca1..4f5313a9 100644 --- a/js/reducers/playlist.js +++ b/js/reducers/playlist.js @@ -16,8 +16,11 @@ import { BUFFER_TRACK, DRAG_SELECTED, SET_MEDIA_TAGS, - SET_MEDIA_DURATION + SET_MEDIA_DURATION, + MEDIA_TAG_REQUEST_INITIALIZED, + MEDIA_TAG_REQUEST_FAILED } from "../actionTypes"; +import { MEDIA_TAG_REQUEST_STATUS } from "../constants"; import { filenameFromUrl } from "../fileUtils"; import { shuffle, moveSelected, mapObject, filterObject } from "../utils"; @@ -148,7 +151,8 @@ const playlist = (state = defaultPlaylistState, action) => { selected: false, defaultName: action.defaultName, duration: null, - url: action.url + url: action.url, + mediaTagsRequestStatus: MEDIA_TAG_REQUEST_STATUS.NOT_REQUESTED } }, // TODO: This could probably be made to work, but we clear it just to be safe. @@ -172,11 +176,34 @@ const playlist = (state = defaultPlaylistState, action) => { ...state.tracks, [action.id]: { ...state.tracks[action.id], + mediaTagsRequestStatus: MEDIA_TAG_REQUEST_STATUS.COMPLETE, title: action.title, artist: action.artist } } }; + case MEDIA_TAG_REQUEST_INITIALIZED: + return { + ...state, + tracks: { + ...state.tracks, + [action.id]: { + ...state.tracks[action.id], + mediaTagsRequestStatus: MEDIA_TAG_REQUEST_STATUS.INITIALIZED + } + } + }; + case MEDIA_TAG_REQUEST_FAILED: + return { + ...state, + tracks: { + ...state.tracks, + [action.id]: { + ...state.tracks[action.id], + mediaTagsRequestStatus: MEDIA_TAG_REQUEST_STATUS.FAILED + } + } + }; case SET_MEDIA_DURATION: return { ...state, diff --git a/js/reducers/playlist.test.js b/js/reducers/playlist.test.js index 6a8fbbaf..bba91498 100644 --- a/js/reducers/playlist.test.js +++ b/js/reducers/playlist.test.js @@ -25,6 +25,7 @@ describe("playlist reducer", () => { selected: false, duration: null, defaultName: "My Track Name", + mediaTagsRequestStatus: "NOT_REQUESTED", url: "url://some-url" } }, @@ -54,6 +55,7 @@ describe("playlist reducer", () => { 100: { selected: false, duration: null, + mediaTagsRequestStatus: "NOT_REQUESTED", defaultName: "My Track Name", url: "url://some-url" } @@ -85,6 +87,7 @@ describe("playlist reducer", () => { 100: { selected: false, duration: null, + mediaTagsRequestStatus: "NOT_REQUESTED", defaultName: "My Track Name", url: "url://some-url" } diff --git a/js/selectors.js b/js/selectors.js index c9d036aa..78789bac 100644 --- a/js/selectors.js +++ b/js/selectors.js @@ -156,6 +156,12 @@ export const getVisibleTrackIds = createSelector( trackOrder.slice(offset, offset + numberOfVisibleTracks) ); +export const getVisibleTracks = createSelector( + getVisibleTrackIds, + getTracks, + (visibleTrackIds, tracks) => visibleTrackIds.map(id => tracks[id]) +); + export const getPlaylist = state => state.playlist; export const getDuration = state => {