From 777486a2883ef6d2bc3843b7b4cffd83353d0ee5 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Wed, 29 Nov 2017 08:39:55 -0800 Subject: [PATCH] Add jsmediatags --- js/actionCreators.js | 48 ++++++++++++++++++++-- js/actionTypes.js | 1 + js/components/MainWindow/Marquee.js | 10 ++--- js/components/PlaylistWindow/TrackList.js | 5 ++- js/components/PlaylistWindow/TrackTitle.js | 11 +++++ js/mediaMiddleware.js | 1 - js/reducers/index.js | 8 ++-- js/reducers/playlist.js | 25 ++++++++++- js/selectors.js | 19 +++++++++ package.json | 1 + yarn.lock | 10 +++++ 11 files changed, 120 insertions(+), 19 deletions(-) create mode 100644 js/components/PlaylistWindow/TrackTitle.js diff --git a/js/actionCreators.js b/js/actionCreators.js index 3d93f668..74370ea1 100644 --- a/js/actionCreators.js +++ b/js/actionCreators.js @@ -1,3 +1,4 @@ +import jsmediatags from "jsmediatags/dist/jsmediatags"; import { parser, creator } from "winamp-eqf"; import MyFile from "./myFile"; import skinParser from "./skinParser"; @@ -42,7 +43,8 @@ import { TOGGLE_VISUALIZER_STYLE, PLAY_TRACK, SET_PLAYLIST_SCROLL_POSITION, - DRAG_SELECTED + DRAG_SELECTED, + SET_MEDIA_TAGS } from "./actionTypes"; function playRandomTrack() { @@ -171,15 +173,55 @@ export function loadFileFromReference(fileReference) { } else if (EQF_FILENAME_MATCHER.test(fileReference.name)) { dispatch(setEqFromFile(file)); } else { + const id = uniqueId(); const url = URL.createObjectURL(fileReference); - dispatch(loadMediaFromUrl(url, fileReference.name, true)); + dispatch(_loadMediaFromUrl(url, fileReference.name, true, id)); + dispatch(fetchMediaTags(fileReference, id)); } }; } 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 loadMediaFromUrl(url, name, autoPlay) { - return { type: LOAD_AUDIO_URL, url, name, autoPlay, id: counter++ }; + return dispatch => { + const id = uniqueId(); + dispatch(_loadMediaFromUrl(url, name, autoPlay, id)); + dispatch(fetchMediaTags(url, 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 => { + try { + jsmediatags.read(file, { + onSuccess: data => { + const { artist, title } = data.tags; + // There's more data here, but we don't have a use for it yet: + // https://github.com/aadsm/jsmediatags#shortcuts + dispatch({ type: SET_MEDIA_TAGS, artist, title, id }); + }, + onError: () => { + // Nothing to do. The filename will have to suffice. + } + }); + } catch (e) { + // 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. + } + }; } export function setSkinFromFile(skinFile) { diff --git a/js/actionTypes.js b/js/actionTypes.js index a536d887..9556cca8 100644 --- a/js/actionTypes.js +++ b/js/actionTypes.js @@ -56,3 +56,4 @@ export const RANDOMIZE_LIST = "RANDOMIZE_LIST"; export const SET_TRACK_ORDER = "SET_TRACK_ORDER"; export const PLAY_TRACK = "PLAY_TRACK"; export const DRAG_SELECTED = "DRAG_SELECTED"; +export const SET_MEDIA_TAGS = "SET_MEDIA_TAGS"; diff --git a/js/components/MainWindow/Marquee.js b/js/components/MainWindow/Marquee.js index 3e130ff3..adbf5180 100644 --- a/js/components/MainWindow/Marquee.js +++ b/js/components/MainWindow/Marquee.js @@ -7,7 +7,7 @@ import { getTimeStr } from "../../utils"; import { STEP_MARQUEE } from "../../actionTypes"; import CharacterString from "../CharacterString"; import { noMarquee } from "../../config"; -import { getCurrentTrackNumber } from "../../selectors"; +import { getMediaText } from "../../selectors"; const CHAR_WIDTH = 5; @@ -30,9 +30,6 @@ export const getPositionText = (duration, seekToPercent) => { return `Seek to: ${newElapsedStr}/${durationStr} (${seekToPercent}%)`; }; -export const getMediaText = (trackNumber, name, duration) => - `${trackNumber}. ${name} (${getTimeStr(duration)}) *** `; - export const getDoubleSizeModeText = enabled => `${enabled ? "Disable" : "Enable"} doublesize mode`; @@ -158,9 +155,8 @@ const getMarqueeText = state => { default: break; } - if (state.media.name) { - const trackNumber = getCurrentTrackNumber(state); - return getMediaText(trackNumber, state.media.name, state.media.length); + if (state.playlist.currentTrack != null) { + return getMediaText(state); } return "Winamp 2.91"; }; diff --git a/js/components/PlaylistWindow/TrackList.js b/js/components/PlaylistWindow/TrackList.js index 5c5fda6a..cf048dd9 100644 --- a/js/components/PlaylistWindow/TrackList.js +++ b/js/components/PlaylistWindow/TrackList.js @@ -3,9 +3,10 @@ import { connect } from "react-redux"; import { getTimeStr } from "../../utils"; import { TRACK_HEIGHT } from "../../constants"; -import { dragSelected} from "../../actionCreators"; +import { dragSelected } from "../../actionCreators"; import { getVisibleTrackIds, getScrollOffset } from "../../selectors"; import TrackCell from "./TrackCell"; +import TrackTitle from "./TrackTitle"; class TrackList extends React.Component { constructor(props) { @@ -47,7 +48,7 @@ class TrackList extends React.Component { {this._renderTracks((id, i) => `${i + 1 + offset}.`)}
- {this._renderTracks(id => tracks[id].title)} + {this._renderTracks(id => )}
{this._renderTracks(id => getTimeStr(tracks[id].duration))} diff --git a/js/components/PlaylistWindow/TrackTitle.js b/js/components/PlaylistWindow/TrackTitle.js new file mode 100644 index 00000000..04a3a362 --- /dev/null +++ b/js/components/PlaylistWindow/TrackTitle.js @@ -0,0 +1,11 @@ +import React from "react"; +import { connect } from "react-redux"; +import { getTrackDisplayName } from "../../selectors"; + +const TrackTitle = props => {props.title}; + +const mapStateToProps = (state, ownProps) => ({ + title: getTrackDisplayName(state, ownProps.id) +}); + +export default connect(mapStateToProps)(TrackTitle); diff --git a/js/mediaMiddleware.js b/js/mediaMiddleware.js index f6f7bd9d..58cb8964 100644 --- a/js/mediaMiddleware.js +++ b/js/mediaMiddleware.js @@ -51,7 +51,6 @@ export default media => store => { kbps: "128", khz: Math.round(media.sampleRate() / 1000).toString(), channels: media.channels(), - name: media.name, length: media.duration(), id: getCurrentTrackId(store.getState()) }); diff --git a/js/reducers/index.js b/js/reducers/index.js index 9bc7a62c..68cb8054 100644 --- a/js/reducers/index.js +++ b/js/reducers/index.js @@ -206,7 +206,6 @@ const media = (state, action) => { khz: null, volume: 50, balance: 0, - name: "", channels: null, shuffle: false, repeat: false, @@ -215,6 +214,7 @@ const media = (state, action) => { }; } switch (action.type) { + // TODO: Make these constants case "PLAY": case "IS_PLAYING": return { ...state, status: "PLAYING" }; @@ -236,8 +236,7 @@ const media = (state, action) => { length: null, kbps: null, khz: null, - channels: null, - name: null + channels: null }; case SET_MEDIA: return { @@ -245,8 +244,7 @@ const media = (state, action) => { length: action.length, kbps: action.kbps, khz: action.khz, - channels: action.channels, - name: action.name + channels: action.channels }; case SET_VOLUME: return { ...state, volume: action.volume }; diff --git a/js/reducers/playlist.js b/js/reducers/playlist.js index 19661c31..954a1de4 100644 --- a/js/reducers/playlist.js +++ b/js/reducers/playlist.js @@ -12,7 +12,8 @@ import { RANDOMIZE_LIST, SET_TRACK_ORDER, PLAY_TRACK, - DRAG_SELECTED + DRAG_SELECTED, + SET_MEDIA_TAGS } from "../actionTypes"; import { shuffle, moveSelected, mapObject, filterObject } from "../utils"; @@ -118,6 +119,18 @@ const playlist = (state = defaultPlaylistState, action) => { } } }; + case SET_MEDIA_TAGS: + return { + ...state, + tracks: { + ...state.tracks, + [action.id]: { + ...state.tracks[action.id], + title: action.title, + artist: action.artist + } + } + }; case PLAY_TRACK: return { ...state, @@ -141,3 +154,13 @@ const playlist = (state = defaultPlaylistState, action) => { }; export default playlist; + +export const getTrackDisplayName = (state, id) => { + const { artist, title } = state.tracks[id]; + if (artist && title) { + return `${artist} - ${title}`; + } else if (title) { + return title; + } + return "???"; +}; diff --git a/js/selectors.js b/js/selectors.js index a8ba84a9..5a5c7303 100644 --- a/js/selectors.js +++ b/js/selectors.js @@ -5,6 +5,7 @@ import { TRACK_HEIGHT } from "./constants"; import { createSelector } from "reselect"; +import * as fromPlaylist from "./reducers/playlist"; export const getEqfData = state => { const { sliders } = state.equalizer; @@ -137,3 +138,21 @@ export const getVisibleTrackIds = createSelector( (offset, trackOrder, numberOfVisibleTracks) => trackOrder.slice(offset, offset + numberOfVisibleTracks) ); + +export const getPlaylist = state => state.playlist; +export const getDuration = state => state.media.length; +export const getTrackDisplayName = (state, trackId) => + fromPlaylist.getTrackDisplayName(getPlaylist(state), trackId); + +export const getCurrentTrackDisplayName = state => { + const id = getCurrentTrackId(state); + return getTrackDisplayName(state, id); +}; + +export const getMediaText = createSelector( + getCurrentTrackNumber, + getCurrentTrackDisplayName, + getDuration, + (trackNumber, name, duration) => + `${trackNumber}. ${name} (${getTimeStr(duration)}) *** ` +); diff --git a/package.json b/package.json index 47aeaa32..546e9cc1 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "classnames": "^2.2.5", "eslint-plugin-import": "^2.7.0", "jest": "^21.2.1", + "jsmediatags": "^3.8.1", "jszip": "^3.1.3", "prettier": "^1.6.0", "prop-types": "^15.5.10", diff --git a/yarn.lock b/yarn.lock index 9c54c66b..ddb3d413 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3635,6 +3635,12 @@ jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" +jsmediatags@^3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/jsmediatags/-/jsmediatags-3.8.1.tgz#e27d26e957b0b330c28f9762c82940c4dcc64720" + dependencies: + xhr2 "^0.1.4" + json-loader@^0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de" @@ -6066,6 +6072,10 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" +xhr2@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f" + xml-name-validator@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"