diff --git a/packages/webamp/js/selectors.ts b/packages/webamp/js/selectors.ts index 1b6ebebc3..4f5e56c9f 100644 --- a/packages/webamp/js/selectors.ts +++ b/packages/webamp/js/selectors.ts @@ -76,6 +76,12 @@ export const getOrderedTracks = createSelector( (tracks, trackOrder) => trackOrder.filter((id) => tracks[id]) ); +export const getPlaylistTracks = createSelector( + getTracks, + getTrackOrder, + (tracks, trackOrder) => trackOrder.map((id) => tracks[id]).filter(Boolean) +); + export const getUserTracks = createSelector( getTracks, getTrackOrder, @@ -135,7 +141,7 @@ export const getRunningTimeMessage = createSelector( )}` ); -// TODO: use slectors to get memoization +// TODO: use selectors to get memoization export const getCurrentTrackIndex = (state: AppState): number => { const { playlist } = state; if (playlist.currentTrack == null) { diff --git a/packages/webamp/js/webampLazy.tsx b/packages/webamp/js/webampLazy.tsx index b5b04447d..212a9ac27 100644 --- a/packages/webamp/js/webampLazy.tsx +++ b/packages/webamp/js/webampLazy.tsx @@ -12,6 +12,7 @@ import { PartialState, Options, MediaStatus, + PlaylistTrack, } from "./types"; import getStore from "./store"; import App from "./components/App"; @@ -230,14 +231,14 @@ class Webamp { } /** - * Seek backward n seconds in the curent track + * Seek backward n seconds in the current track */ seekBackward(seconds: number) { this.store.dispatch(Actions.seekBackward(seconds)); } /** - * Seek forward n seconds in the curent track + * Seek forward n seconds in the current track */ seekForward(seconds: number) { this.store.dispatch(Actions.seekForward(seconds)); @@ -250,6 +251,20 @@ class Webamp { this.store.dispatch(Actions.seekToTime(seconds)); } + /** + * Check if shuffle is enabled + */ + isShuffleEnabled(): boolean { + return Selectors.getShuffle(this.store.getState()); + } + + /** + * Check if repeat is enabled + */ + isRepeatEnabled(): boolean { + return Selectors.getRepeat(this.store.getState()); + } + /** * Play the next track */ @@ -264,6 +279,18 @@ class Webamp { this.store.dispatch(Actions.previous()); } + /** + * Set the current track a specific track in the playlist by zero-based index. + * + * Note: If Webamp is currently playing, the track will begin playing. If + * Webamp is not playing, the track will not start playing. You can use + * `webamp.pause()` before calling this method or `webamp.play()` after + * calling this method to control whether the track starts playing. + */ + setCurrentTrack(index: number): void { + this.store.dispatch(Actions.playTrack(index)); + } + /** * Add an array of `Track`s to the end of the playlist. */ @@ -281,6 +308,13 @@ class Webamp { this.store.dispatch(Actions.loadMediaFiles(tracks, LOAD_STYLE.PLAY)); } + /** + * Get the current playlist in order. + */ + getPlaylistTracks(): PlaylistTrack[] { + return Selectors.getPlaylistTracks(this.store.getState()); + } + /** * Get the current "playing" status. */ @@ -325,14 +359,51 @@ class Webamp { this.store.dispatch(Actions.open()); } + /** + * A callback which will be called whenever a the current track changes. + * + * The callback is passed the current track and the zero-based index of the + * current track's position within the playlist. + * + * Note: This is different from the `onTrackDidChange` callback which is only + * called when a new track first starts loading. + * + * @returns An "unsubscribe" function. Useful if at some point in the future + * you want to stop listening to these events. + */ + onCurrentTrackDidChange( + cb: (currentTrack: PlaylistTrack | null, trackIndex: number) => void + ): () => void { + let previousTrackId: number | null = null; + return this.store.subscribe(() => { + const state = this.store.getState(); + const currentTrack = Selectors.getCurrentTrack(state); + const currentTrackId = currentTrack?.id || null; + if (currentTrackId === previousTrackId) { + return; + } + previousTrackId = currentTrackId; + const trackIndex = Selectors.getCurrentTrackIndex(state); + cb(currentTrack, trackIndex); + }); + } + /** * A callback which will be called when a new track starts loading. * - * This can happen on startup when the first track starts buffering, or when a subsequent track starts playing. - * The callback will be called with an object `({url: 'https://example.com/track.mp3'})` containing the URL of the track. + * This can happen on startup when the first track starts buffering, or when a + * subsequent track starts playing. The callback will be called with an + * object `({url: 'https://example.com/track.mp3'})` containing the URL of the + * track. + * * Note: If the user drags in a track, the URL may be an ObjectURL. * - * @returns An "unsubscribe" function. Useful if at some point in the future you want to stop listening to these events. + * Note: This is different from the `onCurrentTrackDidChange` callback which + * is called every time a track changes. This callback is only called when a + * new track starts loading. + * + * @returns An "unsubscribe" function. Useful if at some point in the future + * you want to stop listening to these events. */ onTrackDidChange(cb: (trackInfo: LoadedURLTrack | null) => void): () => void { let previousTrackId: number | null = null;