UNPKG

@livepeer/react

Version:

React primitives for video apps.

1 lines 93.3 kB
{"version":3,"sources":["../../src/player.tsx","../../src/player/ClipTrigger.tsx","../../src/shared/context.tsx","../../src/shared/primitive.tsx","../../src/shared/utils.ts","../../src/player/Controls.tsx","../../src/player/LiveIndicator.tsx","../../src/player/MuteTrigger.tsx","../../src/player/Play.tsx","../../src/player/Player.tsx","../../src/player/Poster.tsx","../../src/player/RateSelect.tsx","../../src/shared/Select.tsx","../../src/player/Seek.tsx","../../src/shared/Slider.tsx","../../src/player/Video.tsx","../../src/player/VideoQualitySelect.tsx","../../src/player/Volume.tsx","../../src/shared/Container.tsx","../../src/shared/ErrorIndicator.tsx","../../src/shared/Fullscreen.tsx","../../src/shared/LoadingIndicator.tsx","../../src/shared/PictureInPictureTrigger.tsx","../../src/shared/Portal.tsx","../../src/shared/Time.tsx"],"sourcesContent":["export {\n ClipTrigger,\n type ClipTriggerProps,\n} from \"./player/ClipTrigger\";\nexport { Controls, type ControlsProps } from \"./player/Controls\";\nexport {\n LiveIndicator,\n type LiveIndicatorProps,\n} from \"./player/LiveIndicator\";\nexport {\n MuteTrigger,\n type MuteTriggerProps,\n} from \"./player/MuteTrigger\";\nexport {\n PlayingIndicator,\n type PlayingIndicatorProps,\n PlayPauseTrigger,\n type PlayPauseTriggerProps,\n} from \"./player/Play\";\nexport { type PlayerProps, Root } from \"./player/Player\";\nexport {\n Poster,\n type PosterProps,\n} from \"./player/Poster\";\nexport {\n RateSelect,\n RateSelectItem,\n type RateSelectItemProps,\n type RateSelectProps,\n} from \"./player/RateSelect\";\nexport {\n Seek,\n SeekBuffer,\n type SeekBufferProps,\n type SeekProps,\n} from \"./player/Seek\";\nexport { Video, type VideoProps } from \"./player/Video\";\nexport {\n VideoQualitySelect,\n VideoQualitySelectItem,\n type VideoQualitySelectItemProps,\n type VideoQualitySelectProps,\n} from \"./player/VideoQualitySelect\";\nexport {\n Volume,\n VolumeIndicator,\n type VolumeIndicatorProps,\n type VolumeProps,\n} from \"./player/Volume\";\nexport {\n Container,\n type ContainerProps,\n} from \"./shared/Container\";\nexport type { MediaContextValue, MediaScopedProps } from \"./shared/context\";\nexport {\n createMediaScope,\n MediaProvider,\n useMediaContext,\n useStore,\n} from \"./shared/context\";\nexport {\n ErrorIndicator,\n type ErrorIndicatorProps,\n} from \"./shared/ErrorIndicator\";\nexport {\n FullscreenIndicator,\n type FullscreenIndicatorProps,\n FullscreenTrigger,\n type FullscreenTriggerProps,\n} from \"./shared/Fullscreen\";\nexport {\n LoadingIndicator,\n type LoadingIndicatorProps,\n} from \"./shared/LoadingIndicator\";\nexport {\n PictureInPictureTrigger,\n type PictureInPictureTriggerProps,\n} from \"./shared/PictureInPictureTrigger\";\nexport {\n Portal,\n type PortalProps,\n} from \"./shared/Portal\";\nexport {\n SelectArrow,\n type SelectArrowProps,\n SelectContent,\n type SelectContentProps,\n SelectGroup,\n type SelectGroupProps,\n SelectIcon,\n type SelectIconProps,\n SelectItemIndicator,\n type SelectItemIndicatorProps,\n SelectItemText,\n type SelectItemTextProps,\n SelectLabel,\n type SelectLabelProps,\n SelectPortal,\n type SelectPortalProps,\n SelectScrollDownButton,\n type SelectScrollDownButtonProps,\n SelectScrollUpButton,\n type SelectScrollUpButtonProps,\n SelectSeparator,\n type SelectSeparatorProps,\n SelectTrigger,\n type SelectTriggerProps,\n SelectValue,\n type SelectValueProps,\n SelectViewport,\n type SelectViewportProps,\n} from \"./shared/Select\";\nexport {\n Range,\n type RangeProps,\n type SliderProps,\n Thumb,\n type ThumbProps,\n Track,\n type TrackProps,\n} from \"./shared/Slider\";\nexport { Time, type TimeProps } from \"./shared/Time\";\n","\"use client\";\n\nimport { composeEventHandlers } from \"@radix-ui/primitive\";\nimport { Presence } from \"@radix-ui/react-presence\";\n\nimport React, { useEffect } from \"react\";\nimport { useStore } from \"zustand\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { type MediaScopedProps, useMediaContext } from \"../shared/context\";\nimport * as Radix from \"../shared/primitive\";\nimport { noPropagate } from \"../shared/utils\";\n\nconst CLIP_TRIGGER_NAME = \"ClipTrigger\";\n\ntype ClipTriggerElement = React.ElementRef<typeof Radix.Primitive.button>;\n\ninterface ClipTriggerProps\n extends Radix.ComponentPropsWithoutRef<typeof Radix.Primitive.button> {\n /**\n * Used to force mounting when more control is needed. Useful when\n * controlling animation with React animation libraries.\n */\n forceMount?: true;\n\n onClip: (opts: {\n /**\n * Playback ID of the stream or asset to clip\n */\n playbackId: string;\n /**\n * Start time of the clip in milliseconds\n */\n startTime: number;\n /**\n * End time of the clip in milliseconds\n */\n endTime: number;\n }) => Promise<void> | void;\n}\n\nconst ClipTrigger = React.forwardRef<ClipTriggerElement, ClipTriggerProps>(\n (props: MediaScopedProps<ClipTriggerProps>, forwardedRef) => {\n const { __scopeMedia, forceMount, onClip, ...clipTriggerProps } = props;\n\n const context = useMediaContext(CLIP_TRIGGER_NAME, __scopeMedia);\n\n const { clipLength, requestClip, playbackId, title } = useStore(\n context.store,\n useShallow(\n ({ __controls, __controlsFunctions, aria, __initialProps }) => ({\n requestClip: __controlsFunctions.requestClip,\n playbackId: __controls.playbackId,\n clipLength: __initialProps.clipLength,\n title: aria.clip,\n }),\n ),\n );\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: no dependencies\n useEffect(() => {\n if (playbackId) {\n return context.store.subscribe(\n (state) => state.__controls.requestedClipParams,\n (params) => {\n if (params) {\n onClip({ playbackId, ...params });\n }\n },\n );\n }\n }, [playbackId]);\n\n return (\n <Presence present={forceMount || Boolean(clipLength)}>\n <Radix.Primitive.button\n type=\"button\"\n aria-label={title ?? undefined}\n title={title ?? undefined}\n disabled={!playbackId || !requestClip}\n {...clipTriggerProps}\n onClick={composeEventHandlers(\n props.onClick,\n noPropagate(requestClip),\n )}\n ref={forwardedRef}\n data-livepeer-controls-clip-button=\"\"\n data-visible={String(Boolean(clipLength))}\n />\n </Presence>\n );\n },\n);\n\nClipTrigger.displayName = CLIP_TRIGGER_NAME;\n\nexport { ClipTrigger };\nexport type { ClipTriggerProps };\n","import type { MediaControllerStore } from \"@livepeer/core/media\";\nimport type { Scope } from \"@radix-ui/react-context\";\nimport { createContextScope } from \"@radix-ui/react-context\";\nimport { useStore as useStoreZustand } from \"zustand\";\n\nconst MEDIA_NAME = \"Media\";\n\n// biome-ignore lint/complexity/noBannedTypes: allow {}\ntype MediaScopedProps<P = {}> = P & { __scopeMedia?: Scope };\nconst [createMediaContext, createMediaScope] = createContextScope(MEDIA_NAME);\n\ntype MediaContextValue = {\n store: MediaControllerStore;\n};\n\nconst [MediaProvider, useMediaContext] =\n createMediaContext<MediaContextValue>(MEDIA_NAME);\n\nconst useStore = useStoreZustand;\n\nexport { MediaProvider, createMediaScope, useMediaContext, useStore };\nexport type { MediaContextValue, MediaScopedProps };\n","import { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport * as ReactDOM from \"react-dom\";\n\nconst NODES = [\n \"a\",\n \"audio\",\n \"button\",\n \"div\",\n \"form\",\n \"h2\",\n \"h3\",\n \"img\",\n \"input\",\n \"label\",\n \"li\",\n \"nav\",\n \"ol\",\n \"p\",\n \"span\",\n \"svg\",\n \"ul\",\n \"video\",\n] as const;\n\n// Temporary while we await merge of this fix:\n// https://github.com/DefinitelyTyped/DefinitelyTyped/pull/55396\n// biome-ignore lint/suspicious/noExplicitAny: any\ntype PropsWithoutRef<P> = P extends any\n ? \"ref\" extends keyof P\n ? Pick<P, Exclude<keyof P, \"ref\">>\n : P\n : P;\ntype ComponentPropsWithoutRef<T extends React.ElementType> = PropsWithoutRef<\n React.ComponentProps<T>\n>;\n\ntype Primitives = {\n [E in (typeof NODES)[number]]: PrimitiveForwardRefComponent<E>;\n};\ntype PrimitivePropsWithRef<E extends React.ElementType> =\n React.ComponentPropsWithRef<E> & {\n asChild?: boolean;\n };\n\ninterface PrimitiveForwardRefComponent<E extends React.ElementType>\n extends React.ForwardRefExoticComponent<PrimitivePropsWithRef<E>> {}\n\n/* -------------------------------------------------------------------------------------------------\n * Primitive\n * -----------------------------------------------------------------------------------------------*/\n\nconst Primitive = NODES.reduce((primitive, node) => {\n const Node = React.forwardRef(\n // biome-ignore lint/suspicious/noExplicitAny: any\n (props: PrimitivePropsWithRef<typeof node>, forwardedRef: any) => {\n const { asChild, ...primitiveProps } = props;\n // biome-ignore lint/suspicious/noExplicitAny: any\n const Comp: any = asChild ? Slot : node;\n\n React.useEffect(() => {\n // biome-ignore lint/suspicious/noExplicitAny: any\n (window as any)[Symbol.for(\"radix-ui\")] = true;\n }, []);\n\n return <Comp {...primitiveProps} ref={forwardedRef} />;\n },\n );\n\n Node.displayName = `Primitive.${node}`;\n\n // biome-ignore lint/performance/noAccumulatingSpread: no spread\n return { ...primitive, [node]: Node };\n}, {} as Primitives);\n\n/* -------------------------------------------------------------------------------------------------\n * Utils\n * -----------------------------------------------------------------------------------------------*/\n\n/**\n * Flush custom event dispatch\n * https://github.com/radix-ui/primitives/pull/1378\n *\n * React batches *all* event handlers since version 18, this introduces certain considerations when using custom event types.\n *\n * Internally, React prioritizes events in the following order:\n * - discrete\n * - continuous\n * - default\n *\n * https://github.com/facebook/react/blob/a8a4742f1c54493df00da648a3f9d26e3db9c8b5/packages/react-dom/src/events/ReactDOMEventListener.js#L294-L350\n *\n * `discrete` is an important distinction as updates within these events are applied immediately.\n * React however, is not able to infer the priority of custom event types due to how they are detected internally.\n * Because of this, it's possible for updates from custom events to be unexpectedly batched when\n * dispatched by another `discrete` event.\n *\n * In order to ensure that updates from custom events are applied predictably, we need to manually flush the batch.\n * This utility should be used when dispatching a custom event from within another `discrete` event, this utility\n * is not necessary when dispatching known event types, or if dispatching a custom type inside a non-discrete event.\n * For example:\n *\n * dispatching a known click 👎\n * target.dispatchEvent(new Event(‘click’))\n *\n * dispatching a custom type within a non-discrete event 👎\n * onScroll={(event) => event.target.dispatchEvent(new CustomEvent(‘customType’))}\n *\n * dispatching a custom type within a `discrete` event 👍\n * onPointerDown={(event) => dispatchDiscreteCustomEvent(event.target, new CustomEvent(‘customType’))}\n *\n * Note: though React classifies `focus`, `focusin` and `focusout` events as `discrete`, it's not recommended to use\n * this utility with them. This is because it's possible for those handlers to be called implicitly during render\n * e.g. when focus is within a component as it is unmounted, or when managing focus on mount.\n */\n\nfunction dispatchDiscreteCustomEvent<E extends CustomEvent>(\n target: E[\"target\"],\n event: E,\n) {\n if (target) ReactDOM.flushSync(() => target.dispatchEvent(event));\n}\n\n/* -----------------------------------------------------------------------------------------------*/\n\nconst Root = Primitive;\n\nexport {\n Primitive,\n //\n Root,\n //\n dispatchDiscreteCustomEvent,\n};\nexport type { ComponentPropsWithoutRef, PrimitivePropsWithRef };\n","export const noPropagate =\n <\n E extends {\n stopPropagation(): void;\n },\n >(\n // biome-ignore lint/suspicious/noExplicitAny: any\n cb: (...args: any) => any,\n ) =>\n (event: E) => {\n event.stopPropagation();\n\n return cb();\n };\n","\"use client\";\n\nimport { composeEventHandlers } from \"@radix-ui/primitive\";\nimport { Presence } from \"@radix-ui/react-presence\";\nimport React, { useEffect, useMemo } from \"react\";\nimport { useStore } from \"zustand\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { type MediaScopedProps, useMediaContext } from \"../shared/context\";\nimport * as Radix from \"../shared/primitive\";\nimport { noPropagate } from \"../shared/utils\";\n\nconst CONTROLS_NAME = \"Controls\";\n\ntype ControlsElement = React.ElementRef<typeof Radix.Primitive.div>;\n\ninterface ControlsProps\n extends Radix.ComponentPropsWithoutRef<typeof Radix.Primitive.div> {\n /**\n * Used to force mounting when more control is needed. Useful when\n * controlling animation with React animation libraries.\n */\n forceMount?: true;\n\n /**\n * Auto-hide the controls after a mouse or touch interaction (in milliseconds).\n *\n * Defaults to 3000. Set to 0 for no hiding.\n */\n autoHide?: number;\n}\n\nconst Controls = React.forwardRef<ControlsElement, ControlsProps>(\n (props: MediaScopedProps<ControlsProps>, forwardedRef) => {\n const {\n forceMount,\n __scopeMedia,\n onClick,\n style,\n autoHide,\n ...controlsProps\n } = props;\n\n const context = useMediaContext(CONTROLS_NAME, __scopeMedia);\n\n const { hidden, loading, togglePlay, error } = useStore(\n context.store,\n useShallow(({ hidden, loading, __controlsFunctions, error }) => ({\n hidden,\n loading,\n togglePlay: __controlsFunctions.togglePlay,\n error: error?.type ?? null,\n })),\n );\n\n const shown = useMemo(\n () => !hidden && !loading && !error,\n [hidden, loading, error],\n );\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: only set once to prevent flashing\n useEffect(() => {\n if (autoHide !== undefined) {\n context.store.getState().__controlsFunctions.setAutohide(autoHide);\n }\n }, []);\n\n return (\n <Presence present={forceMount || shown}>\n <Radix.Primitive.div\n {...controlsProps}\n ref={forwardedRef}\n data-livepeer-controls=\"\"\n data-visible={String(shown)}\n onClick={composeEventHandlers(onClick, noPropagate(togglePlay))}\n style={{\n ...style,\n // ensures controls expands in ratio\n position: \"absolute\",\n inset: 0,\n }}\n />\n </Presence>\n );\n },\n);\n\nControls.displayName = CONTROLS_NAME;\n\nexport { Controls };\nexport type { ControlsProps };\n","\"use client\";\n\nimport { Presence } from \"@radix-ui/react-presence\";\nimport React, { useMemo } from \"react\";\nimport { useStore } from \"zustand\";\nimport { type MediaScopedProps, useMediaContext } from \"../shared/context\";\nimport * as Radix from \"../shared/primitive\";\n\nconst LIVE_INDICATOR_NAME = \"LiveIndicator\";\n\ntype LiveIndicatorElement = React.ElementRef<typeof Radix.Primitive.button>;\n\ninterface LiveIndicatorProps\n extends Radix.ComponentPropsWithoutRef<typeof Radix.Primitive.button> {\n /**\n * Used to force mounting when more control is needed. Useful when\n * controlling animation with React animation libraries.\n */\n forceMount?: true;\n /** The matcher used to determine whether the element should be shown, given the `live` state (true for live streams, and false for assets). Defaults to `true`. */\n matcher?: boolean | ((live: boolean) => boolean);\n}\n\nconst LiveIndicator = React.forwardRef<\n LiveIndicatorElement,\n LiveIndicatorProps\n>((props: MediaScopedProps<LiveIndicatorProps>, forwardedRef) => {\n const {\n __scopeMedia,\n forceMount,\n matcher = true,\n ...liveIndicatorProps\n } = props;\n\n const context = useMediaContext(LIVE_INDICATOR_NAME, __scopeMedia);\n\n const live = useStore(context.store, ({ live }) => live);\n\n const isPresent = useMemo(\n () => (typeof matcher === \"function\" ? matcher(live) : matcher === live),\n [matcher, live],\n );\n\n return (\n <Presence present={forceMount || isPresent}>\n <Radix.Primitive.span\n aria-label=\"live\"\n {...liveIndicatorProps}\n ref={forwardedRef}\n data-livepeer-controls-live-indicator=\"\"\n data-live={String(Boolean(live))}\n data-visible={String(isPresent)}\n />\n </Presence>\n );\n});\n\nLiveIndicator.displayName = LIVE_INDICATOR_NAME;\n\nexport { LiveIndicator };\nexport type { LiveIndicatorProps };\n","\"use client\";\n\nimport { composeEventHandlers } from \"@radix-ui/primitive\";\n\nimport React from \"react\";\n\nimport { useStore } from \"zustand\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { type MediaScopedProps, useMediaContext } from \"../shared/context\";\nimport * as Radix from \"../shared/primitive\";\nimport { noPropagate } from \"../shared/utils\";\n\nconst MUTE_TRIGGER_NAME = \"MuteTrigger\";\n\ntype MuteTriggerElement = React.ElementRef<typeof Radix.Primitive.button>;\n\ninterface MuteTriggerProps\n extends Radix.ComponentPropsWithoutRef<typeof Radix.Primitive.button> {}\n\nconst MuteTrigger = React.forwardRef<MuteTriggerElement, MuteTriggerProps>(\n (props: MediaScopedProps<MuteTriggerProps>, forwardedRef) => {\n const { __scopeMedia, ...playProps } = props;\n\n const context = useMediaContext(MUTE_TRIGGER_NAME, __scopeMedia);\n\n const { muted, toggleMute } = useStore(\n context.store,\n useShallow(({ __controls, __controlsFunctions }) => ({\n muted: __controls.muted,\n toggleMute: __controlsFunctions.requestToggleMute,\n })),\n );\n\n const title = React.useMemo(\n () => (muted ? \"Unmute (m)\" : \"Mute (m)\"),\n [muted],\n );\n\n return (\n <Radix.Primitive.button\n type=\"button\"\n aria-pressed={muted}\n aria-label={title}\n title={title}\n {...playProps}\n onClick={composeEventHandlers(props.onClick, noPropagate(toggleMute))}\n ref={forwardedRef}\n data-livepeer-controls-mute-trigger=\"\"\n data-muted={String(muted)}\n />\n );\n },\n);\n\nMuteTrigger.displayName = MUTE_TRIGGER_NAME;\n\nexport { MuteTrigger };\nexport type { MuteTriggerProps };\n","\"use client\";\n\nimport { composeEventHandlers } from \"@radix-ui/primitive\";\nimport { Presence } from \"@radix-ui/react-presence\";\nimport React, { useMemo } from \"react\";\nimport { useStore } from \"zustand\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { type MediaScopedProps, useMediaContext } from \"../shared/context\";\nimport * as Radix from \"../shared/primitive\";\nimport { noPropagate } from \"../shared/utils\";\n\n/**\n * PlayPauseTrigger\n */\n\nconst PLAY_PAUSE_TRIGGER_NAME = \"PlayPauseTrigger\";\n\ntype PlayPauseTriggerElement = React.ElementRef<typeof Radix.Primitive.button>;\n\ninterface PlayPauseTriggerProps\n extends Radix.ComponentPropsWithoutRef<typeof Radix.Primitive.button> {}\n\nconst PlayPauseTrigger = React.forwardRef<\n PlayPauseTriggerElement,\n PlayPauseTriggerProps\n>((props: MediaScopedProps<PlayPauseTriggerProps>, forwardedRef) => {\n const { __scopeMedia, ...playProps } = props;\n\n const context = useMediaContext(PLAY_PAUSE_TRIGGER_NAME, __scopeMedia);\n\n const { playing, togglePlay, title } = useStore(\n context.store,\n useShallow(({ playing, __controlsFunctions, aria }) => ({\n playing,\n togglePlay: __controlsFunctions.togglePlay,\n title: aria.playPause,\n })),\n );\n\n return (\n <Radix.Primitive.button\n type=\"button\"\n aria-pressed={playing}\n aria-label={title ?? undefined}\n title={title ?? undefined}\n {...playProps}\n onClick={composeEventHandlers(props.onClick, noPropagate(togglePlay))}\n ref={forwardedRef}\n data-livepeer-controls-play-pause-trigger=\"\"\n data-playing={String(playing)}\n />\n );\n});\n\nPlayPauseTrigger.displayName = PLAY_PAUSE_TRIGGER_NAME;\n\n/**\n * PlayingIndicator\n */\n\nconst PLAYING_INDICATOR_NAME = \"PlayingIndicator\";\n\ntype PlayingIndicatorElement = React.ElementRef<typeof Radix.Primitive.div>;\n\ninterface PlayingIndicatorProps\n extends Radix.ComponentPropsWithoutRef<typeof Radix.Primitive.div> {\n /**\n * Used to force mounting when more control is needed. Useful when\n * controlling animation with React animation libraries.\n */\n forceMount?: true;\n /** The matcher used to determine whether the element should be shown, given the playing state. Defaults to `true`, which equals playing. */\n matcher?: boolean | ((state: boolean) => boolean);\n}\n\nconst PlayingIndicator = React.forwardRef<\n PlayingIndicatorElement,\n PlayingIndicatorProps\n>((props: MediaScopedProps<PlayingIndicatorProps>, forwardedRef) => {\n const {\n __scopeMedia,\n forceMount,\n matcher = true,\n ...playPauseIndicatorProps\n } = props;\n\n const context = useMediaContext(PLAYING_INDICATOR_NAME, __scopeMedia);\n\n const playing = useStore(\n context.store,\n useShallow(({ playing }) => playing),\n );\n\n const isPresent = useMemo(\n () =>\n typeof matcher === \"boolean\" ? matcher === playing : matcher(playing),\n [playing, matcher],\n );\n\n return (\n <Presence present={forceMount || isPresent}>\n <Radix.Primitive.div\n {...playPauseIndicatorProps}\n ref={forwardedRef}\n data-livepeer-controls-play-pause-indicator=\"\"\n data-playing={String(playing)}\n data-visible={String(isPresent)}\n />\n </Presence>\n );\n});\n\nPlayingIndicator.displayName = PLAYING_INDICATOR_NAME;\n\nexport { PlayPauseTrigger, PlayingIndicator };\nexport type { PlayPauseTriggerProps, PlayingIndicatorProps };\n","\"use client\";\n\nimport type { PlaybackEvent } from \"@livepeer/core/media\";\nimport {\n addLegacyMediaMetricsToStore,\n addMetricsToStore,\n createControllerStore,\n type InitialProps,\n type Src,\n} from \"@livepeer/core/media\";\nimport { createStorage, noopStorage } from \"@livepeer/core/storage\";\nimport { version } from \"@livepeer/core/version\";\nimport { getDeviceInfo } from \"@livepeer/core-web/browser\";\nimport React, { type PropsWithChildren, useEffect, useRef } from \"react\";\nimport { MediaProvider, type MediaScopedProps } from \"../shared/context\";\n\ninterface PlayerProps\n extends PropsWithChildren<\n Omit<Partial<InitialProps>, \"creatorId\" | \"hotkeys\">\n > {\n /**\n * The source for the Player. The `Src[]` can be created from calling `getSrc`\n * with Livepeer playback info, Cloudflare stream data, Mux URLs, `string[]`, or `string`.\n */\n src: Src[] | null;\n\n /**\n * Sets a custom playback ID for playback.\n * If not specified, the function defaults to parsing the `src` attribute of the HTMLMediaElement to get the playback ID.\n */\n playbackId?: string;\n\n /**\n * The aspect ratio of the media. Defaults to 16 / 9.\n * This significantly improves cumulative layout shift.\n * Set to `null` to render a plain div primitive.\n *\n * @see {@link https://web.dev/cls/}\n */\n aspectRatio?: number | null;\n\n /**\n * Whether hotkeys are enabled. Defaults to `true`. Allows users to use keyboard shortcuts for player control.\n *\n * This is highly recommended to adhere to ARIA guidelines.\n */\n hotkeys?: boolean;\n\n /**\n * A callback that is called when the player's metrics events are emitted.\n * This can be used to integrate with other analytics providers.\n */\n // biome-ignore lint/suspicious/noExplicitAny: allow any for incoming callback\n onPlaybackEvents?: (events: PlaybackEvent[]) => Promise<any> | any;\n\n /**\n * The interval at which metrics are sent, in ms. Defaults to 5000.\n */\n metricsInterval?: number;\n\n /**\n * The ICE servers to use.\n *\n * If not provided, the default ICE servers will be used.\n */\n iceServers?: RTCIceServer | RTCIceServer[];\n}\n\nconst Player = React.memo((props: MediaScopedProps<PlayerProps>) => {\n const {\n aspectRatio = 16 / 9,\n src,\n children,\n jwt,\n accessKey,\n storage,\n onPlaybackEvents,\n metricsInterval,\n playbackId,\n ...rest\n } = props;\n\n const store = useRef(\n createControllerStore({\n device: getDeviceInfo(version.react),\n storage:\n storage ??\n createStorage(\n storage !== null && typeof window !== \"undefined\"\n ? {\n storage: window.localStorage,\n }\n : {\n storage: noopStorage,\n },\n ),\n src,\n playbackId,\n initialProps: {\n aspectRatio,\n jwt,\n accessKey,\n ...rest,\n },\n }),\n );\n\n useEffect(() => {\n if (jwt) {\n store?.current?.store.setState((prev) => ({\n __initialProps: {\n ...prev.__initialProps,\n jwt,\n },\n }));\n }\n }, [jwt]);\n\n useEffect(() => {\n if (accessKey) {\n store?.current?.store.setState((prev) => ({\n __initialProps: {\n ...prev.__initialProps,\n accessKey,\n },\n }));\n }\n }, [accessKey]);\n\n useEffect(() => {\n return () => {\n store?.current?.destroy?.();\n };\n }, []);\n\n useEffect(() => {\n const metrics = addLegacyMediaMetricsToStore(store.current.store);\n\n return () => {\n metrics.destroy();\n };\n }, []);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: no deps\n useEffect(() => {\n const metrics = addMetricsToStore(store.current.store, {\n onPlaybackEvents,\n interval: metricsInterval,\n });\n\n return () => {\n metrics.destroy();\n };\n }, []);\n\n return (\n <MediaProvider store={store.current.store} scope={props.__scopeMedia}>\n {children}\n </MediaProvider>\n );\n});\n\nPlayer.displayName = \"Player\";\n\nconst Root = Player;\n\nexport { Root };\nexport type { PlayerProps };\n","\"use client\";\n\nimport { Presence } from \"@radix-ui/react-presence\";\nimport React from \"react\";\nimport { useStore } from \"zustand\";\nimport { type MediaScopedProps, useMediaContext } from \"../shared/context\";\nimport * as Radix from \"../shared/primitive\";\n\nconst POSTER_NAME = \"Poster\";\n\ntype PosterElement = React.ElementRef<typeof Radix.Primitive.img>;\n\ninterface PosterProps\n extends Radix.ComponentPropsWithoutRef<typeof Radix.Primitive.img> {\n /**\n * Used to force mounting when more control is needed. Useful when\n * controlling animation with React animation libraries.\n */\n forceMount?: true;\n}\n\nconst Poster = React.forwardRef<PosterElement, PosterProps>(\n (props: MediaScopedProps<PosterProps>, forwardedRef) => {\n const { __scopeMedia, forceMount, src, ...posterProps } = props;\n\n const context = useMediaContext(POSTER_NAME, __scopeMedia);\n\n const poster = useStore(context.store, ({ poster }) => poster);\n\n return (\n <Presence present={forceMount || Boolean(src || poster)}>\n <Radix.Primitive.img\n alt=\"Poster for video\"\n aria-hidden=\"true\"\n {...posterProps}\n src={src || poster || undefined}\n ref={forwardedRef}\n data-livepeer-poster=\"\"\n data-visible={String(Boolean(src || poster))}\n />\n </Presence>\n );\n },\n);\n\nPoster.displayName = POSTER_NAME;\n\nexport { Poster };\nexport type { PosterProps };\n","\"use client\";\n\nimport type { PlaybackRate } from \"@livepeer/core/media\";\n// biome-ignore lint/correctness/noUnusedImports: ignored using `--suppress`\nimport { composeEventHandlers } from \"@radix-ui/primitive\";\nimport React, { useCallback } from \"react\";\nimport { useStore } from \"zustand\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { type MediaScopedProps, useMediaContext } from \"../shared/context\";\nimport type * as Radix from \"../shared/primitive\";\nimport * as SelectPrimitive from \"../shared/Select\";\n\n/**\n * RateSelect\n */\n\nconst RATE_SELECT_NAME = \"RateSelect\";\n\ninterface RateSelectProps\n extends Radix.ComponentPropsWithoutRef<typeof SelectPrimitive.SelectRoot> {}\n\nconst RateSelect = (props: MediaScopedProps<RateSelectProps>) => {\n // biome-ignore lint/correctness/noUnusedVariables: ignored using `--suppress`\n const { __scopeMedia, defaultValue, ...rateSelectProps } = props;\n\n const context = useMediaContext(RATE_SELECT_NAME, __scopeMedia);\n\n const { playbackRate, setPlaybackRate } = useStore(\n context.store,\n useShallow(({ playbackRate, __controlsFunctions }) => ({\n playbackRate,\n setPlaybackRate: __controlsFunctions.setPlaybackRate,\n })),\n );\n\n const onValueChangeComposed = useCallback(\n (value: string) => {\n setPlaybackRate(value as PlaybackRate);\n props.onValueChange?.(value);\n },\n [props.onValueChange, setPlaybackRate],\n );\n\n return (\n <SelectPrimitive.SelectRoot\n {...rateSelectProps}\n value={playbackRate === \"constant\" ? \"constant\" : playbackRate.toFixed(2)}\n onValueChange={onValueChangeComposed}\n data-livepeer-rate-select=\"\"\n data-rate={String(playbackRate)}\n />\n );\n};\n\nRateSelect.displayName = RATE_SELECT_NAME;\n\n/**\n * RateSelectItem\n */\n\nconst RATE_SELECT_ITEM_NAME = \"RateSelectItem\";\n\ntype RateSelectItemElement = React.ElementRef<\n typeof SelectPrimitive.SelectItem\n>;\n\ninterface RateSelectItemProps\n extends Omit<\n Radix.ComponentPropsWithoutRef<typeof SelectPrimitive.SelectItem>,\n \"value\"\n > {\n /**\n * The numerical value of the rate select item. This must be provided and must be a number or `constant`,\n * which indicates a constant playback rate.\n */\n value: PlaybackRate;\n}\n\nconst RateSelectItem = React.forwardRef<\n RateSelectItemElement,\n RateSelectItemProps\n>((props: MediaScopedProps<RateSelectItemProps>, forwardedRef) => {\n const { __scopeMedia, value, ...rateSelectItemProps } = props;\n\n return (\n <SelectPrimitive.SelectItem\n {...rateSelectItemProps}\n ref={forwardedRef}\n value={Number(value).toFixed(2)}\n data-livepeer-rate-select-item=\"\"\n />\n );\n});\n\nRateSelectItem.displayName = RATE_SELECT_ITEM_NAME;\n\nexport { RateSelect, RateSelectItem };\nexport type { RateSelectItemProps, RateSelectProps };\n","\"use client\";\n\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\n\nconst SelectRoot = SelectPrimitive.Root;\nconst SelectTrigger = SelectPrimitive.SelectTrigger;\nconst SelectValue = SelectPrimitive.SelectValue;\nconst SelectIcon = SelectPrimitive.SelectIcon;\nconst SelectPortal = SelectPrimitive.SelectPortal;\nconst SelectContent = SelectPrimitive.SelectContent;\nconst SelectViewport = SelectPrimitive.SelectViewport;\nconst SelectGroup = SelectPrimitive.SelectGroup;\nconst SelectLabel = SelectPrimitive.SelectLabel;\nconst SelectItem = SelectPrimitive.SelectItem;\nconst SelectItemText = SelectPrimitive.SelectItemText;\nconst SelectItemIndicator = SelectPrimitive.SelectItemIndicator;\nconst SelectScrollUpButton = SelectPrimitive.SelectScrollUpButton;\nconst SelectScrollDownButton = SelectPrimitive.SelectScrollDownButton;\nconst SelectSeparator = SelectPrimitive.SelectSeparator;\nconst SelectArrow = SelectPrimitive.SelectArrow;\n\ntype SelectProps = SelectPrimitive.SelectProps;\ntype SelectTriggerProps = SelectPrimitive.SelectTriggerProps;\ntype SelectValueProps = SelectPrimitive.SelectValueProps;\ntype SelectIconProps = SelectPrimitive.SelectIconProps;\ntype SelectPortalProps = SelectPrimitive.SelectPortalProps;\ntype SelectContentProps = SelectPrimitive.SelectContentProps;\ntype SelectViewportProps = SelectPrimitive.SelectViewportProps;\ntype SelectGroupProps = SelectPrimitive.SelectGroupProps;\ntype SelectLabelProps = SelectPrimitive.SelectLabelProps;\ntype SelectItemProps = SelectPrimitive.SelectItemProps;\ntype SelectItemTextProps = SelectPrimitive.SelectItemTextProps;\ntype SelectItemIndicatorProps = SelectPrimitive.SelectItemIndicatorProps;\ntype SelectScrollUpButtonProps = SelectPrimitive.SelectScrollUpButtonProps;\ntype SelectScrollDownButtonProps = SelectPrimitive.SelectScrollDownButtonProps;\ntype SelectSeparatorProps = SelectPrimitive.SelectSeparatorProps;\ntype SelectArrowProps = SelectPrimitive.SelectArrowProps;\n\nexport {\n SelectArrow,\n SelectContent,\n SelectGroup,\n SelectIcon,\n SelectItem,\n SelectItemIndicator,\n SelectItemText,\n SelectLabel,\n SelectPortal,\n SelectRoot,\n SelectScrollDownButton,\n SelectScrollUpButton,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n SelectViewport,\n};\n\nexport type {\n SelectArrowProps,\n SelectContentProps,\n SelectGroupProps,\n SelectIconProps,\n SelectItemIndicatorProps,\n SelectItemProps,\n SelectItemTextProps,\n SelectLabelProps,\n SelectPortalProps,\n SelectProps,\n SelectScrollDownButtonProps,\n SelectScrollUpButtonProps,\n SelectSeparatorProps,\n SelectTriggerProps,\n SelectValueProps,\n SelectViewportProps,\n};\n","\"use client\";\n\n// biome-ignore lint/correctness/noUnusedImports: ignored using `--suppress`\nimport { composeEventHandlers } from \"@radix-ui/primitive\";\nimport { Presence } from \"@radix-ui/react-presence\";\n\nimport React, { useCallback } from \"react\";\n\nimport { useStore } from \"zustand\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { type MediaScopedProps, useMediaContext } from \"../shared/context\";\nimport type * as Radix from \"../shared/primitive\";\nimport * as SliderPrimitive from \"../shared/Slider\";\nimport { noPropagate } from \"../shared/utils\";\n\nconst SEEK_NAME = \"Seek\";\n\ntype SeekElement = React.ElementRef<typeof Radix.Primitive.button>;\n\ninterface SeekProps\n extends Radix.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> {\n /**\n * Used to force mounting when more control is needed. Useful when\n * controlling animation with React animation libraries.\n */\n forceMount?: true;\n}\n\nconst Seek = React.forwardRef<SeekElement, SeekProps>(\n (props: MediaScopedProps<SeekProps>, forwardedRef) => {\n const { __scopeMedia, forceMount, style, ...seekProps } = props;\n\n const context = useMediaContext(SEEK_NAME, __scopeMedia);\n\n const {\n ariaProgress,\n duration,\n buffered,\n bufferedPercent,\n progress,\n live,\n seek,\n } = useStore(\n context.store,\n useShallow(\n ({\n aria,\n duration,\n buffered,\n bufferedPercent,\n progress,\n live,\n __controlsFunctions,\n }) => ({\n ariaProgress: aria.progress,\n duration,\n buffered,\n bufferedPercent,\n progress,\n live,\n seek: __controlsFunctions.requestSeek,\n }),\n ),\n );\n\n const onValueChange = React.useCallback(\n ([value]: number[]) => seek(value),\n [seek],\n );\n const onValueCommit = React.useCallback(\n ([value]: number[]) => seek(value),\n [seek],\n );\n\n const onValueChangeComposed = useCallback(\n (value: number[]) => {\n if (props.onValueChange) {\n props.onValueChange(value);\n }\n onValueChange(value);\n },\n [props.onValueChange, onValueChange],\n );\n\n const onValueCommitComposed = useCallback(\n (value: number[]) => {\n if (props.onValueCommit) {\n props.onValueCommit(value);\n }\n onValueCommit(value);\n },\n [props.onValueCommit, onValueCommit],\n );\n\n return (\n <Presence present={forceMount || !live}>\n <SliderPrimitive.Root\n aria-label={live ? \"Live Seek Slider\" : \"Video Seek Slider\"}\n aria-valuetext={ariaProgress ?? undefined}\n step={0.1}\n max={duration}\n value={[progress]}\n role=\"slider\"\n {...seekProps}\n onValueChange={onValueChangeComposed}\n onValueCommit={onValueCommitComposed}\n onClick={noPropagate(() => {})}\n ref={forwardedRef}\n data-livepeer-controls-seek=\"\"\n data-duration={duration}\n data-progress={progress}\n data-live={String(live)}\n data-buffered={buffered}\n data-visible={String(!live)}\n style={{\n // biome-ignore lint/suspicious/noExplicitAny: player container css var\n [\"--livepeer-player-buffering-width\" as any]: `${\n bufferedPercent ?? 0\n }%`,\n ...style,\n }}\n />\n </Presence>\n );\n },\n);\n\nSeek.displayName = SEEK_NAME;\n\nconst SEEK_BUFFER_NAME = \"SeekBuffer\";\n\ntype SeekBufferElement = React.ElementRef<typeof SliderPrimitive.Track>;\n\ninterface SeekBufferProps\n extends Radix.ComponentPropsWithoutRef<typeof SliderPrimitive.Track> {}\n\nconst SeekBuffer = React.forwardRef<SeekBufferElement, SeekBufferProps>(\n (props: MediaScopedProps<SeekBufferProps>, forwardedRef) => {\n const { __scopeMedia, style, ...bufferProps } = props;\n\n const context = useMediaContext(SEEK_BUFFER_NAME, __scopeMedia);\n\n const { bufferedPercent, buffered } = useStore(\n context.store,\n useShallow(({ bufferedPercent, buffered }) => ({\n buffered,\n bufferedPercent,\n })),\n );\n\n return (\n <SliderPrimitive.Track\n {...bufferProps}\n ref={forwardedRef}\n style={{\n left: 0,\n right: `${100 - (bufferedPercent ?? 0)}%`,\n ...style,\n }}\n data-livepeer-controls-seek-buffer=\"\"\n data-buffered={buffered}\n />\n );\n },\n);\n\nSeekBuffer.displayName = SEEK_BUFFER_NAME;\n\nexport { Seek, SeekBuffer };\nexport type { SeekBufferProps, SeekProps };\n","\"use client\";\n\nimport * as SliderPrimitive from \"@radix-ui/react-slider\";\n\ntype SliderProps = SliderPrimitive.SliderProps;\nconst Root = SliderPrimitive.Root;\ntype TrackProps = SliderPrimitive.SliderTrackProps;\nconst Track = SliderPrimitive.Track;\ntype RangeProps = SliderPrimitive.SliderRangeProps;\nconst Range = SliderPrimitive.Range;\ntype ThumbProps = SliderPrimitive.SliderThumbProps;\nconst Thumb = SliderPrimitive.Thumb;\n\nexport { Range, Root, Thumb, Track };\nexport type { RangeProps, SliderProps, ThumbProps, TrackProps };\n","\"use client\";\n\nimport { addEventListeners, type HlsConfig } from \"@livepeer/core-web/browser\";\nimport { useComposedRefs } from \"@radix-ui/react-compose-refs\";\nimport React, { useEffect } from \"react\";\nimport { useStore } from \"zustand\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { type MediaScopedProps, useMediaContext } from \"../shared/context\";\nimport * as Radix from \"../shared/primitive\";\n\nconst VIDEO_NAME = \"Video\";\n\ntype OmittedProps = \"src\" | \"poster\" | \"autoPlay\" | \"preload\";\n\ntype VideoElement = React.ElementRef<typeof Radix.Primitive.video>;\n\ninterface VideoProps\n extends Omit<\n Radix.ComponentPropsWithoutRef<typeof Radix.Primitive.video>,\n OmittedProps\n > {\n /**\n * Controls the poster source, which by default uses the thumbnail from the Src input.\n *\n * Set to null to disable the default poster image from the Src.\n */\n poster?: string | null;\n\n /**\n * Configures the HLS.js options, for advanced usage of the Player.\n */\n hlsConfig?: HlsConfig;\n}\n\nconst Video = React.forwardRef<VideoElement, VideoProps>(\n (props: MediaScopedProps<VideoProps>, forwardedRef) => {\n const { __scopeMedia, style, poster, hlsConfig, title, ...videoProps } =\n props;\n\n const context = useMediaContext(VIDEO_NAME, __scopeMedia);\n\n const ref = React.useRef<VideoElement | null>(null);\n const composedRefs = useComposedRefs(forwardedRef, ref);\n\n const {\n currentSource,\n setMounted,\n autoPlay,\n preload,\n thumbnailPoster,\n volume,\n requestToggleMute,\n } = useStore(\n context.store,\n useShallow(\n ({\n __controlsFunctions,\n __initialProps,\n currentSource,\n live,\n poster,\n volume,\n }) => ({\n autoPlay: __initialProps.autoPlay,\n currentSource,\n live,\n preload: __initialProps.preload,\n setMounted: __controlsFunctions.setMounted,\n thumbnailPoster: poster,\n volume,\n requestToggleMute: __controlsFunctions.requestToggleMute,\n }),\n ),\n );\n\n useEffect(() => {\n if (ref.current) {\n const { destroy } = addEventListeners(ref.current, context.store);\n\n return destroy;\n }\n }, [context?.store]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: only set once to prevent flashing\n useEffect(() => {\n if (hlsConfig) {\n context.store.getState().__controlsFunctions.setHlsConfig(hlsConfig);\n }\n }, []);\n\n useEffect(() => {\n // we run this on mount to initialize playback\n setMounted();\n }, [setMounted]);\n\n useEffect(() => {\n if (typeof videoProps.muted !== \"undefined\") {\n requestToggleMute(videoProps.muted);\n }\n }, [videoProps.muted, requestToggleMute]);\n\n return (\n <Radix.Primitive.video\n playsInline\n poster={\n poster === null ? undefined : (poster ?? thumbnailPoster ?? undefined)\n }\n muted={volume === 0}\n {...videoProps}\n aria-label={title ?? videoProps[\"aria-label\"]}\n autoPlay={autoPlay}\n preload={preload}\n ref={composedRefs}\n data-livepeer-video=\"\"\n data-livepeer-source-type={currentSource?.type ?? \"none\"}\n style={{\n ...style,\n // ensures video expands in ratio\n position: \"absolute\",\n inset: 0,\n }}\n />\n );\n },\n);\n\nVideo.displayName = VIDEO_NAME;\n\nexport { Video };\nexport type { VideoProps };\n","\"use client\";\n\nimport type { VideoQuality } from \"@livepeer/core/media\";\n// biome-ignore lint/correctness/noUnusedImports: ignored using `--suppress`\nimport { composeEventHandlers } from \"@radix-ui/primitive\";\nimport React, { useCallback } from \"react\";\nimport { useStore } from \"zustand\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { type MediaScopedProps, useMediaContext } from \"../shared/context\";\nimport type * as Radix from \"../shared/primitive\";\nimport * as SelectPrimitive from \"../shared/Select\";\n\n/**\n * VideoQualitySelect\n */\n\nconst VIDEO_QUALITY_SELECT_NAME = \"VideoQualitySelect\";\n\ninterface VideoQualitySelectProps\n extends Radix.ComponentPropsWithoutRef<typeof SelectPrimitive.SelectRoot> {}\n\nconst VideoQualitySelect = (\n props: MediaScopedProps<VideoQualitySelectProps>,\n) => {\n // biome-ignore lint/correctness/noUnusedVariables: ignored using `--suppress`\n const { __scopeMedia, defaultValue, ...videoQualitySelectProps } = props;\n\n const context = useMediaContext(VIDEO_QUALITY_SELECT_NAME, __scopeMedia);\n\n const { videoQuality, setVideoQuality } = useStore(\n context.store,\n useShallow(({ videoQuality, __controlsFunctions }) => ({\n videoQuality,\n setVideoQuality: __controlsFunctions.setVideoQuality,\n })),\n );\n\n const onValueChangeComposed = useCallback(\n (value: string) => {\n if (props.onValueChange) {\n props.onValueChange(value);\n }\n setVideoQuality(value as VideoQuality);\n },\n [props.onValueChange, setVideoQuality],\n );\n\n return (\n <SelectPrimitive.SelectRoot\n {...videoQualitySelectProps}\n value={videoQuality}\n onValueChange={onValueChangeComposed}\n data-livepeer-quality-select=\"\"\n data-video-quality={String(videoQuality)}\n />\n );\n};\n\nVideoQualitySelect.displayName = VIDEO_QUALITY_SELECT_NAME;\n\n/**\n * VideoQualitySelectItem\n */\n\nconst VIDEO_QUALITY_SELECT_ITEM_NAME = \"VideoQualitySelectItem\";\n\ntype VideoQualitySelectItemElement = React.ElementRef<\n typeof SelectPrimitive.SelectItem\n>;\n\ninterface VideoQualitySelectItemProps\n extends Omit<\n Radix.ComponentPropsWithoutRef<typeof SelectPrimitive.SelectItem>,\n \"value\"\n > {\n /**\n * The numerical value of the quality select item. This must be provided.\n */\n value: VideoQuality;\n}\n\nconst VideoQualitySelectItem = React.forwardRef<\n VideoQualitySelectItemElement,\n VideoQualitySelectItemProps\n>((props: MediaScopedProps<VideoQualitySelectItemProps>, forwardedRef) => {\n const { __scopeMedia, ...videoQualitySelectItemProps } = props;\n\n return (\n <SelectPrimitive.SelectItem\n {...videoQualitySelectItemProps}\n ref={forwardedRef}\n data-livepeer-quality-select-item=\"\"\n />\n );\n});\n\nVideoQualitySelectItem.displayName = VIDEO_QUALITY_SELECT_ITEM_NAME;\n\nexport { VideoQualitySelect, VideoQualitySelectItem };\nexport type { VideoQualitySelectItemProps, VideoQualitySelectProps };\n","\"use client\";\n\nimport { warn } from \"@livepeer/core/utils\";\n// biome-ignore lint/correctness/noUnusedImports: ignored using `--suppress`\nimport { composeEventHandlers } from \"@radix-ui/primitive\";\nimport { Presence } from \"@radix-ui/react-presence\";\nimport React, { useCallback, useEffect, useMemo } from \"react\";\nimport { useStore } from \"zustand\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { type MediaScopedProps, useMediaContext } from \"../shared/context\";\nimport * as Radix from \"../shared/primitive\";\nimport * as SliderPrimitive from \"../shared/Slider\";\nimport { noPropagate } from \"../shared/utils\";\n\n/**\n * Volume\n */\n\nconst VOLUME_NAME = \"Volume\";\n\ntype VolumeElement = React.ElementRef<typeof Radix.Primitive.button>;\n\ninterface VolumeProps\n extends Radix.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> {\n /**\n * Used to force mounting when more control is needed. Useful when\n * controlling animation with React animation libraries.\n */\n forceMount?: true;\n}\n\nconst Volume = React.forwardRef<VolumeElement, VolumeProps>(\n (props: MediaScopedProps<VolumeProps>, forwardedRef) => {\n const { __scopeMedia, forceMount, ...volumeProps } = props;\n\n const context = useMediaContext(VOLUME_NAME, __scopeMedia);\n\n const { volume, requestVolume, isVolumeChangeSupported } = useStore(\n context.store,\n useShallow(({ volume, __controlsFunctions, __device }) => ({\n volume,\n requestVolume: __controlsFunctions.requestVolume,\n isVolumeChangeSupported: __device.isVolumeChangeSupported,\n })),\n );\n\n const onValueChange = React.useCallback(\n ([value]: number[]) => requestVolume(value),\n [requestVolume],\n );\n const onValueCommit = React.useCallback(\n ([value]: number[]) => requestVolume(value),\n [requestVolume],\n );\n\n const onValueChangeComposed = useCallback(\n (value: number[]) => {\n if (props.onValueChange) {\n props.onValueChange(value);\n }\n onValueChange(value);\n },\n [props.onValueChange, onValueChange],\n );\n\n const onValueCommitComposed = useCallback(\n (value: number[]) => {\n if (props.onValueCommit) {\n props.onValueCommit(value);\n }\n onValueCommit(value);\n },\n [props.onValueCommit, onValueCommit],\n );\n\n return (\n <Presence present={forceMount || isVolumeChangeSupported}>\n <SliderPrimitive.Root\n aria-label=\"Volume Slider\"\n step={0.01}\n max={1}\n value={[volume]}\n {...volumeProps}\n onClick={noPropagate(() => {})}\n onValueChange={onValueChangeComposed}\n onValueCommit={onValueCommitComposed}\n ref={forwardedRef}\n data-livepeer-controls-volume=\"\"\n data-livepeer-muted={String(volume === 0)}\n data-livepeer-volume={String((100 * volume).toFixed(0))}\n data-visible={String(Boolean(isVolumeChangeSupported))}\n />\n </Presence>\n );\n },\n);\n\nVolume.displayName = VOLUME_NAME;\n\n/**\n * VolumeIndicator\n */\n\nconst VOLUME_INDICATOR_NAME = \"VolumeIndicator\";\n\ntype VolumeIndicatorElement = React.ElementRef<typeof Radix.Primitive.div>;\n\ninterface VolumeIndicatorProps\n extends Radix.ComponentPropsWithoutRef<typeof Radix.Primitive.div> {\n /**\n * Used to force mounting when more control is needed. Useful when\n * controlling animation with React animation libraries.\n */\n forceMount?: true;\n /** The matcher used to determine whether the element should be shown, given the volume. Defaults to match `false`, which is muted. */\n matcher?: boolean | ((volume: number) => boolean);\n}\n\nconst VolumeIndicator = React.forwardRef<\n VolumeIndicatorElement,\n VolumeIndicatorProps\n>((props: MediaScopedProps<VolumeIndicatorProps>, for