From a4a77781e37e25be387a1db9d7ed268c5017390c Mon Sep 17 00:00:00 2001 From: Rahim Alwer Date: Thu, 28 Mar 2024 23:25:27 +1100 Subject: [PATCH] feat(player): add `setAudioGain` method on player --- packages/react/src/components/player.tsx | 2 +- .../ui/buttons/fullscreen-button.tsx | 2 +- .../src/components/ui/buttons/pip-button.tsx | 2 +- packages/react/src/hooks/use-media-state.ts | 2 +- .../vidstack/player/styles/default/menus.css | 6 +++- packages/vidstack/src/components/player.ts | 32 ++++++++++++------- .../ui/buttons/fullscreen-button.ts | 2 +- .../src/components/ui/buttons/pip-button.ts | 2 +- .../vidstack/src/core/api/media-events.ts | 4 +-- .../vidstack/src/core/api/player-props.ts | 6 ++-- .../vidstack/src/core/api/player-state.ts | 4 +-- packages/vidstack/src/core/api/types.ts | 4 +-- .../src/core/quality/video-quality.ts | 2 +- .../src/core/state/media-request-manager.ts | 31 +++++++++++++----- .../vidstack/src/core/state/remote-control.ts | 30 ++++++++--------- .../vidstack/src/core/tracks/audio-tracks.ts | 4 +-- .../src/core/tracks/text/text-tracks.ts | 2 +- 17 files changed, 82 insertions(+), 55 deletions(-) diff --git a/packages/react/src/components/player.tsx b/packages/react/src/components/player.tsx index 4aebc38ea..f69b53313 100644 --- a/packages/react/src/components/player.tsx +++ b/packages/react/src/components/player.tsx @@ -22,7 +22,7 @@ export interface MediaPlayerProps extends Omit( /** * This hook is used to subscribe to the current media state on the nearest parent player. * - * @docs {@link https://vidstack.io/docs/player/core-concepts/state#reading} + * @docs {@link https://vidstack.io/docs/player/core-concepts/state-management#reading} */ export function useMediaStore( ref?: React.RefObject, diff --git a/packages/vidstack/player/styles/default/menus.css b/packages/vidstack/player/styles/default/menus.css index 6f6afd561..eec4b1715 100644 --- a/packages/vidstack/player/styles/default/menus.css +++ b/packages/vidstack/player/styles/default/menus.css @@ -499,7 +499,7 @@ height: var(--size); } -:where(.vds-menu-item-hint, .vds-menu-open-icon) { +:where(.vds-menu-item-hint, .vds-menu-open-icon, .vds-radio-hint) { color: var(--text-hint-color); font-size: var(--media-menu-hint-font-size, 13px); font-weight: var(--media-menu-hint-font-weight, 400); @@ -728,6 +728,10 @@ margin-left: 6px; } +:where(.vds-radio-hint) { + margin-left: auto; +} + /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Color Picker diff --git a/packages/vidstack/src/components/player.ts b/packages/vidstack/src/components/player.ts index f22c77fe4..af7b16d19 100644 --- a/packages/vidstack/src/components/player.ts +++ b/packages/vidstack/src/components/player.ts @@ -434,7 +434,7 @@ export class MediaPlayer /** * A list of all `VideoQuality` objects representing the set of available video renditions. * - * @see {@link https://vidstack.io/docs/player/core-concepts/quality#quality-list} + * @see {@link https://vidstack.io/docs/player/api/video-quality} */ @prop get qualities(): VideoQualityList { @@ -444,7 +444,7 @@ export class MediaPlayer /** * A list of all `AudioTrack` objects representing the set of available audio tracks. * - * @see {@link https://vidstack.io/docs/player/core-concepts/audio-tracks} + * @see {@link https://vidstack.io/docs/player/api/audio-tracks} */ @prop get audioTracks(): AudioTrackList { @@ -454,7 +454,7 @@ export class MediaPlayer /** * A list of all `TextTrack` objects representing the set of available text tracks. * - * @see {@link https://vidstack.io/docs/player/core-concepts/text-tracks} + * @see {@link https://vidstack.io/docs/player/api/text-tracks} */ @prop get textTracks(): TextTrackList { @@ -464,8 +464,6 @@ export class MediaPlayer /** * Contains text renderers which are responsible for loading, parsing, and rendering text * tracks. - * - * @see {@link https://vidstack.io/docs/player/core-concepts/text-tracks#text-renderer} */ @prop get textRenderers(): TextRenderers { @@ -661,7 +659,7 @@ export class MediaPlayer * Attempts to display the player in fullscreen. The promise will resolve if successful, and * reject if not. This method will throw if any fullscreen API is _not_ currently available. * - * @see {@link https://vidstack.io/docs/player/core-concepts/fullscreen} + * @see {@link https://vidstack.io/docs/player/api/fullscreen} */ @method async enterFullscreen(target?: MediaFullscreenRequestTarget, trigger?: Event) { @@ -672,7 +670,7 @@ export class MediaPlayer * Attempts to display the player inline by exiting fullscreen. This method will throw if any * fullscreen API is _not_ currently available. * - * @see {@link https://vidstack.io/docs/player/core-concepts/fullscreen} + * @see {@link https://vidstack.io/docs/player/api/fullscreen} */ @method async exitFullscreen(target?: MediaFullscreenRequestTarget, trigger?: Event) { @@ -684,7 +682,7 @@ export class MediaPlayer * not supported. This method will also return a `PictureInPictureWindow` if the current * provider supports it. * - * @see {@link https://vidstack.io/docs/player/core-concepts/picture-in-picture} + * @see {@link https://vidstack.io/docs/player/api/picture-in-picture} */ @method enterPictureInPicture(trigger?: Event) { @@ -695,7 +693,7 @@ export class MediaPlayer * Attempts to display the player in inline by exiting picture-in-picture mode. This method * will throw if not supported. * - * @see {@link https://vidstack.io/docs/player/core-concepts/picture-in-picture} + * @see {@link https://vidstack.io/docs/player/api/picture-in-picture} */ @method exitPictureInPicture(trigger?: Event) { @@ -706,7 +704,7 @@ export class MediaPlayer * Sets the current time to the live edge (i.e., `duration`). This is a no-op for non-live * streams and will throw if called before media is ready for playback. * - * @see {@link https://vidstack.io/docs/player/core-concepts/live#live-edge} + * @see {@link https://vidstack.io/docs/player/api/live} */ @method seekToLiveEdge(trigger?: Event): void { @@ -717,7 +715,7 @@ export class MediaPlayer * Called when media can begin loading. Calling this method will trigger the initial provider * loading process. Calling it more than once has no effect. * - * @see {@link https://vidstack.io/docs/player/core-concepts/loading#loading-strategies} + * @see {@link https://vidstack.io/docs/player/core-concepts/loading#load-strategies} */ @method startLoading(trigger?: Event): void { @@ -727,7 +725,7 @@ export class MediaPlayer /** * Called when the poster image can begin loading. Calling it more than once has no effect. * - * @see {@link https://vidstack.io/docs/player/core-concepts/loading#loading-strategies} + * @see {@link https://vidstack.io/docs/player/core-concepts/loading#load-strategies} */ @method startLoadingPoster(trigger?: Event) { @@ -751,6 +749,16 @@ export class MediaPlayer return this._requestMgr._requestGoogleCast(trigger); } + /** + * Set the audio gain, amplifying volume and enabling a maximum volume above 100%. + * + * @see {@link https://vidstack.io/docs/player/api/audio-gain} + */ + @method + setAudioGain(gain: number, trigger?: Event) { + return this._requestMgr._setAudioGain(gain, trigger); + } + override destroy() { this._media.remote.setPlayer(null); this.dispatch('destroy'); diff --git a/packages/vidstack/src/components/ui/buttons/fullscreen-button.ts b/packages/vidstack/src/components/ui/buttons/fullscreen-button.ts index 2be6443a5..ca5d04989 100644 --- a/packages/vidstack/src/components/ui/buttons/fullscreen-button.ts +++ b/packages/vidstack/src/components/ui/buttons/fullscreen-button.ts @@ -33,7 +33,7 @@ export interface FullscreenButtonEvents * @attr data-active - Whether fullscreen mode is active. * @attr data-supported - Whether fullscreen mode is supported. * @docs {@link https://www.vidstack.io/docs/player/components/buttons/fullscreen-button} - * @see {@link https://www.vidstack.io/docs/player/core-concepts/fullscreen} + * @see {@link https://www.vidstack.io/docs/player/api/fullscreen} */ export class FullscreenButton extends Component { static props: FullscreenButtonProps = { diff --git a/packages/vidstack/src/components/ui/buttons/pip-button.ts b/packages/vidstack/src/components/ui/buttons/pip-button.ts index fe499520d..b3a4dc9f8 100644 --- a/packages/vidstack/src/components/ui/buttons/pip-button.ts +++ b/packages/vidstack/src/components/ui/buttons/pip-button.ts @@ -20,7 +20,7 @@ export interface PIPButtonEvents * @attr data-active - Whether picture-in-picture mode is active. * @attr data-supported - Whether picture-in-picture mode is available. * @docs {@link https://www.vidstack.io/docs/player/components/buttons/pip-button} - * @see {@link https://www.vidstack.io/docs/player/core-concepts/picture-in-picture} + * @see {@link https://www.vidstack.io/docs/player/api/picture-in-picture} */ export class PIPButton extends Component { static props: PIPButtonProps = ToggleButtonController.props; diff --git a/packages/vidstack/src/core/api/media-events.ts b/packages/vidstack/src/core/api/media-events.ts index d15d3a41d..899caa23a 100644 --- a/packages/vidstack/src/core/api/media-events.ts +++ b/packages/vidstack/src/core/api/media-events.ts @@ -136,7 +136,7 @@ export interface MediaAutoPlayEvent extends MediaEvent * Fired when the player can begin loading the current provider and media. This depends on the * `load` player prop. * - * @see {@link https://vidstack.io/docs/player/core-concepts/loading#loading-strategies} + * @see {@link https://vidstack.io/docs/player/core-concepts/loading#load-strategies} */ export interface MediaCanLoadEvent extends MediaEvent {} @@ -144,7 +144,7 @@ export interface MediaCanLoadEvent extends MediaEvent {} * Fired when the player can begin loading the poster image. This depends on the `posterLoad` * player prop. * - * @see {@link https://vidstack.io/docs/player/core-concepts/loading#loading-strategies} + * @see {@link https://vidstack.io/docs/player/core-concepts/loading#load-strategies} */ export interface MediaCanLoadPosterEvent extends MediaEvent {} diff --git a/packages/vidstack/src/core/api/player-props.ts b/packages/vidstack/src/core/api/player-props.ts index 2ae697de0..4ef4810e3 100644 --- a/packages/vidstack/src/core/api/player-props.ts +++ b/packages/vidstack/src/core/api/player-props.ts @@ -101,7 +101,7 @@ export interface MediaPlayerProps /** * The URL and optionally type of the current media resource/s to be considered for playback. * - * @see {@link https://vidstack.io/docs/player/core-concepts/loading#loading-source} + * @see {@link https://vidstack.io/docs/player/core-concepts/loading#sources} * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/src} * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject} */ @@ -120,7 +120,7 @@ export interface MediaPlayerProps * - `custom`: media will wait for the `startLoading()` method or `media-start-loading` event. * - `play`: media will delay loading until there is a play request. * - * @see {@link https://vidstack.io/docs/player/core-concepts/loading#loading-strategies} + * @see {@link https://vidstack.io/docs/player/core-concepts/loading#load-strategies} */ load: MediaLoadingStrategy; /** @@ -131,7 +131,7 @@ export interface MediaPlayerProps * - `visible`: poster will delay loading until the provider has entered the viewport. * - `custom`: poster will wait for the `startLoadingPoster()` method or `media-poster-start-loading` event. * - * @see {@link https://vidstack.io/docs/player/core-concepts/loading#loading-strategies} + * @see {@link https://vidstack.io/docs/player/core-concepts/loading#load-strategies} */ posterLoad: MediaPosterLoadingStrategy; /** diff --git a/packages/vidstack/src/core/api/player-state.ts b/packages/vidstack/src/core/api/player-state.ts index 83b9e2ab5..e32888bb5 100644 --- a/packages/vidstack/src/core/api/player-state.ts +++ b/packages/vidstack/src/core/api/player-state.ts @@ -362,14 +362,14 @@ export interface MediaState { /** * Whether media is allowed to begin loading. This depends on the `load` player prop. * - * @see {@link https://vidstack.io/docs/player/core-concepts/loading#loading-strategies} + * @see {@link https://vidstack.io/docs/player/core-concepts/loading#load-strategies} */ canLoad: boolean; /** * Whether the media poster is allowed to begin loading. This depends on the `posterLoad` * player prop. * - * @see {@link https://vidstack.io/docs/player/core-concepts/loading#loading-strategies} + * @see {@link https://vidstack.io/docs/player/core-concepts/loading#load-strategies} */ canLoadPoster: boolean; /** diff --git a/packages/vidstack/src/core/api/types.ts b/packages/vidstack/src/core/api/types.ts index 21799902b..0193597cf 100644 --- a/packages/vidstack/src/core/api/types.ts +++ b/packages/vidstack/src/core/api/types.ts @@ -30,14 +30,14 @@ export type MediaViewType = 'unknown' | 'audio' | 'video'; /** * Indicates the type of strategy that should be used to initiate the loading process. * - * @docs {@see https://www.vidstack.io/docs/player/core-concepts/loading#loading-strategies} + * @docs {@see https://www.vidstack.io/docs/player/core-concepts/loading#load-strategies} */ export type MediaLoadingStrategy = 'eager' | 'idle' | 'visible' | 'custom' | 'play'; /** * Indicates the type of strategy that should be used to initiate the poster loading process. * - * @docs {@see https://www.vidstack.io/docs/player/core-concepts/loading#loading-strategies} + * @docs {@see https://www.vidstack.io/docs/player/core-concepts/loading#load-strategies} */ export type MediaPosterLoadingStrategy = 'eager' | 'idle' | 'visible' | 'custom'; diff --git a/packages/vidstack/src/core/quality/video-quality.ts b/packages/vidstack/src/core/quality/video-quality.ts index 8b708f49f..2e262da35 100644 --- a/packages/vidstack/src/core/quality/video-quality.ts +++ b/packages/vidstack/src/core/quality/video-quality.ts @@ -6,7 +6,7 @@ import { ListSymbol } from '../../foundation/list/symbols'; import { QualitySymbol } from './symbols'; /** - * @see {@link https://vidstack.io/docs/player/core-concepts/quality#quality-list} + * @see {@link https://vidstack.io/docs/player/core-concepts/video-quality#quality-list} */ export class VideoQualityList extends SelectList { private _auto = false; diff --git a/packages/vidstack/src/core/state/media-request-manager.ts b/packages/vidstack/src/core/state/media-request-manager.ts index eea059689..f902bc714 100644 --- a/packages/vidstack/src/core/state/media-request-manager.ts +++ b/packages/vidstack/src/core/state/media-request-manager.ts @@ -201,6 +201,24 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR } } + _setAudioGain(gain: number, trigger?: Event) { + const { audioGain, canSetAudioGain } = this.$state; + + if (audioGain() === gain) return; + + const provider = this._$provider(); + + if (!provider?.audioGain || !canSetAudioGain()) { + throw Error('[vidstack] audio gain api not available'); + } + + if (trigger) { + this._request._queue._enqueue('media-audio-gain-change-request', trigger); + } + + provider.audioGain.setGain(gain); + } + _seekToLiveEdge(trigger?: Event) { if (__SERVER__) return; @@ -633,14 +651,11 @@ export class MediaRequestManager extends MediaPlayerController implements MediaR } ['media-audio-gain-change-request'](event: RE.MediaAudioGainChangeRequestEvent) { - const { audioGain, canSetAudioGain } = this.$state; - if (audioGain() === event.detail || !canSetAudioGain()) return; - - const provider = this._$provider(); - if (!provider?.audioGain) return; - - this._request._queue._enqueue('media-audio-gain-change-request', event); - provider.audioGain.setGain(event.detail); + try { + this._setAudioGain(event.detail, event); + } catch (e) { + // no-op + } } ['media-quality-change-request'](event: RE.MediaQualityChangeRequestEvent) { diff --git a/packages/vidstack/src/core/state/remote-control.ts b/packages/vidstack/src/core/state/remote-control.ts index f50aa9ca1..76fb3ef03 100644 --- a/packages/vidstack/src/core/state/remote-control.ts +++ b/packages/vidstack/src/core/state/remote-control.ts @@ -10,8 +10,7 @@ import { isTrackCaptionKind } from '../tracks/text/text-track'; /** * A simple facade for dispatching media requests to the nearest media player element. * - * @docs {@link https://www.vidstack.io/docs/player/core-concepts/state#remote-control} - * @docs {@link https://www.vidstack.io/docs/player/core-concepts/state#updating} + * @docs {@link https://www.vidstack.io/docs/player/core-concepts/state-management#updating} * */ export class MediaRemoteControl { @@ -71,7 +70,7 @@ export class MediaRemoteControl { * Dispatch a request to start the media loading process. This will only work if the media * player has been initialized with a custom loading strategy `load="custom">`. * - * @docs {@link https://www.vidstack.io/docs/player/core-concepts/loading#loading-strategies} + * @docs {@link https://www.vidstack.io/docs/player/core-concepts/loading#load-strategies} */ startLoading(trigger?: Event) { this._dispatchRequest('media-start-loading', trigger); @@ -81,7 +80,7 @@ export class MediaRemoteControl { * Dispatch a request to start the poster loading process. This will only work if the media * player has been initialized with a custom poster loading strategy `posterLoad="custom">`. * - * @docs {@link https://www.vidstack.io/docs/player/core-concepts/loading#loading-strategies} + * @docs {@link https://www.vidstack.io/docs/player/core-concepts/loading#load-strategies} */ startLoadingPoster(trigger?: Event) { this._dispatchRequest('media-poster-start-loading', trigger); @@ -136,7 +135,7 @@ export class MediaRemoteControl { /** * Dispatch a request to enter fullscreen. * - * @docs {@link https://www.vidstack.io/docs/player/core-concepts/fullscreen#remote-control} + * @docs {@link https://www.vidstack.io/docs/player/api/fullscreen#remote-control} */ enterFullscreen(target?: MediaFullscreenRequestTarget, trigger?: Event) { this._dispatchRequest('media-enter-fullscreen-request', trigger, target); @@ -145,7 +144,7 @@ export class MediaRemoteControl { /** * Dispatch a request to exit fullscreen. * - * @docs {@link https://www.vidstack.io/docs/player/core-concepts/fullscreen#remote-control} + * @docs {@link https://www.vidstack.io/docs/player/api/fullscreen#remote-control} */ exitFullscreen(target?: MediaFullscreenRequestTarget, trigger?: Event) { this._dispatchRequest('media-exit-fullscreen-request', trigger, target); @@ -154,7 +153,7 @@ export class MediaRemoteControl { /** * Dispatch a request to lock the screen orientation. * - * @docs {@link https://www.vidstack.io/docs/player/core-concepts/screen-orientation#remote-control} + * @docs {@link https://www.vidstack.io/docs/player/screen-orientation#remote-control} */ lockScreenOrientation(lockType: ScreenOrientationLockType, trigger?: Event) { this._dispatchRequest('media-orientation-lock-request', trigger, lockType); @@ -163,7 +162,7 @@ export class MediaRemoteControl { /** * Dispatch a request to unlock the screen orientation. * - * @docs {@link https://www.vidstack.io/docs/player/core-concepts/screen-orientation#remote-control} + * @docs {@link https://www.vidstack.io/docs/player/api/screen-orientation#remote-control} */ unlockScreenOrientation(trigger?: Event) { this._dispatchRequest('media-orientation-unlock-request', trigger); @@ -172,7 +171,7 @@ export class MediaRemoteControl { /** * Dispatch a request to enter picture-in-picture mode. * - * @docs {@link https://www.vidstack.io/docs/player/core-concepts/picture-in-picture#remote-control} + * @docs {@link https://www.vidstack.io/docs/player/api/picture-in-picture#remote-control} */ enterPictureInPicture(trigger?: Event) { this._dispatchRequest('media-enter-pip-request', trigger); @@ -181,7 +180,7 @@ export class MediaRemoteControl { /** * Dispatch a request to exit picture-in-picture mode. * - * @docs {@link https://www.vidstack.io/docs/player/core-concepts/picture-in-picture#remote-control} + * @docs {@link https://www.vidstack.io/docs/player/api/picture-in-picture#remote-control} */ exitPictureInPicture(trigger?: Event) { this._dispatchRequest('media-exit-pip-request', trigger); @@ -210,6 +209,7 @@ export class MediaRemoteControl { * Dispatch a request to update the media volume to the given `volume` level which is a value * between 0 and 1. * + * @docs {@link https://www.vidstack.io/docs/player/api/audio-gain#remote-control} * @example * ```ts * remote.changeVolume(0); // 0% @@ -291,9 +291,9 @@ export class MediaRemoteControl { * * @example * ```ts - * remote.changeAudioGain(1); // Disable audio gain (100% of current volume) - * remote.changeAudioGain(1.5); // 150% louder than current volume - * remote.changeAudioGain(2); // 200% louder than current volume + * remote.changeAudioGain(1); // Disable audio gain + * remote.changeAudioGain(1.5); // 50% louder + * remote.changeAudioGain(2); // 100% louder * ``` */ changeAudioGain(gain: number, trigger?: Event) { @@ -380,7 +380,7 @@ export class MediaRemoteControl { /** * Dispatch a request to toggle the media fullscreen state. * - * @docs {@link https://www.vidstack.io/docs/player/core-concepts/fullscreen#remote-control} + * @docs {@link https://www.vidstack.io/docs/player/api/fullscreen#remote-control} */ toggleFullscreen(target?: MediaFullscreenRequestTarget, trigger?: Event) { const player = this.getPlayer(trigger?.target); @@ -397,7 +397,7 @@ export class MediaRemoteControl { /** * Dispatch a request to toggle the media picture-in-picture mode. * - * @docs {@link https://www.vidstack.io/docs/player/core-concepts/picture-in-picture#remote-control} + * @docs {@link https://www.vidstack.io/docs/player/api/picture-in-picture#remote-control} */ togglePictureInPicture(trigger?: Event) { const player = this.getPlayer(trigger?.target); diff --git a/packages/vidstack/src/core/tracks/audio-tracks.ts b/packages/vidstack/src/core/tracks/audio-tracks.ts index c8620425c..055623452 100644 --- a/packages/vidstack/src/core/tracks/audio-tracks.ts +++ b/packages/vidstack/src/core/tracks/audio-tracks.ts @@ -4,12 +4,12 @@ import type { ListReadonlyChangeEvent } from '../..//foundation/list/list'; import { SelectList, type SelectListItem } from '../../foundation/list/select-list'; /** - * @see {@link https://vidstack.io/docs/player/core-concepts/audio-tracks} + * @see {@link https://vidstack.io/docs/player/api/audio-tracks} */ export class AudioTrackList extends SelectList {} /** - * @see {@link https://vidstack.io/docs/player/core-concepts/audio-tracks} + * @see {@link https://vidstack.io/docs/player/api/audio-tracks} */ export interface AudioTrack extends SelectListItem { /** diff --git a/packages/vidstack/src/core/tracks/text/text-tracks.ts b/packages/vidstack/src/core/tracks/text/text-tracks.ts index 00415624d..f4a863ea6 100644 --- a/packages/vidstack/src/core/tracks/text/text-tracks.ts +++ b/packages/vidstack/src/core/tracks/text/text-tracks.ts @@ -13,7 +13,7 @@ import { } from './text-track'; /** - * @see {@link https://vidstack.io/docs/player/core-concepts/text-tracks} + * @see {@link https://vidstack.io/docs/player/api/text-tracks} */ export class TextTrackList extends List { private _canLoad = false;