From eb0898fe4eeb907a69f8c184e1113df863753445 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Sun, 29 Jun 2025 22:44:49 -0700 Subject: [PATCH] Add getPlayerMediaStatus instance method --- .../docs/06_API/03_instance-methods.md | 17 +++++++++++++ packages/webamp/CHANGELOG.md | 1 + packages/webamp/js/constants.ts | 11 +++++++- packages/webamp/js/reducers/media.ts | 21 ++++++++++------ packages/webamp/js/selectors.ts | 25 ++++++++++++++++--- packages/webamp/js/types.ts | 14 +++++++++++ packages/webamp/js/webampLazy.tsx | 13 +++++++++- 7 files changed, 90 insertions(+), 12 deletions(-) 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 68477579..cc18fcd2 100644 --- a/packages/webamp-docs/docs/06_API/03_instance-methods.md +++ b/packages/webamp-docs/docs/06_API/03_instance-methods.md @@ -104,6 +104,23 @@ Get the current "playing" status. The return value is one of: `"PLAYING"`, `"STO const isPlaying = webamp.getMediaStatus() === "PLAYING"; ``` +### `getPlayerMediaStatus(): PlayerMediaStatus` + +Get the current "playing" status of the player. Similar to `getMediaStatus()`, but can differentiate between different reasons why the player might not be playing, such as "ENDED" when the end of the playlist has been reached or "CLOSED" when the player has been closed. + +The return value is one of: `"PLAYING"`, `"STOPPED"`, `"PAUSED"`, `"ENDED"`, or `"CLOSED"`. + +**Since** 2.1.3 + +```ts +const playerStatus = webamp.getPlayerMediaStatus(); +if (playerStatus === "ENDED") { + console.log("Playlist has ended"); +} else if (playerStatus === "CLOSED") { + console.log("Player is closed"); +} +``` + ### `pause(): void` Pause the current track. diff --git a/packages/webamp/CHANGELOG.md b/packages/webamp/CHANGELOG.md index a1fc9a84..2e72c0e3 100644 --- a/packages/webamp/CHANGELOG.md +++ b/packages/webamp/CHANGELOG.md @@ -9,6 +9,7 @@ - Added new `Webamp` instance methods: - `webamp.toggleShuffle` - `webamp.toggleRepeat` + - `webamp.getPlayerMediaStatus` - Add new config option `enableMediaSession` to allow Webamp to connect to the browser's Media Session API. This enables OS/hardware level media controls like play/pause/next/previous. - Ensure the promise returned from `renderWhenReady` only resolves after the Webamp instance has been fully mounted and inserted into the DOM. Previously it resolved after the DOM node was created but before it was inserted into the DOM. diff --git a/packages/webamp/js/constants.ts b/packages/webamp/js/constants.ts index c3881b58..da1fe269 100644 --- a/packages/webamp/js/constants.ts +++ b/packages/webamp/js/constants.ts @@ -5,6 +5,7 @@ import { LoadStyle, TimeMode, WindowId, + PlayerMediaStatus, } from "./types"; import baseSkin from "./baseSkin.json"; export const BANDS: Band[] = [ @@ -64,9 +65,17 @@ export const TIME_MODE: Record = { REMAINING: "REMAINING", }; -// TODO: Convert to enum once we are fully Typescript export const MEDIA_STATUS: Record = { PLAYING: "PLAYING", STOPPED: "STOPPED", PAUSED: "PAUSED", }; + +export const PLAYER_MEDIA_STATUS: Record = + { + PLAYING: "PLAYING", + STOPPED: "STOPPED", + PAUSED: "PAUSED", + ENDED: "ENDED", + CLOSED: "CLOSED", + }; diff --git a/packages/webamp/js/reducers/media.ts b/packages/webamp/js/reducers/media.ts index 5879a290..1b169ee6 100644 --- a/packages/webamp/js/reducers/media.ts +++ b/packages/webamp/js/reducers/media.ts @@ -1,4 +1,4 @@ -import { Action, MediaStatus, TimeMode } from "../types"; +import { Action, PlayerMediaStatus, TimeMode } from "../types"; import { PLAY, STOP, @@ -13,8 +13,10 @@ import { TOGGLE_TIME_MODE, UPDATE_TIME_ELAPSED, LOAD_SERIALIZED_STATE, + CLOSE_WINAMP, + OPEN_WINAMP, } from "../actionTypes"; -import { TIME_MODE, MEDIA_STATUS } from "../constants"; +import { TIME_MODE, PLAYER_MEDIA_STATUS } from "../constants"; import { MediaSerializedStateV1 } from "../serializedStates/v1Types"; export interface MediaState { @@ -24,7 +26,7 @@ export interface MediaState { balance: number; shuffle: boolean; repeat: boolean; - status: MediaStatus; + status: PlayerMediaStatus; } const defaultState = { @@ -39,7 +41,7 @@ const defaultState = { shuffle: false, repeat: false, // TODO: Enforce possible values - status: MEDIA_STATUS.STOPPED, + status: PLAYER_MEDIA_STATUS.STOPPED, }; const media = ( @@ -50,12 +52,17 @@ const media = ( // TODO: Make these constants case PLAY: case IS_PLAYING: - return { ...state, status: MEDIA_STATUS.PLAYING }; + return { ...state, status: PLAYER_MEDIA_STATUS.PLAYING }; case PAUSE: - return { ...state, status: MEDIA_STATUS.PAUSED }; + return { ...state, status: PLAYER_MEDIA_STATUS.PAUSED }; case STOP: + return { ...state, status: PLAYER_MEDIA_STATUS.STOPPED }; case IS_STOPPED: - return { ...state, status: MEDIA_STATUS.STOPPED }; + return { ...state, status: PLAYER_MEDIA_STATUS.ENDED }; + case OPEN_WINAMP: + return { ...state, status: PLAYER_MEDIA_STATUS.STOPPED }; + case CLOSE_WINAMP: + return { ...state, status: PLAYER_MEDIA_STATUS.CLOSED }; case TOGGLE_TIME_MODE: const newMode = state.timeMode === TIME_MODE.REMAINING diff --git a/packages/webamp/js/selectors.ts b/packages/webamp/js/selectors.ts index 4f5e56c9..f56fa627 100644 --- a/packages/webamp/js/selectors.ts +++ b/packages/webamp/js/selectors.ts @@ -8,7 +8,6 @@ import { WindowPositions, PlaylistStyle, TransitionType, - MediaStatus, TimeMode, SkinImages, Cursors, @@ -16,6 +15,8 @@ import { GenLetterWidths, MilkdropMessage, DummyVizData, + PlayerMediaStatus, + MediaStatus, } from "./types"; import { createSelector, defaultMemoize } from "reselect"; import * as Utils from "./utils"; @@ -29,6 +30,7 @@ import { MEDIA_TAG_REQUEST_STATUS, WINDOWS, VISUALIZERS, + PLAYER_MEDIA_STATUS, } from "./constants"; import { createPlaylistURL } from "./playlistHtml"; import * as fromTracks from "./reducers/tracks"; @@ -337,11 +339,28 @@ export const getCurrentTrackDisplayName = createSelector( return getName(id); } ); - -export const getMediaStatus = (state: AppState): MediaStatus => { +export const getPlayerMediaStatus = (state: AppState): PlayerMediaStatus => { return state.media.status; }; +export const getMediaStatus = createSelector( + getPlayerMediaStatus, + (status: PlayerMediaStatus): MediaStatus => { + switch (status) { + case "PLAYING": + case "PAUSED": + return status; + case "STOPPED": + case "ENDED": + case "CLOSED": + return "STOPPED"; + default: + const s: never = status; + throw new Error(`Unknown media status: ${s}`); + } + } +); + export const getMediaIsPlaying = (state: AppState) => state.media.status === MEDIA_STATUS.PLAYING; diff --git a/packages/webamp/js/types.ts b/packages/webamp/js/types.ts index 70b4d021..5daf0c9c 100644 --- a/packages/webamp/js/types.ts +++ b/packages/webamp/js/types.ts @@ -564,8 +564,22 @@ export type MediaTagRequestStatus = | "COMPLETE" | "NOT_REQUESTED"; +/** The status of the current media. */ export type MediaStatus = "PLAYING" | "STOPPED" | "PAUSED"; +/** + * The media status of the player. Similar to MediaStatus but can discriminate + * between different reasons for being stopped. + */ +export type PlayerMediaStatus = + | "PLAYING" + | "STOPPED" + | "PAUSED" + /** We have reached the end of the playlist. */ + | "ENDED" + /** The player is closed. */ + | "CLOSED"; + export type LoadStyle = "BUFFER" | "PLAY" | "NONE"; export type TimeMode = "ELAPSED" | "REMAINING"; diff --git a/packages/webamp/js/webampLazy.tsx b/packages/webamp/js/webampLazy.tsx index 13a23f0d..8daf7f5e 100644 --- a/packages/webamp/js/webampLazy.tsx +++ b/packages/webamp/js/webampLazy.tsx @@ -13,6 +13,7 @@ import { Options, MediaStatus, PlaylistTrack, + PlayerMediaStatus, } from "./types"; import getStore from "./store"; import App from "./components/App"; @@ -337,10 +338,20 @@ class Webamp { /** * Get the current "playing" status. */ - getMediaStatus(): MediaStatus | null { + getMediaStatus(): MediaStatus { return Selectors.getMediaStatus(this.store.getState()); } + /** + * Get the current "playing" status of the player. Similar to + * `getMediaStatus()`, but can differentiate between different reasons why the + * player might not be playing, such as "ENDED" when the end of the playlist + * has been reached or "CLOSED" when the player has been closed. + */ + getPlayerMediaStatus(): PlayerMediaStatus { + return Selectors.getPlayerMediaStatus(this.store.getState()); + } + /** * A callback which will be called when Webamp is _about to_ close. Returns an * "unsubscribe" function. The callback will be passed a `cancel` function