Define an interface for IMedia

This commit is contained in:
Jordan Eldredge 2025-06-19 12:59:51 -07:00
parent 6432753d20
commit fd12770ad5
6 changed files with 90 additions and 15 deletions

View file

@ -1,11 +1,11 @@
# Custom Media Impl.
In order to support more advanced use cases where you want Webamp to play media using something other than the Web Audio API, you can provide your own media player implementation. This is done by defining a class which will replace the internal `Media` class.
In order to support more advanced use cases where you want Webamp to play media using something other than the Web Audio API, you can provide your own media player implementation. This is done by defining a class implements the `IMedia` interface.
This can be used (abused) to implement things like [Winampify](https://github.com/remigallego/winampify) which uses the Webamp UI to control playback via the Spotify Web SDK, but could also be used to implement Webamp as a client for something like [Music Player Daemon (MPD)](https://www.musicpd.org/).
:::danger
This is a bit of a hack. The API is not considered stable and may change in future versions of Webamp. You may need to suppress some TypeScript errors to get this to work.
This is a bit of a hack. The API is not considered stable and may change in future versions of Webamp.
:::
## Custom Media Implementation
@ -13,18 +13,18 @@ This is a bit of a hack. The API is not considered stable and may change in futu
```ts
import Webamp from "webamp";
class MyCustomMediaPlayer {
class MyCustomMediaImpl {
// Add methods such as seeking, volume control, etc.
}
const webamp = new Webamp({
// ... other options
__customMediaClass: MyCustomMediaPlayer,
__customMediaClass: MyCustomMediaImpl,
});
webamp.renderWhenReady(document.getElementById("winamp-container"));
```
:::tip
Check the types of the `Media` class (found via the `__customMediaClass` config property) in Webamp's TypeScript types for details of the expected methods and properties. Note that methods prefixed with `_` are not part of the public API and do not need to be implemented, despite what the types say.
Check the types of the `IMedia` interface (found via the `__customMediaClass` config property) in Webamp's TypeScript types for details of the expected methods and properties.
:::

View file

@ -20,7 +20,7 @@ import PlaylistWindow from "./PlaylistWindow";
import EqualizerWindow from "./EqualizerWindow";
import Skin from "./Skin";
import Media from "../media";
import Media, { IMedia } from "../media";
import { useTypedSelector, useActionCreator } from "../hooks";
import Css from "./Css";
@ -29,7 +29,7 @@ import cssText from "../../css/webamp.css?inline";
interface Props {
filePickers: FilePicker[];
media: Media;
media: IMedia;
}
/**

View file

@ -12,10 +12,85 @@ interface StereoBalanceNodeType extends AudioNode {
};
}
export interface IMedia {
/**
* Set the volume from 0 to 100
*/
setVolume(volume: number): void;
/**
* Set the stereo balance from -100 to 100
*/
setBalance(balance: number): void;
/**
* Set the preamp value from 0 to 100
* The input value represents -12db to 12db, where 50 is 0db (no change)
* Equation used is: 10^((dB)/20) = x, where x is the gain value
*/
setPreamp(value: number): void;
/**
* Register an event listener
*/
on(event: string, callback: (...args: any[]) => void): void;
/**
* Get the current playback time in seconds
*/
timeElapsed(): number;
/**
* Get the total duration of the current track in seconds
*/
duration(): number;
/**
* Start or resume playback
*/
play(): Promise<void>;
/**
* Pause playback
*/
pause(): void;
/**
* Stop playback and reset position to beginning
*/
stop(): void;
/**
* Seek to a specific position as a percentage of the total duration
*/
seekToPercentComplete(percent: number): void;
/**
* Load a track from a URL and optionally start playing it
* Used only for the initial load, since it must have a CORS header
*/
loadFromUrl(url: string, autoPlay: boolean): Promise<void>;
/**
* Set the gain value for a specific EQ band
*/
setEqBand(band: Band, value: number): void;
/**
* Disable the equalizer by bypassing all EQ bands
*/
disableEq(): void;
/**
* Enable the equalizer processing
*/
enableEq(): void;
/**
* Get the analyser node for visualizer data
*/
getAnalyser(): AnalyserNode;
/**
* Clean up resources and dispose of the media instance
*/
dispose(): void;
}
// A constructable class that implements the IMedia interface.
export interface IMediaClass {
new (): IMedia;
}
// NOTE: While this is not technically a public API, https://winampify.io/ is
// replacing this class with a custom version. Breaking changes to this API
// surface should be communicated to Remi.
export default class Media {
export default class Media implements IMedia {
_emitter: Emitter;
_context: AudioContext;
_balance: StereoBalanceNodeType;

View file

@ -1,4 +1,4 @@
import Media from "./media";
import { IMedia } from "./media";
import {
IS_PLAYING,
PAUSE,
@ -23,7 +23,7 @@ import * as Selectors from "./selectors";
import { MiddlewareStore, Action, Dispatch } from "./types";
import { objectForEach } from "./utils";
export default (media: Media) => (store: MiddlewareStore) => {
export default (media: IMedia) => (store: MiddlewareStore) => {
const {
media: { volume, balance },
equalizer: { sliders },

View file

@ -9,7 +9,7 @@ import reducer from "./reducers";
import mediaMiddleware from "./mediaMiddleware";
import { merge } from "./utils";
import { UPDATE_TIME_ELAPSED, STEP_MARQUEE } from "./actionTypes";
import Media from "./media";
import { IMedia } from "./media";
import Emitter from "./emitter";
import {
Extras,
@ -26,7 +26,7 @@ const compose = composeWithDevTools({
});
export default function createWebampStore(
media: Media,
media: IMedia,
actionEmitter: Emitter,
customMiddlewares: Middleware[] = [],
stateOverrides: PartialState | undefined,

View file

@ -16,7 +16,7 @@ import {
import getStore from "./store";
import App from "./components/App";
import { bindHotkeys } from "./hotkeys";
import Media from "./media";
import Media, { IMedia, IMediaClass } from "./media";
import * as Selectors from "./selectors";
import * as Actions from "./actionCreators";
@ -43,7 +43,7 @@ export interface PrivateOptions {
__customMiddlewares?: Middleware[];
__butterchurnOptions?: ButterchurnOptions;
// This is used by https://winampify.io/ to proxy through to Spotify's API.
__customMediaClass?: typeof Media; // This should have the same interface as Media
__customMediaClass?: IMediaClass;
}
export interface InjectableDependencies {
@ -57,7 +57,7 @@ class Webamp {
_root: ReactDOM.Root | null;
_disposable: Disposable;
options: Options & PrivateOptions & InjectableDependencies; // TODO: Make this _private
media: Media; // TODO: Make this _private
media: IMedia; // TODO: Make this _private
store: Store; // TODO: Make this _private
/**