From 17f2b2b7453f5f130e322fd4e18f5d630956be76 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Fri, 15 Dec 2017 14:20:35 -0800 Subject: [PATCH] Support loading multiple files at once --- js/actionCreators.js | 56 ++++++++++++++++--- js/actionTypes.js | 4 +- js/components/DropTarget.js | 10 ++-- .../PlaylistWindow/PlaylistShade.test.js | 2 +- js/mediaMiddleware.js | 21 ++++--- js/reducers/index.js | 4 +- js/reducers/playlist.js | 21 +++++-- js/winamp.js | 12 +--- 8 files changed, 92 insertions(+), 38 deletions(-) diff --git a/js/actionCreators.js b/js/actionCreators.js index 01cbaba7..525d7712 100644 --- a/js/actionCreators.js +++ b/js/actionCreators.js @@ -20,7 +20,7 @@ import { } from "./utils"; import { CLOSE_WINAMP, - LOAD_AUDIO_URL, + ADD_TRACK_FROM_URL, OPEN_FILE_DIALOG, SEEK_TO_PERCENT_COMPLETE, SET_BALANCE, @@ -44,9 +44,11 @@ import { SET_TRACK_ORDER, TOGGLE_VISUALIZER_STYLE, PLAY_TRACK, + BUFFER_TRACK, SET_PLAYLIST_SCROLL_POSITION, DRAG_SELECTED, - SET_MEDIA_TAGS + SET_MEDIA_TAGS, + SET_MEDIA_DURATION } from "./actionTypes"; function playRandomTrack() { @@ -168,9 +170,18 @@ function setEqFromFile(file) { }); }; } + +export function loadFilesFromReferences(fileReferences) { + return dispatch => { + Array.from(fileReferences).forEach((file, i) => { + dispatch(loadFileFromReference(file, i === 0 ? "PLAY" : "NONE")); + }); + }; +} + const SKIN_FILENAME_MATCHER = new RegExp("(wsz|zip)$", "i"); const EQF_FILENAME_MATCHER = new RegExp("eqf$", "i"); -export function loadFileFromReference(fileReference) { +export function loadFileFromReference(fileReference, priority) { return dispatch => { const file = new MyFile(); file.setFileReference(fileReference); @@ -181,25 +192,54 @@ export function loadFileFromReference(fileReference) { } else { const id = uniqueId(); const url = URL.createObjectURL(fileReference); - dispatch(_loadMediaFromUrl(url, fileReference.name, true, id)); + dispatch(_addTrackFromUrl(url, fileReference.name, id, priority)); dispatch(fetchMediaTags(fileReference, id)); } }; } +export function fetchMediaDuration(url, id) { + return dispatch => { + // TODO: Does this actually stop downloading the file once it's + // got the duration? + const audio = document.createElement("audio"); + const durationChange = () => { + const { duration } = audio; + dispatch({ type: SET_MEDIA_DURATION, duration, id }); + audio.removeEventListener("durationchange", durationChange); + }; + audio.addEventListener("durationchange", durationChange); + audio.src = url; + }; +} + let counter = 0; function uniqueId() { return counter++; } -export function _loadMediaFromUrl(url, name, autoPlay, id) { - return { type: LOAD_AUDIO_URL, url, name, autoPlay, id }; +export function _addTrackFromUrl(url, name, id, priority) { + return dispatch => { + dispatch({ type: ADD_TRACK_FROM_URL, url, name, id }); + switch (priority) { + case "BUFFER": + dispatch({ type: BUFFER_TRACK, name, id }); + break; + case "PLAY": + dispatch({ type: PLAY_TRACK, name, id }); + break; + default: + // If we're not going to load this right away, + // we should fetch duration on our own + dispatch(fetchMediaDuration(url, id)); + } + }; } -export function loadMediaFromUrl(url, name, autoPlay) { +export function loadMediaFromUrl(url, name, priority) { return dispatch => { const id = uniqueId(); - dispatch(_loadMediaFromUrl(url, name, autoPlay, id)); + dispatch(_addTrackFromUrl(url, name, id, priority)); dispatch(fetchMediaTags(url, id)); }; } diff --git a/js/actionTypes.js b/js/actionTypes.js index 9dd2361c..44365357 100644 --- a/js/actionTypes.js +++ b/js/actionTypes.js @@ -1,4 +1,4 @@ -export const LOAD_AUDIO_URL = "LOAD_AUDIO_URL"; +export const ADD_TRACK_FROM_URL = "ADD_TRACK_FROM_URL"; export const CLOSE_WINAMP = "CLOSE_WINAMP"; export const CLOSE_EQUALIZER_WINDOW = "CLOSE_EQUALIZER_WINDOW"; export const IS_PLAYING = "IS_PLAYING"; @@ -56,5 +56,7 @@ export const REVERSE_LIST = "REVERSE_LIST"; export const RANDOMIZE_LIST = "RANDOMIZE_LIST"; export const SET_TRACK_ORDER = "SET_TRACK_ORDER"; export const PLAY_TRACK = "PLAY_TRACK"; +export const BUFFER_TRACK = "BUFFER_TRACK"; export const DRAG_SELECTED = "DRAG_SELECTED"; export const SET_MEDIA_TAGS = "SET_MEDIA_TAGS"; +export const SET_MEDIA_DURATION = "SET_MEDIA_DURATION"; diff --git a/js/components/DropTarget.js b/js/components/DropTarget.js index 51da601c..5e17b215 100644 --- a/js/components/DropTarget.js +++ b/js/components/DropTarget.js @@ -1,7 +1,7 @@ import React from "react"; import { connect } from "react-redux"; -import { loadFileFromReference } from "../actionCreators"; +import { loadFilesFromReferences } from "../actionCreators"; export class DropTarget extends React.Component { constructor(props) { @@ -17,14 +17,12 @@ export class DropTarget extends React.Component { handleDrop(e) { this.supress(e); const { files } = e.dataTransfer; - if (files[0]) { - this.props.loadFileFromReference(files[0]); - } + this.props.loadFilesFromReferences(files); } render() { // eslint-disable-next-line no-shadow, no-unused-vars - const { loadFileFromReference, ...passThroughProps } = this.props; + const { loadFilesFromReferences, ...passThroughProps } = this.props; return (
{ }); it("renders to snapshot", () => { - store.dispatch(loadMediaFromUrl("http://example.com", "Some Name", false)); + store.dispatch(loadMediaFromUrl("http://example.com", "Some Name", "NONE")); const tree = renderer .create( diff --git a/js/mediaMiddleware.js b/js/mediaMiddleware.js index f6eb4e9e..271cc54a 100644 --- a/js/mediaMiddleware.js +++ b/js/mediaMiddleware.js @@ -1,7 +1,6 @@ import { IS_PLAYING, IS_STOPPED, - LOAD_AUDIO_URL, PAUSE, PLAY, SEEK_TO_PERCENT_COMPLETE, @@ -15,7 +14,8 @@ import { UPDATE_TIME_ELAPSED, SET_EQ_OFF, SET_EQ_ON, - PLAY_TRACK + PLAY_TRACK, + BUFFER_TRACK } from "./actionTypes"; import { next as nextTrack } from "./actionCreators"; import { getCurrentTrackId } from "./selectors"; @@ -84,12 +84,19 @@ export default media => store => { case SEEK_TO_PERCENT_COMPLETE: media.seekToPercentComplete(action.percent); break; - case LOAD_AUDIO_URL: - media.loadFromUrl(action.url, action.name, action.autoPlay); - break; case PLAY_TRACK: - const track = store.getState().playlist.tracks[action.id]; - media.loadFromUrl(track.url, track.title, true); + media.loadFromUrl( + store.getState().playlist.tracks[action.id].url, + action.name, + true + ); + break; + case BUFFER_TRACK: + media.loadFromUrl( + store.getState().playlist.tracks[action.id].url, + action.name, + false + ); break; case SET_BAND_VALUE: if (action.band === "preamp") { diff --git a/js/reducers/index.js b/js/reducers/index.js index 46ed892b..e1e65a13 100644 --- a/js/reducers/index.js +++ b/js/reducers/index.js @@ -38,7 +38,7 @@ import { PLAYLIST_SIZE_CHANGED, SET_AVALIABLE_SKINS, LOAD_AUDIO_FILE, - LOAD_AUDIO_URL + ADD_TRACK_FROM_URL } from "../actionTypes"; import playlist from "./playlist"; @@ -232,7 +232,7 @@ const media = (state, action) => { case UPDATE_TIME_ELAPSED: return { ...state, timeElapsed: action.elapsed }; case LOAD_AUDIO_FILE: - case LOAD_AUDIO_URL: + case ADD_TRACK_FROM_URL: return { ...state, timeElapsed: 0, diff --git a/js/reducers/playlist.js b/js/reducers/playlist.js index a001bf1c..5e47138a 100644 --- a/js/reducers/playlist.js +++ b/js/reducers/playlist.js @@ -8,13 +8,15 @@ import { INVERT_SELECTION, REMOVE_ALL_TRACKS, REMOVE_TRACKS, - LOAD_AUDIO_URL, + ADD_TRACK_FROM_URL, REVERSE_LIST, RANDOMIZE_LIST, SET_TRACK_ORDER, PLAY_TRACK, + BUFFER_TRACK, DRAG_SELECTED, - SET_MEDIA_TAGS + SET_MEDIA_TAGS, + SET_MEDIA_DURATION } from "../actionTypes"; import { shuffle, moveSelected, mapObject, filterObject } from "../utils"; @@ -128,11 +130,10 @@ const playlist = (state = defaultPlaylistState, action) => { case SET_TRACK_ORDER: const { trackOrder } = action; return { ...state, trackOrder }; - case LOAD_AUDIO_URL: + case ADD_TRACK_FROM_URL: return { ...state, trackOrder: [...state.trackOrder, Number(action.id)], - currentTrack: action.id, tracks: { ...state.tracks, [action.id]: { @@ -168,7 +169,19 @@ const playlist = (state = defaultPlaylistState, action) => { } } }; + case SET_MEDIA_DURATION: + return { + ...state, + tracks: { + ...state.tracks, + [action.id]: { + ...state.tracks[action.id], + duration: action.duration + } + } + }; case PLAY_TRACK: + case BUFFER_TRACK: return { ...state, currentTrack: action.id diff --git a/js/winamp.js b/js/winamp.js index ae490ce8..908f1ff3 100644 --- a/js/winamp.js +++ b/js/winamp.js @@ -9,7 +9,7 @@ import Media from "./media"; import { setSkinFromUrl, loadMediaFromUrl, - loadFileFromReference + loadFilesFromReferences } from "./actionCreators"; import { SET_AVALIABLE_SKINS } from "./actionTypes"; @@ -34,9 +34,7 @@ class Winamp { ); this.fileInput.addEventListener("change", e => { - if (e.target.files[0]) { - this.store.dispatch(loadFileFromReference(e.target.files[0], true)); - } + this.store.dispatch(loadFilesFromReferences(e.target.files)); }); if (this.options.initialTrack && this.options.initialTrack.url) { @@ -44,7 +42,7 @@ class Winamp { loadMediaFromUrl( this.options.initialTrack.url, this.options.initialTrack.name, - false + "BUFFER" ) ); } @@ -58,10 +56,6 @@ class Winamp { new Hotkeys(this.fileInput, this.store); } - - loadTrackUrl(url, name) { - this.store.dispatch(loadMediaFromUrl(url, name)); - } } export default Winamp;