mirror of
https://github.com/captbaritone/webamp.git
synced 2026-01-23 10:15:31 +00:00
parent
eb296fc182
commit
b16fb3a2ae
18 changed files with 205 additions and 153 deletions
|
|
@ -267,7 +267,19 @@ export function loadMediaFile(
|
|||
|
||||
if (metaData != null) {
|
||||
const { artist, title, album } = metaData;
|
||||
dispatch({ type: SET_MEDIA_TAGS, artist, title, album, id });
|
||||
dispatch({
|
||||
type: SET_MEDIA_TAGS,
|
||||
artist,
|
||||
title,
|
||||
album,
|
||||
// For now, we lie about these next three things.
|
||||
// TODO: Ideally we would leave these as null and force a media data
|
||||
// fetch when the user starts playing.
|
||||
sampleRate: 44000,
|
||||
bitrate: 192000,
|
||||
numberOfChannels: 2,
|
||||
id
|
||||
});
|
||||
} else if ("blob" in track) {
|
||||
// Blobs can be loaded quickly
|
||||
dispatch(fetchMediaTags(track.blob, id));
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
import { MEDIA_STATUS } from "../constants";
|
||||
import { openMediaFileDialog } from "./";
|
||||
import { GetState, Dispatch, Dispatchable } from "../types";
|
||||
import * as Selectors from "../selectors";
|
||||
|
||||
function playRandomTrack(): Dispatchable {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
|
|
@ -91,14 +92,16 @@ export function previous(): Dispatchable {
|
|||
|
||||
export function seekForward(seconds: number): Dispatchable {
|
||||
return function(dispatch, getState) {
|
||||
const { timeElapsed, length } = getState().media;
|
||||
if (length == null) {
|
||||
const state = getState();
|
||||
const timeElapsed = Selectors.getTimeElapsed(state);
|
||||
const duration = Selectors.getDuration(state);
|
||||
if (duration == null) {
|
||||
return;
|
||||
}
|
||||
const newTimeElapsed = timeElapsed + seconds;
|
||||
dispatch({
|
||||
type: SEEK_TO_PERCENT_COMPLETE,
|
||||
percent: (newTimeElapsed / length) * 100
|
||||
percent: (newTimeElapsed / duration) * 100
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ export const MEDIA_TAG_REQUEST_FAILED = "MEDIA_TAG_REQUEST_FAILED";
|
|||
export const NETWORK_CONNECTED = "NETWORK_CONNECTED";
|
||||
export const NETWORK_DISCONNECTED = "NETWORK_DISCONNECTED";
|
||||
export const UPDATE_WINDOW_POSITIONS = "UPDATE_WINDOW_POSITIONS";
|
||||
export const CHANNEL_COUNT_CHANGED = "CHANNEL_COUNT_CHANGED";
|
||||
export const WINDOW_SIZE_CHANGED = "WINDOW_SIZE_CHANGED";
|
||||
export const TOGGLE_WINDOW_SHADE_MODE = "TOGGLE_WINDOW_SHADE_MODE";
|
||||
export const LOADED = "LOADED";
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import CharacterString from "../CharacterString";
|
||||
|
||||
const Kbps = props => (
|
||||
<div id="kbps">
|
||||
<CharacterString>{props.kbps}</CharacterString>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default connect(state => ({ kbps: state.media.kbps }))(Kbps);
|
||||
22
js/components/MainWindow/Kbps.tsx
Normal file
22
js/components/MainWindow/Kbps.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import CharacterString from "../CharacterString";
|
||||
import { AppState } from "../../types";
|
||||
import * as Selectors from "../../selectors";
|
||||
|
||||
interface StateProps {
|
||||
kbps: string | null;
|
||||
}
|
||||
|
||||
const Kbps = (props: StateProps) => (
|
||||
<div id="kbps">
|
||||
<CharacterString>{props.kbps || ""}</CharacterString>
|
||||
</div>
|
||||
);
|
||||
|
||||
function mapStateToProps(state: AppState): StateProps {
|
||||
return { kbps: Selectors.getKbps(state) };
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Kbps);
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import CharacterString from "../CharacterString";
|
||||
|
||||
const Khz = props => (
|
||||
<div id="khz">
|
||||
<CharacterString>{props.khz}</CharacterString>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default connect(state => state.media)(Khz);
|
||||
22
js/components/MainWindow/Khz.tsx
Normal file
22
js/components/MainWindow/Khz.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import CharacterString from "../CharacterString";
|
||||
import { AppState } from "../../types";
|
||||
import * as Selectors from "../../selectors";
|
||||
|
||||
interface StateProps {
|
||||
khz: string | null;
|
||||
}
|
||||
|
||||
const Khz = (props: StateProps) => (
|
||||
<div id="khz">
|
||||
<CharacterString>{props.khz || ""}</CharacterString>
|
||||
</div>
|
||||
);
|
||||
|
||||
function mapStateToProps(state: AppState): StateProps {
|
||||
return { khz: Selectors.getKhz(state) };
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Khz);
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { AppState, Dispatch } from "../../types";
|
||||
|
||||
import {
|
||||
SEEK_TO_PERCENT_COMPLETE,
|
||||
|
|
@ -7,13 +8,26 @@ import {
|
|||
UNSET_FOCUS,
|
||||
SET_SCRUB_POSITION
|
||||
} from "../../actionTypes";
|
||||
import * as Selectors from "../../selectors";
|
||||
|
||||
interface StateProps {
|
||||
displayedPosition: number;
|
||||
position: number;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
seekToPercentComplete(e: React.MouseEvent<HTMLInputElement>): void;
|
||||
setPosition(e: React.MouseEvent<HTMLInputElement>): void;
|
||||
}
|
||||
|
||||
type Props = StateProps & DispatchProps;
|
||||
|
||||
const Position = ({
|
||||
position,
|
||||
seekToPercentComplete,
|
||||
displayedPosition,
|
||||
setPosition
|
||||
}) => {
|
||||
}: Props) => {
|
||||
// In shade mode, the position slider shows up differently depending on if
|
||||
// it's near the start, middle or end of its progress
|
||||
let className = "";
|
||||
|
|
@ -43,16 +57,15 @@ const Position = ({
|
|||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = ({ media, userInput }) => {
|
||||
let position;
|
||||
if (media.length) {
|
||||
position = (Math.floor(media.timeElapsed) / media.length) * 100;
|
||||
} else {
|
||||
position = 0;
|
||||
}
|
||||
const mapStateToProps = (state: AppState): StateProps => {
|
||||
const duration = Selectors.getDuration(state);
|
||||
const timeElapsed = Selectors.getTimeElapsed(state);
|
||||
const userInputFocus = Selectors.getUserInputFocus(state);
|
||||
const scrubPosition = Selectors.getUserInputScrubPosition(state);
|
||||
const position = duration ? (Math.floor(timeElapsed) / duration) * 100 : 0;
|
||||
|
||||
const displayedPosition =
|
||||
userInput.focus === "position" ? userInput.scrubPosition : position;
|
||||
userInputFocus === "position" ? scrubPosition : position;
|
||||
|
||||
return {
|
||||
displayedPosition,
|
||||
|
|
@ -60,14 +73,20 @@ const mapStateToProps = ({ media, userInput }) => {
|
|||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
|
||||
seekToPercentComplete: e => {
|
||||
dispatch({ type: SEEK_TO_PERCENT_COMPLETE, percent: e.target.value });
|
||||
dispatch({
|
||||
type: SEEK_TO_PERCENT_COMPLETE,
|
||||
percent: Number((e.target as HTMLInputElement).value)
|
||||
});
|
||||
dispatch({ type: UNSET_FOCUS });
|
||||
},
|
||||
setPosition: e => {
|
||||
dispatch({ type: SET_FOCUS, input: "position" });
|
||||
dispatch({ type: SET_SCRUB_POSITION, position: e.target.value });
|
||||
dispatch({
|
||||
type: SET_SCRUB_POSITION,
|
||||
position: Number((e.target as HTMLInputElement).value)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -4,11 +4,12 @@ import { TimeMode, AppState, Dispatch } from "../../types";
|
|||
import { getTimeObj } from "../../utils";
|
||||
|
||||
import * as Actions from "../../actionCreators";
|
||||
import * as Selectors from "../../selectors";
|
||||
import { TIME_MODE } from "../../constants";
|
||||
|
||||
interface StateProps {
|
||||
timeElapsed: number;
|
||||
length: number;
|
||||
duration: number;
|
||||
timeMode: TimeMode;
|
||||
}
|
||||
|
||||
|
|
@ -18,12 +19,12 @@ interface DispatchProps {
|
|||
|
||||
const Time = ({
|
||||
timeElapsed,
|
||||
length,
|
||||
duration,
|
||||
timeMode,
|
||||
toggleTimeMode
|
||||
}: StateProps & DispatchProps) => {
|
||||
const seconds =
|
||||
timeMode === TIME_MODE.ELAPSED ? timeElapsed : length - timeElapsed;
|
||||
timeMode === TIME_MODE.ELAPSED ? timeElapsed : duration - timeElapsed;
|
||||
|
||||
const timeObj = getTimeObj(seconds);
|
||||
return (
|
||||
|
|
@ -50,8 +51,10 @@ const Time = ({
|
|||
};
|
||||
|
||||
const mapStateToProps = (state: AppState): StateProps => {
|
||||
const { timeElapsed, length, timeMode } = state.media;
|
||||
return { timeElapsed, length: length || 0, timeMode };
|
||||
const timeElapsed = Selectors.getTimeElapsed(state);
|
||||
const duration = Selectors.getDuration(state);
|
||||
const { timeMode } = state.media;
|
||||
return { timeElapsed, duration: duration || 0, timeMode };
|
||||
};
|
||||
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
|
||||
toggleTimeMode: () => dispatch(Actions.toggleTimeMode())
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { AppState, Action } from "../types";
|
||||
import { AppState, Action, Dispatchable, Dispatch } from "../types";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import classnames from "classnames";
|
||||
|
|
@ -6,6 +6,7 @@ import { getTimeObj } from "../utils";
|
|||
import { TOGGLE_TIME_MODE } from "../actionTypes";
|
||||
import { TIME_MODE, MEDIA_STATUS } from "../constants";
|
||||
import Character from "./Character";
|
||||
import * as Selectors from "../selectors";
|
||||
|
||||
import "../../css/mini-time.css";
|
||||
|
||||
|
|
@ -26,15 +27,20 @@ const Background = () => (
|
|||
</React.Fragment>
|
||||
);
|
||||
|
||||
type StateProps = {
|
||||
interface StateProps {
|
||||
status: string | null;
|
||||
timeMode: string;
|
||||
timeElapsed: number;
|
||||
length: number | null;
|
||||
toggle: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
const MiniTime = (props: StateProps) => {
|
||||
interface DispatchProps {
|
||||
toggle: () => void;
|
||||
}
|
||||
|
||||
type Props = StateProps & DispatchProps;
|
||||
|
||||
const MiniTime = (props: Props) => {
|
||||
let seconds = null;
|
||||
// TODO: Clean this up: If stopped, just render the background, rather than
|
||||
// rendering spaces twice.
|
||||
|
|
@ -66,14 +72,14 @@ const MiniTime = (props: StateProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: AppState) => ({
|
||||
const mapStateToProps = (state: AppState): StateProps => ({
|
||||
status: state.media.status,
|
||||
timeMode: state.media.timeMode,
|
||||
timeElapsed: state.media.timeElapsed,
|
||||
length: state.media.length
|
||||
timeElapsed: Selectors.getTimeElapsed(state),
|
||||
length: Selectors.getDuration(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: (action: Action) => void) => ({
|
||||
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
|
||||
// TODO: move to actionCreators
|
||||
toggle: () => {
|
||||
dispatch({ type: TOGGLE_TIME_MODE });
|
||||
|
|
|
|||
|
|
@ -147,16 +147,6 @@ export default class ElementSource {
|
|||
return this._audio.currentTime;
|
||||
}
|
||||
|
||||
getNumberOfChannels() {
|
||||
return this._source.channelCount;
|
||||
}
|
||||
|
||||
getSampleRate() {
|
||||
// This is a lie. This is the sample rate of the context, not the
|
||||
// underlying source media.
|
||||
return this._context.sampleRate;
|
||||
}
|
||||
|
||||
_setStatus(status: MediaStatus) {
|
||||
this._status = status;
|
||||
this._emitter.trigger("statusChange");
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ function createStereoPanner(context: AudioContext): StereoPannerNode {
|
|||
export default class Media {
|
||||
_emitter: Emitter;
|
||||
_context: AudioContext;
|
||||
_channels: number | null;
|
||||
_balance: number;
|
||||
_staticSource: AnalyserNode;
|
||||
_preamp: GainNode;
|
||||
|
|
@ -52,8 +51,6 @@ export default class Media {
|
|||
document.body.addEventListener("click", resume, false);
|
||||
document.body.addEventListener("keydown", resume, false);
|
||||
}
|
||||
// We don't currently know how many channels
|
||||
this._channels = null;
|
||||
this._balance = 0;
|
||||
|
||||
// The _source node has to be recreated each time it's stopped or
|
||||
|
|
@ -152,11 +149,6 @@ export default class Media {
|
|||
this._gainNode.connect(this._context.destination);
|
||||
}
|
||||
|
||||
_setChannels(num: number | null) {
|
||||
this._channels = num;
|
||||
this._emitter.trigger("channelupdate");
|
||||
}
|
||||
|
||||
getAnalyser() {
|
||||
return this._analyser;
|
||||
}
|
||||
|
|
@ -178,14 +170,6 @@ export default class Media {
|
|||
return (this.timeElapsed() / this.duration()) * 100;
|
||||
}
|
||||
|
||||
channels() {
|
||||
return this._channels == null ? 2 : this._channels;
|
||||
}
|
||||
|
||||
sampleRate() {
|
||||
return this._source.getSampleRate();
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
async play() {
|
||||
await this._source.play();
|
||||
|
|
@ -248,7 +232,6 @@ export default class Media {
|
|||
async loadFromUrl(url: string, autoPlay: boolean) {
|
||||
this._emitter.trigger("waiting");
|
||||
await this._source.loadUrl(url);
|
||||
this._setChannels(null);
|
||||
this._emitter.trigger("stopWaiting");
|
||||
if (autoPlay) {
|
||||
this.play();
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import {
|
|||
SET_EQ_ON,
|
||||
PLAY_TRACK,
|
||||
BUFFER_TRACK,
|
||||
CHANNEL_COUNT_CHANGED,
|
||||
LOAD_SERIALIZED_STATE
|
||||
} from "./actionTypes";
|
||||
import { next as nextTrack } from "./actionCreators";
|
||||
|
|
@ -70,19 +69,12 @@ export default (media: Media) => (store: MiddlewareStore) => {
|
|||
id,
|
||||
type: SET_MEDIA,
|
||||
kbps: "128",
|
||||
khz: Math.round(media.sampleRate() / 1000).toString(),
|
||||
channels: media.channels(),
|
||||
khz: "44",
|
||||
channels: 2,
|
||||
length: media.duration()
|
||||
});
|
||||
});
|
||||
|
||||
media.on("channelupdate", () => {
|
||||
store.dispatch({
|
||||
type: CHANNEL_COUNT_CHANGED,
|
||||
channels: media.channels()
|
||||
});
|
||||
});
|
||||
|
||||
return (next: Dispatch) => (action: Action) => {
|
||||
const returnValue = next(action);
|
||||
const state = store.getState();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import {
|
|||
TOGGLE_TIME_MODE,
|
||||
UPDATE_TIME_ELAPSED,
|
||||
ADD_TRACK_FROM_URL,
|
||||
CHANNEL_COUNT_CHANGED,
|
||||
LOAD_SERIALIZED_STATE
|
||||
} from "../actionTypes";
|
||||
import { TIME_MODE, MEDIA_STATUS } from "../constants";
|
||||
|
|
@ -23,12 +22,8 @@ import { MediaSerializedStateV1 } from "../serializedStates/v1Types";
|
|||
export interface MediaState {
|
||||
timeMode: TimeMode; // TODO: Convert this to an enum
|
||||
timeElapsed: number;
|
||||
length: number | null;
|
||||
kbps: string | null;
|
||||
khz: string | null;
|
||||
volume: number;
|
||||
balance: number;
|
||||
channels: number | null; // TODO: Convert this to an enum
|
||||
shuffle: boolean;
|
||||
repeat: boolean;
|
||||
status: MediaStatus | null; // TODO: Convert this to an enum
|
||||
|
|
@ -37,15 +32,12 @@ export interface MediaState {
|
|||
const defaultState = {
|
||||
timeMode: TIME_MODE.ELAPSED,
|
||||
timeElapsed: 0,
|
||||
length: null, // Consider renaming to "duration"
|
||||
kbps: null,
|
||||
khz: null,
|
||||
|
||||
// The winamp ini file declares the default volume as "200".
|
||||
// The UI seems to show a default volume near 78, which would
|
||||
// math with the default value being 200 out of 255.
|
||||
volume: Math.round((200 / 255) * 100),
|
||||
balance: 0,
|
||||
channels: null,
|
||||
shuffle: false,
|
||||
repeat: false,
|
||||
// TODO: Enforce possible values
|
||||
|
|
@ -66,8 +58,6 @@ const media = (
|
|||
case STOP:
|
||||
case IS_STOPPED:
|
||||
return { ...state, status: MEDIA_STATUS.STOPPED };
|
||||
case CHANNEL_COUNT_CHANGED:
|
||||
return { ...state, channels: action.channels };
|
||||
case TOGGLE_TIME_MODE:
|
||||
const newMode =
|
||||
state.timeMode === TIME_MODE.REMAINING
|
||||
|
|
@ -79,28 +69,11 @@ const media = (
|
|||
case ADD_TRACK_FROM_URL:
|
||||
return {
|
||||
...state,
|
||||
timeElapsed: 0,
|
||||
length: null,
|
||||
kbps: null,
|
||||
khz: null,
|
||||
channels: null
|
||||
timeElapsed: 0
|
||||
};
|
||||
case SET_MEDIA:
|
||||
return {
|
||||
...state,
|
||||
length: action.length,
|
||||
kbps: action.kbps,
|
||||
khz: action.khz,
|
||||
channels: action.channels
|
||||
};
|
||||
case SET_MEDIA_TAGS:
|
||||
const { sampleRate, bitrate, numberOfChannels } = action;
|
||||
const { kbps, khz, channels } = state;
|
||||
return {
|
||||
...state,
|
||||
kbps: bitrate != null ? String(Math.round(bitrate / 1000)) : kbps,
|
||||
khz: sampleRate != null ? String(Math.round(sampleRate / 1000)) : khz,
|
||||
channels: numberOfChannels != null ? numberOfChannels : channels
|
||||
...state
|
||||
};
|
||||
case SET_VOLUME:
|
||||
return { ...state, volume: action.volume };
|
||||
|
|
|
|||
|
|
@ -42,19 +42,6 @@ const tracks = (
|
|||
[action.id]: newTrack
|
||||
};
|
||||
}
|
||||
case SET_MEDIA_TAGS: {
|
||||
return {
|
||||
...state,
|
||||
[action.id]: {
|
||||
...state[action.id],
|
||||
mediaTagsRequestStatus: MEDIA_TAG_REQUEST_STATUS.COMPLETE,
|
||||
title: action.title,
|
||||
artist: action.artist,
|
||||
album: action.album,
|
||||
albumArtUrl: action.albumArtUrl
|
||||
}
|
||||
};
|
||||
}
|
||||
case MEDIA_TAG_REQUEST_INITIALIZED:
|
||||
return {
|
||||
...state,
|
||||
|
|
@ -80,6 +67,32 @@ const tracks = (
|
|||
}
|
||||
};
|
||||
}
|
||||
case SET_MEDIA_TAGS:
|
||||
const track = state[action.id];
|
||||
const {
|
||||
sampleRate,
|
||||
bitrate,
|
||||
numberOfChannels,
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
albumArtUrl
|
||||
} = action;
|
||||
const { kbps, khz, channels } = track;
|
||||
return {
|
||||
...state,
|
||||
[action.id]: {
|
||||
...track,
|
||||
mediaTagsRequestStatus: MEDIA_TAG_REQUEST_STATUS.COMPLETE,
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
albumArtUrl,
|
||||
kbps: bitrate != null ? String(Math.round(bitrate / 1000)) : kbps,
|
||||
khz: sampleRate != null ? String(Math.round(sampleRate / 1000)) : khz,
|
||||
channels: numberOfChannels != null ? numberOfChannels : channels
|
||||
}
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,28 +7,40 @@ const defaultTracksState = {
|
|||
title: "Llama Whipping Intro",
|
||||
artist: "DJ Mike Llama",
|
||||
duration: 5,
|
||||
url: ""
|
||||
url: "",
|
||||
kbps: "128",
|
||||
khz: "44",
|
||||
channels: 2
|
||||
},
|
||||
"1": {
|
||||
id: 1,
|
||||
title: "Rock Is Dead",
|
||||
artist: "Marilyn Manson",
|
||||
duration: 191, // 3:11
|
||||
url: ""
|
||||
url: "",
|
||||
kbps: "128",
|
||||
khz: "44",
|
||||
channels: 2
|
||||
},
|
||||
"2": {
|
||||
id: 2,
|
||||
title: "Spybreak! (Short One)",
|
||||
artist: "Propellerheads",
|
||||
duration: 240, // 4:00
|
||||
url: ""
|
||||
url: "",
|
||||
kbps: "128",
|
||||
khz: "44",
|
||||
channels: 2
|
||||
},
|
||||
"3": {
|
||||
id: 3,
|
||||
title: "Bad Blood",
|
||||
artist: "Ministry",
|
||||
duration: 300, // 5:00
|
||||
url: ""
|
||||
url: "",
|
||||
kbps: "128",
|
||||
khz: "44",
|
||||
channels: 2
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -50,11 +62,7 @@ const initialState: DeepPartial<AppState> = {
|
|||
},
|
||||
media: {
|
||||
status: "PLAYING",
|
||||
kbps: "128",
|
||||
khz: "44",
|
||||
length: 5,
|
||||
timeElapsed: 3,
|
||||
channels: 2
|
||||
timeElapsed: 3
|
||||
},
|
||||
playlist: {
|
||||
trackOrder: [0, 1, 2, 3],
|
||||
|
|
|
|||
|
|
@ -454,7 +454,17 @@ export const getBalance = (state: AppState) => state.media.balance;
|
|||
export const getShuffle = (state: AppState) => state.media.shuffle;
|
||||
export const getRepeat = (state: AppState) => state.media.repeat;
|
||||
|
||||
export const getChannels = (state: AppState) => state.media.channels;
|
||||
export const getChannels = createSelector(
|
||||
getCurrentTrack,
|
||||
(track: PlaylistTrack | null): number | null => {
|
||||
return track != null ? track.channels || null : null;
|
||||
}
|
||||
);
|
||||
|
||||
export const getTimeElapsed = (state: AppState): number => {
|
||||
return state.media.timeElapsed;
|
||||
};
|
||||
|
||||
export function getSerlializedState(state: AppState): SerializedStateV1 {
|
||||
return {
|
||||
version: 1,
|
||||
|
|
@ -496,25 +506,33 @@ export const getStackedLayoutPositions = createSelector(
|
|||
}
|
||||
);
|
||||
|
||||
export const getUserInputFocus = (state: AppState): string | null => {
|
||||
return state.userInput.focus;
|
||||
};
|
||||
|
||||
export const getUserInputScrubPosition = (state: AppState): number => {
|
||||
return state.userInput.scrubPosition;
|
||||
};
|
||||
// TODO: Make this a reselect selector
|
||||
export const getMarqueeText = (state: AppState): string => {
|
||||
const defaultText = "Winamp 2.91";
|
||||
if (state.userInput.userMessage != null) {
|
||||
return state.userInput.userMessage;
|
||||
}
|
||||
switch (state.userInput.focus) {
|
||||
switch (getUserInputFocus(state)) {
|
||||
case "balance":
|
||||
return MarqueeUtils.getBalanceText(state.media.balance);
|
||||
case "volume":
|
||||
return MarqueeUtils.getVolumeText(state.media.volume);
|
||||
case "position":
|
||||
if (state.media.length == null) {
|
||||
const duration = getDuration(state);
|
||||
if (duration == null) {
|
||||
// This probably can't ever happen.
|
||||
return defaultText;
|
||||
}
|
||||
return MarqueeUtils.getPositionText(
|
||||
state.media.length,
|
||||
state.userInput.scrubPosition
|
||||
duration,
|
||||
getUserInputScrubPosition(state)
|
||||
);
|
||||
case "double":
|
||||
return MarqueeUtils.getDoubleSizeModeText(state.display.doubled);
|
||||
|
|
@ -539,6 +557,20 @@ export const getMarqueeText = (state: AppState): string => {
|
|||
return defaultText;
|
||||
};
|
||||
|
||||
export const getKbps = createSelector(
|
||||
getCurrentTrack,
|
||||
(track: PlaylistTrack | null): string | null => {
|
||||
return track != null ? track.kbps || null : null;
|
||||
}
|
||||
);
|
||||
|
||||
export const getKhz = createSelector(
|
||||
getCurrentTrack,
|
||||
(track: PlaylistTrack | null): string | null => {
|
||||
return track != null ? track.khz || null : null;
|
||||
}
|
||||
);
|
||||
|
||||
export function getDebugData(state: AppState) {
|
||||
return {
|
||||
...state,
|
||||
|
|
|
|||
|
|
@ -168,10 +168,6 @@ export type Action =
|
|||
| {
|
||||
type: "IS_STOPPED";
|
||||
}
|
||||
| {
|
||||
type: "CHANNEL_COUNT_CHANGED";
|
||||
channels: number;
|
||||
}
|
||||
| {
|
||||
type: "TOGGLE_TIME_MODE";
|
||||
}
|
||||
|
|
@ -526,6 +522,9 @@ export interface PlaylistTrack {
|
|||
albumArtUrl?: string | null;
|
||||
mediaTagsRequestStatus: MediaTagRequestStatus;
|
||||
duration: number | null;
|
||||
kbps?: string;
|
||||
khz: string;
|
||||
channels?: number;
|
||||
}
|
||||
|
||||
export interface AppState {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue