UNPKG

@league-of-foundry-developers/foundry-vtt-types

Version:
510 lines (434 loc) 17.7 kB
import type { Brand, InexactPartial, MaybePromise, NullishProps, Identity } from "#utils"; import type EventEmitterMixin from "#common/utils/event-emitter.d.mts"; import type { AmbientSound } from "#client/canvas/placeables/_module.d.mts"; declare namespace Sound { interface Any extends AnySound {} interface AnyConstructor extends Identity<typeof AnySound> {} /** @internal */ type _ConstructorOptions = NullishProps<{ /** * Force use of an AudioBufferSourceNode even if the audio duration is long * @defaultValue `false` */ forceBuffer: boolean; /** * A non-default audio context within which the sound should play * @defaultValue `game.audio.music` */ context: AudioContext; }>; interface ConstructorOptions extends _ConstructorOptions {} type STATES = Brand<number, "Sound.STATES">; interface States { readonly FAILED: -1 & STATES; readonly NONE: 0 & STATES; readonly LOADING: 1 & STATES; readonly LOADED: 2 & STATES; readonly STARTING: 3 & STATES; readonly PLAYING: 4 & STATES; readonly PAUSED: 5 & STATES; readonly STOPPING: 6 & STATES; readonly STOPPED: 7 & STATES; } /** @internal */ type _LoadOptions = NullishProps<{ /** * Automatically begin playback of the sound once loaded * @defaultValue `false` */ autoplay: boolean; }> & InexactPartial<{ /** * Playback options passed to Sound#play, if autoplay * @defaultValue `{}` * @remarks Can't be `null` as it only has a parameter default */ autoplayOptions: Sound.PlaybackOptions; }>; interface LoadOptions extends _LoadOptions {} /** * Since `Sound##playback` isn't exposed, this interface can *just* be accurate to what's allowable to pass * to {@link Sound.play | `Sound#play`} or {@link Sound.stop | `#stop`}, which in reality is what's allowed * by `Sound##configurePlayback` * * @internal */ type _PlaybackOptions = NullishProps<{ /** * A delay in seconds by which to delay playback * @defaultValue `0` * @remarks Unlike other properties of this interface, the above default is static. */ delay: number; /** * A limited duration in seconds for which to play * @defaultValue `undefined` */ duration: number; /** * A duration in milliseconds over which to fade in playback * @defaultValue `0` * @remarks Unlike other properties of this interface, the above default is static. */ fade: number; /** * Should sound playback loop? * @defaultValue `false` * @remarks The above default is true for initial calls, but the actual default * value, if this is passed nullish or omitted, is whatever the current value is */ loop: boolean; /** * Seconds of the AudioBuffer when looped playback should start. Only works for AudioBufferSourceNode. * @defaultValue `0` * @remarks The above default is true for initial calls, but the actual default * value, if this is passed nullish or omitted, is whatever the current value is */ loopStart: number; /** * Seconds of the Audio buffer when looped playback should restart. Only works for AudioBufferSourceNode. * @defaultValue `undefined` * @remarks The above default is true for initial calls, but the actual default * value, if this is passed nullish or omitted, is whatever the current value is */ loopEnd: number; /** * An offset in seconds at which to start playback * @defaultValue `0` * @remarks The above default is true for initial calls, but the actual default * value, if this is passed nullish or omitted, is whatever the current value of * `loopStart` is */ offset: number; /** * A callback function attached to the source node * @defaultValue `null` * @remarks The above default is true for initial calls, but the actual default * value, if this is passed nullish or omitted, is whatever the current value is */ onended: ScheduleCallback | null; /** * The volume at which to play the sound * @defaultValue `1.0` * @remarks The above default is true for initial calls, but the actual default * value, if this is passed nullish or omitted, is whatever the current value is */ volume: number; }>; /** @remarks Default values here are what `Sound##configurePlayback` would use if passed an empty object with no prior calls */ interface PlaybackOptions extends _PlaybackOptions {} /** @internal */ type _FadeOptions = InexactPartial<{ /** * The duration of the fade effect in milliseconds * @defaultValue `1000` * @remarks Can't be `null` as it only has a parameter default */ duration: number; /** * The type of fade easing, "linear" or "exponential" * @defaultValue `"linear"` * @remarks Can't be `null` as it only has a parameter default */ type: "linear" | "exponential"; }> & NullishProps<{ /** * A volume level to start from, the current volume by default * @defaultValue `this.gain.value` */ from: number; }>; interface FadeOptions extends _FadeOptions {} type ScheduleCallback = (sound: Sound) => MaybePromise<unknown>; } /** * A container around an AudioNode which manages sound playback in Foundry Virtual Tabletop. * Each Sound is either an AudioBufferSourceNode (for short sources) or a MediaElementAudioSourceNode (for long ones). * This class provides an interface around both types which allows standardized control over playback. */ declare class Sound extends EventEmitterMixin() { /** * Construct a Sound by providing the source URL and other options. * @param src - The audio source path, either a relative path or a remote URL * @param options - Additional options which configure the Sound */ // options: not null (destructured) constructor(src: string, options?: Sound.ConstructorOptions); /** * The sequence of container loading states. */ static STATES: Sound.States; /** * The maximum duration, in seconds, for which an AudioBufferSourceNode will be used. * Otherwise, a MediaElementAudioSourceNode will be used. * @defaultValue `10 * 60; // 10 Minutes` */ static MAX_BUFFER_DURATION: number; /** * @defaultValue `["load", "play", "pause", "end", "stop"]` */ static override emittedEvents: string[]; /** * A unique integer identifier for this sound. * @remarks Set via `Object.defineProperty` during construction, `writable: false, enumerable: true, configurable: false` */ readonly id: number; /** * The audio source path. * Either a relative path served by the running Foundry VTT game server or a remote URL. * @remarks Set via `Object.defineProperty` during construction, `writable: false, enumerable: true, configurable: false` */ readonly src: string; /** * The audio context within which this Sound is played. * @remarks Can return `undefined` if no context was passed at construction *and* `game.audio.music` hasn't been initialized yet, * which doesn't happen until the first sound is played */ get context(): AudioContext | undefined; /** * The AudioSourceNode used to control sound playback. * @remarks Returns `undefined` if the Sound has either not yet been played or has been stopped */ get sourceNode(): AudioBufferSourceNode | MediaElementAudioSourceNode | undefined; /** * The GainNode used to control volume for this sound. * @remarks Only `undefined` prior to first `play()` */ gainNode: GainNode | undefined; /** * An AudioBuffer instance, if this Sound uses an AudioBufferSourceNode for playback. */ buffer: AudioBuffer | null; /** * An HTMLAudioElement, if this Sound uses a MediaElementAudioSourceNode for playback. */ element: HTMLAudioElement | null; /** * The life-cycle state of the sound. * @defaultValue `Sound.STATES.NONE` */ protected _state: Sound.STATES; /** * Has the audio file been loaded either fully or for streaming. */ get loaded(): boolean; /** * Did the audio file fail to load. */ get failed(): boolean; /** * Is this sound currently playing? */ get playing(): boolean; /** * Does this Sound use an AudioBufferSourceNode? * Otherwise, the Sound uses a streamed MediaElementAudioSourceNode. */ get isBuffer(): boolean; /** * A convenience reference to the GainNode gain audio parameter. * @remarks `undefined` if {@link Sound.gainNode | `Sound#gainNode`} is. */ get gain(): AudioParam | undefined; /** * The AudioNode destination which is the output target for the Sound. * @remarks Only `undefined` prior to first `play()` */ destination: AudioNode | undefined; /** * A pipeline of AudioNode instances to be applied to Sound playback. */ effects: AudioNode[]; /** * The currently playing volume of the sound. * Undefined until playback has started for the first time. */ get volume(): number | undefined; set volume(value: number); /** * The time in seconds at which playback was started. * @remarks `undefined` if the Sound has either not yet been played or has been stopped */ startTime: number | undefined; /** * The time in seconds at which playback was paused. * @remarks `undefined` until the Sound is paused, and after the Sound is stopped or playback has been resumed */ pausedTime: number | undefined; /** * The total duration of the audio source in seconds. * @remarks `undefined` if the sound isn't loaded yet, or if the sourceNode type is element and also doesn't exist */ get duration(): number | undefined; /** * The current playback time of the sound. * @remarks `undefined` if the Sound has either not yet been played or has been stopped */ get currentTime(): number | undefined; /** * Is the sound looping? */ get loop(): boolean; set loop(value); /** * An internal reference to some object which is managing this Sound instance. * @defaultValue `null` * @remarks Foundry marked `@internal` * * Only ever set *or* read externally by core, so not protected * * @privateRemarks Foundry types this as `Object|null` but the only place in Core this gets set is in `AmbientSound`, to `this` */ _manager: AmbientSound.Implementation | null; /** * Load the audio source and prepare it for playback, either using an AudioBuffer or a streamed HTMLAudioElement. * @param options - Additional options which affect resource loading * @returns A Promise which resolves to the Sound once it is loaded */ // options: not null (destructured) load(options?: Sound.LoadOptions): Promise<this>; /** * An inner method which handles loading so that it can be de-duplicated under a single shared Promise resolution. * This method is factored out to allow for subclasses to override loading behavior. * @returns A Promise which resolves once the sound is loaded * @throws An error if loading failed for any reason */ protected _load(): Promise<void>; /** * Begin playback for the Sound. * This method is asynchronous because playback may not start until after an initially provided delay. * The Promise resolves *before* the fade-in of any configured volume transition. * @param options - Options which configure the beginning of sound playback * @returns A Promise which resolves once playback has started (excluding fade) */ play(options?: Sound.PlaybackOptions): Promise<this>; /** * @deprecated since v12, until v14 * @remarks "`Sound#play` now takes an object of playback options instead of positional arguments." */ play(offset: number, onended?: Sound.ScheduleCallback | null): Promise<this>; /** * Begin playback for the configured pipeline and playback options. * This method is factored out so that subclass implementations of Sound can implement alternative behavior. */ protected _play(): void; /** * Pause playback of the Sound. * For AudioBufferSourceNode this stops playback after recording the current time. * Calling Sound#play will resume playback from the pausedTime unless some other offset is passed. * For a MediaElementAudioSourceNode this simply calls the HTMLAudioElement#pause method directly. * @throws If called while the Sound isn't playing */ pause(): void; /** * Pause playback of the Sound. * This method is factored out so that subclass implementations of Sound can implement alternative behavior. */ protected _pause(): void; /** * Stop playback for the Sound. * This method is asynchronous because playback may not stop until after an initially provided delay. * The Promise resolves *after* the fade-out of any configured volume transition. * @param options - Options which configure the stopping of sound playback * @returns A Promise which resolves once playback is fully stopped (including fade) */ // options: not null (parameter default only, destructured where forwarded) stop(options?: Sound.PlaybackOptions): Promise<this>; /** * Stop playback of the Sound. * This method is factored out so that subclass implementations of Sound can implement alternative behavior. */ protected _stop(): void; /** * Fade the volume for this sound between its current level and a desired target volume. * @param volume - The desired target volume level between 0 and 1 * @param options - Additional options that configure the fade operation * @returns A Promise that resolves after the requested fade duration */ // options: not null (destructured) fade(volume: number, options?: Sound.FadeOptions): Promise<void>; /** * Wait a certain scheduled duration within this sound's own AudioContext. * @param duration - The duration to wait in milliseconds * @returns A promise which resolves after the waited duration */ wait(duration: number): Promise<void>; /** * Schedule a function to occur at the next occurrence of a specific playbackTime for this Sound. * @param fn - A function that will be called with this Sound as its single argument * @param playbackTime - The desired playback time at which the function should be called * @returns A Promise which resolves to the returned value of the provided function once it has been evaluated. * * @example Schedule audio playback changes * ```js * sound.schedule(() => console.log("Do something exactly 30 seconds into the track"), 30); * sound.schedule(() => console.log("Do something next time the track loops back to the beginning"), 0); * sound.schedule(() => console.log("Do something 5 seconds before the end of the track"), sound.duration - 5); * ``` */ schedule<R extends Sound.ScheduleCallback>(fn: R, playbackTime: number): Promise<Awaited<ReturnType<R>>>; /** * Update the array of effects applied to a Sound instance. * Optionally a new array of effects can be assigned. If no effects are passed, the current effects are re-applied. * @param effects - An array of AudioNode effects to apply */ applyEffects(effects?: AudioNode[]): void; /** * Create any AudioNode instances required for playback of this Sound. */ protected _createNodes(): void; /** * Create the audio pipeline used to play this Sound. * The GainNode is reused each time to link volume changes across multiple playbacks. * The AudioSourceNode is re-created every time that Sound#play is called. */ protected _connectPipeline(): void; /** * Disconnect the audio pipeline once playback is stopped. * Walk backwards along the Sound##pipeline from the Sound#destination, disconnecting each node. */ protected _disconnectPipeline(): void; /** * @deprecated since v12, will be removed in v14 * @remarks "`AudioContainer.LOAD_STATES` is deprecated in favor of {@linkcode Sound.STATES}" */ static get LOAD_STATES(): Sound.States; /** * @deprecated since v12, will be removed in v14 * @remarks "`AudioContainer#loadState` is deprecated in favor of {@link Sound._state | `Sound#_state`}" */ get loadState(): Sound.STATES; /** * @deprecated since v12, will be removed in v14 * @remarks "`Sound#container` is deprecated without replacement because the `Sound` and `AudioContainer` classes are now merged" */ get container(): this; /** * @deprecated since v12, will be removed in v14 * @remarks "`Sound#node` is renamed {@link Sound.sourceNode | `Sound#sourceNode`}" */ get node(): this["sourceNode"]; /** * @deprecated since v12, will be removed in v14 * @remarks "`Sound#on` is deprecated in favor of {@link Sound.addEventListener | `Sound#addEventListener`}" */ on(eventName: string, fn: EventEmitterMixin.EventListener, options?: EventEmitterMixin.AddListenerOptions): void; /** * @deprecated since v12, will be removed in v14 * @remarks "`Sound#off` is deprecated in favor of {@link Sound.removeEventListener | `Sound#removeEventListener`}" */ off(eventName: string, fn: EventEmitterMixin.EventListener): void; /** * @deprecated since v12, will be removed in v14 * @remarks "`Sound#emit` is deprecated in favor of {@link Sound.dispatchEvent | `Sound#dispatchEvent`}" * * This method still takes a string, then creates an `Event` with it and passes that along to `dispatchEvent` */ emit(eventName: string): void; } export default Sound; declare abstract class AnySound extends Sound { constructor(...args: never); }