diff --git a/packages/webamp-docs/docs/06_API/05_custom-media-impl.md b/packages/webamp-docs/docs/06_API/05_custom-media-impl.md index 4e9ff0fc..ddaf293e 100644 --- a/packages/webamp-docs/docs/06_API/05_custom-media-impl.md +++ b/packages/webamp-docs/docs/06_API/05_custom-media-impl.md @@ -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. ::: diff --git a/packages/webamp/js/components/App.tsx b/packages/webamp/js/components/App.tsx index f962a15b..bbf74881 100644 --- a/packages/webamp/js/components/App.tsx +++ b/packages/webamp/js/components/App.tsx @@ -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; } /** diff --git a/packages/webamp/js/media/index.ts b/packages/webamp/js/media/index.ts index 8779f6c8..49585c95 100644 --- a/packages/webamp/js/media/index.ts +++ b/packages/webamp/js/media/index.ts @@ -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; + /** + * 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; + /** + * 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; diff --git a/packages/webamp/js/mediaMiddleware.ts b/packages/webamp/js/mediaMiddleware.ts index 9eafb91e..0583b4ee 100644 --- a/packages/webamp/js/mediaMiddleware.ts +++ b/packages/webamp/js/mediaMiddleware.ts @@ -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 }, diff --git a/packages/webamp/js/store.ts b/packages/webamp/js/store.ts index c25e0453..d52ff505 100644 --- a/packages/webamp/js/store.ts +++ b/packages/webamp/js/store.ts @@ -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, diff --git a/packages/webamp/js/webampLazy.tsx b/packages/webamp/js/webampLazy.tsx index ddaf3827..214317c3 100644 --- a/packages/webamp/js/webampLazy.tsx +++ b/packages/webamp/js/webampLazy.tsx @@ -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 /**