@livepeer/react
Version:
React primitives for video apps.
1,253 lines (1,226 loc) • 40.6 kB
JavaScript
"use client";
// src/player/ClipTrigger.tsx
import { composeEventHandlers } from "@radix-ui/primitive";
import { Presence } from "@radix-ui/react-presence";
import React2, { useEffect as useEffect2 } from "react";
import { useStore as useStore2 } from "zustand";
import { useShallow } from "zustand/react/shallow";
// src/shared/context.tsx
import { createContextScope } from "@radix-ui/react-context";
import { useStore as useStoreZustand } from "zustand";
var MEDIA_NAME = "Media";
var [createMediaContext, createMediaScope] = createContextScope(MEDIA_NAME);
var [MediaProvider, useMediaContext] = createMediaContext(MEDIA_NAME);
var useStore = useStoreZustand;
// src/shared/primitive.tsx
import { Slot } from "@radix-ui/react-slot";
import * as React from "react";
import * as ReactDOM from "react-dom";
var NODES = [
"a",
"audio",
"button",
"div",
"form",
"h2",
"h3",
"img",
"input",
"label",
"li",
"nav",
"ol",
"p",
"span",
"svg",
"ul",
"video"
];
var Primitive = NODES.reduce((primitive, node) => {
const Node = React.forwardRef(
// biome-ignore lint/suspicious/noExplicitAny: any
(props, forwardedRef) => {
const { asChild, ...primitiveProps } = props;
const Comp = asChild ? Slot : node;
React.useEffect(() => {
window[Symbol.for("radix-ui")] = true;
}, []);
return /* @__PURE__ */ React.createElement(Comp, { ...primitiveProps, ref: forwardedRef });
}
);
Node.displayName = `Primitive.${node}`;
return { ...primitive, [node]: Node };
}, {});
// src/shared/utils.ts
var noPropagate = (cb) => (event) => {
event.stopPropagation();
return cb();
};
// src/player/ClipTrigger.tsx
var CLIP_TRIGGER_NAME = "ClipTrigger";
var ClipTrigger = React2.forwardRef(
(props, forwardedRef) => {
const { __scopeMedia, forceMount, onClip, ...clipTriggerProps } = props;
const context = useMediaContext(CLIP_TRIGGER_NAME, __scopeMedia);
const { clipLength, requestClip, playbackId, title } = useStore2(
context.store,
useShallow(
({ __controls, __controlsFunctions, aria, __initialProps }) => ({
requestClip: __controlsFunctions.requestClip,
playbackId: __controls.playbackId,
clipLength: __initialProps.clipLength,
title: aria.clip
})
)
);
useEffect2(() => {
if (playbackId) {
return context.store.subscribe(
(state) => state.__controls.requestedClipParams,
(params) => {
if (params) {
onClip({ playbackId, ...params });
}
}
);
}
}, [playbackId]);
return /* @__PURE__ */ React2.createElement(Presence, { present: forceMount || Boolean(clipLength) }, /* @__PURE__ */ React2.createElement(
Primitive.button,
{
type: "button",
"aria-label": title ?? void 0,
title: title ?? void 0,
disabled: !playbackId || !requestClip,
...clipTriggerProps,
onClick: composeEventHandlers(
props.onClick,
noPropagate(requestClip)
),
ref: forwardedRef,
"data-livepeer-controls-clip-button": "",
"data-visible": String(Boolean(clipLength))
}
));
}
);
ClipTrigger.displayName = CLIP_TRIGGER_NAME;
// src/player/Controls.tsx
import { composeEventHandlers as composeEventHandlers2 } from "@radix-ui/primitive";
import { Presence as Presence2 } from "@radix-ui/react-presence";
import React3, { useEffect as useEffect3, useMemo } from "react";
import { useStore as useStore3 } from "zustand";
import { useShallow as useShallow2 } from "zustand/react/shallow";
var CONTROLS_NAME = "Controls";
var Controls = React3.forwardRef(
(props, forwardedRef) => {
const {
forceMount,
__scopeMedia,
onClick,
style,
autoHide,
...controlsProps
} = props;
const context = useMediaContext(CONTROLS_NAME, __scopeMedia);
const { hidden, loading, togglePlay, error } = useStore3(
context.store,
useShallow2(({ hidden: hidden2, loading: loading2, __controlsFunctions, error: error2 }) => ({
hidden: hidden2,
loading: loading2,
togglePlay: __controlsFunctions.togglePlay,
error: error2?.type ?? null
}))
);
const shown = useMemo(
() => !hidden && !loading && !error,
[hidden, loading, error]
);
useEffect3(() => {
if (autoHide !== void 0) {
context.store.getState().__controlsFunctions.setAutohide(autoHide);
}
}, []);
return /* @__PURE__ */ React3.createElement(Presence2, { present: forceMount || shown }, /* @__PURE__ */ React3.createElement(
Primitive.div,
{
...controlsProps,
ref: forwardedRef,
"data-livepeer-controls": "",
"data-visible": String(shown),
onClick: composeEventHandlers2(onClick, noPropagate(togglePlay)),
style: {
...style,
// ensures controls expands in ratio
position: "absolute",
inset: 0
}
}
));
}
);
Controls.displayName = CONTROLS_NAME;
// src/player/LiveIndicator.tsx
import { Presence as Presence3 } from "@radix-ui/react-presence";
import React4, { useMemo as useMemo2 } from "react";
import { useStore as useStore4 } from "zustand";
var LIVE_INDICATOR_NAME = "LiveIndicator";
var LiveIndicator = React4.forwardRef((props, forwardedRef) => {
const {
__scopeMedia,
forceMount,
matcher = true,
...liveIndicatorProps
} = props;
const context = useMediaContext(LIVE_INDICATOR_NAME, __scopeMedia);
const live = useStore4(context.store, ({ live: live2 }) => live2);
const isPresent = useMemo2(
() => typeof matcher === "function" ? matcher(live) : matcher === live,
[matcher, live]
);
return /* @__PURE__ */ React4.createElement(Presence3, { present: forceMount || isPresent }, /* @__PURE__ */ React4.createElement(
Primitive.span,
{
"aria-label": "live",
...liveIndicatorProps,
ref: forwardedRef,
"data-livepeer-controls-live-indicator": "",
"data-live": String(Boolean(live)),
"data-visible": String(isPresent)
}
));
});
LiveIndicator.displayName = LIVE_INDICATOR_NAME;
// src/player/MuteTrigger.tsx
import { composeEventHandlers as composeEventHandlers3 } from "@radix-ui/primitive";
import React5 from "react";
import { useStore as useStore5 } from "zustand";
import { useShallow as useShallow3 } from "zustand/react/shallow";
var MUTE_TRIGGER_NAME = "MuteTrigger";
var MuteTrigger = React5.forwardRef(
(props, forwardedRef) => {
const { __scopeMedia, ...playProps } = props;
const context = useMediaContext(MUTE_TRIGGER_NAME, __scopeMedia);
const { muted, toggleMute } = useStore5(
context.store,
useShallow3(({ __controls, __controlsFunctions }) => ({
muted: __controls.muted,
toggleMute: __controlsFunctions.requestToggleMute
}))
);
const title = React5.useMemo(
() => muted ? "Unmute (m)" : "Mute (m)",
[muted]
);
return /* @__PURE__ */ React5.createElement(
Primitive.button,
{
type: "button",
"aria-pressed": muted,
"aria-label": title,
title,
...playProps,
onClick: composeEventHandlers3(props.onClick, noPropagate(toggleMute)),
ref: forwardedRef,
"data-livepeer-controls-mute-trigger": "",
"data-muted": String(muted)
}
);
}
);
MuteTrigger.displayName = MUTE_TRIGGER_NAME;
// src/player/Play.tsx
import { composeEventHandlers as composeEventHandlers4 } from "@radix-ui/primitive";
import { Presence as Presence4 } from "@radix-ui/react-presence";
import React6, { useMemo as useMemo3 } from "react";
import { useStore as useStore6 } from "zustand";
import { useShallow as useShallow4 } from "zustand/react/shallow";
var PLAY_PAUSE_TRIGGER_NAME = "PlayPauseTrigger";
var PlayPauseTrigger = React6.forwardRef((props, forwardedRef) => {
const { __scopeMedia, ...playProps } = props;
const context = useMediaContext(PLAY_PAUSE_TRIGGER_NAME, __scopeMedia);
const { playing, togglePlay, title } = useStore6(
context.store,
useShallow4(({ playing: playing2, __controlsFunctions, aria }) => ({
playing: playing2,
togglePlay: __controlsFunctions.togglePlay,
title: aria.playPause
}))
);
return /* @__PURE__ */ React6.createElement(
Primitive.button,
{
type: "button",
"aria-pressed": playing,
"aria-label": title ?? void 0,
title: title ?? void 0,
...playProps,
onClick: composeEventHandlers4(props.onClick, noPropagate(togglePlay)),
ref: forwardedRef,
"data-livepeer-controls-play-pause-trigger": "",
"data-playing": String(playing)
}
);
});
PlayPauseTrigger.displayName = PLAY_PAUSE_TRIGGER_NAME;
var PLAYING_INDICATOR_NAME = "PlayingIndicator";
var PlayingIndicator = React6.forwardRef((props, forwardedRef) => {
const {
__scopeMedia,
forceMount,
matcher = true,
...playPauseIndicatorProps
} = props;
const context = useMediaContext(PLAYING_INDICATOR_NAME, __scopeMedia);
const playing = useStore6(
context.store,
useShallow4(({ playing: playing2 }) => playing2)
);
const isPresent = useMemo3(
() => typeof matcher === "boolean" ? matcher === playing : matcher(playing),
[playing, matcher]
);
return /* @__PURE__ */ React6.createElement(Presence4, { present: forceMount || isPresent }, /* @__PURE__ */ React6.createElement(
Primitive.div,
{
...playPauseIndicatorProps,
ref: forwardedRef,
"data-livepeer-controls-play-pause-indicator": "",
"data-playing": String(playing),
"data-visible": String(isPresent)
}
));
});
PlayingIndicator.displayName = PLAYING_INDICATOR_NAME;
// src/player/Player.tsx
import {
addLegacyMediaMetricsToStore,
addMetricsToStore,
createControllerStore
} from "@livepeer/core/media";
import { createStorage, noopStorage } from "@livepeer/core/storage";
import { version } from "@livepeer/core/version";
import { getDeviceInfo } from "@livepeer/core-web/browser";
import React7, { useEffect as useEffect4, useRef } from "react";
var Player = React7.memo((props) => {
const {
aspectRatio = 16 / 9,
src,
children,
jwt,
accessKey,
storage,
onPlaybackEvents,
metricsInterval,
playbackId,
...rest
} = props;
const store = useRef(
createControllerStore({
device: getDeviceInfo(version.react),
storage: storage ?? createStorage(
storage !== null && typeof window !== "undefined" ? {
storage: window.localStorage
} : {
storage: noopStorage
}
),
src,
playbackId,
initialProps: {
aspectRatio,
jwt,
accessKey,
...rest
}
})
);
useEffect4(() => {
if (jwt) {
store?.current?.store.setState((prev) => ({
__initialProps: {
...prev.__initialProps,
jwt
}
}));
}
}, [jwt]);
useEffect4(() => {
if (accessKey) {
store?.current?.store.setState((prev) => ({
__initialProps: {
...prev.__initialProps,
accessKey
}
}));
}
}, [accessKey]);
useEffect4(() => {
return () => {
store?.current?.destroy?.();
};
}, []);
useEffect4(() => {
const metrics = addLegacyMediaMetricsToStore(store.current.store);
return () => {
metrics.destroy();
};
}, []);
useEffect4(() => {
const metrics = addMetricsToStore(store.current.store, {
onPlaybackEvents,
interval: metricsInterval
});
return () => {
metrics.destroy();
};
}, []);
return /* @__PURE__ */ React7.createElement(MediaProvider, { store: store.current.store, scope: props.__scopeMedia }, children);
});
Player.displayName = "Player";
var Root = Player;
// src/player/Poster.tsx
import { Presence as Presence5 } from "@radix-ui/react-presence";
import React8 from "react";
import { useStore as useStore7 } from "zustand";
var POSTER_NAME = "Poster";
var Poster = React8.forwardRef(
(props, forwardedRef) => {
const { __scopeMedia, forceMount, src, ...posterProps } = props;
const context = useMediaContext(POSTER_NAME, __scopeMedia);
const poster = useStore7(context.store, ({ poster: poster2 }) => poster2);
return /* @__PURE__ */ React8.createElement(Presence5, { present: forceMount || Boolean(src || poster) }, /* @__PURE__ */ React8.createElement(
Primitive.img,
{
alt: "Poster for video",
"aria-hidden": "true",
...posterProps,
src: src || poster || void 0,
ref: forwardedRef,
"data-livepeer-poster": "",
"data-visible": String(Boolean(src || poster))
}
));
}
);
Poster.displayName = POSTER_NAME;
// src/player/RateSelect.tsx
import React9, { useCallback } from "react";
import { useStore as useStore8 } from "zustand";
import { useShallow as useShallow5 } from "zustand/react/shallow";
// src/shared/Select.tsx
import * as SelectPrimitive from "@radix-ui/react-select";
var SelectRoot = SelectPrimitive.Root;
var SelectTrigger2 = SelectPrimitive.SelectTrigger;
var SelectValue2 = SelectPrimitive.SelectValue;
var SelectIcon2 = SelectPrimitive.SelectIcon;
var SelectPortal2 = SelectPrimitive.SelectPortal;
var SelectContent2 = SelectPrimitive.SelectContent;
var SelectViewport2 = SelectPrimitive.SelectViewport;
var SelectGroup2 = SelectPrimitive.SelectGroup;
var SelectLabel2 = SelectPrimitive.SelectLabel;
var SelectItem2 = SelectPrimitive.SelectItem;
var SelectItemText2 = SelectPrimitive.SelectItemText;
var SelectItemIndicator2 = SelectPrimitive.SelectItemIndicator;
var SelectScrollUpButton2 = SelectPrimitive.SelectScrollUpButton;
var SelectScrollDownButton2 = SelectPrimitive.SelectScrollDownButton;
var SelectSeparator2 = SelectPrimitive.SelectSeparator;
var SelectArrow2 = SelectPrimitive.SelectArrow;
// src/player/RateSelect.tsx
var RATE_SELECT_NAME = "RateSelect";
var RateSelect = (props) => {
const { __scopeMedia, defaultValue, ...rateSelectProps } = props;
const context = useMediaContext(RATE_SELECT_NAME, __scopeMedia);
const { playbackRate, setPlaybackRate } = useStore8(
context.store,
useShallow5(({ playbackRate: playbackRate2, __controlsFunctions }) => ({
playbackRate: playbackRate2,
setPlaybackRate: __controlsFunctions.setPlaybackRate
}))
);
const onValueChangeComposed = useCallback(
(value) => {
setPlaybackRate(value);
props.onValueChange?.(value);
},
[props.onValueChange, setPlaybackRate]
);
return /* @__PURE__ */ React9.createElement(
SelectRoot,
{
...rateSelectProps,
value: playbackRate === "constant" ? "constant" : playbackRate.toFixed(2),
onValueChange: onValueChangeComposed,
"data-livepeer-rate-select": "",
"data-rate": String(playbackRate)
}
);
};
RateSelect.displayName = RATE_SELECT_NAME;
var RATE_SELECT_ITEM_NAME = "RateSelectItem";
var RateSelectItem = React9.forwardRef((props, forwardedRef) => {
const { __scopeMedia, value, ...rateSelectItemProps } = props;
return /* @__PURE__ */ React9.createElement(
SelectItem2,
{
...rateSelectItemProps,
ref: forwardedRef,
value: Number(value).toFixed(2),
"data-livepeer-rate-select-item": ""
}
);
});
RateSelectItem.displayName = RATE_SELECT_ITEM_NAME;
// src/player/Seek.tsx
import { Presence as Presence6 } from "@radix-ui/react-presence";
import React10, { useCallback as useCallback2 } from "react";
import { useStore as useStore9 } from "zustand";
import { useShallow as useShallow6 } from "zustand/react/shallow";
// src/shared/Slider.tsx
import * as SliderPrimitive from "@radix-ui/react-slider";
var Root4 = SliderPrimitive.Root;
var Track2 = SliderPrimitive.Track;
var Range2 = SliderPrimitive.Range;
var Thumb2 = SliderPrimitive.Thumb;
// src/player/Seek.tsx
var SEEK_NAME = "Seek";
var Seek = React10.forwardRef(
(props, forwardedRef) => {
const { __scopeMedia, forceMount, style, ...seekProps } = props;
const context = useMediaContext(SEEK_NAME, __scopeMedia);
const {
ariaProgress,
duration,
buffered,
bufferedPercent,
progress,
live,
seek
} = useStore9(
context.store,
useShallow6(
({
aria,
duration: duration2,
buffered: buffered2,
bufferedPercent: bufferedPercent2,
progress: progress2,
live: live2,
__controlsFunctions
}) => ({
ariaProgress: aria.progress,
duration: duration2,
buffered: buffered2,
bufferedPercent: bufferedPercent2,
progress: progress2,
live: live2,
seek: __controlsFunctions.requestSeek
})
)
);
const onValueChange = React10.useCallback(
([value]) => seek(value),
[seek]
);
const onValueCommit = React10.useCallback(
([value]) => seek(value),
[seek]
);
const onValueChangeComposed = useCallback2(
(value) => {
if (props.onValueChange) {
props.onValueChange(value);
}
onValueChange(value);
},
[props.onValueChange, onValueChange]
);
const onValueCommitComposed = useCallback2(
(value) => {
if (props.onValueCommit) {
props.onValueCommit(value);
}
onValueCommit(value);
},
[props.onValueCommit, onValueCommit]
);
return /* @__PURE__ */ React10.createElement(Presence6, { present: forceMount || !live }, /* @__PURE__ */ React10.createElement(
Root4,
{
"aria-label": live ? "Live Seek Slider" : "Video Seek Slider",
"aria-valuetext": ariaProgress ?? void 0,
step: 0.1,
max: duration,
value: [progress],
role: "slider",
...seekProps,
onValueChange: onValueChangeComposed,
onValueCommit: onValueCommitComposed,
onClick: noPropagate(() => {
}),
ref: forwardedRef,
"data-livepeer-controls-seek": "",
"data-duration": duration,
"data-progress": progress,
"data-live": String(live),
"data-buffered": buffered,
"data-visible": String(!live),
style: {
// biome-ignore lint/suspicious/noExplicitAny: player container css var
["--livepeer-player-buffering-width"]: `${bufferedPercent ?? 0}%`,
...style
}
}
));
}
);
Seek.displayName = SEEK_NAME;
var SEEK_BUFFER_NAME = "SeekBuffer";
var SeekBuffer = React10.forwardRef(
(props, forwardedRef) => {
const { __scopeMedia, style, ...bufferProps } = props;
const context = useMediaContext(SEEK_BUFFER_NAME, __scopeMedia);
const { bufferedPercent, buffered } = useStore9(
context.store,
useShallow6(({ bufferedPercent: bufferedPercent2, buffered: buffered2 }) => ({
buffered: buffered2,
bufferedPercent: bufferedPercent2
}))
);
return /* @__PURE__ */ React10.createElement(
Track2,
{
...bufferProps,
ref: forwardedRef,
style: {
left: 0,
right: `${100 - (bufferedPercent ?? 0)}%`,
...style
},
"data-livepeer-controls-seek-buffer": "",
"data-buffered": buffered
}
);
}
);
SeekBuffer.displayName = SEEK_BUFFER_NAME;
// src/player/Video.tsx
import { addEventListeners } from "@livepeer/core-web/browser";
import { useComposedRefs } from "@radix-ui/react-compose-refs";
import React11, { useEffect as useEffect5 } from "react";
import { useStore as useStore10 } from "zustand";
import { useShallow as useShallow7 } from "zustand/react/shallow";
var VIDEO_NAME = "Video";
var Video = React11.forwardRef(
(props, forwardedRef) => {
const { __scopeMedia, style, poster, hlsConfig, title, ...videoProps } = props;
const context = useMediaContext(VIDEO_NAME, __scopeMedia);
const ref = React11.useRef(null);
const composedRefs = useComposedRefs(forwardedRef, ref);
const {
currentSource,
setMounted,
autoPlay,
preload,
thumbnailPoster,
volume,
requestToggleMute
} = useStore10(
context.store,
useShallow7(
({
__controlsFunctions,
__initialProps,
currentSource: currentSource2,
live,
poster: poster2,
volume: volume2
}) => ({
autoPlay: __initialProps.autoPlay,
currentSource: currentSource2,
live,
preload: __initialProps.preload,
setMounted: __controlsFunctions.setMounted,
thumbnailPoster: poster2,
volume: volume2,
requestToggleMute: __controlsFunctions.requestToggleMute
})
)
);
useEffect5(() => {
if (ref.current) {
const { destroy } = addEventListeners(ref.current, context.store);
return destroy;
}
}, [context?.store]);
useEffect5(() => {
if (hlsConfig) {
context.store.getState().__controlsFunctions.setHlsConfig(hlsConfig);
}
}, []);
useEffect5(() => {
setMounted();
}, [setMounted]);
useEffect5(() => {
if (typeof videoProps.muted !== "undefined") {
requestToggleMute(videoProps.muted);
}
}, [videoProps.muted, requestToggleMute]);
return /* @__PURE__ */ React11.createElement(
Primitive.video,
{
playsInline: true,
poster: poster === null ? void 0 : poster ?? thumbnailPoster ?? void 0,
muted: volume === 0,
...videoProps,
"aria-label": title ?? videoProps["aria-label"],
autoPlay,
preload,
ref: composedRefs,
"data-livepeer-video": "",
"data-livepeer-source-type": currentSource?.type ?? "none",
style: {
...style,
// ensures video expands in ratio
position: "absolute",
inset: 0
}
}
);
}
);
Video.displayName = VIDEO_NAME;
// src/player/VideoQualitySelect.tsx
import React12, { useCallback as useCallback3 } from "react";
import { useStore as useStore11 } from "zustand";
import { useShallow as useShallow8 } from "zustand/react/shallow";
var VIDEO_QUALITY_SELECT_NAME = "VideoQualitySelect";
var VideoQualitySelect = (props) => {
const { __scopeMedia, defaultValue, ...videoQualitySelectProps } = props;
const context = useMediaContext(VIDEO_QUALITY_SELECT_NAME, __scopeMedia);
const { videoQuality, setVideoQuality } = useStore11(
context.store,
useShallow8(({ videoQuality: videoQuality2, __controlsFunctions }) => ({
videoQuality: videoQuality2,
setVideoQuality: __controlsFunctions.setVideoQuality
}))
);
const onValueChangeComposed = useCallback3(
(value) => {
if (props.onValueChange) {
props.onValueChange(value);
}
setVideoQuality(value);
},
[props.onValueChange, setVideoQuality]
);
return /* @__PURE__ */ React12.createElement(
SelectRoot,
{
...videoQualitySelectProps,
value: videoQuality,
onValueChange: onValueChangeComposed,
"data-livepeer-quality-select": "",
"data-video-quality": String(videoQuality)
}
);
};
VideoQualitySelect.displayName = VIDEO_QUALITY_SELECT_NAME;
var VIDEO_QUALITY_SELECT_ITEM_NAME = "VideoQualitySelectItem";
var VideoQualitySelectItem = React12.forwardRef((props, forwardedRef) => {
const { __scopeMedia, ...videoQualitySelectItemProps } = props;
return /* @__PURE__ */ React12.createElement(
SelectItem2,
{
...videoQualitySelectItemProps,
ref: forwardedRef,
"data-livepeer-quality-select-item": ""
}
);
});
VideoQualitySelectItem.displayName = VIDEO_QUALITY_SELECT_ITEM_NAME;
// src/player/Volume.tsx
import { warn } from "@livepeer/core/utils";
import { Presence as Presence7 } from "@radix-ui/react-presence";
import React13, { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo4 } from "react";
import { useStore as useStore12 } from "zustand";
import { useShallow as useShallow9 } from "zustand/react/shallow";
var VOLUME_NAME = "Volume";
var Volume = React13.forwardRef(
(props, forwardedRef) => {
const { __scopeMedia, forceMount, ...volumeProps } = props;
const context = useMediaContext(VOLUME_NAME, __scopeMedia);
const { volume, requestVolume, isVolumeChangeSupported } = useStore12(
context.store,
useShallow9(({ volume: volume2, __controlsFunctions, __device }) => ({
volume: volume2,
requestVolume: __controlsFunctions.requestVolume,
isVolumeChangeSupported: __device.isVolumeChangeSupported
}))
);
const onValueChange = React13.useCallback(
([value]) => requestVolume(value),
[requestVolume]
);
const onValueCommit = React13.useCallback(
([value]) => requestVolume(value),
[requestVolume]
);
const onValueChangeComposed = useCallback4(
(value) => {
if (props.onValueChange) {
props.onValueChange(value);
}
onValueChange(value);
},
[props.onValueChange, onValueChange]
);
const onValueCommitComposed = useCallback4(
(value) => {
if (props.onValueCommit) {
props.onValueCommit(value);
}
onValueCommit(value);
},
[props.onValueCommit, onValueCommit]
);
return /* @__PURE__ */ React13.createElement(Presence7, { present: forceMount || isVolumeChangeSupported }, /* @__PURE__ */ React13.createElement(
Root4,
{
"aria-label": "Volume Slider",
step: 0.01,
max: 1,
value: [volume],
...volumeProps,
onClick: noPropagate(() => {
}),
onValueChange: onValueChangeComposed,
onValueCommit: onValueCommitComposed,
ref: forwardedRef,
"data-livepeer-controls-volume": "",
"data-livepeer-muted": String(volume === 0),
"data-livepeer-volume": String((100 * volume).toFixed(0)),
"data-visible": String(Boolean(isVolumeChangeSupported))
}
));
}
);
Volume.displayName = VOLUME_NAME;
var VOLUME_INDICATOR_NAME = "VolumeIndicator";
var VolumeIndicator = React13.forwardRef((props, forwardedRef) => {
const {
__scopeMedia,
forceMount,
matcher = false,
...volumeIndicatorProps
} = props;
const context = useMediaContext(VOLUME_INDICATOR_NAME, __scopeMedia);
const { volume, muted, isVolumeChangeSupported } = useStore12(
context.store,
useShallow9(({ volume: volume2, __device, __controls }) => ({
volume: volume2,
muted: __controls.muted,
isVolumeChangeSupported: __device.isVolumeChangeSupported
}))
);
const isPresent = useMemo4(
() => matcher !== void 0 ? typeof matcher === "boolean" ? matcher ? !muted : muted : matcher(volume) : muted,
[volume, matcher, muted]
);
useEffect6(() => {
if (isVolumeChangeSupported && typeof matcher !== "boolean") {
warn("Volume change is not supported on this device.");
}
}, [isVolumeChangeSupported, matcher]);
return /* @__PURE__ */ React13.createElement(Presence7, { present: forceMount || isPresent }, /* @__PURE__ */ React13.createElement(
Primitive.div,
{
...volumeIndicatorProps,
ref: forwardedRef,
"data-livepeer-muted": String(muted),
"data-livepeer-volume": String((100 * volume).toFixed(0)),
"data-livepeer-controls-volume-indicator": "",
"data-visible": String(isPresent)
}
));
});
VolumeIndicator.displayName = VOLUME_INDICATOR_NAME;
// src/shared/Container.tsx
import * as RadixAspectRatio from "@radix-ui/react-aspect-ratio";
import React14 from "react";
import { useStore as useStore13 } from "zustand";
import { useShallow as useShallow10 } from "zustand/react/shallow";
var CONTAINER_NAME = "Container";
var Container = React14.memo(
React14.forwardRef(
(props, forwardedRef) => {
const { __scopeMedia, ...aspectRatioProps } = props;
const context = useMediaContext(CONTAINER_NAME, __scopeMedia);
const {
aspectRatio,
fullscreen,
playing,
canPlay,
rate,
error,
live,
hasPlayed,
hidden,
pictureInPicture,
loading,
videoQuality
} = useStore13(
context.store,
useShallow10(
({
__initialProps,
fullscreen: fullscreen2,
playing: playing2,
canPlay: canPlay2,
playbackRate,
error: error2,
live: live2,
hasPlayed: hasPlayed2,
hidden: hidden2,
pictureInPicture: pictureInPicture2,
loading: loading2,
videoQuality: videoQuality2
}) => ({
aspectRatio: __initialProps.aspectRatio,
fullscreen: fullscreen2,
playing: playing2,
canPlay: canPlay2,
error: Boolean(error2),
rate: playbackRate === "constant" ? "constant" : playbackRate > 1 ? "fast" : playbackRate < 1 ? "slow" : "normal",
live: live2,
hasPlayed: hasPlayed2,
hidden: hidden2,
pictureInPicture: pictureInPicture2,
loading: loading2,
videoQuality: videoQuality2
})
)
);
return aspectRatio ? /* @__PURE__ */ React14.createElement(
RadixAspectRatio.Root,
{
ratio: aspectRatio,
...aspectRatioProps,
ref: forwardedRef,
"data-livepeer-aspect-ratio": "",
"data-fullscreen": String(fullscreen),
"data-playing": String(playing),
"data-can-play": String(canPlay),
"data-playback-rate": rate,
"data-error": String(error),
"data-loading": String(loading),
"data-live": String(live),
"data-has-played": String(hasPlayed),
"data-controls-hidden": String(hidden),
"data-picture-in-picture": String(pictureInPicture),
"data-video-quality": String(videoQuality)
}
) : /* @__PURE__ */ React14.createElement(
Primitive.div,
{
...aspectRatioProps,
ref: forwardedRef,
"data-livepeer-wrapper": "",
"data-fullscreen": String(fullscreen),
"data-playing": String(playing),
"data-can-play": String(canPlay),
"data-playback-rate": rate,
"data-error": String(error),
"data-loading": String(loading),
"data-live": String(live),
"data-has-played": String(hasPlayed),
"data-controls-hidden": String(hidden),
"data-picture-in-picture": String(pictureInPicture),
"data-video-quality": String(videoQuality)
}
);
}
)
);
Container.displayName = CONTAINER_NAME;
// src/shared/ErrorIndicator.tsx
import { Presence as Presence8 } from "@radix-ui/react-presence";
import React15, { useMemo as useMemo5 } from "react";
import { useStore as useStore14 } from "zustand";
var ERROR_INDICATOR_NAME = "ErrorIndicator";
var ErrorIndicator = React15.forwardRef((props, forwardedRef) => {
const { __scopeMedia, forceMount, matcher, ...offlineErrorProps } = props;
const context = useMediaContext(ERROR_INDICATOR_NAME, __scopeMedia);
const error = useStore14(context.store, ({ error: error2 }) => error2);
const isPresent = useMemo5(
() => error ? typeof matcher === "string" ? matcher === "all" ? true : matcher === "not-permissions" ? error.type !== "permissions" : matcher === error.type : matcher(error.type) : false,
[error, matcher]
);
return /* @__PURE__ */ React15.createElement(Presence8, { present: forceMount || isPresent }, /* @__PURE__ */ React15.createElement(
Primitive.div,
{
...offlineErrorProps,
ref: forwardedRef,
"data-livepeer-error-indicator": "",
"data-error-state": String(Boolean(error)),
"data-error-type": error?.type ?? "none",
"data-visible": String(isPresent)
}
));
});
ErrorIndicator.displayName = ERROR_INDICATOR_NAME;
// src/shared/Fullscreen.tsx
import { composeEventHandlers as composeEventHandlers5 } from "@radix-ui/primitive";
import { Presence as Presence9 } from "@radix-ui/react-presence";
import React16, { useMemo as useMemo6 } from "react";
import { useStore as useStore15 } from "zustand";
import { useShallow as useShallow11 } from "zustand/react/shallow";
var FULLSCREEN_INDICATOR_NAME = "FullscreenIndicator";
var FullscreenIndicator = React16.forwardRef((props, forwardedRef) => {
const {
__scopeMedia,
forceMount,
matcher = true,
...fullscreenIndicatorProps
} = props;
const context = useMediaContext(FULLSCREEN_INDICATOR_NAME, __scopeMedia);
const fullscreen = useStore15(
context.store,
useShallow11(({ fullscreen: fullscreen2 }) => fullscreen2)
);
const isPresent = useMemo6(
() => typeof matcher === "function" ? matcher(fullscreen) : matcher === fullscreen,
[matcher, fullscreen]
);
return /* @__PURE__ */ React16.createElement(Presence9, { present: forceMount || isPresent }, /* @__PURE__ */ React16.createElement(
Primitive.div,
{
...fullscreenIndicatorProps,
ref: forwardedRef,
"data-livepeer-controls-fullscreen-indicator": "",
"data-fullscreen": String(Boolean(fullscreen)),
"data-visible": String(isPresent)
}
));
});
FullscreenIndicator.displayName = FULLSCREEN_INDICATOR_NAME;
var FULLSCREEN_TRIGGER_NAME = "FullscreenTrigger";
var FullscreenTrigger = React16.forwardRef((props, forwardedRef) => {
const { __scopeMedia, ...fullscreenProps } = props;
const context = useMediaContext(FULLSCREEN_TRIGGER_NAME, __scopeMedia);
const { title, fullscreen, requestToggleFullscreen } = useStore15(
context.store,
useShallow11(({ fullscreen: fullscreen2, __controlsFunctions, aria }) => ({
fullscreen: fullscreen2,
requestToggleFullscreen: __controlsFunctions.requestToggleFullscreen,
title: aria.fullscreen
}))
);
return /* @__PURE__ */ React16.createElement(
Primitive.button,
{
type: "button",
"aria-pressed": fullscreen,
"aria-label": title ?? void 0,
title: title ?? void 0,
...fullscreenProps,
onClick: composeEventHandlers5(
props.onClick,
noPropagate(requestToggleFullscreen)
),
ref: forwardedRef,
"data-livepeer-controls-fullscreen-trigger": "",
"data-fullscreen-state": String(Boolean(fullscreen))
}
);
});
FullscreenTrigger.displayName = FULLSCREEN_TRIGGER_NAME;
// src/shared/LoadingIndicator.tsx
import { Presence as Presence10 } from "@radix-ui/react-presence";
import React17, { useMemo as useMemo7 } from "react";
import { useStore as useStore16 } from "zustand";
var LOADING_INDICATOR_NAME = "LoadingIndicator";
var LoadingIndicator = React17.forwardRef((props, forwardedRef) => {
const {
__scopeMedia,
forceMount,
matcher = true,
...offlineErrorProps
} = props;
const context = useMediaContext(LOADING_INDICATOR_NAME, __scopeMedia);
const loading = useStore16(context.store, ({ loading: loading2 }) => loading2);
const isPresent = useMemo7(
() => typeof matcher === "function" ? matcher(loading) : matcher === loading,
[matcher, loading]
);
return /* @__PURE__ */ React17.createElement(Presence10, { present: forceMount || isPresent }, /* @__PURE__ */ React17.createElement(
Primitive.div,
{
"aria-label": "Loading",
...offlineErrorProps,
ref: forwardedRef,
"data-livepeer-loading-indicator": "",
"data-loading": String(Boolean(loading)),
"data-visible": String(isPresent)
}
));
});
LoadingIndicator.displayName = LOADING_INDICATOR_NAME;
// src/shared/PictureInPictureTrigger.tsx
import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primitive";
import { Presence as Presence11 } from "@radix-ui/react-presence";
import React18 from "react";
import { useStore as useStore17 } from "zustand";
import { useShallow as useShallow12 } from "zustand/react/shallow";
var PICTURE_IN_PICTURE_TRIGGER_NAME = "PictureInPictureTrigger";
var PictureInPictureTrigger = React18.forwardRef((props, forwardedRef) => {
const { __scopeMedia, forceMount, ...pictureInPictureProps } = props;
const context = useMediaContext(
PICTURE_IN_PICTURE_TRIGGER_NAME,
__scopeMedia
);
const {
pictureInPicture,
requestTogglePictureInPicture,
isPictureInPictureSupported,
fullscreen,
title
} = useStore17(
context.store,
useShallow12(
({
pictureInPicture: pictureInPicture2,
__controlsFunctions,
__device,
fullscreen: fullscreen2,
aria
}) => ({
pictureInPicture: pictureInPicture2,
requestTogglePictureInPicture: __controlsFunctions.requestTogglePictureInPicture,
isPictureInPictureSupported: __device.isPictureInPictureSupported,
fullscreen: fullscreen2,
title: aria.pictureInPicture
})
)
);
return (
// do not show button if it is not supported or if currently fullscreen
/* @__PURE__ */ React18.createElement(
Presence11,
{
present: forceMount || isPictureInPictureSupported && !fullscreen
},
/* @__PURE__ */ React18.createElement(
Primitive.button,
{
type: "button",
"aria-pressed": pictureInPicture,
"aria-label": title ?? void 0,
title: title ?? void 0,
...pictureInPictureProps,
onClick: composeEventHandlers6(
props.onClick,
noPropagate(requestTogglePictureInPicture)
),
ref: forwardedRef,
"data-livepeer-controls-picture-in-picture-trigger": "",
"data-picture-in-picture": String(Boolean(pictureInPicture)),
"data-visible": String(isPictureInPictureSupported && !fullscreen)
}
)
)
);
});
PictureInPictureTrigger.displayName = PICTURE_IN_PICTURE_TRIGGER_NAME;
// src/shared/Portal.tsx
import * as RadixPortal from "@radix-ui/react-portal";
import React19 from "react";
var PORTAL_NAME = "Portal";
var Portal = (props) => {
return /* @__PURE__ */ React19.createElement(RadixPortal.Root, { ...props });
};
Portal.displayName = PORTAL_NAME;
// src/shared/Time.tsx
import React20 from "react";
import { useStore as useStore18 } from "zustand";
import { useShallow as useShallow13 } from "zustand/react/shallow";
var TIME_NAME = "Time";
var Time = React20.forwardRef(
(props, forwardedRef) => {
const { __scopeMedia, ...timeProps } = props;
const context = useMediaContext(TIME_NAME, __scopeMedia);
const { progress, duration, live, formattedTime } = useStore18(
context.store,
useShallow13(({ progress: progress2, duration: duration2, live: live2, aria }) => ({
formattedTime: aria.time,
progress: progress2,
duration: duration2,
live: live2
}))
);
return /* @__PURE__ */ React20.createElement(
Primitive.span,
{
"aria-label": formattedTime ?? void 0,
title: formattedTime ?? void 0,
...timeProps,
ref: forwardedRef,
"data-livepeer-controls-time": "",
"data-duration": duration,
"data-progress": progress,
"data-live": String(live)
},
formattedTime
);
}
);
Time.displayName = TIME_NAME;
export {
ClipTrigger,
Container,
Controls,
ErrorIndicator,
FullscreenIndicator,
FullscreenTrigger,
LiveIndicator,
LoadingIndicator,
MediaProvider,
MuteTrigger,
PictureInPictureTrigger,
PlayPauseTrigger,
PlayingIndicator,
Portal,
Poster,
Range2 as Range,
RateSelect,
RateSelectItem,
Root,
Seek,
SeekBuffer,
SelectArrow2 as SelectArrow,
SelectContent2 as SelectContent,
SelectGroup2 as SelectGroup,
SelectIcon2 as SelectIcon,
SelectItemIndicator2 as SelectItemIndicator,
SelectItemText2 as SelectItemText,
SelectLabel2 as SelectLabel,
SelectPortal2 as SelectPortal,
SelectScrollDownButton2 as SelectScrollDownButton,
SelectScrollUpButton2 as SelectScrollUpButton,
SelectSeparator2 as SelectSeparator,
SelectTrigger2 as SelectTrigger,
SelectValue2 as SelectValue,
SelectViewport2 as SelectViewport,
Thumb2 as Thumb,
Time,
Track2 as Track,
Video,
VideoQualitySelect,
VideoQualitySelectItem,
Volume,
VolumeIndicator,
createMediaScope,
useMediaContext,
useStore
};
//# sourceMappingURL=index.js.map