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"