docs(site): complete menu radio group references with demos and options hooks (#1807)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Darius Cepulis
2026-07-14 15:34:52 -07:00
committed by GitHub
co-authored by Claude Fable 5
parent 82b9e43bb5
commit a9a09a7e52
54 changed files with 1953 additions and 77 deletions
@@ -0,0 +1,9 @@
---
import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro';
import html from './BasicUsage.html?raw';
---
<HtmlDemo html={html} />
<script>
import "./BasicUsage.ts";
</script>
@@ -0,0 +1,75 @@
.video-player,
.video-player media-container {
position: relative;
display: block;
}
.video-media {
width: 100%;
}
.menu-bar {
position: absolute;
right: 10px;
bottom: 10px;
}
.settings-trigger {
padding: 6px 16px;
color: black;
cursor: pointer;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(255, 255, 255, 0.35);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
.menu {
--media-menu-side-offset: 8px;
box-sizing: border-box;
display: grid;
gap: 2px;
min-width: 180px;
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
padding: 6px;
overflow: auto;
overscroll-behavior: none;
font-size: 14px;
color: white;
background: rgba(0, 0, 0, 0.88);
border-radius: 8px;
backdrop-filter: blur(10px);
}
.menu-group {
display: grid;
gap: 2px;
}
.menu-item {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
min-height: 32px;
padding: 0 10px;
font: inherit;
color: inherit;
cursor: pointer;
background: none;
border: 0;
border-radius: 6px;
}
.menu-item[data-highlighted] {
background: rgba(255, 255, 255, 0.16);
}
.menu-indicator {
opacity: 0;
}
media-menu-radio-item[aria-checked="true"] .menu-indicator {
opacity: 1;
}
@@ -0,0 +1,26 @@
<video-player class="video-player">
<media-container>
<hlsjs-video
class="video-media"
src="https://stream.mux.com/s41JYeqIpBMBzE4OzxDyGR2yrp2hD1CQ6gJN9SlVGDQ.m3u8"
autoplay
crossorigin="anonymous"
muted
playsinline
loop
></hlsjs-video>
<div class="menu-bar">
<button type="button" commandfor="audio-menu" class="settings-trigger">Audio</button>
<media-menu id="audio-menu" side="top" align="end" class="menu">
<media-audio-track-radio-group class="menu-group" label="Audio tracks">
<template>
<media-menu-radio-item class="menu-item">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="menu-indicator"></media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-audio-track-radio-group>
</media-menu>
</div>
</media-container>
</video-player>
@@ -0,0 +1,4 @@
import '@videojs/html/video/player';
import '@videojs/html/media/hlsjs-video';
import '@videojs/html/ui/menu';
import '@videojs/html/ui/audio-track-radio-group';
@@ -0,0 +1,75 @@
.media-container {
position: relative;
}
.media-container video {
width: 100%;
}
.menu-bar {
position: absolute;
right: 10px;
bottom: 10px;
}
.settings-trigger {
padding: 6px 16px;
color: black;
cursor: pointer;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(255, 255, 255, 0.35);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
.menu {
--media-menu-side-offset: 8px;
box-sizing: border-box;
display: grid;
gap: 2px;
min-width: 180px;
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
padding: 6px;
margin: 0;
overflow: auto;
overscroll-behavior: none;
font-size: 14px;
color: white;
background: rgba(0, 0, 0, 0.88);
border: 0;
border-radius: 8px;
backdrop-filter: blur(10px);
}
.menu-group {
display: grid;
gap: 2px;
}
.menu-item {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
min-height: 32px;
padding: 0 10px;
font: inherit;
color: inherit;
cursor: pointer;
background: none;
border: 0;
border-radius: 6px;
}
.menu-item[data-highlighted] {
background: rgba(255, 255, 255, 0.16);
}
.menu-indicator {
opacity: 0;
}
[role="menuitemradio"][aria-checked="true"] .menu-indicator {
opacity: 1;
}
@@ -0,0 +1,50 @@
import { createPlayer, Menu, useAudioTrackOptions } from '@videojs/react';
import { HlsJsVideo } from '@videojs/react/media/hlsjs-video';
import { videoFeatures } from '@videojs/react/video';
import type { ReactNode } from 'react';
const Player = createPlayer({ features: videoFeatures });
const src = 'https://stream.mux.com/s41JYeqIpBMBzE4OzxDyGR2yrp2hD1CQ6gJN9SlVGDQ.m3u8';
function AudioMenu(): ReactNode {
const audioTrack = useAudioTrackOptions();
if (audioTrack?.state.availability !== 'available') return null;
return (
<Menu.Root side="top" align="end">
<Menu.Trigger className="settings-trigger" render={<button type="button" />}>
Audio
</Menu.Trigger>
<Menu.Content className="menu">
<Menu.RadioGroup
className="menu-group"
value={audioTrack.value}
onValueChange={audioTrack.setValue}
aria-label="Audio tracks"
>
{audioTrack.options.map((option) => (
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled} className="menu-item">
{option.label}
<Menu.ItemIndicator checked={option.value === audioTrack.value} forceMount className="menu-indicator">
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
);
}
export default function BasicUsage() {
return (
<Player.Provider>
<Player.Container className="media-container">
<HlsJsVideo src={src} autoPlay crossOrigin="anonymous" muted playsInline loop />
<div className="menu-bar">
<AudioMenu />
</div>
</Player.Container>
</Player.Provider>
);
}
@@ -0,0 +1,9 @@
---
import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro';
import html from './BasicUsage.html?raw';
---
<HtmlDemo html={html} />
<script>
import "./BasicUsage.ts";
</script>
@@ -0,0 +1,75 @@
.video-player,
.video-player media-container {
position: relative;
display: block;
}
.video-player video {
width: 100%;
}
.menu-bar {
position: absolute;
right: 10px;
bottom: 10px;
}
.settings-trigger {
padding: 6px 16px;
color: black;
cursor: pointer;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(255, 255, 255, 0.35);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
.menu {
--media-menu-side-offset: 8px;
box-sizing: border-box;
display: grid;
gap: 2px;
min-width: 180px;
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
padding: 6px;
overflow: auto;
overscroll-behavior: none;
font-size: 14px;
color: white;
background: rgba(0, 0, 0, 0.88);
border-radius: 8px;
backdrop-filter: blur(10px);
}
.menu-group {
display: grid;
gap: 2px;
}
.menu-item {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
min-height: 32px;
padding: 0 10px;
font: inherit;
color: inherit;
cursor: pointer;
background: none;
border: 0;
border-radius: 6px;
}
.menu-item[data-highlighted] {
background: rgba(255, 255, 255, 0.16);
}
.menu-indicator {
opacity: 0;
}
media-menu-radio-item[aria-checked="true"] .menu-indicator {
opacity: 1;
}
@@ -0,0 +1,27 @@
<video-player class="video-player">
<media-container>
<video
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
playsinline
loop
>
<track kind="captions" src="/docs/demos/captions-button/captions.vtt" srclang="en" label="English" />
<track kind="subtitles" src="/docs/demos/captions-button/captions.vtt" srclang="es" label="Spanish" />
</video>
<div class="menu-bar">
<button type="button" commandfor="captions-menu" class="settings-trigger">Captions</button>
<media-menu id="captions-menu" side="top" align="end" class="menu">
<media-captions-radio-group class="menu-group" label="Captions">
<template>
<media-menu-radio-item class="menu-item">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="menu-indicator"></media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-captions-radio-group>
</media-menu>
</div>
</media-container>
</video-player>
@@ -0,0 +1,3 @@
import '@videojs/html/video/player';
import '@videojs/html/ui/menu';
import '@videojs/html/ui/captions-radio-group';
@@ -0,0 +1,75 @@
.media-container {
position: relative;
}
.media-container video {
width: 100%;
}
.menu-bar {
position: absolute;
right: 10px;
bottom: 10px;
}
.settings-trigger {
padding: 6px 16px;
color: black;
cursor: pointer;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(255, 255, 255, 0.35);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
.menu {
--media-menu-side-offset: 8px;
box-sizing: border-box;
display: grid;
gap: 2px;
min-width: 180px;
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
padding: 6px;
margin: 0;
overflow: auto;
overscroll-behavior: none;
font-size: 14px;
color: white;
background: rgba(0, 0, 0, 0.88);
border: 0;
border-radius: 8px;
backdrop-filter: blur(10px);
}
.menu-group {
display: grid;
gap: 2px;
}
.menu-item {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
min-height: 32px;
padding: 0 10px;
font: inherit;
color: inherit;
cursor: pointer;
background: none;
border: 0;
border-radius: 6px;
}
.menu-item[data-highlighted] {
background: rgba(255, 255, 255, 0.16);
}
.menu-indicator {
opacity: 0;
}
[role="menuitemradio"][aria-checked="true"] .menu-indicator {
opacity: 1;
}
@@ -0,0 +1,57 @@
import { createPlayer, Menu, useCaptionsOptions } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
import type { ReactNode } from 'react';
const Player = createPlayer({ features: videoFeatures });
function CaptionsMenu(): ReactNode {
const captions = useCaptionsOptions();
if (captions?.state.availability !== 'available') return null;
return (
<Menu.Root side="top" align="end">
<Menu.Trigger className="settings-trigger" render={<button type="button" />}>
Captions
</Menu.Trigger>
<Menu.Content className="menu">
<Menu.RadioGroup
className="menu-group"
value={captions.value}
onValueChange={captions.setValue}
aria-label="Captions"
>
{captions.options.map((option) => (
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled} className="menu-item">
{option.label}
<Menu.ItemIndicator checked={option.value === captions.value} forceMount className="menu-indicator">
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
);
}
export default function BasicUsage() {
return (
<Player.Provider>
<Player.Container className="media-container">
<Video
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoPlay
muted
playsInline
loop
>
<track kind="captions" src="/docs/demos/captions-button/captions.vtt" srcLang="en" label="English" />
<track kind="subtitles" src="/docs/demos/captions-button/captions.vtt" srcLang="es" label="Spanish" />
</Video>
<div className="menu-bar">
<CaptionsMenu />
</div>
</Player.Container>
</Player.Provider>
);
}
@@ -2,7 +2,7 @@
<media-container>
<hlsjs-video
class="video-media"
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4.m3u8"
src="https://stream.mux.com/s41JYeqIpBMBzE4OzxDyGR2yrp2hD1CQ6gJN9SlVGDQ.m3u8"
autoplay
crossorigin="anonymous"
muted
@@ -11,7 +11,7 @@ import { videoFeatures } from '@videojs/react/video';
import type { ReactNode } from 'react';
const Player = createPlayer({ features: videoFeatures });
const src = 'https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4.m3u8';
const src = 'https://stream.mux.com/s41JYeqIpBMBzE4OzxDyGR2yrp2hD1CQ6gJN9SlVGDQ.m3u8';
function SettingsMenu(): ReactNode {
const playbackRate = usePlaybackRateOptions();
@@ -0,0 +1,9 @@
---
import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro';
import html from './BasicUsage.html?raw';
---
<HtmlDemo html={html} />
<script>
import "./BasicUsage.ts";
</script>
@@ -0,0 +1,75 @@
.video-player,
.video-player media-container {
position: relative;
display: block;
}
.video-player video {
width: 100%;
}
.menu-bar {
position: absolute;
right: 10px;
bottom: 10px;
}
.settings-trigger {
padding: 6px 16px;
color: black;
cursor: pointer;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(255, 255, 255, 0.35);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
.menu {
--media-menu-side-offset: 8px;
box-sizing: border-box;
display: grid;
gap: 2px;
min-width: 180px;
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
padding: 6px;
overflow: auto;
overscroll-behavior: none;
font-size: 14px;
color: white;
background: rgba(0, 0, 0, 0.88);
border-radius: 8px;
backdrop-filter: blur(10px);
}
.menu-group {
display: grid;
gap: 2px;
}
.menu-item {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
min-height: 32px;
padding: 0 10px;
font: inherit;
color: inherit;
cursor: pointer;
background: none;
border: 0;
border-radius: 6px;
}
.menu-item[data-highlighted] {
background: rgba(255, 255, 255, 0.16);
}
.menu-indicator {
opacity: 0;
}
media-menu-radio-item[aria-checked="true"] .menu-indicator {
opacity: 1;
}
@@ -0,0 +1,24 @@
<video-player class="video-player">
<media-container>
<video
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoplay
muted
playsinline
loop
></video>
<div class="menu-bar">
<button type="button" commandfor="speed-menu" class="settings-trigger">Speed</button>
<media-menu id="speed-menu" side="top" align="end" class="menu">
<media-playback-rate-radio-group class="menu-group" aria-label="Speed">
<template>
<media-menu-radio-item class="menu-item">
<span data-part="label"></span>
<media-menu-item-indicator force-mount class="menu-indicator"></media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-playback-rate-radio-group>
</media-menu>
</div>
</media-container>
</video-player>
@@ -0,0 +1,3 @@
import '@videojs/html/video/player';
import '@videojs/html/ui/menu';
import '@videojs/html/ui/playback-rate-radio-group';
@@ -0,0 +1,75 @@
.media-container {
position: relative;
}
.media-container video {
width: 100%;
}
.menu-bar {
position: absolute;
right: 10px;
bottom: 10px;
}
.settings-trigger {
padding: 6px 16px;
color: black;
cursor: pointer;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(255, 255, 255, 0.35);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
.menu {
--media-menu-side-offset: 8px;
box-sizing: border-box;
display: grid;
gap: 2px;
min-width: 180px;
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
padding: 6px;
margin: 0;
overflow: auto;
overscroll-behavior: none;
font-size: 14px;
color: white;
background: rgba(0, 0, 0, 0.88);
border: 0;
border-radius: 8px;
backdrop-filter: blur(10px);
}
.menu-group {
display: grid;
gap: 2px;
}
.menu-item {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
min-height: 32px;
padding: 0 10px;
font: inherit;
color: inherit;
cursor: pointer;
background: none;
border: 0;
border-radius: 6px;
}
.menu-item[data-highlighted] {
background: rgba(255, 255, 255, 0.16);
}
.menu-indicator {
opacity: 0;
}
[role="menuitemradio"][aria-checked="true"] .menu-indicator {
opacity: 1;
}
@@ -0,0 +1,54 @@
import { createPlayer, Menu, usePlaybackRateOptions } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
import type { ReactNode } from 'react';
const Player = createPlayer({ features: videoFeatures });
function SpeedMenu(): ReactNode {
const playbackRate = usePlaybackRateOptions();
if (playbackRate?.state.availability !== 'available') return null;
return (
<Menu.Root side="top" align="end">
<Menu.Trigger className="settings-trigger" render={<button type="button" />}>
Speed
</Menu.Trigger>
<Menu.Content className="menu">
<Menu.RadioGroup
className="menu-group"
value={playbackRate.value}
onValueChange={playbackRate.setValue}
aria-label="Speed"
>
{playbackRate.options.map((option) => (
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled} className="menu-item">
{option.label}
<Menu.ItemIndicator checked={option.value === playbackRate.value} forceMount className="menu-indicator">
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
);
}
export default function BasicUsage() {
return (
<Player.Provider>
<Player.Container className="media-container">
<Video
src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4"
autoPlay
muted
playsInline
loop
/>
<div className="menu-bar">
<SpeedMenu />
</div>
</Player.Container>
</Player.Provider>
);
}
@@ -0,0 +1,9 @@
---
import HtmlDemo from '@/components/docs/demos/HtmlDemo.astro';
import html from './BasicUsage.html?raw';
---
<HtmlDemo html={html} />
<script>
import "./BasicUsage.ts";
</script>
@@ -0,0 +1,85 @@
.video-player,
.video-player media-container {
position: relative;
display: block;
}
.video-media {
width: 100%;
}
.menu-bar {
position: absolute;
right: 10px;
bottom: 10px;
}
.settings-trigger {
padding: 6px 16px;
color: black;
cursor: pointer;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(255, 255, 255, 0.35);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
.menu {
--media-menu-side-offset: 8px;
box-sizing: border-box;
display: grid;
gap: 2px;
min-width: 180px;
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
padding: 6px;
overflow: auto;
overscroll-behavior: none;
font-size: 14px;
color: white;
background: rgba(0, 0, 0, 0.88);
border-radius: 8px;
backdrop-filter: blur(10px);
}
.menu-group {
display: grid;
gap: 2px;
}
.menu-item {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
min-height: 32px;
padding: 0 10px;
font: inherit;
color: inherit;
cursor: pointer;
background: none;
border: 0;
border-radius: 6px;
}
.menu-item[data-highlighted] {
background: rgba(255, 255, 255, 0.16);
}
.menu-tier {
margin-left: 2px;
font-size: 10px;
}
.menu-badge {
margin-left: auto;
color: rgba(255, 255, 255, 0.72);
}
.menu-indicator {
opacity: 0;
}
media-menu-radio-item[aria-checked="true"] .menu-indicator {
opacity: 1;
}
@@ -0,0 +1,30 @@
<video-player class="video-player">
<media-container>
<hlsjs-video
class="video-media"
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4.m3u8"
autoplay
crossorigin="anonymous"
muted
playsinline
loop
></hlsjs-video>
<div class="menu-bar">
<button type="button" commandfor="quality-menu" class="settings-trigger">Quality</button>
<media-menu id="quality-menu" side="top" align="end" class="menu">
<media-quality-radio-group class="menu-group" label="Quality">
<template>
<media-menu-radio-item class="menu-item">
<span>
<span data-part="label"></span>
<sup data-part="tier" class="menu-tier"></sup>
</span>
<span data-part="badge" class="menu-badge"></span>
<media-menu-item-indicator force-mount class="menu-indicator"></media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-quality-radio-group>
</media-menu>
</div>
</media-container>
</video-player>
@@ -0,0 +1,4 @@
import '@videojs/html/video/player';
import '@videojs/html/media/hlsjs-video';
import '@videojs/html/ui/menu';
import '@videojs/html/ui/quality-radio-group';
@@ -0,0 +1,85 @@
.media-container {
position: relative;
}
.media-container video {
width: 100%;
}
.menu-bar {
position: absolute;
right: 10px;
bottom: 10px;
}
.settings-trigger {
padding: 6px 16px;
color: black;
cursor: pointer;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(255, 255, 255, 0.35);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
.menu {
--media-menu-side-offset: 8px;
box-sizing: border-box;
display: grid;
gap: 2px;
min-width: 180px;
max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
padding: 6px;
margin: 0;
overflow: auto;
overscroll-behavior: none;
font-size: 14px;
color: white;
background: rgba(0, 0, 0, 0.88);
border: 0;
border-radius: 8px;
backdrop-filter: blur(10px);
}
.menu-group {
display: grid;
gap: 2px;
}
.menu-item {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
min-height: 32px;
padding: 0 10px;
font: inherit;
color: inherit;
cursor: pointer;
background: none;
border: 0;
border-radius: 6px;
}
.menu-item[data-highlighted] {
background: rgba(255, 255, 255, 0.16);
}
.menu-tier {
margin-left: 2px;
font-size: 10px;
}
.menu-badge {
margin-left: auto;
color: rgba(255, 255, 255, 0.72);
}
.menu-indicator {
opacity: 0;
}
[role="menuitemradio"][aria-checked="true"] .menu-indicator {
opacity: 1;
}
@@ -0,0 +1,54 @@
import { createPlayer, Menu, useQualityOptions } from '@videojs/react';
import { HlsJsVideo } from '@videojs/react/media/hlsjs-video';
import { videoFeatures } from '@videojs/react/video';
import type { ReactNode } from 'react';
const Player = createPlayer({ features: videoFeatures });
const src = 'https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4.m3u8';
function QualityMenu(): ReactNode {
const quality = useQualityOptions();
if (quality?.state.availability !== 'available') return null;
return (
<Menu.Root side="top" align="end">
<Menu.Trigger className="settings-trigger" render={<button type="button" />}>
Quality
</Menu.Trigger>
<Menu.Content className="menu">
<Menu.RadioGroup
className="menu-group"
value={quality.value}
onValueChange={quality.setValue}
aria-label="Quality"
>
{quality.options.map((option) => (
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled} className="menu-item">
<span>
{option.label}
{option.tier ? <sup className="menu-tier">{option.tier}</sup> : null}
</span>
{option.badge ? <span className="menu-badge">{option.badge}</span> : null}
<Menu.ItemIndicator checked={option.value === quality.value} forceMount className="menu-indicator">
</Menu.ItemIndicator>
</Menu.RadioItem>
))}
</Menu.RadioGroup>
</Menu.Content>
</Menu.Root>
);
}
export default function BasicUsage() {
return (
<Player.Provider>
<Player.Container className="media-container">
<HlsJsVideo src={src} autoPlay crossOrigin="anonymous" muted playsInline loop />
<div className="menu-bar">
<QualityMenu />
</div>
</Player.Container>
</Player.Provider>
);
}
@@ -8,6 +8,19 @@ description: A menu radio group for selecting an audio track
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import Demo from "@/components/docs/demos/Demo.astro";
{/* React demos */}
import BasicUsageDemoReact from "@/components/docs/demos/audio-track-radio-group/react/css/BasicUsage";
import basicUsageReactTsx from "@/components/docs/demos/audio-track-radio-group/react/css/BasicUsage.tsx?raw";
import basicUsageReactCss from "@/components/docs/demos/audio-track-radio-group/react/css/BasicUsage.css?raw";
{/* HTML demos */}
import BasicUsageDemoHtml from "@/components/docs/demos/audio-track-radio-group/html/css/BasicUsage.astro";
import basicUsageHtml from "@/components/docs/demos/audio-track-radio-group/html/css/BasicUsage.html?raw";
import basicUsageHtmlCss from "@/components/docs/demos/audio-track-radio-group/html/css/BasicUsage.css?raw";
import basicUsageHtmlTs from "@/components/docs/demos/audio-track-radio-group/html/css/BasicUsage.ts?raw";
Creates radio items from the player audio track state and selects the enabled track.
@@ -15,16 +28,11 @@ Creates radio items from the player audio track state and selects the enabled tr
<FrameworkCase frameworks={["react"]}>
```tsx
const audioTrack = useAudioTrackOptions();
<Menu.RadioGroup value={audioTrack.value} onValueChange={audioTrack.setValue}>
<Menu.GroupLabel>Audio</Menu.GroupLabel>
{audioTrack.options.map((option) => (
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled}>
{option.label}
<Menu.ItemIndicator checked={option.value === audioTrack.value} />
</Menu.RadioItem>
))}
<Menu.RadioGroup>
<Menu.GroupLabel />
<Menu.RadioItem>
<Menu.ItemIndicator />
</Menu.RadioItem>
</Menu.RadioGroup>
```
</FrameworkCase>
@@ -47,7 +55,7 @@ Creates radio items from the player audio track state and selects the enabled tr
The group is available when the configured media exposes more than one audio track. Labels use the track label, then language, then kind. Pass `formatTrack` to customize the visible labels.
<FrameworkCase frameworks={["react"]}>
`useAudioTrackOptions` returns `null` when the <DocsLink slug="reference/feature-audio-track">audio track feature</DocsLink> is not configured. Use the returned options with <DocsLink slug="reference/menu">`Menu.RadioGroup`</DocsLink>.
<DocsLink slug="reference/use-audio-track-options">`useAudioTrackOptions`</DocsLink> returns `null` when the <DocsLink slug="reference/feature-audio-track">audio track feature</DocsLink> is not configured. Use the returned options with <DocsLink slug="reference/menu">`Menu.RadioGroup`</DocsLink>.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
@@ -72,7 +80,42 @@ The group is available when the configured media exposes more than one audio tra
## Accessibility
The group uses the menu radio group pattern. It receives an accessible label from the `label` prop or defaults to `Audio`.
The group uses the menu radio group pattern.
<FrameworkCase frameworks={["react"]}>
Give `Menu.RadioGroup` an accessible name with `aria-label` or a nested `Menu.GroupLabel`.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
The element receives an accessible label from the `label` property or defaults to `Audio`.
</FrameworkCase>
## Examples
### Basic usage
<FrameworkCase frameworks={["react"]}>
<StyleCase styles={["css"]}>
<Demo files={[
{ title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
{ title: "App.css", code: basicUsageReactCss, lang: "css" },
]}>
<BasicUsageDemoReact client:idle />
</Demo>
</StyleCase>
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
<StyleCase styles={["css"]}>
<Demo files={[
{ title: "index.html", code: basicUsageHtml, lang: "html" },
{ title: "index.css", code: basicUsageHtmlCss, lang: "css" },
{ title: "index.ts", code: basicUsageHtmlTs, lang: "ts" },
]}>
<BasicUsageDemoHtml />
</Demo>
</StyleCase>
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
<ComponentReference component="AudioTrackRadioGroup" />
@@ -0,0 +1,122 @@
---
title: CaptionsRadioGroup
frameworkTitle:
html: media-captions-radio-group
description: A menu radio group for selecting caption and subtitle tracks
---
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import Demo from "@/components/docs/demos/Demo.astro";
{/* React demos */}
import BasicUsageDemoReact from "@/components/docs/demos/captions-radio-group/react/css/BasicUsage";
import basicUsageReactTsx from "@/components/docs/demos/captions-radio-group/react/css/BasicUsage.tsx?raw";
import basicUsageReactCss from "@/components/docs/demos/captions-radio-group/react/css/BasicUsage.css?raw";
{/* HTML demos */}
import BasicUsageDemoHtml from "@/components/docs/demos/captions-radio-group/html/css/BasicUsage.astro";
import basicUsageHtml from "@/components/docs/demos/captions-radio-group/html/css/BasicUsage.html?raw";
import basicUsageHtmlCss from "@/components/docs/demos/captions-radio-group/html/css/BasicUsage.css?raw";
import basicUsageHtmlTs from "@/components/docs/demos/captions-radio-group/html/css/BasicUsage.ts?raw";
Creates radio items from the player text track state and selects the showing caption or subtitle track.
## Anatomy
<FrameworkCase frameworks={["react"]}>
```tsx
<Menu.RadioGroup>
<Menu.GroupLabel />
<Menu.RadioItem>
<Menu.ItemIndicator />
</Menu.RadioItem>
</Menu.RadioGroup>
```
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
```html
<media-captions-radio-group>
<template>
<media-menu-radio-item>
<span data-part="label"></span>
<media-menu-item-indicator></media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-captions-radio-group>
```
</FrameworkCase>
## Behavior
The group is available when the configured media exposes at least one caption or subtitle track. The first generated option is `Off`; selecting it hides captions. Track labels use the track label, then language, then kind. Pass `formatTrack` to customize the visible labels.
<FrameworkCase frameworks={["react"]}>
<DocsLink slug="reference/use-captions-options">`useCaptionsOptions`</DocsLink> returns `null` when the <DocsLink slug="reference/feature-text-tracks">text tracks feature</DocsLink> is not configured. Use the returned options with <DocsLink slug="reference/menu">`Menu.RadioGroup`</DocsLink>.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
`<media-captions-radio-group>` generates `<media-menu-radio-item>` children, including the `Off` item. Add an optional `<template>` with a single `<media-menu-radio-item>` to customize each generated item.
</FrameworkCase>
## Styling
| Attribute | Values | Description |
| --- | --- | --- |
| `data-active` | Present / absent | Present when captions are enabled. |
| `data-disabled` | Present / absent | Present when track selection is disabled. |
| `data-availability` | `"available"` / `"unavailable"` | Whether caption or subtitle tracks are available. |
<FrameworkCase frameworks={["html"]}>
```css
media-captions-radio-group[data-availability="unavailable"] {
display: none;
}
```
</FrameworkCase>
## Accessibility
The group uses the menu radio group pattern.
<FrameworkCase frameworks={["react"]}>
Give `Menu.RadioGroup` an accessible name with `aria-label` or a nested `Menu.GroupLabel`.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
The element receives an accessible label from the `label` property or defaults to `Enable captions` / `Disable captions` based on the current state.
</FrameworkCase>
## Examples
### Basic usage
<FrameworkCase frameworks={["react"]}>
<StyleCase styles={["css"]}>
<Demo files={[
{ title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
{ title: "App.css", code: basicUsageReactCss, lang: "css" },
]}>
<BasicUsageDemoReact client:idle />
</Demo>
</StyleCase>
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
<StyleCase styles={["css"]}>
<Demo files={[
{ title: "index.html", code: basicUsageHtml, lang: "html" },
{ title: "index.css", code: basicUsageHtmlCss, lang: "css" },
{ title: "index.ts", code: basicUsageHtmlTs, lang: "ts" },
]}>
<BasicUsageDemoHtml />
</Demo>
</StyleCase>
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
<ComponentReference component="CaptionsRadioGroup" />
</FrameworkCase>
+1 -1
View File
@@ -108,7 +108,7 @@ Menus open from a trigger and close when you select an item, click outside, move
Create a submenu by putting another menu inside the root menu's content. Its trigger stays in the root list, and its content becomes the next view. In HTML, give the submenu a matching `id` and point the trigger at it with `commandfor`. Wrap the root list in `Menu.View` or `<media-menu-view>` when the root list and submenus share one animated viewport.
Set `type="playback-rate"`, `type="quality"`, `type="audio-track"`, or `type="captions"` on a submenu trigger when it represents a media setting. `Menu.ItemValue` and `<media-menu-item-value>` read that setting context and display the current value, such as `1.5x`, `Auto`, `English`, or `Off`. See <DocsLink slug="reference/quality-radio-group">QualityRadioGroup</DocsLink> and <DocsLink slug="reference/audio-track-radio-group">AudioTrackRadioGroup</DocsLink> for the generated option groups.
Set `type="playback-rate"`, `type="quality"`, `type="audio-track"`, or `type="captions"` on a submenu trigger when it represents a media setting. `Menu.ItemValue` and `<media-menu-item-value>` read that setting context and display the current value, such as `1.5x`, `Auto`, `English`, or `Off`. See <DocsLink slug="reference/playback-rate-radio-group">PlaybackRateRadioGroup</DocsLink>, <DocsLink slug="reference/quality-radio-group">QualityRadioGroup</DocsLink>, <DocsLink slug="reference/audio-track-radio-group">AudioTrackRadioGroup</DocsLink>, and <DocsLink slug="reference/captions-radio-group">CaptionsRadioGroup</DocsLink> for the generated option groups.
## Styling
@@ -0,0 +1,122 @@
---
title: PlaybackRateRadioGroup
frameworkTitle:
html: media-playback-rate-radio-group
description: A menu radio group for selecting playback speed
---
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import Demo from "@/components/docs/demos/Demo.astro";
{/* React demos */}
import BasicUsageDemoReact from "@/components/docs/demos/playback-rate-radio-group/react/css/BasicUsage";
import basicUsageReactTsx from "@/components/docs/demos/playback-rate-radio-group/react/css/BasicUsage.tsx?raw";
import basicUsageReactCss from "@/components/docs/demos/playback-rate-radio-group/react/css/BasicUsage.css?raw";
{/* HTML demos */}
import BasicUsageDemoHtml from "@/components/docs/demos/playback-rate-radio-group/html/css/BasicUsage.astro";
import basicUsageHtml from "@/components/docs/demos/playback-rate-radio-group/html/css/BasicUsage.html?raw";
import basicUsageHtmlCss from "@/components/docs/demos/playback-rate-radio-group/html/css/BasicUsage.css?raw";
import basicUsageHtmlTs from "@/components/docs/demos/playback-rate-radio-group/html/css/BasicUsage.ts?raw";
Creates radio items from the player playback rate state and selects the playback speed.
## Anatomy
<FrameworkCase frameworks={["react"]}>
```tsx
<Menu.RadioGroup>
<Menu.GroupLabel />
<Menu.RadioItem>
<Menu.ItemIndicator />
</Menu.RadioItem>
</Menu.RadioGroup>
```
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
```html
<media-playback-rate-radio-group>
<template>
<media-menu-radio-item>
<span data-part="label"></span>
<media-menu-item-indicator></media-menu-item-indicator>
</media-menu-radio-item>
</template>
</media-playback-rate-radio-group>
```
</FrameworkCase>
## Behavior
The group is available when the configured media exposes playback rates. Option labels default to the rate followed by a multiplication sign (e.g. `1.5×`); pass `formatRate` to customize them.
<FrameworkCase frameworks={["react"]}>
<DocsLink slug="reference/use-playback-rate-options">`usePlaybackRateOptions`</DocsLink> returns `null` when the <DocsLink slug="reference/feature-playback-rate">playback rate feature</DocsLink> is not configured. Use the returned options with <DocsLink slug="reference/menu">`Menu.RadioGroup`</DocsLink>.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
`<media-playback-rate-radio-group>` generates `<media-menu-radio-item>` children. Add an optional `<template>` with a single `<media-menu-radio-item>` to customize each generated item.
</FrameworkCase>
## Styling
| Attribute | Values | Description |
| --- | --- | --- |
| `data-rate` | `number` | Current playback rate. |
| `data-disabled` | Present / absent | Present when playback rate selection is disabled. |
| `data-availability` | `"available"` / `"unavailable"` | Whether playback rates are available. |
<FrameworkCase frameworks={["html"]}>
```css
media-playback-rate-radio-group[data-availability="unavailable"] {
display: none;
}
```
</FrameworkCase>
## Accessibility
The group uses the menu radio group pattern.
<FrameworkCase frameworks={["react"]}>
Give `Menu.RadioGroup` an accessible name with `aria-label` or a nested `Menu.GroupLabel`.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
The element defaults to `Playback rate {rate}` (e.g. `Playback rate 1.5`). Override its accessible name with `aria-label` or `aria-labelledby`.
</FrameworkCase>
## Examples
### Basic usage
<FrameworkCase frameworks={["react"]}>
<StyleCase styles={["css"]}>
<Demo files={[
{ title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
{ title: "App.css", code: basicUsageReactCss, lang: "css" },
]}>
<BasicUsageDemoReact client:idle />
</Demo>
</StyleCase>
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
<StyleCase styles={["css"]}>
<Demo files={[
{ title: "index.html", code: basicUsageHtml, lang: "html" },
{ title: "index.css", code: basicUsageHtmlCss, lang: "css" },
{ title: "index.ts", code: basicUsageHtmlTs, lang: "ts" },
]}>
<BasicUsageDemoHtml />
</Demo>
</StyleCase>
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
<ComponentReference component="PlaybackRateRadioGroup" />
</FrameworkCase>
@@ -8,6 +8,19 @@ description: A menu radio group for selecting video quality
import ComponentReference from "@/components/docs/api-reference/ComponentReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import StyleCase from "@/components/docs/StyleCase.astro";
import Demo from "@/components/docs/demos/Demo.astro";
{/* React demos */}
import BasicUsageDemoReact from "@/components/docs/demos/quality-radio-group/react/css/BasicUsage";
import basicUsageReactTsx from "@/components/docs/demos/quality-radio-group/react/css/BasicUsage.tsx?raw";
import basicUsageReactCss from "@/components/docs/demos/quality-radio-group/react/css/BasicUsage.css?raw";
{/* HTML demos */}
import BasicUsageDemoHtml from "@/components/docs/demos/quality-radio-group/html/css/BasicUsage.astro";
import basicUsageHtml from "@/components/docs/demos/quality-radio-group/html/css/BasicUsage.html?raw";
import basicUsageHtmlCss from "@/components/docs/demos/quality-radio-group/html/css/BasicUsage.css?raw";
import basicUsageHtmlTs from "@/components/docs/demos/quality-radio-group/html/css/BasicUsage.ts?raw";
Creates radio items from the player video rendition state and selects automatic or manual quality.
@@ -15,16 +28,11 @@ Creates radio items from the player video rendition state and selects automatic
<FrameworkCase frameworks={["react"]}>
```tsx
const quality = useQualityOptions();
<Menu.RadioGroup value={quality.value} onValueChange={quality.setValue}>
<Menu.GroupLabel>Quality</Menu.GroupLabel>
{quality.options.map((option) => (
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled}>
{option.label}
<Menu.ItemIndicator checked={option.value === quality.value} />
</Menu.RadioItem>
))}
<Menu.RadioGroup>
<Menu.GroupLabel />
<Menu.RadioItem>
<Menu.ItemIndicator />
</Menu.RadioItem>
</Menu.RadioGroup>
```
</FrameworkCase>
@@ -49,7 +57,7 @@ Creates radio items from the player video rendition state and selects automatic
The group is available when the configured media exposes more than one video rendition. The first generated option is `Auto`; selecting it returns control to adaptive bitrate selection. Pass `formatRendition` to customize visible rendition labels.
<FrameworkCase frameworks={["react"]}>
`useQualityOptions` returns `null` when the <DocsLink slug="reference/feature-quality">quality feature</DocsLink> is not configured. Use the returned options with <DocsLink slug="reference/menu">`Menu.RadioGroup`</DocsLink>.
<DocsLink slug="reference/use-quality-options">`useQualityOptions`</DocsLink> returns `null` when the <DocsLink slug="reference/feature-quality">quality feature</DocsLink> is not configured. Use the returned options with <DocsLink slug="reference/menu">`Menu.RadioGroup`</DocsLink>.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
@@ -74,7 +82,42 @@ The group is available when the configured media exposes more than one video ren
## Accessibility
The group uses the menu radio group pattern. It receives an accessible label from the `label` prop or defaults to `Quality`.
The group uses the menu radio group pattern.
<FrameworkCase frameworks={["react"]}>
Give `Menu.RadioGroup` an accessible name with `aria-label` or a nested `Menu.GroupLabel`.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
The element receives an accessible label from the `label` property or defaults to `Quality`.
</FrameworkCase>
## Examples
### Basic usage
<FrameworkCase frameworks={["react"]}>
<StyleCase styles={["css"]}>
<Demo files={[
{ title: "App.tsx", code: basicUsageReactTsx, lang: "tsx" },
{ title: "App.css", code: basicUsageReactCss, lang: "css" },
]}>
<BasicUsageDemoReact client:idle />
</Demo>
</StyleCase>
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
<StyleCase styles={["css"]}>
<Demo files={[
{ title: "index.html", code: basicUsageHtml, lang: "html" },
{ title: "index.css", code: basicUsageHtmlCss, lang: "css" },
{ title: "index.ts", code: basicUsageHtmlTs, lang: "ts" },
]}>
<BasicUsageDemoHtml />
</Demo>
</StyleCase>
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
<ComponentReference component="QualityRadioGroup" />
@@ -0,0 +1,35 @@
---
title: useAudioTrackOptions
description: Hook to build audio track menu options from the player audio track state
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`useAudioTrackOptions` returns the currently enabled audio track, the available options, and a `setValue` callback for wiring track selection into a menu. It returns `null` when the <DocsLink slug="reference/feature-audio-track">audio track feature</DocsLink> is not configured.
```tsx title="AudioMenu.tsx"
import { Menu, useAudioTrackOptions } from "@videojs/react";
function AudioMenu() {
const audioTrack = useAudioTrackOptions();
if (audioTrack?.state.availability !== "available") return null;
return (
<Menu.RadioGroup value={audioTrack.value} onValueChange={audioTrack.setValue} aria-label="Audio">
{audioTrack.options.map((option) => (
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled}>
{option.label}
<Menu.ItemIndicator checked={option.value === audioTrack.value} />
</Menu.RadioItem>
))}
</Menu.RadioGroup>
);
}
```
Option labels use the track label, then language, then kind. Pass `formatTrack` to customize the visible labels.
See <DocsLink slug="reference/audio-track-radio-group">AudioTrackRadioGroup</DocsLink> for the component-level pattern, and <DocsLink slug="reference/menu">Menu</DocsLink> for a complete settings menu example.
<UtilReference util="useAudioTrackOptions" />
@@ -0,0 +1,35 @@
---
title: useCaptionsOptions
description: Hook to build captions menu options from the player text track state
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`useCaptionsOptions` returns the currently showing caption track, the available options (starting with an `Off` option), and a `setValue` callback for wiring caption selection into a menu. It returns `null` when the <DocsLink slug="reference/feature-text-tracks">text tracks feature</DocsLink> is not configured.
```tsx title="CaptionsMenu.tsx"
import { Menu, useCaptionsOptions } from "@videojs/react";
function CaptionsMenu() {
const captions = useCaptionsOptions();
if (captions?.state.availability !== "available") return null;
return (
<Menu.RadioGroup value={captions.value} onValueChange={captions.setValue} aria-label="Captions">
{captions.options.map((option) => (
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled}>
{option.label}
<Menu.ItemIndicator checked={option.value === captions.value} />
</Menu.RadioItem>
))}
</Menu.RadioGroup>
);
}
```
Option labels use the track label, then language, then kind. Pass `formatTrack` to customize the visible labels. The result also exposes `showMenu`, which is `true` when more than one caption or subtitle track is available — the same threshold <DocsLink slug="reference/captions-button">CaptionsButton</DocsLink> uses to open a menu instead of toggling in `menuTrigger` mode.
See <DocsLink slug="reference/captions-radio-group">CaptionsRadioGroup</DocsLink> for the component-level pattern, and <DocsLink slug="reference/menu">Menu</DocsLink> for a complete settings menu example.
<UtilReference util="useCaptionsOptions" />
@@ -0,0 +1,35 @@
---
title: usePlaybackRateOptions
description: Hook to build playback rate menu options from the player playback rate state
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`usePlaybackRateOptions` returns the current playback rate, the available options, and `setRate` / `setValue` callbacks for wiring rate selection into a menu. It returns `null` when the <DocsLink slug="reference/feature-playback-rate">playback rate feature</DocsLink> is not configured.
```tsx title="SpeedMenu.tsx"
import { Menu, usePlaybackRateOptions } from "@videojs/react";
function SpeedMenu() {
const playbackRate = usePlaybackRateOptions();
if (playbackRate?.state.availability !== "available") return null;
return (
<Menu.RadioGroup value={playbackRate.value} onValueChange={playbackRate.setValue} aria-label="Speed">
{playbackRate.options.map((option) => (
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled}>
{option.label}
<Menu.ItemIndicator checked={option.value === playbackRate.value} />
</Menu.RadioItem>
))}
</Menu.RadioGroup>
);
}
```
Options come from the player's configured playback rates. Labels default to the rate followed by a multiplication sign (e.g. `1.5×`); pass `formatRate` to customize them. `setValue` accepts an option's string value, while `setRate` accepts a number directly.
See <DocsLink slug="reference/playback-rate-radio-group">PlaybackRateRadioGroup</DocsLink> for the component-level pattern, and <DocsLink slug="reference/menu">Menu</DocsLink> for a complete settings menu example.
<UtilReference util="usePlaybackRateOptions" />
@@ -0,0 +1,35 @@
---
title: useQualityOptions
description: Hook to build video quality menu options from the player rendition state
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
`useQualityOptions` returns the current quality value, the available options, and a `setValue` callback for wiring quality selection into a menu. It returns `null` when the <DocsLink slug="reference/feature-quality">quality feature</DocsLink> is not configured.
```tsx title="QualityMenu.tsx"
import { Menu, useQualityOptions } from "@videojs/react";
function QualityMenu() {
const quality = useQualityOptions();
if (quality?.state.availability !== "available") return null;
return (
<Menu.RadioGroup value={quality.value} onValueChange={quality.setValue} aria-label="Quality">
{quality.options.map((option) => (
<Menu.RadioItem key={option.value} value={option.value} disabled={option.disabled}>
{option.label}
<Menu.ItemIndicator checked={option.value === quality.value} />
</Menu.RadioItem>
))}
</Menu.RadioGroup>
);
}
```
The first option is always `Auto`; selecting it returns control to adaptive bitrate selection. Options carry optional `tier` and `badge` fields (e.g. `4K`, `HD`) for richer labels. Pass `formatRendition` to customize the visible rendition labels.
See <DocsLink slug="reference/quality-radio-group">QualityRadioGroup</DocsLink> for the component-level pattern, and <DocsLink slug="reference/menu">Menu</DocsLink> for a complete settings menu example.
<UtilReference util="useQualityOptions" />
@@ -98,7 +98,7 @@ React and HTML demos for the same variant should use matching BEM structures.
### Video and poster sources
All demos use these URLs:
Demos default to these URLs:
These demo assets are hosted by [Mux](https://www.mux.com?utm_source=videojs&utm_campaign=vjs10):
@@ -107,6 +107,15 @@ Video: https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highe
Poster: https://image.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/thumbnail.jpg
```
Components that need adaptive streams use `<HlsJsVideo>` / `<hlsjs-video>` with an HLS source instead. Pick by what the component consumes:
```
Renditions (quality): https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4.m3u8
Multi-language audio (+ renditions): https://stream.mux.com/s41JYeqIpBMBzE4OzxDyGR2yrp2hD1CQ6gJN9SlVGDQ.m3u8
```
The default mp4 has a single rendition and a single audio track — quality and audio-track demos render nothing against it.
All demo videos use `autoplay muted playsinline loop` (React: `autoPlay muted playsInline loop`).
## Create the MDX reference page
@@ -161,7 +170,7 @@ import basicUsageHtmlTs from "@/components/docs/demos/{name}/html/css/BasicUsage
After imports, the page follows this order:
1. **Anatomy** — show the component markup for each framework using `<FrameworkCase>`
1. **Anatomy** — show part nesting with self-closing placeholders for each framework using `<FrameworkCase>`, following the [Base UI anatomy convention](https://base-ui.com/react/overview/quick-start). No hooks, state, handlers, or option mapping — working code belongs in Examples.
2. **Prose sections** (optional, as needed):
- **Behavior** — state transitions, timing, interaction logic
- **Styling** — data attribute CSS selectors
+6
View File
@@ -65,6 +65,7 @@ export const sidebar: Sidebar = [
{ slug: 'reference/audio-track-radio-group' },
{ slug: 'reference/buffering-indicator' },
{ slug: 'reference/captions-button' },
{ slug: 'reference/captions-radio-group' },
{ slug: 'reference/cast-button' },
{ slug: 'reference/controls' },
{ slug: 'reference/fullscreen-button' },
@@ -73,6 +74,7 @@ export const sidebar: Sidebar = [
{ slug: 'reference/pip-button' },
{ slug: 'reference/play-button' },
{ slug: 'reference/playback-rate-button' },
{ slug: 'reference/playback-rate-radio-group' },
{ slug: 'reference/popover' },
{ slug: 'reference/poster' },
{ slug: 'reference/quality-radio-group' },
@@ -113,10 +115,14 @@ export const sidebar: Sidebar = [
defaultOpen: false,
contents: [
{ slug: 'reference/render-element' },
{ slug: 'reference/use-audio-track-options' },
{ slug: 'reference/use-button' },
{ slug: 'reference/use-captions-options' },
{ slug: 'reference/use-container-attach' },
{ slug: 'reference/use-media-attach' },
{ slug: 'reference/use-playback-rate-options' },
{ slug: 'reference/use-player-context' },
{ slug: 'reference/use-quality-options' },
{ slug: 'reference/use-selector' },
{ slug: 'reference/use-snapshot' },
],