diff --git a/packages/webamp-docs/docs/06_API/03_instance-methods.md b/packages/webamp-docs/docs/06_API/03_instance-methods.md index c59565e0..0a046155 100644 --- a/packages/webamp-docs/docs/06_API/03_instance-methods.md +++ b/packages/webamp-docs/docs/06_API/03_instance-methods.md @@ -134,6 +134,64 @@ Stop the currently playing audio. Equivilant to pressing the "stop" button. webamp.stop(); ``` +### `setVolume(volume: number): void` + +Set volume from 0 - 100. + +**Since** 1.3.0 + +```ts +webamp.setVolume(75); +``` + +### `setCurrentTrack(index: number): void` + +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. + +**Since** 2.1.0 + +```ts +// Play the third track in the playlist +webamp.setCurrentTrack(2); +``` + +### `getPlaylistTracks(): PlaylistTrack[]` + +Get the current playlist in order. + +**Since** 2.1.0 + +```ts +const tracks = webamp.getPlaylistTracks(); +console.log(`Playlist has ${tracks.length} tracks`); +``` + +### `isShuffleEnabled(): boolean` + +Check if shuffle is enabled. + +**Since** 2.1.0 + +```ts +if (webamp.isShuffleEnabled()) { + console.log("Shuffle is enabled"); +} +``` + +### `isRepeatEnabled(): boolean` + +Check if repeat is enabled. + +**Since** 2.1.0 + +```ts +if (webamp.isRepeatEnabled()) { + console.log("Repeat is enabled"); +} +``` + ### `renderWhenReady(domNode: HTMLElement): Promise` Webamp will wait until it has fetched the skin and fully parsed it, and then render itself into a new DOM node at the end of the `` tag. @@ -157,6 +215,8 @@ Returns an "unsubscribe" function. **Note:** If the user drags in a track, the URL may be an [ObjectURL](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL) +**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. + ```ts const unsubscribe = webamp.onTrackDidChange((track => { console.log("New track playing:", track.url); @@ -166,6 +226,31 @@ const unsubscribe = webamp.onTrackDidChange((track => { unsubscribe(); ``` +### `onCurrentTrackDidChange(cb: (currentTrack: PlaylistTrack | null, trackIndex: number) => void): () => void` + +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. + +Returns an "unsubscribe" function. + +**Note:** This is different from the `onTrackDidChange` callback which is only called when a new track first starts loading. + +**Since** 2.1.0 + +```ts +const unsubscribe = webamp.onCurrentTrackDidChange( + (currentTrack, trackIndex) => { + if (currentTrack) { + console.log(`Now playing track ${trackIndex + 1}: ${currentTrack.title}`); + } + } +); + +// If at some point in the future you want to stop listening to these events: +unsubscribe(); +``` + ### `onWillClose(cb: (cancel: () => void) => void): () => void` A callback which will be called when Webamp is _about to_ close. Returns an "unsubscribe" function. The callback will be passed a `cancel` function which you can use to conditionally prevent Webamp from being closed. diff --git a/packages/webamp/CHANGELOG.md b/packages/webamp/CHANGELOG.md index a38fa216..c436db0f 100644 --- a/packages/webamp/CHANGELOG.md +++ b/packages/webamp/CHANGELOG.md @@ -8,6 +8,12 @@ - Improve skin parsing performance and avoid Chrome console warning by adding `willReadFrequently` to canvas contexts. - Define an explicit `IMedia` interface for custom media implementations, which allows for better type checking and documentation. +- Added new `Webamp` instance methods: + - `webamp.onCurrentTrackDidChange` + - `webamp.isShuffleEnabled` + - `webamp.isRepeatEnabled` + - `webamp.setCurrentTrack` + - `webamp.getPlaylistTracks` ## 2.0.1 [CURRENT] diff --git a/packages/webamp/js/selectors.ts b/packages/webamp/js/selectors.ts index 1b6ebebc..4f5e56c9 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 214317c3..42935868 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"; @@ -225,14 +226,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)); @@ -245,6 +246,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 */ @@ -259,6 +274,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. */ @@ -276,6 +303,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. */ @@ -320,14 +354,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;