UNPKG

@aidenlx/vidstack-react

Version:

UI component library for building high-quality, accessible video and audio experiences on the web.

935 lines (912 loc) 79.3 kB
"use client" import * as React from 'react'; import { useSignal, composeRefs } from 'maverick.js/react'; import { isBoolean, uppercaseFirstChar, isUndefined, isString, camelToKebabCase, keysOf, isArray, isKeyboardClick, listenEvent, toggleClass } from 'maverick.js/std'; import { createComputed, createSignal, MediaAnnouncer, Root, Trigger, Content, GoogleCastButton, Captions, useChapterOptions, Root$1 as Root$5, Root$2 as Root$6, Root$3 as Root$7, useScoped, Root$4 as Root$a, Group, useChapterTitle, createEffect, useActiveTextTrack, ChapterTitle as ChapterTitle$1, Title, Root$5 as Root$b, Track as Track$1, TrackFill as TrackFill$1 } from './vidstack-CglTGItW.js'; import { useColorSchemePreference, useActive, useResizeObserver, useLayoutName, useTransitionActive } from './vidstack-BxdfIicl.js'; import { useMediaContext, MuteButton, LiveButton, SeekButton, PlayButton, CaptionButton, appendParamsToURL, PIPButton, FullscreenButton, AirPlayButton, Items, Root$3 as Root$1, Item, Root as Root$2, Img, Root$2 as Root$3, Button, Portal, Track, TrackFill, Thumb, Steps, useMediaPlayer, Root$5 as Root$4, useAudioOptions, useCaptionOptions, Root$4 as Root$8, Preview, Value, Root$1 as Root$9, Chapters, Progress, Thumbnail, ChapterTitle, Time, Gesture } from './vidstack-p9VvwQcz.js'; import { useMediaState, isTrackCaptionKind, getDownloadFile, isRemotionSrc, IS_SERVER, useMediaContext as useMediaContext$1, sortVideoQualities, Primitive, mediaContext } from './vidstack-CkT8sP15.js'; import { signal, onDispose, scoped, effect, useContext } from 'maverick.js'; import { flushSync } from 'react-dom'; import { RemotionThumbnail, RemotionSliderThumbnail } from './vidstack-DcGAdum5.js'; const DefaultLayoutContext = React.createContext({}); DefaultLayoutContext.displayName = "DefaultLayoutContext"; function useDefaultLayoutContext() { return React.useContext(DefaultLayoutContext); } function useDefaultLayoutWord(word) { const { translations } = useDefaultLayoutContext(); return i18n(translations, word); } function i18n(translations, word) { return translations?.[word] ?? word; } function useColorSchemeClass(colorScheme) { const systemColorPreference = useColorSchemePreference(); if (colorScheme === "default") { return null; } else if (colorScheme === "system") { return systemColorPreference; } else { return colorScheme; } } function createDefaultMediaLayout({ type, smLayoutWhen, renderLayout }) { const Layout = React.forwardRef( ({ children, className, disableTimeSlider = false, hideQualityBitrate = false, icons, colorScheme = "system", download = null, menuContainer = null, menuGroup = "bottom", noAudioGain = false, audioGains = { min: 0, max: 300, step: 25 }, noGestures = false, noKeyboardAnimations = false, noModal = false, noScrubGesture, playbackRates = { min: 0, max: 2, step: 0.25 }, seekStep = 10, showMenuDelay, showTooltipDelay = 700, sliderChaptersMinWidth = 325, slots, smallLayoutWhen = smLayoutWhen, thumbnails = null, translations, ...props }, forwardRef) => { const media = useMediaContext(), $load = useSignal(media.$props.load), $canLoad = useMediaState("canLoad"), $viewType = useMediaState("viewType"), $streamType = useMediaState("streamType"), $smallWhen = createComputed(() => { return isBoolean(smallLayoutWhen) ? smallLayoutWhen : smallLayoutWhen(media.player.state); }, [smallLayoutWhen]), userPrefersAnnouncements = createSignal(true), userPrefersKeyboardAnimations = createSignal(true), isMatch = $viewType === type, isSmallLayout = $smallWhen(), isForcedLayout = isBoolean(smallLayoutWhen), isLoadLayout = $load === "play" && !$canLoad, canRender = $canLoad || isForcedLayout || isLoadLayout, colorSchemeClass = useColorSchemeClass(colorScheme), layoutEl = createSignal(null); useSignal($smallWhen); return /* @__PURE__ */ React.createElement( "div", { ...props, className: `vds-${type}-layout` + (colorSchemeClass ? ` ${colorSchemeClass}` : "") + (className ? ` ${className}` : ""), "data-match": isMatch ? "" : null, "data-sm": isSmallLayout ? "" : null, "data-lg": !isSmallLayout ? "" : null, "data-size": isSmallLayout ? "sm" : "lg", "data-no-scrub-gesture": noScrubGesture ? "" : null, ref: composeRefs(layoutEl.set, forwardRef) }, canRender && isMatch ? /* @__PURE__ */ React.createElement( DefaultLayoutContext.Provider, { value: { disableTimeSlider, hideQualityBitrate, icons, colorScheme, download, isSmallLayout, menuContainer, menuGroup, noAudioGain, audioGains, layoutEl, noGestures, noKeyboardAnimations, noModal, noScrubGesture, showMenuDelay, showTooltipDelay, sliderChaptersMinWidth, slots, seekStep, playbackRates, thumbnails, translations, userPrefersAnnouncements, userPrefersKeyboardAnimations } }, renderLayout({ streamType: $streamType, isSmallLayout, isLoadLayout }), children ) : null ); } ); Layout.displayName = "DefaultMediaLayout"; return Layout; } function useDefaultAudioLayoutSlots() { return React.useContext(DefaultLayoutContext).slots; } function useDefaultVideoLayoutSlots() { return React.useContext(DefaultLayoutContext).slots; } function slot(slots, name, defaultValue) { const slot2 = slots?.[name], capitalizedName = uppercaseFirstChar(name); return /* @__PURE__ */ React.createElement(React.Fragment, null, slots?.[`before${capitalizedName}`], isUndefined(slot2) ? defaultValue : slot2, slots?.[`after${capitalizedName}`]); } function DefaultAnnouncer() { const { userPrefersAnnouncements, translations } = useDefaultLayoutContext(), $userPrefersAnnouncements = useSignal(userPrefersAnnouncements); if (!$userPrefersAnnouncements) return null; return /* @__PURE__ */ React.createElement(MediaAnnouncer, { translations }); } DefaultAnnouncer.displayName = "DefaultAnnouncer"; function DefaultTooltip({ content, placement, children }) { const { showTooltipDelay } = useDefaultLayoutContext(); return /* @__PURE__ */ React.createElement(Root, { showDelay: showTooltipDelay }, /* @__PURE__ */ React.createElement(Trigger, { asChild: true }, children), /* @__PURE__ */ React.createElement(Content, { className: "vds-tooltip-content", placement }, content)); } DefaultTooltip.displayName = "DefaultTooltip"; function DefaultPlayButton({ tooltip }) { const { icons: Icons } = useDefaultLayoutContext(), playText = useDefaultLayoutWord("Play"), pauseText = useDefaultLayoutWord("Pause"), $paused = useMediaState("paused"), $ended = useMediaState("ended"); return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: $paused ? playText : pauseText, placement: tooltip }, /* @__PURE__ */ React.createElement(PlayButton, { className: "vds-play-button vds-button", "aria-label": playText }, $ended ? /* @__PURE__ */ React.createElement(Icons.PlayButton.Replay, { className: "vds-icon" }) : $paused ? /* @__PURE__ */ React.createElement(Icons.PlayButton.Play, { className: "vds-icon" }) : /* @__PURE__ */ React.createElement(Icons.PlayButton.Pause, { className: "vds-icon" }))); } DefaultPlayButton.displayName = "DefaultPlayButton"; const DefaultMuteButton = React.forwardRef( ({ tooltip }, forwardRef) => { const { icons: Icons } = useDefaultLayoutContext(), muteText = useDefaultLayoutWord("Mute"), unmuteText = useDefaultLayoutWord("Unmute"), $muted = useMediaState("muted"), $volume = useMediaState("volume"); return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: $muted ? unmuteText : muteText, placement: tooltip }, /* @__PURE__ */ React.createElement(MuteButton, { className: "vds-mute-button vds-button", "aria-label": muteText, ref: forwardRef }, $muted || $volume == 0 ? /* @__PURE__ */ React.createElement(Icons.MuteButton.Mute, { className: "vds-icon" }) : $volume < 0.5 ? /* @__PURE__ */ React.createElement(Icons.MuteButton.VolumeLow, { className: "vds-icon" }) : /* @__PURE__ */ React.createElement(Icons.MuteButton.VolumeHigh, { className: "vds-icon" }))); } ); DefaultMuteButton.displayName = "DefaultMuteButton"; function DefaultCaptionButton({ tooltip }) { const { icons: Icons } = useDefaultLayoutContext(), captionsText = useDefaultLayoutWord("Captions"), onText = useDefaultLayoutWord("Closed-Captions On"), offText = useDefaultLayoutWord("Closed-Captions Off"), $track = useMediaState("textTrack"), isOn = $track && isTrackCaptionKind($track); return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: isOn ? onText : offText, placement: tooltip }, /* @__PURE__ */ React.createElement(CaptionButton, { className: "vds-caption-button vds-button", "aria-label": captionsText }, isOn ? /* @__PURE__ */ React.createElement(Icons.CaptionButton.On, { className: "vds-icon" }) : /* @__PURE__ */ React.createElement(Icons.CaptionButton.Off, { className: "vds-icon" }))); } DefaultCaptionButton.displayName = "DefaultCaptionButton"; function DefaultPIPButton({ tooltip }) { const { icons: Icons } = useDefaultLayoutContext(), pipText = useDefaultLayoutWord("PiP"), enterText = useDefaultLayoutWord("Enter PiP"), exitText = useDefaultLayoutWord("Exit PiP"), $pip = useMediaState("pictureInPicture"); return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: $pip ? exitText : enterText, placement: tooltip }, /* @__PURE__ */ React.createElement(PIPButton, { className: "vds-pip-button vds-button", "aria-label": pipText }, $pip ? /* @__PURE__ */ React.createElement(Icons.PIPButton.Exit, { className: "vds-icon" }) : /* @__PURE__ */ React.createElement(Icons.PIPButton.Enter, { className: "vds-icon" }))); } DefaultPIPButton.displayName = "DefaultPIPButton"; function DefaultFullscreenButton({ tooltip }) { const { icons: Icons } = useDefaultLayoutContext(), fullscreenText = useDefaultLayoutWord("Fullscreen"), enterText = useDefaultLayoutWord("Enter Fullscreen"), exitText = useDefaultLayoutWord("Exit Fullscreen"), $fullscreen = useMediaState("fullscreen"); return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: $fullscreen ? exitText : enterText, placement: tooltip }, /* @__PURE__ */ React.createElement(FullscreenButton, { className: "vds-fullscreen-button vds-button", "aria-label": fullscreenText }, $fullscreen ? /* @__PURE__ */ React.createElement(Icons.FullscreenButton.Exit, { className: "vds-icon" }) : /* @__PURE__ */ React.createElement(Icons.FullscreenButton.Enter, { className: "vds-icon" }))); } DefaultFullscreenButton.displayName = "DefaultFullscreenButton"; function DefaultSeekButton({ backward, tooltip }) { const { icons: Icons, seekStep } = useDefaultLayoutContext(), seekForwardText = useDefaultLayoutWord("Seek Forward"), seekBackwardText = useDefaultLayoutWord("Seek Backward"), seconds = (backward ? -1 : 1) * seekStep, label = seconds >= 0 ? seekForwardText : seekBackwardText; return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: label, placement: tooltip }, /* @__PURE__ */ React.createElement(SeekButton, { className: "vds-seek-button vds-button", seconds, "aria-label": label }, seconds >= 0 ? /* @__PURE__ */ React.createElement(Icons.SeekButton.Forward, { className: "vds-icon" }) : /* @__PURE__ */ React.createElement(Icons.SeekButton.Backward, { className: "vds-icon" }))); } DefaultSeekButton.displayName = "DefaultSeekButton"; function DefaultAirPlayButton({ tooltip }) { const { icons: Icons } = useDefaultLayoutContext(), airPlayText = useDefaultLayoutWord("AirPlay"), $state = useMediaState("remotePlaybackState"), stateText = useDefaultLayoutWord(uppercaseFirstChar($state)), label = `${airPlayText} ${stateText}`, Icon = ($state === "connecting" ? Icons.AirPlayButton.Connecting : $state === "connected" ? Icons.AirPlayButton.Connected : null) ?? Icons.AirPlayButton.Default; return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: airPlayText, placement: tooltip }, /* @__PURE__ */ React.createElement(AirPlayButton, { className: "vds-airplay-button vds-button", "aria-label": label }, /* @__PURE__ */ React.createElement(Icon, { className: "vds-icon" }))); } DefaultAirPlayButton.displayName = "DefaultAirPlayButton"; function DefaultGoogleCastButton({ tooltip }) { const { icons: Icons } = useDefaultLayoutContext(), googleCastText = useDefaultLayoutWord("Google Cast"), $state = useMediaState("remotePlaybackState"), stateText = useDefaultLayoutWord(uppercaseFirstChar($state)), label = `${googleCastText} ${stateText}`, Icon = ($state === "connecting" ? Icons.GoogleCastButton.Connecting : $state === "connected" ? Icons.GoogleCastButton.Connected : null) ?? Icons.GoogleCastButton.Default; return /* @__PURE__ */ React.createElement(DefaultTooltip, { content: googleCastText, placement: tooltip }, /* @__PURE__ */ React.createElement(GoogleCastButton, { className: "vds-google-cast-button vds-button", "aria-label": label }, /* @__PURE__ */ React.createElement(Icon, { className: "vds-icon" }))); } DefaultGoogleCastButton.displayName = "DefaultGoogleCastButton"; function DefaultLiveButton() { const $live = useMediaState("live"), label = useDefaultLayoutWord("Skip To Live"), liveText = useDefaultLayoutWord("LIVE"); return $live ? /* @__PURE__ */ React.createElement(LiveButton, { className: "vds-live-button", "aria-label": label }, /* @__PURE__ */ React.createElement("span", { className: "vds-live-button-text" }, liveText)) : null; } DefaultLiveButton.displayName = "DefaultLiveButton"; function DefaultDownloadButton() { const { download, icons: Icons } = useDefaultLayoutContext(), $src = useMediaState("source"), $title = useMediaState("title"), file = getDownloadFile({ title: $title, src: $src, download }), downloadText = useDefaultLayoutWord("Download"); return isString(file?.url) ? /* @__PURE__ */ React.createElement(DefaultTooltip, { content: downloadText, placement: "top" }, /* @__PURE__ */ React.createElement( "a", { role: "button", className: "vds-download-button vds-button", "aria-label": downloadText, href: appendParamsToURL(file.url, { download: file.name }), download: file.name, target: "_blank" }, Icons.DownloadButton ? /* @__PURE__ */ React.createElement(Icons.DownloadButton.Default, { className: "vds-icon" }) : null )) : null; } DefaultDownloadButton.displayName = "DefaultDownloadButton"; function DefaultCaptions() { const exampleText = useDefaultLayoutWord("Captions look like this"); return /* @__PURE__ */ React.createElement(Captions, { className: "vds-captions", exampleText }); } DefaultCaptions.displayName = "DefaultCaptions"; function DefaultControlsSpacer() { return /* @__PURE__ */ React.createElement("div", { className: "vds-controls-spacer" }); } DefaultControlsSpacer.displayName = "DefaultControlsSpacer"; function useParentDialogEl() { const { layoutEl } = useDefaultLayoutContext(), $layoutEl = useSignal(layoutEl); return React.useMemo(() => $layoutEl?.closest("dialog"), [$layoutEl]); } function DefaultChaptersMenu({ tooltip, placement, portalClass = "" }) { const { showMenuDelay, noModal, isSmallLayout, icons: Icons, menuGroup, menuContainer, colorScheme } = useDefaultLayoutContext(), chaptersText = useDefaultLayoutWord("Chapters"), options = useChapterOptions(), disabled = !options.length, { thumbnails } = useDefaultLayoutContext(), $src = useMediaState("currentSrc"), $viewType = useMediaState("viewType"), $offset = !isSmallLayout && menuGroup === "bottom" && $viewType === "video" ? 26 : 0, $RemotionThumbnail = useSignal(RemotionThumbnail), colorSchemeClass = useColorSchemeClass(colorScheme), [isOpen, setIsOpen] = React.useState(false), dialogEl = useParentDialogEl(); if (disabled) return null; function onOpen() { flushSync(() => { setIsOpen(true); }); } function onClose() { setIsOpen(false); } const Content = /* @__PURE__ */ React.createElement( Items, { className: "vds-chapters-menu-items vds-menu-items", placement, offset: $offset }, isOpen ? /* @__PURE__ */ React.createElement( Root$1, { className: "vds-chapters-radio-group vds-radio-group", value: options.selectedValue, "data-thumbnails": thumbnails ? "" : null }, options.map( ({ cue, label, value, startTimeText, durationText, select, setProgressVar }) => /* @__PURE__ */ React.createElement( Item, { className: "vds-chapter-radio vds-radio", value, key: value, onSelect: select, ref: setProgressVar }, thumbnails ? /* @__PURE__ */ React.createElement(Root$2, { src: thumbnails, className: "vds-thumbnail", time: cue.startTime }, /* @__PURE__ */ React.createElement(Img, null)) : $RemotionThumbnail && isRemotionSrc($src) ? /* @__PURE__ */ React.createElement($RemotionThumbnail, { className: "vds-thumbnail", frame: cue.startTime * $src.fps }) : null, /* @__PURE__ */ React.createElement("div", { className: "vds-chapter-radio-content" }, /* @__PURE__ */ React.createElement("span", { className: "vds-chapter-radio-label" }, label), /* @__PURE__ */ React.createElement("span", { className: "vds-chapter-radio-start-time" }, startTimeText), /* @__PURE__ */ React.createElement("span", { className: "vds-chapter-radio-duration" }, durationText)) ) ) ) : null ); return /* @__PURE__ */ React.createElement( Root$3, { className: "vds-chapters-menu vds-menu", showDelay: showMenuDelay, onOpen, onClose }, /* @__PURE__ */ React.createElement(DefaultTooltip, { content: chaptersText, placement: tooltip }, /* @__PURE__ */ React.createElement( Button, { className: "vds-menu-button vds-button", disabled, "aria-label": chaptersText }, /* @__PURE__ */ React.createElement(Icons.Menu.Chapters, { className: "vds-icon" }) )), noModal || !isSmallLayout ? Content : /* @__PURE__ */ React.createElement( Portal, { container: menuContainer ?? dialogEl, className: portalClass + (colorSchemeClass ? ` ${colorSchemeClass}` : ""), disabled: "fullscreen", "data-sm": isSmallLayout ? "" : null, "data-lg": !isSmallLayout ? "" : null, "data-size": isSmallLayout ? "sm" : "lg" }, Content ) ); } DefaultChaptersMenu.displayName = "DefaultChaptersMenu"; const FONT_COLOR_OPTION = { type: "color" }; const FONT_FAMILY_OPTION = { type: "radio", values: { "Monospaced Serif": "mono-serif", "Proportional Serif": "pro-serif", "Monospaced Sans-Serif": "mono-sans", "Proportional Sans-Serif": "pro-sans", Casual: "casual", Cursive: "cursive", "Small Capitals": "capitals" } }; const FONT_SIZE_OPTION = { type: "slider", min: 0, max: 400, step: 25, upIcon: null, downIcon: null }; const FONT_OPACITY_OPTION = { type: "slider", min: 0, max: 100, step: 5, upIcon: null, downIcon: null }; const FONT_TEXT_SHADOW_OPTION = { type: "radio", values: ["None", "Drop Shadow", "Raised", "Depressed", "Outline"] }; const FONT_DEFAULTS = { fontFamily: "pro-sans", fontSize: "100%", textColor: "#ffffff", textOpacity: "100%", textShadow: "none", textBg: "#000000", textBgOpacity: "100%", displayBg: "#000000", displayBgOpacity: "0%" }; const FONT_SIGNALS = Object.keys(FONT_DEFAULTS).reduce( (prev, type) => ({ ...prev, [type]: signal(FONT_DEFAULTS[type]) }), {} ); if (!IS_SERVER) { for (const type of Object.keys(FONT_SIGNALS)) { const value = localStorage.getItem(`vds-player:${camelToKebabCase(type)}`); if (isString(value)) FONT_SIGNALS[type].set(value); } } function onFontReset() { for (const type of Object.keys(FONT_SIGNALS)) { const defaultValue = FONT_DEFAULTS[type]; FONT_SIGNALS[type].set(defaultValue); } } function hexToRgb(hex) { const { style } = new Option(); style.color = hex; return style.color.match(/\((.*?)\)/)[1].replace(/,/g, " "); } let isWatchingVars = false, players = /* @__PURE__ */ new Set(); function updateFontCssVars() { if (IS_SERVER) return; const { player } = useMediaContext$1(); players.add(player); onDispose(() => players.delete(player)); if (!isWatchingVars) { scoped(() => { for (const type of keysOf(FONT_SIGNALS)) { const $value = FONT_SIGNALS[type], defaultValue = FONT_DEFAULTS[type], varName = `--media-user-${camelToKebabCase(type)}`, storageKey = `vds-player:${camelToKebabCase(type)}`; effect(() => { const value = $value(), isDefaultVarValue = value === defaultValue, varValue = !isDefaultVarValue ? getCssVarValue(player, type, value) : null; for (const player2 of players) { player2.el?.style.setProperty(varName, varValue); } if (isDefaultVarValue) { localStorage.removeItem(storageKey); } else { localStorage.setItem(storageKey, value); } }); } }, null); isWatchingVars = true; } } function getCssVarValue(player, type, value) { switch (type) { case "fontFamily": const fontVariant = value === "capitals" ? "small-caps" : ""; player.el?.style.setProperty("--media-user-font-variant", fontVariant); return getFontFamilyCSSVarValue(value); case "fontSize": case "textOpacity": case "textBgOpacity": case "displayBgOpacity": return percentToRatio(value); case "textColor": return `rgb(${hexToRgb(value)} / var(--media-user-text-opacity, 1))`; case "textShadow": return getTextShadowCssVarValue(value); case "textBg": return `rgb(${hexToRgb(value)} / var(--media-user-text-bg-opacity, 1))`; case "displayBg": return `rgb(${hexToRgb(value)} / var(--media-user-display-bg-opacity, 1))`; } } function percentToRatio(value) { return (parseInt(value) / 100).toString(); } function getFontFamilyCSSVarValue(value) { switch (value) { case "mono-serif": return '"Courier New", Courier, "Nimbus Mono L", "Cutive Mono", monospace'; case "mono-sans": return '"Deja Vu Sans Mono", "Lucida Console", Monaco, Consolas, "PT Mono", monospace'; case "pro-sans": return 'Roboto, "Arial Unicode Ms", Arial, Helvetica, Verdana, "PT Sans Caption", sans-serif'; case "casual": return '"Comic Sans MS", Impact, Handlee, fantasy'; case "cursive": return '"Monotype Corsiva", "URW Chancery L", "Apple Chancery", "Dancing Script", cursive'; case "capitals": return '"Arial Unicode Ms", Arial, Helvetica, Verdana, "Marcellus SC", sans-serif + font-variant=small-caps'; default: return '"Times New Roman", Times, Georgia, Cambria, "PT Serif Caption", serif'; } } function getTextShadowCssVarValue(value) { switch (value) { case "drop shadow": return "rgb(34, 34, 34) 1.86389px 1.86389px 2.79583px, rgb(34, 34, 34) 1.86389px 1.86389px 3.72778px, rgb(34, 34, 34) 1.86389px 1.86389px 4.65972px"; case "raised": return "rgb(34, 34, 34) 1px 1px, rgb(34, 34, 34) 2px 2px"; case "depressed": return "rgb(204, 204, 204) 1px 1px, rgb(34, 34, 34) -1px -1px"; case "outline": return "rgb(34, 34, 34) 0px 0px 1.86389px, rgb(34, 34, 34) 0px 0px 1.86389px, rgb(34, 34, 34) 0px 0px 1.86389px, rgb(34, 34, 34) 0px 0px 1.86389px, rgb(34, 34, 34) 0px 0px 1.86389px"; default: return ""; } } function DefaultMenuSection({ label, value, children }) { const id = React.useId(); if (!label) { return /* @__PURE__ */ React.createElement("div", { className: "vds-menu-section" }, /* @__PURE__ */ React.createElement("div", { className: "vds-menu-section-body" }, children)); } return /* @__PURE__ */ React.createElement("section", { className: "vds-menu-section", role: "group", "aria-labelledby": id }, /* @__PURE__ */ React.createElement("div", { className: "vds-menu-section-title" }, /* @__PURE__ */ React.createElement("header", { id }, label), value ? /* @__PURE__ */ React.createElement("div", { className: "vds-menu-section-value" }, value) : null), /* @__PURE__ */ React.createElement("div", { className: "vds-menu-section-body" }, children)); } DefaultMenuSection.displayName = "DefaultMenuSection"; function DefaultMenuButton({ label, hint = "", Icon, disabled = false }) { const { icons: Icons } = React.useContext(DefaultLayoutContext); return /* @__PURE__ */ React.createElement(Button, { className: "vds-menu-item", disabled }, /* @__PURE__ */ React.createElement(Icons.Menu.ArrowLeft, { className: "vds-menu-close-icon vds-icon" }), Icon ? /* @__PURE__ */ React.createElement(Icon, { className: "vds-menu-item-icon vds-icon" }) : null, /* @__PURE__ */ React.createElement("span", { className: "vds-menu-item-label" }, label), /* @__PURE__ */ React.createElement("span", { className: "vds-menu-item-hint" }, hint), /* @__PURE__ */ React.createElement(Icons.Menu.ArrowRight, { className: "vds-menu-open-icon vds-icon" })); } DefaultMenuButton.displayName = "DefaultMenuButton"; function DefaultMenuItem({ label, children }) { return /* @__PURE__ */ React.createElement("div", { className: "vds-menu-item" }, /* @__PURE__ */ React.createElement("div", { className: "vds-menu-item-label" }, label), children); } DefaultMenuItem.displayName = "DefaultMenuItem"; function DefaultMenuRadioGroup({ value, options, onChange }) { const { icons: Icons } = useDefaultLayoutContext(); return /* @__PURE__ */ React.createElement(Root$1, { className: "vds-radio-group", value, onChange }, options.map((option) => /* @__PURE__ */ React.createElement(Item, { className: "vds-radio", value: option.value, key: option.value }, /* @__PURE__ */ React.createElement(Icons.Menu.RadioCheck, { className: "vds-icon" }), /* @__PURE__ */ React.createElement("span", { className: "vds-radio-label", "data-part": "label" }, option.label)))); } DefaultMenuRadioGroup.displayName = "DefaultMenuRadioGroup"; function createRadioOptions(entries) { return React.useMemo( () => isArray(entries) ? entries.map((entry) => ({ label: entry, value: entry.toLowerCase() })) : Object.keys(entries).map((label) => ({ label, value: entries[label] })), [entries] ); } function DefaultMenuSliderItem({ label, value, UpIcon, DownIcon, children, isMin, isMax }) { const hasTitle = label || value, Content = /* @__PURE__ */ React.createElement(React.Fragment, null, DownIcon ? /* @__PURE__ */ React.createElement(DownIcon, { className: "vds-icon down" }) : null, children, UpIcon ? /* @__PURE__ */ React.createElement(UpIcon, { className: "vds-icon up" }) : null); return /* @__PURE__ */ React.createElement( "div", { className: `vds-menu-item vds-menu-slider-item${hasTitle ? " group" : ""}`, "data-min": isMin ? "" : null, "data-max": isMax ? "" : null }, hasTitle ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "vds-menu-slider-title" }, label ? /* @__PURE__ */ React.createElement("div", null, label) : null, value ? /* @__PURE__ */ React.createElement("div", null, value) : null), /* @__PURE__ */ React.createElement("div", { className: "vds-menu-slider-body" }, Content)) : Content ); } DefaultMenuSliderItem.displayName = "DefaultMenuSliderItem"; function DefaultSliderParts() { return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Track, { className: "vds-slider-track" }), /* @__PURE__ */ React.createElement(TrackFill, { className: "vds-slider-track-fill vds-slider-track" }), /* @__PURE__ */ React.createElement(Thumb, { className: "vds-slider-thumb" })); } DefaultSliderParts.displayName = "DefaultSliderParts"; function DefaultSliderSteps() { return /* @__PURE__ */ React.createElement(Steps, { className: "vds-slider-steps" }, (step) => /* @__PURE__ */ React.createElement("div", { className: "vds-slider-step", key: String(step) })); } DefaultSliderSteps.displayName = "DefaultSliderSteps"; function DefaultFontMenu() { const label = useDefaultLayoutWord("Caption Styles"), $hasCaptions = useMediaState("hasCaptions"), fontSectionLabel = useDefaultLayoutWord("Font"), textSectionLabel = useDefaultLayoutWord("Text"), textBgSectionLabel = useDefaultLayoutWord("Text Background"), displayBgSectionLabel = useDefaultLayoutWord("Display Background"); if (!$hasCaptions) return null; return /* @__PURE__ */ React.createElement(Root$3, { className: "vds-font-menu vds-menu" }, /* @__PURE__ */ React.createElement(DefaultMenuButton, { label }), /* @__PURE__ */ React.createElement(Items, { className: "vds-font-style-items vds-menu-items" }, /* @__PURE__ */ React.createElement(DefaultMenuSection, { label: fontSectionLabel }, /* @__PURE__ */ React.createElement(DefaultFontFamilyMenu, null), /* @__PURE__ */ React.createElement(DefaultFontSizeSlider, null)), /* @__PURE__ */ React.createElement(DefaultMenuSection, { label: textSectionLabel }, /* @__PURE__ */ React.createElement(DefaultTextColorInput, null), /* @__PURE__ */ React.createElement(DefaultTextShadowMenu, null), /* @__PURE__ */ React.createElement(DefaultTextOpacitySlider, null)), /* @__PURE__ */ React.createElement(DefaultMenuSection, { label: textBgSectionLabel }, /* @__PURE__ */ React.createElement(DefaultTextBgInput, null), /* @__PURE__ */ React.createElement(DefaultTextBgOpacitySlider, null)), /* @__PURE__ */ React.createElement(DefaultMenuSection, { label: displayBgSectionLabel }, /* @__PURE__ */ React.createElement(DefaultDisplayBgInput, null), /* @__PURE__ */ React.createElement(DefaultDisplayBgOpacitySlider, null)), /* @__PURE__ */ React.createElement(DefaultMenuSection, null, /* @__PURE__ */ React.createElement(DefaultResetMenuItem, null)))); } DefaultFontMenu.displayName = "DefaultFontMenu"; function DefaultFontFamilyMenu() { return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Family", type: "fontFamily", option: FONT_FAMILY_OPTION }); } DefaultFontFamilyMenu.displayName = "DefaultFontFamilyMenu"; function DefaultFontSizeSlider() { const { icons: Icons } = useDefaultLayoutContext(), option = { ...FONT_SIZE_OPTION, upIcon: Icons.Menu.FontSizeUp, downIcon: Icons.Menu.FontSizeDown }; return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Size", type: "fontSize", option }); } DefaultFontSizeSlider.displayName = "DefaultFontSizeSlider"; function DefaultTextColorInput() { return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Color", type: "textColor", option: FONT_COLOR_OPTION }); } DefaultTextColorInput.displayName = "DefaultTextColorInput"; function DefaultTextOpacitySlider() { const { icons: Icons } = useDefaultLayoutContext(), option = { ...FONT_OPACITY_OPTION, upIcon: Icons.Menu.OpacityUp, downIcon: Icons.Menu.OpacityDown }; return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Opacity", type: "textOpacity", option }); } DefaultTextOpacitySlider.displayName = "DefaultTextOpacitySlider"; function DefaultTextShadowMenu() { return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Shadow", type: "textShadow", option: FONT_TEXT_SHADOW_OPTION }); } DefaultTextShadowMenu.displayName = "DefaultTextShadowMenu"; function DefaultTextBgInput() { return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Color", type: "textBg", option: FONT_COLOR_OPTION }); } DefaultTextBgInput.displayName = "DefaultTextBgInput"; function DefaultTextBgOpacitySlider() { const { icons: Icons } = useDefaultLayoutContext(), option = { ...FONT_OPACITY_OPTION, upIcon: Icons.Menu.OpacityUp, downIcon: Icons.Menu.OpacityDown }; return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Opacity", type: "textBgOpacity", option }); } DefaultTextBgOpacitySlider.displayName = "DefaultTextBgOpacitySlider"; function DefaultDisplayBgInput() { return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Color", type: "displayBg", option: FONT_COLOR_OPTION }); } DefaultDisplayBgInput.displayName = "DefaultDisplayBgInput"; function DefaultDisplayBgOpacitySlider() { const { icons: Icons } = useDefaultLayoutContext(), option = { ...FONT_OPACITY_OPTION, upIcon: Icons.Menu.OpacityUp, downIcon: Icons.Menu.OpacityDown }; return /* @__PURE__ */ React.createElement(DefaultFontSetting, { label: "Opacity", type: "displayBgOpacity", option }); } DefaultDisplayBgOpacitySlider.displayName = "DefaultDisplayBgOpacitySlider"; function DefaultFontSetting({ label, option, type }) { const player = useMediaPlayer(), $currentValue = FONT_SIGNALS[type], $value = useSignal($currentValue), translatedLabel = useDefaultLayoutWord(label); const notify = React.useCallback(() => { player?.dispatchEvent(new Event("vds-font-change")); }, [player]); const onChange = React.useCallback( (newValue) => { $currentValue.set(newValue); notify(); }, [$currentValue, notify] ); if (option.type === "color") { let onColorChange2 = function(event) { onChange(event.target.value); }; return /* @__PURE__ */ React.createElement(DefaultMenuItem, { label: translatedLabel }, /* @__PURE__ */ React.createElement("input", { className: "vds-color-picker", type: "color", value: $value, onChange: onColorChange2 })); } if (option.type === "slider") { let onSliderValueChange2 = function(value) { onChange(value + "%"); }; const { min, max, step, upIcon, downIcon } = option; return /* @__PURE__ */ React.createElement( DefaultMenuSliderItem, { label: translatedLabel, value: $value, UpIcon: upIcon, DownIcon: downIcon, isMin: $value === min + "%", isMax: $value === max + "%" }, /* @__PURE__ */ React.createElement( Root$4, { className: "vds-slider", min, max, step, keyStep: step, value: parseInt($value), "aria-label": translatedLabel, onValueChange: onSliderValueChange2, onDragValueChange: onSliderValueChange2 }, /* @__PURE__ */ React.createElement(DefaultSliderParts, null), /* @__PURE__ */ React.createElement(DefaultSliderSteps, null) ) ); } if (option.type === "radio") { return /* @__PURE__ */ React.createElement( DefaultFontRadioGroup, { id: camelToKebabCase(type), label: translatedLabel, value: $value, values: option.values, onChange } ); } return null; } DefaultFontSetting.displayName = "DefaultFontSetting"; function DefaultFontRadioGroup({ id, label, value, values, onChange }) { const radioOptions = createRadioOptions(values), { translations } = useDefaultLayoutContext(), hint = React.useMemo(() => { const label2 = radioOptions.find((radio) => radio.value === value)?.label || ""; return i18n(translations, label2); }, [value, radioOptions]); return /* @__PURE__ */ React.createElement(Root$3, { className: `vds-${id}-menu vds-menu` }, /* @__PURE__ */ React.createElement(DefaultMenuButton, { label, hint }), /* @__PURE__ */ React.createElement(Items, { className: "vds-menu-items" }, /* @__PURE__ */ React.createElement(DefaultMenuRadioGroup, { value, options: radioOptions, onChange }))); } DefaultFontRadioGroup.displayName = "DefaultFontRadioGroup"; function DefaultResetMenuItem() { const resetText = useDefaultLayoutWord("Reset"); return /* @__PURE__ */ React.createElement("button", { className: "vds-menu-item", role: "menuitem", onClick: onFontReset }, /* @__PURE__ */ React.createElement("span", { className: "vds-menu-item-label" }, resetText)); } DefaultResetMenuItem.displayName = "DefaultResetMenuItem"; function DefaultMenuCheckbox({ label, checked, storageKey, defaultChecked = false, onChange }) { const [isChecked, setIsChecked] = React.useState(defaultChecked), [isActive, setIsActive] = React.useState(false); React.useEffect(() => { const savedValue = storageKey ? localStorage.getItem(storageKey) : null, checked2 = !!(savedValue ?? defaultChecked); setIsChecked(checked2); onChange?.(checked2); }, []); React.useEffect(() => { if (isBoolean(checked)) setIsChecked(checked); }, [checked]); function onPress(event) { if (event && "button" in event && event?.button === 1) return; const toggledCheck = !isChecked; setIsChecked(toggledCheck); if (storageKey) localStorage.setItem(storageKey, toggledCheck ? "1" : ""); onChange?.(toggledCheck, event?.nativeEvent); setIsActive(false); } function onActive(event) { if (event.button !== 0) return; setIsActive(true); } function onKeyDown(event) { if (isKeyboardClick(event.nativeEvent)) onPress(); } return /* @__PURE__ */ React.createElement( "div", { className: "vds-menu-checkbox", role: "menuitemcheckbox", tabIndex: 0, "aria-label": label, "aria-checked": isChecked ? "true" : "false", "data-active": isActive ? "" : null, onPointerUp: onPress, onPointerDown: onActive, onKeyDown } ); } DefaultMenuCheckbox.displayName = "DefaultMenuCheckbox"; function DefaultAccessibilityMenu({ slots }) { const label = useDefaultLayoutWord("Accessibility"), { icons: Icons } = useDefaultLayoutContext(); return /* @__PURE__ */ React.createElement(Root$3, { className: "vds-accessibility-menu vds-menu" }, /* @__PURE__ */ React.createElement(DefaultMenuButton, { label, Icon: Icons.Menu.Accessibility }), /* @__PURE__ */ React.createElement(Items, { className: "vds-menu-items" }, slot(slots, "accessibilityMenuItemsStart", null), /* @__PURE__ */ React.createElement(DefaultMenuSection, null, /* @__PURE__ */ React.createElement(DefaultAnnouncementsMenuCheckbox, null), /* @__PURE__ */ React.createElement(DefaultKeyboardAnimationsMenuCheckbox, null)), /* @__PURE__ */ React.createElement(DefaultMenuSection, null, /* @__PURE__ */ React.createElement(DefaultFontMenu, null)), slot(slots, "accessibilityMenuItemsEnd", null))); } DefaultAccessibilityMenu.displayName = "DefaultAccessibilityMenu"; function DefaultAnnouncementsMenuCheckbox() { const { userPrefersAnnouncements } = useDefaultLayoutContext(), label = useDefaultLayoutWord("Announcements"); function onChange(checked) { userPrefersAnnouncements.set(checked); } return /* @__PURE__ */ React.createElement(DefaultMenuItem, { label }, /* @__PURE__ */ React.createElement( DefaultMenuCheckbox, { label, defaultChecked: true, storageKey: "vds-player::announcements", onChange } )); } DefaultAnnouncementsMenuCheckbox.displayName = "DefaultAnnouncementsMenuCheckbox"; function DefaultKeyboardAnimationsMenuCheckbox() { const $viewType = useMediaState("viewType"), { userPrefersKeyboardAnimations, noKeyboardAnimations } = useDefaultLayoutContext(), label = useDefaultLayoutWord("Keyboard Animations"); if ($viewType !== "video" || noKeyboardAnimations) return null; function onChange(checked) { userPrefersKeyboardAnimations.set(checked); } return /* @__PURE__ */ React.createElement(DefaultMenuItem, { label }, /* @__PURE__ */ React.createElement( DefaultMenuCheckbox, { label, defaultChecked: true, storageKey: "vds-player::keyboard-animations", onChange } )); } DefaultKeyboardAnimationsMenuCheckbox.displayName = "DefaultKeyboardAnimationsMenuCheckbox"; function DefaultAudioMenu({ slots }) { const label = useDefaultLayoutWord("Audio"), $canSetAudioGain = useMediaState("canSetAudioGain"), $audioTracks = useMediaState("audioTracks"), { noAudioGain, icons: Icons } = useDefaultLayoutContext(), hasGainSlider = $canSetAudioGain && !noAudioGain, $disabled = !hasGainSlider && $audioTracks.length <= 1; if ($disabled) return null; return /* @__PURE__ */ React.createElement(Root$3, { className: "vds-audio-menu vds-menu" }, /* @__PURE__ */ React.createElement(DefaultMenuButton, { label, Icon: Icons.Menu.Audio }), /* @__PURE__ */ React.createElement(Items, { className: "vds-menu-items" }, slot(slots, "audioMenuItemsStart", null), /* @__PURE__ */ React.createElement(DefaultAudioTracksMenu, null), hasGainSlider ? /* @__PURE__ */ React.createElement(DefaultAudioBoostMenuSection, null) : null, slot(slots, "audioMenuItemsEnd", null))); } DefaultAudioMenu.displayName = "DefaultAudioMenu"; function DefaultAudioBoostMenuSection() { const $audioGain = useMediaState("audioGain"), label = useDefaultLayoutWord("Boost"), value = Math.round((($audioGain ?? 1) - 1) * 100) + "%", $canSetAudioGain = useMediaState("canSetAudioGain"), { noAudioGain, icons: Icons } = useDefaultLayoutContext(), $disabled = !$canSetAudioGain || noAudioGain, min = useGainMin(), max = useGainMax(); if ($disabled) return null; return /* @__PURE__ */ React.createElement(DefaultMenuSection, { label, value }, /* @__PURE__ */ React.createElement( DefaultMenuSliderItem, { UpIcon: Icons.Menu.AudioBoostUp, DownIcon: Icons.Menu.AudioBoostDown, isMin: (($audioGain ?? 1) - 1) * 100 <= min, isMax: (($audioGain ?? 1) - 1) * 100 === max }, /* @__PURE__ */ React.createElement(DefaultAudioGainSlider, null) )); } DefaultAudioBoostMenuSection.displayName = "DefaultAudioBoostMenuSection"; function useGainMin() { const { audioGains } = useDefaultLayoutContext(), min = isArray(audioGains) ? audioGains[0] : audioGains?.min; return min ?? 0; } function useGainMax() { const { audioGains } = useDefaultLayoutContext(), max = isArray(audioGains) ? audioGains[audioGains.length - 1] : audioGains?.max; return max ?? 300; } function useGainStep() { const { audioGains } = useDefaultLayoutContext(), step = isArray(audioGains) ? audioGains[1] - audioGains[0] : audioGains?.step; return step || 25; } function DefaultAudioGainSlider() { const label = useDefaultLayoutWord("Audio Boost"), min = useGainMin(), max = useGainMax(), step = useGainStep(); return /* @__PURE__ */ React.createElement( Root$5, { className: "vds-audio-gain-slider vds-slider", "aria-label": label, min, max, step, keyStep: step }, /* @__PURE__ */ React.createElement(DefaultSliderParts, null), /* @__PURE__ */ React.createElement(DefaultSliderSteps, null) ); } DefaultAudioGainSlider.displayName = "DefaultAudioGainSlider"; function DefaultAudioTracksMenu() { const { icons: Icons } = useDefaultLayoutContext(), label = useDefaultLayoutWord("Track"), defaultText = useDefaultLayoutWord("Default"), $track = useMediaState("audioTrack"), options = useAudioOptions(); if (options.disabled) return null; return /* @__PURE__ */ React.createElement(Root$3, { className: "vds-audio-track-menu vds-menu" }, /* @__PURE__ */ React.createElement( DefaultMenuButton, { label, hint: $track?.label ?? defaultText, disabled: options.disabled, Icon: Icons.Menu.Audio } ), /* @__PURE__ */ React.createElement(Items, { className: "vds-menu-items" }, /* @__PURE__ */ React.createElement( Root$1, { className: "vds-audio-radio-group vds-radio-group", value: options.selectedValue }, options.map(({ label: label2, value, select }) => /* @__PURE__ */ React.createElement( Item, { className: "vds-audio-radio vds-radio", value, onSelect: select, key: value }, /* @__PURE__ */ React.createElement(Icons.Menu.RadioCheck, { className: "vds-icon" }), /* @__PURE__ */ React.createElement("span", { className: "vds-radio-label" }, label2) )) ))); } DefaultAudioTracksMenu.displayName = "DefaultAudioTracksMenu"; function DefaultCaptionMenu({ slots }) { const { icons: Icons } = useDefaultLayoutContext(), label = useDefaultLayoutWord("Captions"), offText = useDefaultLayoutWord("Off"), options = useCaptionOptions({ off: offText }), hint = options.selectedTrack?.label ?? offText; if (options.disabled) return null; return /* @__PURE__ */ React.createElement(Root$3, { className: "vds-captions-menu vds-menu" }, /* @__PURE__ */ React.createElement( DefaultMenuButton, { label, hint, disabled: options.disabled, Icon: Icons.Menu.Captions } ), /* @__PURE__ */ React.createElement(Items, { className: "vds-menu-items" }, slot(slots, "captionsMenuItemsStart", null), /* @__PURE__ */ React.createElement( Root$1, { className: "vds-captions-radio-group vds-radio-group", value: options.selectedValue }, options.map(({ label: label2, value, select }) => /* @__PURE__ */ React.createElement( Item, { className: "vds-caption-radio vds-radio", value, onSelect: select, key: value }, /* @__PURE__ */ React.createElement(Icons.Menu.RadioCheck, { className: "vds-icon" }), /* @__PURE__ */ React.createElement("span", { className: "vds-radio-label" }, label2) )) ), slot(slots, "captionsMenuItemsEnd", null))); } DefaultCaptionMenu.displayName = "DefaultCaptionMenu"; function DefaultPlaybackMenu({ slots }) { const label = useDefaultLayoutWord("Playback"), { icons: Icons } = useDefaultLayoutContext(); return /* @__PURE__ */ React.createElement(Root$3, { className: "vds-playback-menu vds-menu" }, /* @__PURE__ */ React.createElement(DefaultMenuButton, { label, Icon: Icons.Menu.Playback }), /* @__PURE__ */ React.createElement(Items, { className: "vds-menu-items" }, slot(slots, "playbackMenuItemsStart", null), /* @__PURE__ */ React.createElement(DefaultMenuSection, null, slot(slots, "playbackMenuLoop", /* @__PURE__ */ React.createElement(DefaultLoopMenuCheckbox, null))), /* @__PURE__ */ React.createElement(DefaultSpeedMenuSection, null), /* @__PURE__ */ React.createElement(DefaultQualityMenuSection, null), slot(slots, "playbackMenuItemsEnd", null))); } DefaultPlaybackMenu.displayName = "DefaultPlaybackMenu"; function DefaultLoopMenuCheckbox() { const { remote } = useMediaContext(), label = useDefaultLayoutWord("Loop"); function onChange(checked, trigger) { remote.userPrefersLoopChange(checked, trigger); } return /* @__PURE__ */ React.createElement(DefaultMenuItem, { label }, /* @__PURE__ */ React.createElement(DefaultMenuCheckbox, { label, storageKey: "vds-player::user-loop", onChange })); } DefaultLoopMenuCheckbox.displayName = "DefaultLoopMenuCheckbox"; function DefaultAutoQualityMenuCheckbox() { const { remote, qualities } = useMediaContext(), $autoQuality = useMediaState("autoQuality"), label = useDefaultLayoutWord("Auto"); function onChange(checked, trigger) { if (checked) { remote.requestAutoQuality(trigger); } else { remote.changeQuality(qualities.selectedIndex, trigger); } } return /* @__PURE__ */ React.createElement(DefaultMenuItem, { label }, /* @__PURE__ */ React.createElement( DefaultMenuCheckbox, { label, checked: $autoQuality, onChange, defaultChecked: $autoQuality } )); } DefaultAutoQualityMenuCheckbox.displayName = "DefaultAutoQualityMenuCheckbox"; function DefaultQualityMenuSection() { const { hideQualityBitrate, icons: Icons } = useDefaultLayoutContext(), $canSetQuality = useMediaState("canSetQuality"), $qualities = useMediaState("qualities"), $quality = useMediaState("quality"), label = useDefaultLayoutWord("Quality"), autoText = useDefaultLayoutWord("Auto"), sortedQualities = React.useMemo(() => sortVideoQualities($qualities), [$qualities]); if (!$canSetQuality || $qualities.length <= 1) return null; const height = $quality?.height, bitrate = !hideQualityBitrate ? $quality?.bitrate : null, bitrateText = bitrate && bitrate > 0 ? `${(bitrate / 1e6).toFixed(2)} Mbps` : null, value = height ? `${height}p${bitrateText ? ` (${bitrateText})` : ""}` : autoText, isMin = sortedQualities[0] === $quality, isMax = sortedQualities.at(-1) === $quality; return /* @__PURE__ */ React.createElement(DefaultMenuSection, { label, value }, /* @__PURE__ */ React.createElement( DefaultMenuSliderItem, { UpIcon: Icons.Menu.QualityUp, DownIcon: Icons.Menu.QualityDown, isMin, isMax }, /* @__PURE__ */ React.createElement(DefaultQualitySlider, null) ), /* @__PURE__ */ React.createElement(DefaultAutoQualityMenuCheckbox, null)); } DefaultQualityMenuSection.displayName = "DefaultQualityMenuSection"; function DefaultQualitySlider() { const label = useDefaultLayoutWord("Quality"); return /* @__PURE__ */ React.createElement(Root$7, { className: "vds-quality-slider vds-slider", "aria-label": label }, /* @__PURE__ */ React.createElement(DefaultSliderParts, null), /* @__PURE__ */ React.createElement(DefaultSliderSteps, null)); } DefaultQualitySlider.displayName = "DefaultQualitySlider"; function DefaultSpeedMenuSection() { const { icons: Icons } = useDefaultLayoutContext(), $playbackRate = useMediaState("playbackRate"), $canSetPlaybackRate = useMediaState("canSetPlaybackRate"), label = useDefaultLayoutWord("Speed"), normalText = useDefaultLayoutWord("Normal"), min = useSpeedMin(), max = useSpeedMax(), value = $playbackRate === 1 ? normalText : $playbackRate + "x"; if (!$canSetPlaybackRate) return null; return /* @__PURE__ */ React.createElement(DefaultMenuSection, { label, value }, /* @__PURE__ */ React.crea