@hanifhan1f/vidstack-react 1.12.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.templates/sandbox/document.css +27 -0
- package/.templates/sandbox/favicon-32x32.png +0 -0
- package/.templates/sandbox/index.html +21 -0
- package/.templates/sandbox/main.tsx +12 -0
- package/.templates/sandbox/player.css +39 -0
- package/.templates/sandbox/player.tsx +121 -0
- package/.templates/sandbox/tracks.ts +23 -0
- package/LICENSE +21 -0
- package/README.md +23 -0
- package/analyze.config.ts +7 -0
- package/build/build-icons.js +62 -0
- package/npm/analyze.json.d.ts +7 -0
- package/package.json +162 -0
- package/rollup.config.ts +256 -0
- package/src/components/announcer.tsx +47 -0
- package/src/components/layouts/default/audio-layout.tsx +231 -0
- package/src/components/layouts/default/context.ts +28 -0
- package/src/components/layouts/default/hooks.ts +13 -0
- package/src/components/layouts/default/icons.tsx +225 -0
- package/src/components/layouts/default/index.ts +11 -0
- package/src/components/layouts/default/media-layout.tsx +259 -0
- package/src/components/layouts/default/slots.tsx +98 -0
- package/src/components/layouts/default/ui/announcer.tsx +22 -0
- package/src/components/layouts/default/ui/buttons.tsx +301 -0
- package/src/components/layouts/default/ui/captions.tsx +16 -0
- package/src/components/layouts/default/ui/controls.tsx +12 -0
- package/src/components/layouts/default/ui/keyboard-display.tsx +132 -0
- package/src/components/layouts/default/ui/menus/accessibility-menu.tsx +100 -0
- package/src/components/layouts/default/ui/menus/audio-menu.tsx +167 -0
- package/src/components/layouts/default/ui/menus/captions-menu.tsx +61 -0
- package/src/components/layouts/default/ui/menus/chapters-menu.tsx +132 -0
- package/src/components/layouts/default/ui/menus/font-menu.tsx +331 -0
- package/src/components/layouts/default/ui/menus/items/menu-checkbox.tsx +72 -0
- package/src/components/layouts/default/ui/menus/items/menu-items.tsx +135 -0
- package/src/components/layouts/default/ui/menus/items/menu-slider.tsx +92 -0
- package/src/components/layouts/default/ui/menus/playback-menu.tsx +232 -0
- package/src/components/layouts/default/ui/menus/settings-menu.tsx +114 -0
- package/src/components/layouts/default/ui/menus/utils.ts +12 -0
- package/src/components/layouts/default/ui/sliders.tsx +136 -0
- package/src/components/layouts/default/ui/time.tsx +73 -0
- package/src/components/layouts/default/ui/title.tsx +24 -0
- package/src/components/layouts/default/ui/tooltip.tsx +27 -0
- package/src/components/layouts/default/ui.ts +8 -0
- package/src/components/layouts/default/video-layout.tsx +344 -0
- package/src/components/layouts/plyr/context.ts +26 -0
- package/src/components/layouts/plyr/icons/plyr-airplay.js +1 -0
- package/src/components/layouts/plyr/icons/plyr-captions-off.js +1 -0
- package/src/components/layouts/plyr/icons/plyr-captions-on.js +1 -0
- package/src/components/layouts/plyr/icons/plyr-download.js +1 -0
- package/src/components/layouts/plyr/icons/plyr-enter-fullscreen.js +1 -0
- package/src/components/layouts/plyr/icons/plyr-exit-fullscreen.js +1 -0
- package/src/components/layouts/plyr/icons/plyr-fast-forward.js +1 -0
- package/src/components/layouts/plyr/icons/plyr-muted.js +1 -0
- package/src/components/layouts/plyr/icons/plyr-pause.js +1 -0
- package/src/components/layouts/plyr/icons/plyr-pip.js +1 -0
- package/src/components/layouts/plyr/icons/plyr-play.js +1 -0
- package/src/components/layouts/plyr/icons/plyr-restart.js +1 -0
- package/src/components/layouts/plyr/icons/plyr-rewind.js +1 -0
- package/src/components/layouts/plyr/icons/plyr-settings.js +1 -0
- package/src/components/layouts/plyr/icons/plyr-volume.js +1 -0
- package/src/components/layouts/plyr/icons.tsx +71 -0
- package/src/components/layouts/plyr/index.ts +11 -0
- package/src/components/layouts/plyr/layout.tsx +1024 -0
- package/src/components/layouts/plyr/props.ts +104 -0
- package/src/components/layouts/plyr/slots.tsx +52 -0
- package/src/components/layouts/remotion-ui.ts +13 -0
- package/src/components/layouts/utils.ts +17 -0
- package/src/components/player-callbacks.ts +67 -0
- package/src/components/player.tsx +67 -0
- package/src/components/primitives/instances.ts +92 -0
- package/src/components/primitives/nodes.tsx +58 -0
- package/src/components/primitives/slot.tsx +132 -0
- package/src/components/provider.tsx +187 -0
- package/src/components/text-track.tsx +106 -0
- package/src/components/ui/buttons/airplay-button.tsx +53 -0
- package/src/components/ui/buttons/caption-button.tsx +55 -0
- package/src/components/ui/buttons/fullscreen-button.tsx +55 -0
- package/src/components/ui/buttons/google-cast-button.tsx +53 -0
- package/src/components/ui/buttons/live-button.tsx +56 -0
- package/src/components/ui/buttons/mute-button.tsx +60 -0
- package/src/components/ui/buttons/pip-button.tsx +54 -0
- package/src/components/ui/buttons/play-button.tsx +53 -0
- package/src/components/ui/buttons/seek-button.tsx +55 -0
- package/src/components/ui/buttons/toggle-button.tsx +51 -0
- package/src/components/ui/caption.tsx +70 -0
- package/src/components/ui/captions.tsx +41 -0
- package/src/components/ui/chapter-title.tsx +40 -0
- package/src/components/ui/controls.tsx +90 -0
- package/src/components/ui/gesture.tsx +43 -0
- package/src/components/ui/menu.tsx +251 -0
- package/src/components/ui/poster.tsx +101 -0
- package/src/components/ui/radio-group.tsx +88 -0
- package/src/components/ui/sliders/audio-gain-slider.tsx +55 -0
- package/src/components/ui/sliders/quality-slider.tsx +54 -0
- package/src/components/ui/sliders/slider-callbacks.ts +14 -0
- package/src/components/ui/sliders/slider-value.tsx +13 -0
- package/src/components/ui/sliders/slider.tsx +254 -0
- package/src/components/ui/sliders/speed-slider.tsx +54 -0
- package/src/components/ui/sliders/time-slider.tsx +379 -0
- package/src/components/ui/sliders/volume-slider.tsx +55 -0
- package/src/components/ui/spinner.tsx +105 -0
- package/src/components/ui/thumbnail.tsx +82 -0
- package/src/components/ui/time.tsx +77 -0
- package/src/components/ui/title.tsx +32 -0
- package/src/components/ui/tooltip.tsx +135 -0
- package/src/globals.d.ts +3 -0
- package/src/hooks/create-text-track.ts +22 -0
- package/src/hooks/options/use-audio-gain-options.ts +75 -0
- package/src/hooks/options/use-audio-options.ts +71 -0
- package/src/hooks/options/use-caption-options.ts +95 -0
- package/src/hooks/options/use-chapter-options.ts +97 -0
- package/src/hooks/options/use-playback-rate-options.ts +75 -0
- package/src/hooks/options/use-video-quality-options.ts +123 -0
- package/src/hooks/use-active-text-cues.ts +28 -0
- package/src/hooks/use-active-text-track.ts +19 -0
- package/src/hooks/use-chapter-title.ts +12 -0
- package/src/hooks/use-dom.ts +121 -0
- package/src/hooks/use-media-context.ts +6 -0
- package/src/hooks/use-media-player.ts +19 -0
- package/src/hooks/use-media-provider.ts +31 -0
- package/src/hooks/use-media-remote.ts +37 -0
- package/src/hooks/use-media-state.ts +58 -0
- package/src/hooks/use-signals.ts +24 -0
- package/src/hooks/use-slider-preview.ts +126 -0
- package/src/hooks/use-slider-state.ts +63 -0
- package/src/hooks/use-state.ts +47 -0
- package/src/hooks/use-text-cues.ts +33 -0
- package/src/hooks/use-thumbnails.ts +69 -0
- package/src/icon.ts +37 -0
- package/src/icons.ts +754 -0
- package/src/index.ts +181 -0
- package/src/providers/remotion/index.ts +10 -0
- package/src/providers/remotion/layout-engine.ts +123 -0
- package/src/providers/remotion/loader.ts +35 -0
- package/src/providers/remotion/playback-engine.ts +142 -0
- package/src/providers/remotion/provider.tsx +514 -0
- package/src/providers/remotion/type-check.ts +13 -0
- package/src/providers/remotion/types.ts +94 -0
- package/src/providers/remotion/ui/context.tsx +120 -0
- package/src/providers/remotion/ui/error-boundary.tsx +57 -0
- package/src/providers/remotion/ui/poster.tsx +33 -0
- package/src/providers/remotion/ui/slider-thumbnail.tsx +41 -0
- package/src/providers/remotion/ui/thumbnail.tsx +166 -0
- package/src/providers/remotion/validate.ts +220 -0
- package/src/source.ts +5 -0
- package/src/utils.ts +27 -0
- package/tsconfig.build.json +10 -0
- package/tsconfig.json +11 -0
- package/types/react/src/components/announcer.d.ts +16 -0
- package/types/react/src/components/layouts/default/audio-layout.d.ts +27 -0
- package/types/react/src/components/layouts/default/context.d.ts +14 -0
- package/types/react/src/components/layouts/default/hooks.d.ts +2 -0
- package/types/react/src/components/layouts/default/icons.d.ts +95 -0
- package/types/react/src/components/layouts/default/index.d.ts +5 -0
- package/types/react/src/components/layouts/default/media-layout.d.ts +133 -0
- package/types/react/src/components/layouts/default/slots.d.ts +22 -0
- package/types/react/src/components/layouts/default/ui/announcer.d.ts +6 -0
- package/types/react/src/components/layouts/default/ui/buttons.d.ts +54 -0
- package/types/react/src/components/layouts/default/ui/captions.d.ts +6 -0
- package/types/react/src/components/layouts/default/ui/controls.d.ts +6 -0
- package/types/react/src/components/layouts/default/ui/keyboard-display.d.ts +8 -0
- package/types/react/src/components/layouts/default/ui/menus/accessibility-menu.d.ts +10 -0
- package/types/react/src/components/layouts/default/ui/menus/audio-menu.d.ts +10 -0
- package/types/react/src/components/layouts/default/ui/menus/captions-menu.d.ts +10 -0
- package/types/react/src/components/layouts/default/ui/menus/chapters-menu.d.ts +7 -0
- package/types/react/src/components/layouts/default/ui/menus/font-menu.d.ts +6 -0
- package/types/react/src/components/layouts/default/ui/menus/items/menu-checkbox.d.ts +13 -0
- package/types/react/src/components/layouts/default/ui/menus/items/menu-items.d.ts +49 -0
- package/types/react/src/components/layouts/default/ui/menus/items/menu-slider.d.ts +26 -0
- package/types/react/src/components/layouts/default/ui/menus/playback-menu.d.ts +10 -0
- package/types/react/src/components/layouts/default/ui/menus/settings-menu.d.ts +15 -0
- package/types/react/src/components/layouts/default/ui/menus/utils.d.ts +1 -0
- package/types/react/src/components/layouts/default/ui/sliders.d.ts +24 -0
- package/types/react/src/components/layouts/default/ui/time.d.ts +30 -0
- package/types/react/src/components/layouts/default/ui/title.d.ts +6 -0
- package/types/react/src/components/layouts/default/ui/tooltip.d.ts +12 -0
- package/types/react/src/components/layouts/default/ui.d.ts +8 -0
- package/types/react/src/components/layouts/default/video-layout.d.ts +47 -0
- package/types/react/src/components/layouts/plyr/context.d.ts +12 -0
- package/types/react/src/components/layouts/plyr/icons/plyr-airplay.d.ts +2 -0
- package/types/react/src/components/layouts/plyr/icons/plyr-captions-off.d.ts +2 -0
- package/types/react/src/components/layouts/plyr/icons/plyr-captions-on.d.ts +2 -0
- package/types/react/src/components/layouts/plyr/icons/plyr-download.d.ts +2 -0
- package/types/react/src/components/layouts/plyr/icons/plyr-enter-fullscreen.d.ts +2 -0
- package/types/react/src/components/layouts/plyr/icons/plyr-exit-fullscreen.d.ts +2 -0
- package/types/react/src/components/layouts/plyr/icons/plyr-fast-forward.d.ts +2 -0
- package/types/react/src/components/layouts/plyr/icons/plyr-muted.d.ts +2 -0
- package/types/react/src/components/layouts/plyr/icons/plyr-pause.d.ts +2 -0
- package/types/react/src/components/layouts/plyr/icons/plyr-pip.d.ts +2 -0
- package/types/react/src/components/layouts/plyr/icons/plyr-play.d.ts +2 -0
- package/types/react/src/components/layouts/plyr/icons/plyr-restart.d.ts +2 -0
- package/types/react/src/components/layouts/plyr/icons/plyr-rewind.d.ts +2 -0
- package/types/react/src/components/layouts/plyr/icons/plyr-settings.d.ts +2 -0
- package/types/react/src/components/layouts/plyr/icons/plyr-volume.d.ts +2 -0
- package/types/react/src/components/layouts/plyr/icons.d.ts +25 -0
- package/types/react/src/components/layouts/plyr/index.d.ts +6 -0
- package/types/react/src/components/layouts/plyr/layout.d.ts +17 -0
- package/types/react/src/components/layouts/plyr/props.d.ts +71 -0
- package/types/react/src/components/layouts/plyr/slots.d.ts +9 -0
- package/types/react/src/components/layouts/remotion-ui.d.ts +3 -0
- package/types/react/src/components/layouts/utils.d.ts +1 -0
- package/types/react/src/components/player-callbacks.d.ts +6 -0
- package/types/react/src/components/player.d.ts +32 -0
- package/types/react/src/components/primitives/instances.d.ts +83 -0
- package/types/react/src/components/primitives/nodes.d.ts +15 -0
- package/types/react/src/components/primitives/slot.d.ts +11 -0
- package/types/react/src/components/provider.d.ts +26 -0
- package/types/react/src/components/text-track.d.ts +100 -0
- package/types/react/src/components/ui/buttons/airplay-button.d.ts +22 -0
- package/types/react/src/components/ui/buttons/caption-button.d.ts +24 -0
- package/types/react/src/components/ui/buttons/fullscreen-button.d.ts +24 -0
- package/types/react/src/components/ui/buttons/google-cast-button.d.ts +22 -0
- package/types/react/src/components/ui/buttons/live-button.d.ts +26 -0
- package/types/react/src/components/ui/buttons/mute-button.d.ts +30 -0
- package/types/react/src/components/ui/buttons/pip-button.d.ts +24 -0
- package/types/react/src/components/ui/buttons/play-button.d.ts +23 -0
- package/types/react/src/components/ui/buttons/seek-button.d.ts +25 -0
- package/types/react/src/components/ui/buttons/toggle-button.d.ts +22 -0
- package/types/react/src/components/ui/caption.d.ts +11 -0
- package/types/react/src/components/ui/captions.d.ts +20 -0
- package/types/react/src/components/ui/chapter-title.d.ts +20 -0
- package/types/react/src/components/ui/controls.d.ts +40 -0
- package/types/react/src/components/ui/gesture.d.ts +20 -0
- package/types/react/src/components/ui/menu.d.ts +102 -0
- package/types/react/src/components/ui/poster.d.ts +25 -0
- package/types/react/src/components/ui/radio-group.d.ts +39 -0
- package/types/react/src/components/ui/sliders/audio-gain-slider.d.ts +29 -0
- package/types/react/src/components/ui/sliders/quality-slider.d.ts +28 -0
- package/types/react/src/components/ui/sliders/slider-callbacks.d.ts +6 -0
- package/types/react/src/components/ui/sliders/slider-value.d.ts +9 -0
- package/types/react/src/components/ui/sliders/slider.d.ts +134 -0
- package/types/react/src/components/ui/sliders/speed-slider.d.ts +28 -0
- package/types/react/src/components/ui/sliders/time-slider.d.ts +124 -0
- package/types/react/src/components/ui/sliders/volume-slider.d.ts +29 -0
- package/types/react/src/components/ui/spinner.d.ts +31 -0
- package/types/react/src/components/ui/thumbnail.d.ts +26 -0
- package/types/react/src/components/ui/time.d.ts +20 -0
- package/types/react/src/components/ui/title.d.ts +15 -0
- package/types/react/src/components/ui/tooltip.d.ts +63 -0
- package/types/react/src/hooks/create-text-track.d.ts +7 -0
- package/types/react/src/hooks/options/use-audio-gain-options.d.ts +22 -0
- package/types/react/src/hooks/options/use-audio-options.d.ts +17 -0
- package/types/react/src/hooks/options/use-caption-options.d.ts +24 -0
- package/types/react/src/hooks/options/use-chapter-options.d.ts +18 -0
- package/types/react/src/hooks/options/use-playback-rate-options.d.ts +22 -0
- package/types/react/src/hooks/options/use-video-quality-options.d.ts +35 -0
- package/types/react/src/hooks/use-active-text-cues.d.ts +6 -0
- package/types/react/src/hooks/use-active-text-track.d.ts +5 -0
- package/types/react/src/hooks/use-chapter-title.d.ts +4 -0
- package/types/react/src/hooks/use-dom.d.ts +9 -0
- package/types/react/src/hooks/use-media-context.d.ts +1 -0
- package/types/react/src/hooks/use-media-player.d.ts +7 -0
- package/types/react/src/hooks/use-media-provider.d.ts +7 -0
- package/types/react/src/hooks/use-media-remote.d.ts +12 -0
- package/types/react/src/hooks/use-media-state.d.ts +15 -0
- package/types/react/src/hooks/use-signals.d.ts +5 -0
- package/types/react/src/hooks/use-slider-preview.d.ts +27 -0
- package/types/react/src/hooks/use-slider-state.d.ts +16 -0
- package/types/react/src/hooks/use-state.d.ts +18 -0
- package/types/react/src/hooks/use-text-cues.d.ts +6 -0
- package/types/react/src/hooks/use-thumbnails.d.ts +16 -0
- package/types/react/src/icon.d.ts +17 -0
- package/types/react/src/icons.d.ts +215 -0
- package/types/react/src/index.d.ts +78 -0
- package/types/react/src/providers/remotion/index.d.ts +7 -0
- package/types/react/src/providers/remotion/layout-engine.d.ts +8 -0
- package/types/react/src/providers/remotion/loader.d.ts +9 -0
- package/types/react/src/providers/remotion/playback-engine.d.ts +11 -0
- package/types/react/src/providers/remotion/provider.d.ts +26 -0
- package/types/react/src/providers/remotion/type-check.d.ts +6 -0
- package/types/react/src/providers/remotion/types.d.ts +91 -0
- package/types/react/src/providers/remotion/ui/context.d.ts +17 -0
- package/types/react/src/providers/remotion/ui/error-boundary.d.ts +21 -0
- package/types/react/src/providers/remotion/ui/poster.d.ts +18 -0
- package/types/react/src/providers/remotion/ui/slider-thumbnail.d.ts +17 -0
- package/types/react/src/providers/remotion/ui/thumbnail.d.ts +32 -0
- package/types/react/src/providers/remotion/validate.d.ts +12 -0
- package/types/react/src/source.d.ts +3 -0
- package/types/react/src/utils.d.ts +3 -0
- package/types/vidstack/src/core/api/src-types.d.ts +50 -0
- package/types/vidstack/src/utils/mime.d.ts +15 -0
- package/types/vidstack/src/utils/network.d.ts +17 -0
- package/types/vidstack/src/utils/support.d.ts +72 -0
- package/vite.config.ts +23 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { useSignal } from 'maverick.js/react';
|
|
4
|
+
import { isString } from 'maverick.js/std';
|
|
5
|
+
import { sortVideoQualities, type VideoQuality } from 'vidstack';
|
|
6
|
+
|
|
7
|
+
import { useMediaContext } from '../use-media-context';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @docs {@link https://www.vidstack.io/docs/player/api/hooks/use-video-quality-options}
|
|
11
|
+
*/
|
|
12
|
+
export function useVideoQualityOptions({
|
|
13
|
+
auto = true,
|
|
14
|
+
sort = 'descending',
|
|
15
|
+
}: UseVideoQualityOptions = {}): VideoQualityOptions {
|
|
16
|
+
const media = useMediaContext(),
|
|
17
|
+
{ qualities, quality, autoQuality, canSetQuality } = media.$state,
|
|
18
|
+
$qualities = useSignal(qualities);
|
|
19
|
+
|
|
20
|
+
// Trigger updates.
|
|
21
|
+
useSignal(quality);
|
|
22
|
+
useSignal(autoQuality);
|
|
23
|
+
useSignal(canSetQuality);
|
|
24
|
+
|
|
25
|
+
return React.useMemo(() => {
|
|
26
|
+
const sortedQualities = sortVideoQualities($qualities, sort === 'descending'),
|
|
27
|
+
options = sortedQualities.map<VideoQualityOption>((q) => {
|
|
28
|
+
return {
|
|
29
|
+
quality: q,
|
|
30
|
+
label: q.height + 'p',
|
|
31
|
+
value: getQualityValue(q),
|
|
32
|
+
bitrateText:
|
|
33
|
+
q.bitrate && q.bitrate > 0 ? `${(q.bitrate / 1000000).toFixed(2)} Mbps` : null,
|
|
34
|
+
get selected() {
|
|
35
|
+
return q === quality();
|
|
36
|
+
},
|
|
37
|
+
get autoSelected() {
|
|
38
|
+
return autoQuality();
|
|
39
|
+
},
|
|
40
|
+
select(trigger) {
|
|
41
|
+
const index = qualities().indexOf(q);
|
|
42
|
+
if (index >= 0) media.remote.changeQuality(index, trigger);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (auto) {
|
|
48
|
+
options.unshift({
|
|
49
|
+
quality: null,
|
|
50
|
+
label: isString(auto) ? auto : 'Auto',
|
|
51
|
+
value: 'auto',
|
|
52
|
+
bitrateText: null,
|
|
53
|
+
get selected() {
|
|
54
|
+
return autoQuality();
|
|
55
|
+
},
|
|
56
|
+
get autoSelected() {
|
|
57
|
+
return autoQuality();
|
|
58
|
+
},
|
|
59
|
+
select(trigger) {
|
|
60
|
+
media.remote.requestAutoQuality(trigger);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Object.defineProperty(options, 'disabled', {
|
|
66
|
+
get() {
|
|
67
|
+
return !canSetQuality() || $qualities.length <= 1;
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
Object.defineProperty(options, 'selectedQuality', {
|
|
72
|
+
get() {
|
|
73
|
+
return quality();
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
Object.defineProperty(options, 'selectedValue', {
|
|
78
|
+
get() {
|
|
79
|
+
const $quality = quality();
|
|
80
|
+
return !autoQuality() && $quality ? getQualityValue($quality) : 'auto';
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return options as VideoQualityOptions;
|
|
85
|
+
}, [$qualities, sort]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface UseVideoQualityOptions {
|
|
89
|
+
/**
|
|
90
|
+
* Whether an auto option should be included. A string can be provided to specify the label.
|
|
91
|
+
*/
|
|
92
|
+
auto?: boolean | string;
|
|
93
|
+
/**
|
|
94
|
+
* Specifies how the options should be sorted. The sorting algorithm looks at both the quality
|
|
95
|
+
* resolution and bitrate.
|
|
96
|
+
*
|
|
97
|
+
* - Ascending: 480p, 720p, 720p (higher bitrate), 1080p
|
|
98
|
+
* - Descending: 1080p, 720p (higher bitrate), 720p, 480p
|
|
99
|
+
*
|
|
100
|
+
* @default 'descending'
|
|
101
|
+
*/
|
|
102
|
+
sort?: 'ascending' | 'descending';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type VideoQualityOptions = VideoQualityOption[] & {
|
|
106
|
+
readonly disabled: boolean;
|
|
107
|
+
readonly selectedQuality: VideoQuality | null;
|
|
108
|
+
readonly selectedValue: string;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export interface VideoQualityOption {
|
|
112
|
+
readonly quality: VideoQuality | null;
|
|
113
|
+
readonly label: string;
|
|
114
|
+
readonly value: string;
|
|
115
|
+
readonly selected: boolean;
|
|
116
|
+
readonly autoSelected: boolean;
|
|
117
|
+
readonly bitrateText: string | null;
|
|
118
|
+
select(trigger?: Event): void;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getQualityValue(quality: VideoQuality) {
|
|
122
|
+
return quality.height + '_' + quality.bitrate;
|
|
123
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { listenEvent } from 'maverick.js/std';
|
|
4
|
+
import type { VTTCue } from 'media-captions';
|
|
5
|
+
import type { TextTrack } from 'vidstack';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @docs {@link https://www.vidstack.io/docs/player/api/hooks/use-active-text-cues}
|
|
9
|
+
*/
|
|
10
|
+
export function useActiveTextCues(track: TextTrack | null): VTTCue[] {
|
|
11
|
+
const [activeCues, setActiveCues] = React.useState<VTTCue[]>([]);
|
|
12
|
+
|
|
13
|
+
React.useEffect(() => {
|
|
14
|
+
if (!track) {
|
|
15
|
+
setActiveCues([]);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function onCuesChange() {
|
|
20
|
+
if (track) setActiveCues(track.activeCues as VTTCue[]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
onCuesChange();
|
|
24
|
+
return listenEvent(track, 'cue-change', onCuesChange);
|
|
25
|
+
}, [track]);
|
|
26
|
+
|
|
27
|
+
return activeCues;
|
|
28
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { watchActiveTextTrack, type TextTrack } from 'vidstack';
|
|
4
|
+
|
|
5
|
+
import { useMediaContext } from './use-media-context';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @docs {@link https://www.vidstack.io/docs/player/api/hooks/use-active-text-track}
|
|
9
|
+
*/
|
|
10
|
+
export function useActiveTextTrack(kind: TextTrackKind | TextTrackKind[]): TextTrack | null {
|
|
11
|
+
const media = useMediaContext(),
|
|
12
|
+
[track, setTrack] = React.useState<TextTrack | null>(null);
|
|
13
|
+
|
|
14
|
+
React.useEffect(() => {
|
|
15
|
+
return watchActiveTextTrack(media.textTracks, kind, setTrack);
|
|
16
|
+
}, [kind]);
|
|
17
|
+
|
|
18
|
+
return track;
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useActiveTextCues } from './use-active-text-cues';
|
|
2
|
+
import { useActiveTextTrack } from './use-active-text-track';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @docs {@link https://www.vidstack.io/docs/player/api/hooks/use-chapter-title}
|
|
6
|
+
*/
|
|
7
|
+
export function useChapterTitle(): string {
|
|
8
|
+
const $track = useActiveTextTrack('chapters'),
|
|
9
|
+
$cues = useActiveTextCues($track);
|
|
10
|
+
|
|
11
|
+
return $cues[0]?.text || '';
|
|
12
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { animationFrameThrottle, EventsController, listenEvent, setStyle } from 'maverick.js/std';
|
|
4
|
+
|
|
5
|
+
export function useClassName(el: HTMLElement | null, className?: string) {
|
|
6
|
+
React.useEffect(() => {
|
|
7
|
+
if (!el || !className) return;
|
|
8
|
+
|
|
9
|
+
const tokens = className.split(' ');
|
|
10
|
+
for (const token of tokens) el.classList.add(token);
|
|
11
|
+
|
|
12
|
+
return () => {
|
|
13
|
+
for (const token of tokens) el.classList.remove(token);
|
|
14
|
+
};
|
|
15
|
+
}, [el, className]);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function useResizeObserver(el: Element | null | undefined, callback: () => void) {
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
if (!el) return;
|
|
21
|
+
|
|
22
|
+
callback();
|
|
23
|
+
|
|
24
|
+
const observer = new ResizeObserver(animationFrameThrottle(callback));
|
|
25
|
+
observer.observe(el);
|
|
26
|
+
|
|
27
|
+
return () => observer.disconnect();
|
|
28
|
+
}, [el, callback]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function useTransitionActive(el: Element | null) {
|
|
32
|
+
const [isActive, setIsActive] = React.useState(false);
|
|
33
|
+
|
|
34
|
+
React.useEffect(() => {
|
|
35
|
+
if (!el) return;
|
|
36
|
+
|
|
37
|
+
const events = new EventsController(el)
|
|
38
|
+
.add('transitionstart', () => setIsActive(true))
|
|
39
|
+
.add('transitionend', () => setIsActive(false));
|
|
40
|
+
|
|
41
|
+
return () => events.abort();
|
|
42
|
+
}, [el]);
|
|
43
|
+
|
|
44
|
+
return isActive;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function useMouseEnter(el: Element | null) {
|
|
48
|
+
const [isMouseEnter, setIsMouseEnter] = React.useState(false);
|
|
49
|
+
|
|
50
|
+
React.useEffect(() => {
|
|
51
|
+
if (!el) return;
|
|
52
|
+
|
|
53
|
+
const events = new EventsController(el)
|
|
54
|
+
.add('mouseenter', () => setIsMouseEnter(true))
|
|
55
|
+
.add('mouseleave', () => setIsMouseEnter(false));
|
|
56
|
+
|
|
57
|
+
return () => events.abort();
|
|
58
|
+
}, [el]);
|
|
59
|
+
|
|
60
|
+
return isMouseEnter;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function useFocusIn(el: Element | null) {
|
|
64
|
+
const [isFocusIn, setIsFocusIn] = React.useState(false);
|
|
65
|
+
|
|
66
|
+
React.useEffect(() => {
|
|
67
|
+
if (!el) return;
|
|
68
|
+
|
|
69
|
+
const events = new EventsController(el)
|
|
70
|
+
.add('focusin', () => setIsFocusIn(true))
|
|
71
|
+
.add('focusout', () => setIsFocusIn(false));
|
|
72
|
+
|
|
73
|
+
return () => events.abort();
|
|
74
|
+
}, [el]);
|
|
75
|
+
|
|
76
|
+
return isFocusIn;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function useActive(el: Element | null) {
|
|
80
|
+
const isMouseEnter = useMouseEnter(el),
|
|
81
|
+
isFocusIn = useFocusIn(el),
|
|
82
|
+
prevMouseEnter = React.useRef(false);
|
|
83
|
+
|
|
84
|
+
if (prevMouseEnter.current && !isMouseEnter) return false;
|
|
85
|
+
|
|
86
|
+
prevMouseEnter.current = isMouseEnter;
|
|
87
|
+
return isMouseEnter || isFocusIn;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function useRectCSSVars(root: Element | null, el: Element | null, prefix: string) {
|
|
91
|
+
const onResize = React.useCallback(() => {
|
|
92
|
+
if (root && el) setRectCSSVars(root, el, prefix);
|
|
93
|
+
}, [root, el, prefix]);
|
|
94
|
+
|
|
95
|
+
useResizeObserver(el, onResize);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function setRectCSSVars(root: Element, el: Element, prefix: string) {
|
|
99
|
+
const rect = el.getBoundingClientRect();
|
|
100
|
+
for (const side of ['top', 'left', 'bottom', 'right']) {
|
|
101
|
+
setStyle(root as HTMLElement, `--${prefix}-${side}`, `${rect[side]}px`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function useColorSchemePreference() {
|
|
106
|
+
const [colorScheme, setColorScheme] = React.useState<'light' | 'dark'>('dark');
|
|
107
|
+
|
|
108
|
+
React.useEffect(() => {
|
|
109
|
+
const media = window.matchMedia('(prefers-color-scheme: light)');
|
|
110
|
+
|
|
111
|
+
function onChange() {
|
|
112
|
+
setColorScheme(media.matches ? 'light' : 'dark');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
onChange();
|
|
116
|
+
|
|
117
|
+
return listenEvent(media, 'change', onChange);
|
|
118
|
+
}, []);
|
|
119
|
+
|
|
120
|
+
return colorScheme;
|
|
121
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MediaPlayerInstance } from '../components/primitives/instances';
|
|
2
|
+
import { useMediaContext } from './use-media-context';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns the nearest parent player component.
|
|
6
|
+
*
|
|
7
|
+
* @docs {@link https://www.vidstack.io/docs/player/api/hooks/use-media-player}
|
|
8
|
+
*/
|
|
9
|
+
export function useMediaPlayer(): MediaPlayerInstance | null {
|
|
10
|
+
const context = useMediaContext();
|
|
11
|
+
|
|
12
|
+
if (__DEV__ && !context) {
|
|
13
|
+
throw Error(
|
|
14
|
+
'[vidstack] no media context was found - was this called outside of `<MediaPlayer>`?',
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return context?.player || null;
|
|
19
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { effect } from 'maverick.js';
|
|
4
|
+
import { type MediaProviderAdapter } from 'vidstack';
|
|
5
|
+
|
|
6
|
+
import { useMediaContext } from './use-media-context';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Returns the current parent media provider.
|
|
10
|
+
*
|
|
11
|
+
* @docs {@link https://www.vidstack.io/docs/player/api/hooks/use-media-provider}
|
|
12
|
+
*/
|
|
13
|
+
export function useMediaProvider(): MediaProviderAdapter | null {
|
|
14
|
+
const [provider, setProvider] = React.useState<MediaProviderAdapter | null>(null),
|
|
15
|
+
context = useMediaContext();
|
|
16
|
+
|
|
17
|
+
if (__DEV__ && !context) {
|
|
18
|
+
throw Error(
|
|
19
|
+
'[vidstack] no media context was found - was this called outside of `<MediaPlayer>`?',
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
if (!context) return;
|
|
25
|
+
return effect(() => {
|
|
26
|
+
setProvider(context.$provider());
|
|
27
|
+
});
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
return provider;
|
|
31
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { MediaRemoteControl } from 'vidstack';
|
|
4
|
+
|
|
5
|
+
import { MediaPlayerInstance } from '../components/primitives/instances';
|
|
6
|
+
import { useMediaContext } from './use-media-context';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A media remote provides a simple facade for dispatching media requests to the nearest media
|
|
10
|
+
* player.
|
|
11
|
+
*
|
|
12
|
+
* @param target - The DOM event target to dispatch request events from. Defaults to player
|
|
13
|
+
* if no target is provided.
|
|
14
|
+
*
|
|
15
|
+
* @docs {@link https://www.vidstack.io/docs/player/api/hooks/use-media-remote}
|
|
16
|
+
*/
|
|
17
|
+
export function useMediaRemote(
|
|
18
|
+
target?: EventTarget | null | React.RefObject<EventTarget | null>,
|
|
19
|
+
): MediaRemoteControl {
|
|
20
|
+
const media = useMediaContext(),
|
|
21
|
+
remote = React.useRef<MediaRemoteControl>(null!);
|
|
22
|
+
|
|
23
|
+
if (!remote.current) {
|
|
24
|
+
remote.current = new MediaRemoteControl();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
const ref = target && 'current' in target ? target.current : target,
|
|
29
|
+
isPlayerRef = ref instanceof MediaPlayerInstance,
|
|
30
|
+
player = isPlayerRef ? ref : media?.player;
|
|
31
|
+
|
|
32
|
+
remote.current!.setPlayer(player ?? null);
|
|
33
|
+
remote.current!.setTarget(ref ?? null);
|
|
34
|
+
}, [media, target && 'current' in target ? target.current : target]);
|
|
35
|
+
|
|
36
|
+
return remote.current;
|
|
37
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { useSignal, useSignalRecord, useStateContext } from 'maverick.js/react';
|
|
4
|
+
import { mediaState, type MediaState } from 'vidstack';
|
|
5
|
+
|
|
6
|
+
import { MediaPlayerInstance } from '../components/primitives/instances';
|
|
7
|
+
|
|
8
|
+
const mediaStateRecord = MediaPlayerInstance.state.record,
|
|
9
|
+
initialMediaStore = Object.keys(mediaStateRecord).reduce(
|
|
10
|
+
(store, prop) => ({
|
|
11
|
+
...store,
|
|
12
|
+
[prop]() {
|
|
13
|
+
return mediaStateRecord[prop];
|
|
14
|
+
},
|
|
15
|
+
}),
|
|
16
|
+
{},
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* This hook is used to subscribe to a specific media state.
|
|
21
|
+
*
|
|
22
|
+
* @docs {@link https://www.vidstack.io/docs/player/api/hooks/use-media-state}
|
|
23
|
+
*/
|
|
24
|
+
export function useMediaState<T extends keyof MediaState>(
|
|
25
|
+
prop: T,
|
|
26
|
+
ref?: React.RefObject<MediaPlayerInstance | null>,
|
|
27
|
+
): MediaState[T] {
|
|
28
|
+
const $state = useStateContext(mediaState);
|
|
29
|
+
|
|
30
|
+
if (__DEV__ && !$state && !ref) {
|
|
31
|
+
console.warn(
|
|
32
|
+
`[vidstack] \`useMediaState\` requires \`RefObject<MediaPlayerInstance>\` argument if called` +
|
|
33
|
+
' outside the `<MediaPlayer>` component',
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return useSignal((ref?.current?.$state || $state || initialMediaStore)[prop]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* This hook is used to subscribe to the current media state on the nearest parent player.
|
|
42
|
+
*
|
|
43
|
+
* @docs {@link https://vidstack.io/docs/player/core-concepts/state-management#reading}
|
|
44
|
+
*/
|
|
45
|
+
export function useMediaStore(
|
|
46
|
+
ref?: React.RefObject<MediaPlayerInstance | null>,
|
|
47
|
+
): Readonly<MediaState> {
|
|
48
|
+
const $state = useStateContext(mediaState);
|
|
49
|
+
|
|
50
|
+
if (__DEV__ && !$state && !ref) {
|
|
51
|
+
console.warn(
|
|
52
|
+
`[vidstack] \`useMediaStore\` requires \`RefObject<MediaPlayerInstance>\` argument if called` +
|
|
53
|
+
' outside the `<MediaPlayer>` component',
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return useSignalRecord(ref?.current ? ref.current.$state : $state || initialMediaStore);
|
|
58
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { computed, effect, scoped, signal, type MaybeStopEffect } from 'maverick.js';
|
|
4
|
+
import { useReactScope } from 'maverick.js/react';
|
|
5
|
+
|
|
6
|
+
export function createSignal<T>(initialValue: T, deps: any[] = []) {
|
|
7
|
+
const scope = useReactScope();
|
|
8
|
+
return React.useMemo(() => scoped(() => signal(initialValue), scope)!, [scope, ...deps]);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createComputed<T>(compute: () => T, deps: any[] = []) {
|
|
12
|
+
const scope = useReactScope();
|
|
13
|
+
return React.useMemo(() => scoped(() => computed(compute), scope)!, [scope, ...deps]);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createEffect(compute: () => MaybeStopEffect, deps: any[] = []) {
|
|
17
|
+
const scope = useReactScope();
|
|
18
|
+
React.useEffect(() => scoped(() => effect(compute), scope)!, [scope, ...deps]);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useScoped<T>(compute: () => T) {
|
|
22
|
+
const scope = useReactScope();
|
|
23
|
+
return React.useMemo(() => scoped(compute, scope)!, [scope]);
|
|
24
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { effect, signal } from 'maverick.js';
|
|
4
|
+
import { EventsController, listenEvent } from 'maverick.js/std';
|
|
5
|
+
import { updateSliderPreviewPlacement, type SliderOrientation } from 'vidstack';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @docs {@link https://www.vidstack.io/docs/player/api/hooks/use-slider-preview}
|
|
9
|
+
*/
|
|
10
|
+
export function useSliderPreview({
|
|
11
|
+
clamp = false,
|
|
12
|
+
offset = 0,
|
|
13
|
+
orientation = 'horizontal',
|
|
14
|
+
}: UseSliderPreview = {}) {
|
|
15
|
+
const [rootRef, setRootRef] = React.useState<HTMLElement | null>(null),
|
|
16
|
+
[previewRef, setPreviewRef] = React.useState<HTMLElement | null>(null),
|
|
17
|
+
[pointerValue, setPointerValue] = React.useState(0),
|
|
18
|
+
[isVisible, setIsVisible] = React.useState(false);
|
|
19
|
+
|
|
20
|
+
React.useEffect(() => {
|
|
21
|
+
if (!rootRef) return;
|
|
22
|
+
|
|
23
|
+
const dragging = signal(false);
|
|
24
|
+
|
|
25
|
+
function updatePointerValue(event: PointerEvent) {
|
|
26
|
+
if (!rootRef) return;
|
|
27
|
+
setPointerValue(getPointerValue(rootRef, event, orientation));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return effect(() => {
|
|
31
|
+
if (!dragging()) {
|
|
32
|
+
new EventsController(rootRef)
|
|
33
|
+
.add('pointerenter', () => {
|
|
34
|
+
setIsVisible(true);
|
|
35
|
+
previewRef?.setAttribute('data-visible', '');
|
|
36
|
+
})
|
|
37
|
+
.add('pointerdown', (event) => {
|
|
38
|
+
dragging.set(true);
|
|
39
|
+
updatePointerValue(event);
|
|
40
|
+
})
|
|
41
|
+
.add('pointerleave', () => {
|
|
42
|
+
setIsVisible(false);
|
|
43
|
+
previewRef?.removeAttribute('data-visible');
|
|
44
|
+
})
|
|
45
|
+
.add('pointermove', updatePointerValue);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
previewRef?.setAttribute('data-dragging', '');
|
|
49
|
+
|
|
50
|
+
new EventsController(document)
|
|
51
|
+
.add('pointerup', (event) => {
|
|
52
|
+
dragging.set(false);
|
|
53
|
+
previewRef?.removeAttribute('data-dragging');
|
|
54
|
+
updatePointerValue(event);
|
|
55
|
+
})
|
|
56
|
+
.add('pointermove', updatePointerValue)
|
|
57
|
+
.add('touchmove', (e) => e.preventDefault(), { passive: false });
|
|
58
|
+
});
|
|
59
|
+
}, [rootRef]);
|
|
60
|
+
|
|
61
|
+
React.useEffect(() => {
|
|
62
|
+
if (previewRef) {
|
|
63
|
+
previewRef.style.setProperty('--slider-pointer', pointerValue + '%');
|
|
64
|
+
}
|
|
65
|
+
}, [previewRef, pointerValue]);
|
|
66
|
+
|
|
67
|
+
React.useEffect(() => {
|
|
68
|
+
if (!previewRef) return;
|
|
69
|
+
|
|
70
|
+
const update = () => {
|
|
71
|
+
updateSliderPreviewPlacement(previewRef, {
|
|
72
|
+
offset,
|
|
73
|
+
clamp,
|
|
74
|
+
orientation,
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
update();
|
|
79
|
+
const resize = new ResizeObserver(update);
|
|
80
|
+
resize.observe(previewRef);
|
|
81
|
+
return () => resize.disconnect();
|
|
82
|
+
}, [previewRef, clamp, offset, orientation]);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
previewRootRef: setRootRef,
|
|
86
|
+
previewRef: setPreviewRef,
|
|
87
|
+
previewValue: pointerValue,
|
|
88
|
+
isPreviewVisible: isVisible,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface UseSliderPreview {
|
|
93
|
+
/**
|
|
94
|
+
* Whether the preview should be clamped to the start and end of the slider root. If `true` the
|
|
95
|
+
* preview won't be placed outside the root bounds.
|
|
96
|
+
*/
|
|
97
|
+
clamp?: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* The distance in pixels between the preview and the slider root. You can also set
|
|
100
|
+
* the CSS variable `--media-slider-preview-offset` to adjust this offset.
|
|
101
|
+
*/
|
|
102
|
+
offset?: number;
|
|
103
|
+
/**
|
|
104
|
+
* The orientation of the slider.
|
|
105
|
+
*/
|
|
106
|
+
orientation?: SliderOrientation;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getPointerValue(root: HTMLElement, event: PointerEvent, orientation: SliderOrientation) {
|
|
110
|
+
let thumbPositionRate: number,
|
|
111
|
+
rect = root.getBoundingClientRect();
|
|
112
|
+
|
|
113
|
+
if (orientation === 'vertical') {
|
|
114
|
+
const { bottom: trackBottom, height: trackHeight } = rect;
|
|
115
|
+
thumbPositionRate = (trackBottom - event.clientY) / trackHeight;
|
|
116
|
+
} else {
|
|
117
|
+
const { left: trackLeft, width: trackWidth } = rect;
|
|
118
|
+
thumbPositionRate = (event.clientX - trackLeft) / trackWidth;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return round(Math.max(0, Math.min(100, 100 * thumbPositionRate)));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function round(num: number) {
|
|
125
|
+
return Number(num.toFixed(3));
|
|
126
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { useSignal, useSignalRecord, useStateContext } from 'maverick.js/react';
|
|
4
|
+
import { sliderState, type SliderState } from 'vidstack';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
SliderInstance,
|
|
8
|
+
type TimeSliderInstance,
|
|
9
|
+
type VolumeSliderInstance,
|
|
10
|
+
} from '../components/primitives/instances';
|
|
11
|
+
|
|
12
|
+
const sliderStateRecord = SliderInstance.state.record,
|
|
13
|
+
initialSliderStore = Object.keys(sliderStateRecord).reduce(
|
|
14
|
+
(store, prop) => ({
|
|
15
|
+
...store,
|
|
16
|
+
[prop]() {
|
|
17
|
+
return sliderStateRecord[prop];
|
|
18
|
+
},
|
|
19
|
+
}),
|
|
20
|
+
{},
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* This hook is used to subscribe to a specific slider state.
|
|
25
|
+
*
|
|
26
|
+
* @docs {@link https://www.vidstack.io/docs/player/api/hooks/use-slider-state}
|
|
27
|
+
*/
|
|
28
|
+
export function useSliderState<T extends keyof SliderState>(
|
|
29
|
+
prop: T,
|
|
30
|
+
ref?: React.RefObject<SliderInstance | VolumeSliderInstance | TimeSliderInstance | null>,
|
|
31
|
+
): SliderState[T] {
|
|
32
|
+
const $state = useStateContext(sliderState);
|
|
33
|
+
|
|
34
|
+
if (__DEV__ && !$state && !ref) {
|
|
35
|
+
console.warn(
|
|
36
|
+
`[vidstack] \`useSliderState\` requires \`RefObject<SliderInstance>\` argument if called` +
|
|
37
|
+
' outside of a slider component',
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return useSignal((ref?.current?.$state || $state || initialSliderStore)[prop]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* This hook is used to subscribe to the current slider state on the given or nearest slider
|
|
46
|
+
* component.
|
|
47
|
+
*
|
|
48
|
+
* @docs {@link https://www.vidstack.io/docs/player/api/hooks/use-slider-state#store}
|
|
49
|
+
*/
|
|
50
|
+
export function useSliderStore(
|
|
51
|
+
ref?: React.RefObject<SliderInstance | VolumeSliderInstance | TimeSliderInstance | null>,
|
|
52
|
+
): Readonly<SliderState> {
|
|
53
|
+
const $state = useStateContext(sliderState);
|
|
54
|
+
|
|
55
|
+
if (__DEV__ && !$state && !ref) {
|
|
56
|
+
console.warn(
|
|
57
|
+
`[vidstack] \`useSliderStore\` requires \`RefObject<SliderInstance>\` argument if called` +
|
|
58
|
+
' outside of a slider component',
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return useSignalRecord(ref?.current ? ref.current.$state : $state || initialSliderStore);
|
|
63
|
+
}
|