@glitchlab/react-video-player 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -99,7 +99,7 @@ No client-component wrapper required.
99
99
  | `className` | `string` | `""` | Extra class added to the outer container (alongside `.gvp-root`). |
100
100
  | `muted` | `boolean` | `true` | Mute the video. Required for autoplay in most browsers. |
101
101
  | `loop` | `boolean` | `false` | Loop playback. |
102
- | `controls` | `boolean` | `false` | Show native browser controls. |
102
+ | `controls` | `boolean \| "custom" \| "native"` | `true` | `true` (default) / `"custom"` — branded control bar (play/seek/time/speed/captions/volume/PiP/fullscreen) that auto-hides during playback. `"native"` — native browser controls. `false` — no controls (just the play overlay). See [Custom controls](#custom-controls). |
103
103
  | `autoPlay` | `boolean` | `false` | Start playback as soon as the source is ready. Works for HLS (after `MANIFEST_PARSED`), native MP4/WebM (after `loadedmetadata`), and YouTube embeds. Browsers block sound-on autoplay, so this only fires when `muted` is also `true` (the default). |
104
104
  | `frameMaxWidth` | `{ desktop?: string; mobile?: string }` | `{ desktop: "960px", mobile: "420px" }` | Max width of the player in each device mode. |
105
105
  | `aspectRatio` | `{ desktop?: AspectRatio; mobile?: AspectRatio }` | `{ desktop: "16/9", mobile: "9/16" }` | Aspect ratio per device mode. `AspectRatio` is `` `${number}/${number}` ``. |
@@ -108,6 +108,71 @@ No client-component wrapper required.
108
108
 
109
109
  ---
110
110
 
111
+ ## Custom controls
112
+
113
+ The branded control bar is **on by default** — it looks and behaves the same across every browser and OS, no inconsistent native chrome:
114
+
115
+ ```tsx
116
+ {/* Custom control bar — this is the default, controls={true} */}
117
+ <ReactVideoPlayer src="/videos/movie.m3u8" />
118
+
119
+ {/* Same thing, explicit */}
120
+ <ReactVideoPlayer src="/videos/movie.m3u8" controls="custom" />
121
+
122
+ {/* Native browser controls instead */}
123
+ <ReactVideoPlayer src="/videos/movie.m3u8" controls="native" />
124
+
125
+ {/* No controls at all */}
126
+ <ReactVideoPlayer src="/videos/movie.m3u8" controls={false} />
127
+ ```
128
+
129
+ The bar includes, left to right:
130
+
131
+ - **Play / pause**
132
+ - **Seek bar** with a buffered indicator (live scrub — the video previews as you drag)
133
+ - **Time** — `current / duration`
134
+ - **Playback speed** — `0.5×` to `2×` menu
135
+ - **Captions** — appears only when the source has `<track>` subtitle/caption tracks
136
+ - **Volume** — mute toggle + slider (slider expands on hover)
137
+ - **Picture-in-Picture** — appears only where the browser supports it
138
+ - **Fullscreen**
139
+
140
+ It **auto-hides 3 seconds** after the last interaction during playback and reappears on mouse move; it stays visible while paused. Clicking anywhere on the video toggles play/pause.
141
+
142
+ ### Keyboard shortcuts
143
+
144
+ When the player has focus (`controls="custom"`):
145
+
146
+ | Key | Action |
147
+ |----------------|-------------------|
148
+ | `Space` / `K` | Play / pause |
149
+ | `←` / `→` | Seek ∓ 5s |
150
+ | `↑` / `↓` | Volume ± 10% |
151
+ | `M` | Mute toggle |
152
+ | `F` | Fullscreen toggle |
153
+ | `P` | Picture-in-Picture |
154
+
155
+ ### Platform notes
156
+
157
+ The bar degrades gracefully where a platform can't support a control:
158
+
159
+ - **iPhone Safari** — the volume slider is hidden (`HTMLVideoElement.volume` is read-only on iOS); the mute toggle remains. Fullscreen uses Apple's native player.
160
+ - **Firefox** — the Picture-in-Picture button is hidden (no JS API; use Firefox's own PiP affordance).
161
+ - **YouTube URLs** — the custom bar does not render; YouTube's iframe can't be driven by it, so YouTube's own controls are shown instead.
162
+
163
+ ### Styling
164
+
165
+ Every part has a `.gvp-*` class hook — override what you need:
166
+
167
+ ```css
168
+ .gvp-controls { /* the bar */ }
169
+ .gvp-ctrl-btn { /* generic control button */ }
170
+ .gvp-seek-progress { background: deeppink; }
171
+ .gvp-volume-fill { /* volume slider fill */ }
172
+ ```
173
+
174
+ ---
175
+
111
176
  ## YouTube URLs
112
177
 
113
178
  Pass any common YouTube URL as `src` and the player swaps the `<video>` element for a privacy-enhanced (`youtube-nocookie.com`) embed inside the same styled frame — no extra prop needed:
package/dist/index.cjs CHANGED
@@ -1,3 +1,3 @@
1
1
  "use client";
2
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("react/jsx-runtime"),l=require("react"),N=require("hls.js");function R(e){var n,s,t="";if(typeof e=="string"||typeof e=="number")t+=e;else if(typeof e=="object")if(Array.isArray(e)){var a=e.length;for(n=0;n<a;n++)e[n]&&(s=R(e[n]))&&(t&&(t+=" "),t+=s)}else for(s in e)e[s]&&(t&&(t+=" "),t+=s);return t}function I(){for(var e,n,s=0,t="",a=arguments.length;s<a;s++)(e=arguments[s])&&(n=R(e))&&(t&&(t+=" "),t+=n);return t}const $=l.forwardRef(({src:e,hlsConfig:n,isHls:s,autoPlay:t,children:a,...h},p)=>{const i=l.useRef(null),c=l.useRef(null);l.useImperativeHandle(p,()=>i.current);const y=globalThis.window!==void 0&&N.isSupported(),b=!!s||y&&typeof e=="string"&&e.endsWith(".m3u8");return l.useEffect(()=>{if(!e)return;const o=i.current;if(!o)return;const d=()=>{t&&o.play().catch(()=>{})};for(c.current&&(c.current.destroy(),c.current=null),o.pause(),o.removeAttribute("src");o.firstChild;)o.firstChild.remove();if(b){const u=new N(n);c.current=u,u.attachMedia(o),u.loadSource(e),u.on(N.Events.MANIFEST_PARSED,d),u.on(N.Events.ERROR,(M,P)=>{P.fatal&&(u.destroy(),c.current=null)})}else o.src=e,o.load(),o.addEventListener("loadedmetadata",d,{once:!0});return()=>{for(o.removeEventListener("loadedmetadata",d),c.current&&(c.current.destroy(),c.current=null),o.pause(),o.removeAttribute("src");o.firstChild;)o.firstChild.remove();o.load()}},[e,b,n,t]),r.jsx("video",{ref:i,...h,children:a})});$.displayName="HLSPlayer";const H="gvp-icon",S=e=>e?`${H} ${e}`:H,J=({className:e})=>r.jsxs("svg",{className:S(e),width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:[r.jsx("path",{d:"M14 2H10C6.72077 2 5.08116 2 3.91891 2.81382C3.48891 3.1149 3.1149 3.48891 2.81382 3.91891C2 5.08116 2 6.72077 2 10C2 13.2792 2 14.9188 2.81382 16.0811C3.1149 16.5111 3.48891 16.8851 3.91891 17.1862C5.08116 18 6.72077 18 10 18H14C17.2792 18 18.9188 18 20.0811 17.1862C20.5111 16.8851 20.8851 16.5111 21.1862 16.0811C22 14.9188 22 13.2792 22 10C22 6.72077 22 5.08116 21.1862 3.91891C20.8851 3.48891 20.5111 3.1149 20.0811 2.81382C18.9188 2 17.2792 2 14 2Z",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"}),r.jsx("path",{d:"M11 15H13",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"}),r.jsx("path",{d:"M14.5 22L14.1845 21.5811C13.4733 20.6369 13.2969 19.1944 13.7468 18M9.5 22L9.8155 21.5811C10.5267 20.6369 10.7031 19.1944 10.2532 18",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"}),r.jsx("path",{d:"M7 22H17",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),W=({className:e})=>r.jsxs("svg",{className:S(e),width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:[r.jsx("path",{d:"M5 9C5 5.70017 5 4.05025 6.02513 3.02513C7.05025 2 8.70017 2 12 2C15.2998 2 16.9497 2 17.9749 3.02513C19 4.05025 19 5.70017 19 9V15C19 18.2998 19 19.9497 17.9749 20.9749C16.9497 22 15.2998 22 12 22C8.70017 22 7.05025 22 6.02513 20.9749C5 19.9497 5 18.2998 5 15V9Z",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round"}),r.jsx("path",{d:"M11 19H13",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"}),r.jsx("path",{d:"M9 2L9.089 2.53402C9.28188 3.69129 9.37832 4.26993 9.77519 4.62204C10.1892 4.98934 10.7761 5 12 5C13.2239 5 13.8108 4.98934 14.2248 4.62204C14.6217 4.26993 14.7181 3.69129 14.911 2.53402L15 2",stroke:"currentColor",strokeWidth:"2",strokeLinejoin:"round"})]}),X=({className:e})=>r.jsx("svg",{className:S(e),width:"14",height:"14",viewBox:"0 0 14 14",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:r.jsx("path",{d:"M6.94994 5.53594L12.1929 0.292938C12.5834 -0.0975275 13.2165 -0.0975279 13.6069 0.292938C13.9974 0.683403 13.9974 1.31647 13.6069 1.70694L8.36394 6.94994L13.6069 12.1929C13.9974 12.5834 13.9974 13.2165 13.6069 13.6069C13.2165 13.9974 12.5834 13.9974 12.1929 13.6069L6.94994 8.36394L1.70694 13.6069C1.31647 13.9974 0.683403 13.9974 0.292938 13.6069C-0.0975279 13.2165 -0.0975277 12.5834 0.292938 12.1929L5.53594 6.94994L0.292938 1.70694C-0.0975279 1.31647 -0.0975279 0.683403 0.292938 0.292938C0.683403 -0.0975279 1.31647 -0.0975277 1.70694 0.292938L6.94994 5.53594Z",fill:"currentColor"})}),G=({className:e})=>r.jsx("svg",{className:S(e),width:"22",height:"22",viewBox:"0 0 16 16",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:r.jsx("path",{d:"M5.3335 11.45V4.54997C5.3335 4.36108 5.40016 4.20275 5.5335 4.07497C5.66683 3.94719 5.82238 3.8833 6.00016 3.8833C6.05572 3.8833 6.11405 3.89163 6.17516 3.9083C6.23627 3.92497 6.29461 3.94997 6.35016 3.9833L11.7835 7.4333C11.8835 7.49997 11.9585 7.5833 12.0085 7.6833C12.0585 7.7833 12.0835 7.88886 12.0835 7.99997C12.0835 8.11108 12.0585 8.21663 12.0085 8.31663C11.9585 8.41663 11.8835 8.49997 11.7835 8.56663L6.35016 12.0166C6.29461 12.05 6.23627 12.075 6.17516 12.0916C6.11405 12.1083 6.05572 12.1166 6.00016 12.1166C5.82238 12.1166 5.66683 12.0527 5.5335 11.925C5.40016 11.7972 5.3335 11.6389 5.3335 11.45Z",fill:"currentColor"})});function Z(e){if(!e)return null;if(/^[A-Za-z0-9_-]{11}$/.test(e))return e;let n;try{n=new URL(e)}catch{return null}const s=n.hostname.replace(/^www\./,"");if(s==="youtu.be"){const t=n.pathname.slice(1).split("/")[0];return/^[A-Za-z0-9_-]{11}$/.test(t)?t:null}if(s==="youtube.com"||s==="m.youtube.com"||s==="music.youtube.com"||s==="youtube-nocookie.com"){const t=n.searchParams.get("v");if(t&&/^[A-Za-z0-9_-]{11}$/.test(t))return t;const a=/^\/(?:embed|shorts|v|live)\/([A-Za-z0-9_-]{11})/.exec(n.pathname);if(a)return a[1]}return null}function U(e){try{const n=new URL(e),s=n.searchParams.get("t")??n.searchParams.get("start");if(!s)return null;if(/^\d+s?$/.test(s))return Number.parseInt(s,10);const t=/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/.exec(s);if(t){const a=Number.parseInt(t[1]??"0",10),h=Number.parseInt(t[2]??"0",10),p=Number.parseInt(t[3]??"0",10),i=a*3600+h*60+p;return i>0?i:null}}catch{}return null}function Y(e,n={}){const{autoPlay:s=!1,muted:t=!0,loop:a=!1,controls:h=!0,startSeconds:p}=n,i=new URLSearchParams({rel:"0",modestbranding:"1",playsinline:"1",controls:h?"1":"0"});return s?(i.set("autoplay","1"),i.set("mute","1")):t&&i.set("mute","1"),a&&(i.set("loop","1"),i.set("playlist",e)),p&&p>0&&i.set("start",String(p)),`https://www.youtube-nocookie.com/embed/${e}?${i.toString()}`}const K=({src:e,poster:n,showDeviceToggle:s=!0,defaultDevice:t="desktop",hoverPlay:a=!1,tooltipText:h,onClose:p,className:i="",muted:c=!0,loop:y=!1,controls:b=!1,autoPlay:o=!1,frameMaxWidth:d,aspectRatio:u,hlsConfig:M,children:P})=>{const m=l.useRef(null),w=l.useRef(null),[v,T]=l.useState(t),[_,g]=l.useState(!1),[B,k]=l.useState(!1),j=l.useMemo(()=>Z(e),[e]),C=j!==null,V=l.useMemo(()=>v==="mobile"?(u==null?void 0:u.mobile)??"9/16":(u==null?void 0:u.desktop)??"16/9",[v,u]),z=l.useMemo(()=>v==="mobile"?(d==null?void 0:d.mobile)??"420px":(d==null?void 0:d.desktop)??"960px",[v,d]),D=l.useMemo(()=>j?Y(j,{autoPlay:o,muted:c,loop:y,controls:b,startSeconds:U(e)}):null,[j,e,o,c,y,b]),x=l.useCallback(async()=>{const f=m.current;if(f){if(w.current)try{await w.current}catch{}f.pause()}},[]),L=l.useCallback(async()=>{const f=m.current;if(f)try{f.readyState<2&&f.load();const E=f.play();w.current=E,await E,g(!0)}catch{g(!1)}finally{w.current=null}},[]),q=l.useCallback(()=>{!a||C||L()},[a,C,L]),O=l.useCallback(()=>{!a||C||x().then(()=>g(!1))},[a,C,x]),A=l.useCallback(async()=>{const f=m.current;f&&(f.paused?await L():(await x(),g(!1)))},[L,x]);return r.jsxs("div",{className:I("gvp-root",i),style:{width:z,aspectRatio:V},onMouseEnter:()=>{k(!0),q()},onMouseLeave:()=>{k(!1),O()},children:[C?r.jsx("iframe",{className:"gvp-video gvp-youtube",src:D??void 0,title:"YouTube video player",allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",allowFullScreen:!0,referrerPolicy:"strict-origin-when-cross-origin"}):r.jsx($,{ref:m,src:e,poster:n,muted:c,loop:y,playsInline:!0,preload:"metadata",controls:b,autoPlay:o,hlsConfig:M,className:"gvp-video",onPlay:()=>g(!0),onPause:()=>g(!1),children:P}),!C&&r.jsx("div",{className:"gvp-vignette"}),s&&r.jsx("div",{className:"gvp-toggle",children:r.jsxs("div",{className:"gvp-toggle-pill",children:[r.jsx("button",{type:"button",onClick:()=>T("desktop"),className:I("gvp-toggle-btn",v==="desktop"&&"is-active"),"aria-label":"Desktop view","aria-pressed":v==="desktop",children:r.jsx(J,{})}),r.jsx("div",{className:"gvp-toggle-divider"}),r.jsx("button",{type:"button",onClick:()=>T("mobile"),className:I("gvp-toggle-btn",v==="mobile"&&"is-active"),"aria-label":"Mobile view","aria-pressed":v==="mobile",children:r.jsx(W,{})})]})}),p&&r.jsx("button",{type:"button",onClick:p,className:"gvp-close","aria-label":"Close",children:r.jsx(X,{})}),!C&&!_&&r.jsx("div",{className:"gvp-play-wrap",children:r.jsxs("button",{type:"button",onClick:()=>void A(),onMouseEnter:()=>k(!0),onMouseLeave:()=>k(!1),className:"gvp-play","aria-label":"Play",children:[r.jsx(G,{}),h&&B&&r.jsx("span",{className:"gvp-tooltip",role:"tooltip",children:h})]})}),!C&&r.jsx("div",{className:"gvp-bottom-fade"})]})};exports.ReactVideoPlayer=K;exports.parseYouTubeId=Z;exports.parseYouTubeStart=U;exports.youTubeEmbedUrl=Y;
2
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),o=require("react"),H=require("hls.js");function z(e){var n,s,r="";if(typeof e=="string"||typeof e=="number")r+=e;else if(typeof e=="object")if(Array.isArray(e)){var u=e.length;for(n=0;n<u;n++)e[n]&&(s=z(e[n]))&&(r&&(r+=" "),r+=s)}else for(s in e)e[s]&&(r&&(r+=" "),r+=s);return r}function M(){for(var e,n,s=0,r="",u=arguments.length;s<u;s++)(e=arguments[s])&&(n=z(e))&&(r&&(r+=" "),r+=n);return r}const _="gvp-icon",w=e=>e?`${_} ${e}`:_,de=({className:e})=>t.jsxs("svg",{className:w(e),width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:[t.jsx("path",{d:"M14 2H10C6.72077 2 5.08116 2 3.91891 2.81382C3.48891 3.1149 3.1149 3.48891 2.81382 3.91891C2 5.08116 2 6.72077 2 10C2 13.2792 2 14.9188 2.81382 16.0811C3.1149 16.5111 3.48891 16.8851 3.91891 17.1862C5.08116 18 6.72077 18 10 18H14C17.2792 18 18.9188 18 20.0811 17.1862C20.5111 16.8851 20.8851 16.5111 21.1862 16.0811C22 14.9188 22 13.2792 22 10C22 6.72077 22 5.08116 21.1862 3.91891C20.8851 3.48891 20.5111 3.1149 20.0811 2.81382C18.9188 2 17.2792 2 14 2Z",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"}),t.jsx("path",{d:"M11 15H13",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"}),t.jsx("path",{d:"M14.5 22L14.1845 21.5811C13.4733 20.6369 13.2969 19.1944 13.7468 18M9.5 22L9.8155 21.5811C10.5267 20.6369 10.7031 19.1944 10.2532 18",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"}),t.jsx("path",{d:"M7 22H17",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),he=({className:e})=>t.jsxs("svg",{className:w(e),width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:[t.jsx("path",{d:"M5 9C5 5.70017 5 4.05025 6.02513 3.02513C7.05025 2 8.70017 2 12 2C15.2998 2 16.9497 2 17.9749 3.02513C19 4.05025 19 5.70017 19 9V15C19 18.2998 19 19.9497 17.9749 20.9749C16.9497 22 15.2998 22 12 22C8.70017 22 7.05025 22 6.02513 20.9749C5 19.9497 5 18.2998 5 15V9Z",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round"}),t.jsx("path",{d:"M11 19H13",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"}),t.jsx("path",{d:"M9 2L9.089 2.53402C9.28188 3.69129 9.37832 4.26993 9.77519 4.62204C10.1892 4.98934 10.7761 5 12 5C13.2239 5 13.8108 4.98934 14.2248 4.62204C14.6217 4.26993 14.7181 3.69129 14.911 2.53402L15 2",stroke:"currentColor",strokeWidth:"2",strokeLinejoin:"round"})]}),pe=({className:e})=>t.jsx("svg",{className:w(e),width:"14",height:"14",viewBox:"0 0 14 14",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:t.jsx("path",{d:"M6.94994 5.53594L12.1929 0.292938C12.5834 -0.0975275 13.2165 -0.0975279 13.6069 0.292938C13.9974 0.683403 13.9974 1.31647 13.6069 1.70694L8.36394 6.94994L13.6069 12.1929C13.9974 12.5834 13.9974 13.2165 13.6069 13.6069C13.2165 13.9974 12.5834 13.9974 12.1929 13.6069L6.94994 8.36394L1.70694 13.6069C1.31647 13.9974 0.683403 13.9974 0.292938 13.6069C-0.0975279 13.2165 -0.0975277 12.5834 0.292938 12.1929L5.53594 6.94994L0.292938 1.70694C-0.0975279 1.31647 -0.0975279 0.683403 0.292938 0.292938C0.683403 -0.0975279 1.31647 -0.0975277 1.70694 0.292938L6.94994 5.53594Z",fill:"currentColor"})}),me=({className:e})=>t.jsx("svg",{className:w(e),width:"22",height:"22",viewBox:"0 0 16 16",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:t.jsx("path",{d:"M5.3335 11.45V4.54997C5.3335 4.36108 5.40016 4.20275 5.5335 4.07497C5.66683 3.94719 5.82238 3.8833 6.00016 3.8833C6.05572 3.8833 6.11405 3.89163 6.17516 3.9083C6.23627 3.92497 6.29461 3.94997 6.35016 3.9833L11.7835 7.4333C11.8835 7.49997 11.9585 7.5833 12.0085 7.6833C12.0585 7.7833 12.0835 7.88886 12.0835 7.99997C12.0835 8.11108 12.0585 8.21663 12.0085 8.31663C11.9585 8.41663 11.8835 8.49997 11.7835 8.56663L6.35016 12.0166C6.29461 12.05 6.23627 12.075 6.17516 12.0916C6.11405 12.1083 6.05572 12.1166 6.00016 12.1166C5.82238 12.1166 5.66683 12.0527 5.5335 11.925C5.40016 11.7972 5.3335 11.6389 5.3335 11.45Z",fill:"currentColor"})}),fe=({className:e})=>t.jsxs("svg",{className:w(e),width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:[t.jsx("path",{d:"M3 14V10C3 9.44772 3.44772 9 4 9H6.58579C6.851 9 7.10536 8.89464 7.29289 8.70711L11.2929 4.70711C11.9229 4.07714 13 4.52331 13 5.41421V18.5858C13 19.4767 11.9229 19.9229 11.2929 19.2929L7.29289 15.2929C7.10536 15.1054 6.851 15 6.58579 15H4C3.44772 15 3 14.5523 3 14Z",stroke:"currentColor",strokeWidth:"1.5",strokeLinejoin:"round"}),t.jsx("path",{d:"M16.5 8C17.5 9 18 10.5 18 12C18 13.5 17.5 15 16.5 16M19 5.5C20.5 7 21.5 9.5 21.5 12C21.5 14.5 20.5 17 19 18.5",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),ge=({className:e})=>t.jsx("svg",{className:w(e),width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:t.jsx("path",{d:"M7 5.5V18.5C7 19.2659 7.84856 19.7261 8.4899 19.3071L19.0801 12.4014C19.6644 12.0204 19.6644 11.9796 19.0801 11.5986L8.4899 4.69288C7.84856 4.27388 7 4.73408 7 5.5Z",fill:"currentColor"})}),ve=({className:e})=>t.jsxs("svg",{className:w(e),width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:[t.jsx("rect",{x:"6",y:"5",width:"4",height:"14",rx:"1",fill:"currentColor"}),t.jsx("rect",{x:"14",y:"5",width:"4",height:"14",rx:"1",fill:"currentColor"})]}),xe=({className:e})=>t.jsxs("svg",{className:w(e),width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:[t.jsx("path",{d:"M3 14V10C3 9.44772 3.44772 9 4 9H6.58579C6.851 9 7.10536 8.89464 7.29289 8.70711L11.2929 4.70711C11.9229 4.07714 13 4.52331 13 5.41421V18.5858C13 19.4767 11.9229 19.9229 11.2929 19.2929L7.29289 15.2929C7.10536 15.1054 6.851 15 6.58579 15H4C3.44772 15 3 14.5523 3 14Z",stroke:"currentColor",strokeWidth:"1.5",strokeLinejoin:"round"}),t.jsx("path",{d:"M16.5 8C17.5 9 18 10.5 18 12C18 13.5 17.5 15 16.5 16M19 5.5C20.5 7 21.5 9.5 21.5 12C21.5 14.5 20.5 17 19 18.5",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),ke=({className:e})=>t.jsxs("svg",{className:w(e),width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:[t.jsx("path",{d:"M3 14V10C3 9.44772 3.44772 9 4 9H6.58579C6.851 9 7.10536 8.89464 7.29289 8.70711L11.2929 4.70711C11.9229 4.07714 13 4.52331 13 5.41421V18.5858C13 19.4767 11.9229 19.9229 11.2929 19.2929L7.29289 15.2929C7.10536 15.1054 6.851 15 6.58579 15H4C3.44772 15 3 14.5523 3 14Z",stroke:"currentColor",strokeWidth:"1.5",strokeLinejoin:"round"}),t.jsx("path",{d:"M16.5 8C17.5 9 18 10.5 18 12C18 13.5 17.5 15 16.5 16",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),be=({className:e})=>t.jsxs("svg",{className:w(e),width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:[t.jsx("path",{d:"M3 14V10C3 9.44772 3.44772 9 4 9H6.58579C6.851 9 7.10536 8.89464 7.29289 8.70711L11.2929 4.70711C11.9229 4.07714 13 4.52331 13 5.41421V18.5858C13 19.4767 11.9229 19.9229 11.2929 19.2929L7.29289 15.2929C7.10536 15.1054 6.851 15 6.58579 15H4C3.44772 15 3 14.5523 3 14Z",stroke:"currentColor",strokeWidth:"1.5",strokeLinejoin:"round"}),t.jsx("path",{d:"M16 9L22 15M22 9L16 15",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),we=({className:e})=>t.jsx("svg",{className:w(e),width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:t.jsx("path",{d:"M4 9V4H9M15 4H20V9M20 15V20H15M9 20H4V15",stroke:"currentColor",strokeWidth:"1.75",strokeLinecap:"round",strokeLinejoin:"round"})}),Ce=({className:e})=>t.jsx("svg",{className:w(e),width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:t.jsx("path",{d:"M9 4V9H4M15 9V4H20M15 20V15H20M9 15H4V20",stroke:"currentColor",strokeWidth:"1.75",strokeLinecap:"round",strokeLinejoin:"round"})}),je=({className:e})=>t.jsxs("svg",{className:w(e),width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:[t.jsx("rect",{x:"2",y:"4",width:"20",height:"16",rx:"2",stroke:"currentColor",strokeWidth:"1.5"}),t.jsx("rect",{x:"12",y:"12",width:"8",height:"5",rx:"1",fill:"currentColor"})]}),Le=({className:e})=>t.jsxs("svg",{className:w(e),width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:[t.jsx("rect",{x:"2",y:"5",width:"20",height:"14",rx:"3",stroke:"currentColor",strokeWidth:"1.5"}),t.jsx("path",{d:"M7 12H11M13 12H17M7 15H9M11 15H13M15 15H17",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round"})]}),Ee=({className:e})=>t.jsxs("svg",{className:w(e),width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:[t.jsx("path",{d:"M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2Z",stroke:"currentColor",strokeWidth:"1.5"}),t.jsx("path",{d:"M12 8V12L14.5 14.5",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round"})]}),ye=({className:e})=>t.jsx("svg",{className:w(e),width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",children:t.jsx("path",{d:"M6 9L12 15L18 9",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})});function q(e){if(!Number.isFinite(e)||e<0)return"--:--";const n=Math.floor(e),s=Math.floor(n/3600),r=Math.floor(n%3600/60),d=(n%60).toString().padStart(2,"0");if(s>0){const c=r.toString().padStart(2,"0");return`${s}:${c}:${d}`}return`${r}:${d}`}const F=(()=>{if(typeof navigator>"u")return!1;const e=navigator.userAgent;return/iPad|iPhone|iPod/.test(e)||e.includes("Mac")&&navigator.maxTouchPoints>1})(),U=typeof navigator>"u"?!1:/iPhone|iPod/.test(navigator.userAgent),Ne=3e3,P={isSupported(){return typeof document>"u"?!1:!!(document.fullscreenEnabled||document.webkitFullscreenEnabled)},element(){return typeof document>"u"?null:document.fullscreenElement??document.webkitFullscreenElement??null},request(e){const n=e.requestFullscreen??e.webkitRequestFullscreen;return n?n.call(e):Promise.reject(new Error("Fullscreen not supported"))},exit(){const e=document.exitFullscreen??document.webkitExitFullscreen;return e?e.call(document):Promise.reject(new Error("Fullscreen not supported"))},onChange(e){return document.addEventListener("fullscreenchange",e),document.addEventListener("webkitfullscreenchange",e),()=>{document.removeEventListener("fullscreenchange",e),document.removeEventListener("webkitfullscreenchange",e)}}},Se=({video:e,isPlaying:n,container:s,onTogglePlay:r})=>{const[u,d]=o.useState(!0),c=o.useRef(null),l=o.useCallback(()=>{c.current!==null&&(clearTimeout(c.current),c.current=null)},[]),i=o.useCallback(()=>{l(),n&&(c.current=setTimeout(()=>d(!1),Ne))},[l,n]),f=o.useCallback(()=>{d(!0),i()},[i]);return o.useEffect(()=>{if(!n){l(),d(!0);return}return i(),l},[n,i,l]),o.useEffect(()=>{if(!s)return;const g=()=>f();return s.addEventListener("mousemove",g),s.addEventListener("touchstart",g),s.addEventListener("focusin",g),()=>{s.removeEventListener("mousemove",g),s.removeEventListener("touchstart",g),s.removeEventListener("focusin",g)}},[s,f]),o.useEffect(()=>{if(!s||!e)return;const g=a=>{if(!(!s.contains(document.activeElement)||a.target.tagName==="INPUT"&&a.key!==" "))switch(a.key){case" ":case"k":a.preventDefault(),r(),f();break;case"ArrowLeft":a.preventDefault(),e.currentTime=Math.max(0,e.currentTime-5),f();break;case"ArrowRight":a.preventDefault(),e.currentTime=Math.min(e.duration||0,e.currentTime+5),f();break;case"ArrowUp":a.preventDefault(),e.volume=Math.min(1,e.volume+.1),e.muted&&(e.muted=!1),f();break;case"ArrowDown":a.preventDefault(),e.volume=Math.max(0,e.volume-.1),f();break;case"m":e.muted=!e.muted,f();break;case"f":P.element()===s?P.exit().catch(()=>{}):P.request(s).catch(()=>{}),f();break;case"p":document.pictureInPictureElement?document.exitPictureInPicture().catch(()=>{}):document.pictureInPictureEnabled&&e.requestPictureInPicture().catch(()=>{}),f();break}};return document.addEventListener("keydown",g),()=>document.removeEventListener("keydown",g)},[s,e,r,f]),t.jsx("div",{role:"toolbar","aria-label":"Video controls",className:M("gvp-controls",!u&&"is-hidden"),onMouseEnter:l,onMouseLeave:i,children:t.jsxs("div",{className:"gvp-controls-row",children:[t.jsx(Me,{isPlaying:n,onToggle:r}),t.jsx(Pe,{video:e}),t.jsx(Te,{video:e}),t.jsx(Ve,{video:e}),t.jsx(Re,{video:e}),t.jsx(Ie,{video:e}),t.jsx(Be,{video:e}),t.jsx(De,{container:s,video:e})]})})},Me=({isPlaying:e,onToggle:n})=>t.jsx("button",{type:"button",className:"gvp-ctrl-btn","aria-label":e?"Pause":"Play","aria-pressed":e,onClick:n,children:e?t.jsx(ve,{}):t.jsx(ge,{})}),Pe=({video:e})=>{const[n,s]=o.useState(0),[r,u]=o.useState(0),[d,c]=o.useState(0);o.useEffect(()=>{if(!e)return;const a=()=>s(e.currentTime),m=()=>u(Number.isFinite(e.duration)?e.duration:0),v=()=>{const h=e.buffered;if(!h||h.length===0){c(0);return}let x=h.end(h.length-1);for(let b=0;b<h.length;b++)if(h.start(b)<=e.currentTime&&e.currentTime<=h.end(b)){x=h.end(b);break}c(x)};return a(),m(),v(),e.addEventListener("timeupdate",a),e.addEventListener("durationchange",m),e.addEventListener("loadedmetadata",m),e.addEventListener("progress",v),e.addEventListener("timeupdate",v),()=>{e.removeEventListener("timeupdate",a),e.removeEventListener("durationchange",m),e.removeEventListener("loadedmetadata",m),e.removeEventListener("progress",v),e.removeEventListener("timeupdate",v)}},[e]);const l=r>0,i=l?n/r*100:0,f=l?d/r*100:0,g=a=>{if(!e||!l)return;const m=Number(a.target.value)/100*r;e.currentTime=m,s(m)};return t.jsxs("div",{className:"gvp-seek",children:[t.jsxs("div",{className:"gvp-seek-track",children:[t.jsx("div",{className:"gvp-seek-buffered",style:{width:`${f}%`}}),t.jsx("div",{className:"gvp-seek-progress",style:{width:`${i}%`}})]}),t.jsx("input",{type:"range",className:"gvp-seek-input",min:0,max:100,step:.1,value:i,onChange:g,disabled:!l,"aria-label":"Seek","aria-valuemin":0,"aria-valuemax":Math.floor(r),"aria-valuenow":Math.floor(n)})]})},Te=({video:e})=>{const[n,s]=o.useState(0),[r,u]=o.useState(0);return o.useEffect(()=>{if(!e)return;const d=()=>s(e.currentTime),c=()=>u(Number.isFinite(e.duration)?e.duration:0);return d(),c(),e.addEventListener("timeupdate",d),e.addEventListener("durationchange",c),e.addEventListener("loadedmetadata",c),()=>{e.removeEventListener("timeupdate",d),e.removeEventListener("durationchange",c),e.removeEventListener("loadedmetadata",c)}},[e]),t.jsxs("span",{className:"gvp-time","aria-live":"off",children:[q(n)," / ",q(r)]})},Ie=({video:e})=>{const[n,s]=o.useState(1),[r,u]=o.useState(!1);o.useEffect(()=>{if(!e)return;const f=()=>{s(e.volume),u(e.muted)};return f(),e.addEventListener("volumechange",f),()=>e.removeEventListener("volumechange",f)},[e]);const d=()=>{e&&(e.muted=!e.muted)},c=f=>{if(!e)return;const g=Number(f.target.value)/100;e.volume=g,g>0&&e.muted&&(e.muted=!1)};let l=xe;r||n===0?l=be:n<.5&&(l=ke);const i=r?0:Math.round(n*100);return t.jsxs("div",{className:M("gvp-volume",!F&&"is-expandable"),children:[t.jsx("button",{type:"button",className:"gvp-ctrl-btn","aria-label":r?"Unmute":"Mute","aria-pressed":r,onClick:d,children:t.jsx(l,{})}),!F&&t.jsxs("div",{className:"gvp-volume-slider-wrap",children:[t.jsx("div",{className:"gvp-volume-track",children:t.jsx("div",{className:"gvp-volume-fill",style:{width:`${i}%`}})}),t.jsx("input",{type:"range",className:"gvp-volume-input",min:0,max:100,step:1,value:i,onChange:c,"aria-label":"Volume","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":i})]})]})},He=[.5,.75,1,1.25,1.5,2],Ve=({video:e})=>{const[n,s]=o.useState(1),[r,u]=o.useState(!1),d=o.useRef(null);o.useEffect(()=>{if(!e)return;const i=()=>s(e.playbackRate);return i(),e.addEventListener("ratechange",i),()=>e.removeEventListener("ratechange",i)},[e]),o.useEffect(()=>{if(!r)return;const i=g=>{d.current&&!d.current.contains(g.target)&&u(!1)},f=g=>{g.key==="Escape"&&u(!1)};return document.addEventListener("mousedown",i),document.addEventListener("keydown",f),()=>{document.removeEventListener("mousedown",i),document.removeEventListener("keydown",f)}},[r]);const c=i=>{e&&(e.playbackRate=i),s(i),u(!1)},l=n===1?"1×":`${n}×`;return t.jsxs("div",{className:"gvp-speed",ref:d,children:[t.jsxs("button",{type:"button",className:"gvp-ctrl-btn gvp-speed-btn","aria-haspopup":"listbox","aria-expanded":r,"aria-label":`Playback speed: ${l}`,onClick:()=>u(i=>!i),children:[t.jsx(Ee,{}),t.jsx("span",{className:"gvp-speed-label",children:l})]}),r&&t.jsx("ul",{className:"gvp-speed-menu",role:"listbox","aria-label":"Playback speed",children:He.map(i=>t.jsx("li",{children:t.jsx("button",{type:"button",role:"option","aria-selected":i===n,className:M("gvp-speed-menu-item",i===n&&"is-active"),onClick:()=>c(i),children:i===1?"Normal":`${i}×`})},i))})]})},Re=({video:e})=>{const[n,s]=o.useState([]),[r,u]=o.useState(-1),[d,c]=o.useState(!1),l=o.useRef(null),i=o.useCallback(a=>{const m=[];for(let h=0;h<a.length;h++){const x=a[h];(x.kind==="subtitles"||x.kind==="captions")&&m.push({index:h,label:x.label||x.language||`Track ${h+1}`,language:x.language})}s(m);let v=-1;for(let h=0;h<a.length;h++)if(a[h].mode==="showing"){v=h;break}u(v)},[]);o.useEffect(()=>{if(!e){s([]),u(-1);return}const a=e.textTracks;i(a);const m=()=>i(a);if(typeof a.addEventListener=="function")return a.addEventListener("addtrack",m),a.addEventListener("removetrack",m),a.addEventListener("change",m),()=>{a.removeEventListener("addtrack",m),a.removeEventListener("removetrack",m),a.removeEventListener("change",m)}},[e,i]),o.useEffect(()=>{if(!d)return;const a=v=>{l.current&&!l.current.contains(v.target)&&c(!1)},m=v=>{v.key==="Escape"&&c(!1)};return document.addEventListener("mousedown",a),document.addEventListener("keydown",m),()=>{document.removeEventListener("mousedown",a),document.removeEventListener("keydown",m)}},[d]);const f=a=>{if(!e)return;const m=e.textTracks;for(let v=0;v<m.length;v++)m[v].mode=v===a?"showing":"hidden";u(a),c(!1)},g=()=>{if(!e)return;const a=e.textTracks;for(let m=0;m<a.length;m++)a[m].mode="hidden";u(-1),c(!1)};return n.length===0?null:t.jsxs("div",{className:"gvp-captions",ref:l,children:[t.jsx("button",{type:"button",className:M("gvp-ctrl-btn",r>=0&&"is-active"),"aria-haspopup":"listbox","aria-expanded":d,"aria-label":"Captions","aria-pressed":r>=0,onClick:()=>c(a=>!a),children:t.jsx(Le,{})}),d&&t.jsxs("ul",{className:"gvp-captions-menu",role:"listbox","aria-label":"Captions",children:[t.jsx("li",{children:t.jsx("button",{type:"button",role:"option","aria-selected":r===-1,className:M("gvp-captions-menu-item",r===-1&&"is-active"),onClick:g,children:"Off"})}),n.map(a=>t.jsx("li",{children:t.jsx("button",{type:"button",role:"option","aria-selected":a.index===r,className:M("gvp-captions-menu-item",a.index===r&&"is-active"),onClick:()=>f(a.index),children:a.label})},a.index))]})]})},Be=({video:e})=>{const[n,s]=o.useState(!1);if(o.useEffect(()=>{if(!e)return;const u=()=>s(!0),d=()=>s(!1);return e.addEventListener("enterpictureinpicture",u),e.addEventListener("leavepictureinpicture",d),()=>{e.removeEventListener("enterpictureinpicture",u),e.removeEventListener("leavepictureinpicture",d)}},[e]),!document.pictureInPictureEnabled)return null;const r=()=>{document.pictureInPictureElement?document.exitPictureInPicture().catch(()=>{}):e==null||e.requestPictureInPicture().catch(()=>{})};return t.jsx("button",{type:"button",className:M("gvp-ctrl-btn",n&&"is-active"),"aria-label":n?"Exit picture-in-picture":"Picture-in-picture","aria-pressed":n,onClick:r,children:t.jsx(je,{})})},De=({container:e,video:n})=>{const[s,r]=o.useState(!1);if(o.useEffect(()=>{if(U)return;const i=()=>r(P.element()===e);return i(),P.onChange(i)},[e]),!(P.isSupported()||U&&n!==null&&typeof n.webkitEnterFullscreen=="function"))return null;const d=()=>{var i;if(U){(i=n==null?void 0:n.webkitEnterFullscreen)==null||i.call(n);return}s?P.exit().catch(()=>{}):e&&P.request(e).catch(()=>{})},c=s?Ce:we,l=s?"Exit fullscreen":"Enter fullscreen";return t.jsx("button",{type:"button",className:"gvp-ctrl-btn","aria-label":l,"aria-pressed":s,onClick:d,children:t.jsx(c,{})})};function Y(e,n,s){const r=e==null?void 0:e.trim();if(r)return r;if(n){try{const d=new Intl.DisplayNames(void 0,{type:"language"}).of(n);if(d&&d!==n)return d}catch{}return n}return`Audio ${s+1}`}const K=o.forwardRef(({src:e,hlsConfig:n,isHls:s,autoPlay:r,children:u,onAudioTracks:d,audioTrackIndex:c,...l},i)=>{const f=o.useRef(null),g=o.useRef(null),a=o.useRef(d);a.current=d,o.useImperativeHandle(i,()=>f.current);const m=globalThis.window!==void 0&&H.isSupported(),v=!!s||m&&typeof e=="string"&&e.endsWith(".m3u8");return o.useEffect(()=>{var y,V,T;if(!e)return;const h=f.current;if(!h)return;const x=p=>{var C;(C=a.current)==null||C.call(a,p)},b=()=>{r&&h.play().catch(()=>{})};for(g.current&&(g.current.destroy(),g.current=null),h.pause(),h.removeAttribute("src");h.firstChild;)h.firstChild.remove();x([]);let L;if(v){const p=new H(n);g.current=p;const C=()=>{const E=p.audioTracks.map((k,N)=>({index:N,label:Y(k.name,k.lang,N),lang:k.lang||void 0}));x(E)};p.on(H.Events.MANIFEST_PARSED,b),p.on(H.Events.AUDIO_TRACKS_UPDATED,C),p.on(H.Events.AUDIO_TRACK_SWITCHED,C),p.on(H.Events.ERROR,(E,k)=>{k.fatal&&(p.destroy(),g.current=null)}),p.attachMedia(h),p.loadSource(e)}else{h.src=e,h.load(),h.addEventListener("loadedmetadata",b,{once:!0});const p=h.audioTracks;if(p){const C=()=>{const E=[];for(let k=0;k<p.length;k++){const N=p[k];E.push({index:k,label:Y(N.label,N.language,k),lang:N.language||void 0})}x(E)};(y=p.addEventListener)==null||y.call(p,"addtrack",C),(V=p.addEventListener)==null||V.call(p,"removetrack",C),(T=p.addEventListener)==null||T.call(p,"change",C),p.length>0&&C(),L=()=>{var E,k,N;(E=p.removeEventListener)==null||E.call(p,"addtrack",C),(k=p.removeEventListener)==null||k.call(p,"removetrack",C),(N=p.removeEventListener)==null||N.call(p,"change",C)}}}return()=>{for(h.removeEventListener("loadedmetadata",b),L==null||L(),g.current&&(g.current.destroy(),g.current=null),h.pause(),h.removeAttribute("src");h.firstChild;)h.firstChild.remove();h.load(),x([])}},[e,v,n,r]),o.useEffect(()=>{var b;if(c==null||c<0)return;const h=g.current;if(h){h.audioTrack!==c&&(h.audioTrack=c);return}const x=(b=f.current)==null?void 0:b.audioTracks;if(x)for(let L=0;L<x.length;L++)x[L].enabled=L===c},[c]),t.jsx("video",{ref:f,...l,children:u})});K.displayName="HLSPlayer";function J(e){if(!e)return null;if(/^[A-Za-z0-9_-]{11}$/.test(e))return e;let n;try{n=new URL(e)}catch{return null}const s=n.hostname.replace(/^www\./,"");if(s==="youtu.be"){const r=n.pathname.slice(1).split("/")[0];return/^[A-Za-z0-9_-]{11}$/.test(r)?r:null}if(s==="youtube.com"||s==="m.youtube.com"||s==="music.youtube.com"||s==="youtube-nocookie.com"){const r=n.searchParams.get("v");if(r&&/^[A-Za-z0-9_-]{11}$/.test(r))return r;const u=/^\/(?:embed|shorts|v|live)\/([A-Za-z0-9_-]{11})/.exec(n.pathname);if(u)return u[1]}return null}function X(e){try{const n=new URL(e),s=n.searchParams.get("t")??n.searchParams.get("start");if(!s)return null;if(/^\d+s?$/.test(s))return Number.parseInt(s,10);const r=/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/.exec(s);if(r){const u=Number.parseInt(r[1]??"0",10),d=Number.parseInt(r[2]??"0",10),c=Number.parseInt(r[3]??"0",10),l=u*3600+d*60+c;return l>0?l:null}}catch{}return null}function G(e,n={}){const{autoPlay:s=!1,muted:r=!0,loop:u=!1,controls:d=!0,startSeconds:c}=n,l=new URLSearchParams({rel:"0",modestbranding:"1",playsinline:"1",controls:d?"1":"0"});return s?(l.set("autoplay","1"),l.set("mute","1")):r&&l.set("mute","1"),u&&(l.set("loop","1"),l.set("playlist",e)),c&&c>0&&l.set("start",String(c)),`https://www.youtube-nocookie.com/embed/${e}?${l.toString()}`}const Ae=({tracks:e,activeIndex:n,onSelect:s})=>{const[r,u]=o.useState(!1),d=o.useRef(null);o.useEffect(()=>{if(!r)return;const l=f=>{d.current&&!d.current.contains(f.target)&&u(!1)},i=f=>{f.key==="Escape"&&u(!1)};return document.addEventListener("mousedown",l),document.addEventListener("keydown",i),()=>{document.removeEventListener("mousedown",l),document.removeEventListener("keydown",i)}},[r]);const c=e.find(l=>l.index===n)??e[0];return t.jsxs("div",{className:"gvp-audio",ref:d,children:[t.jsxs("button",{type:"button",className:"gvp-audio-btn","aria-haspopup":"listbox","aria-expanded":r,"aria-label":"Audio track",onClick:()=>u(l=>!l),children:[t.jsx(fe,{}),t.jsx("span",{className:"gvp-audio-label",children:c==null?void 0:c.label}),t.jsx(ye,{})]}),r&&t.jsx("ul",{className:"gvp-audio-menu",role:"listbox","aria-label":"Audio tracks",children:e.map(l=>t.jsx("li",{children:t.jsx("button",{type:"button",role:"option","aria-selected":l.index===n,className:M("gvp-audio-menu-item",l.index===n&&"is-active"),onClick:()=>{s(l.index),u(!1)},children:l.label})},l.index))})]})},$e=({src:e,poster:n,showDeviceToggle:s=!0,defaultDevice:r="desktop",hoverPlay:u=!1,tooltipText:d,onClose:c,className:l="",muted:i=!0,loop:f=!1,controls:g=!0,autoPlay:a=!1,frameMaxWidth:m,aspectRatio:v,hlsConfig:h,children:x})=>{const b=o.useRef(null),L=o.useRef(null),[y,V]=o.useState(r),[T,p]=o.useState(!1),[C,E]=o.useState(!1),[k,N]=o.useState([]),[A,Z]=o.useState(-1),[Q,ee]=o.useState(null),[te,ne]=o.useState(null),re=o.useCallback(j=>{b.current=j,ne(j)},[]),I=g===!0||g==="custom",$=g==="native",R=o.useMemo(()=>J(e),[e]),S=R!==null,se=o.useMemo(()=>y==="mobile"?(v==null?void 0:v.mobile)??"9/16":(v==null?void 0:v.desktop)??"16/9",[y,v]),oe=o.useMemo(()=>y==="mobile"?(m==null?void 0:m.mobile)??"420px":(m==null?void 0:m.desktop)??"960px",[y,m]),ae=o.useMemo(()=>R?G(R,{autoPlay:a,muted:i,loop:f,controls:I?!0:$,startSeconds:X(e)}):null,[R,e,a,i,f,I,$]),le=o.useCallback(j=>{N(j),Z(-1)},[]),B=o.useCallback(async()=>{const j=b.current;if(j){if(L.current)try{await L.current}catch{}j.pause()}},[]),D=o.useCallback(async()=>{const j=b.current;if(j)try{j.readyState<2&&j.load();const O=j.play();L.current=O,await O,p(!0)}catch{p(!1)}finally{L.current=null}},[]),ie=o.useCallback(()=>{!u||S||D()},[u,S,D]),ce=o.useCallback(()=>{!u||S||B().then(()=>p(!1))},[u,S,B]),W=o.useCallback(async()=>{const j=b.current;j&&(j.paused?await D():(await B(),p(!1)))},[D,B]),ue=!S&&k.length>1;return t.jsxs("div",{ref:ee,className:M("gvp-root",l),style:{width:oe,aspectRatio:se},onMouseEnter:()=>{E(!0),ie()},onMouseLeave:()=>{E(!1),ce()},children:[S?t.jsx("iframe",{className:"gvp-video gvp-youtube",src:ae??void 0,title:"YouTube video player",allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",allowFullScreen:!0,referrerPolicy:"strict-origin-when-cross-origin"}):t.jsx(K,{ref:re,src:e,poster:n,muted:i,loop:f,playsInline:!0,preload:"metadata",controls:$,autoPlay:a,hlsConfig:h,className:"gvp-video",onPlay:()=>p(!0),onPause:()=>p(!1),onAudioTracks:le,audioTrackIndex:A,children:x}),!S&&t.jsx("div",{className:"gvp-vignette"}),s&&t.jsx("div",{className:"gvp-toggle",children:t.jsxs("div",{className:"gvp-toggle-pill",children:[t.jsx("button",{type:"button",onClick:()=>V("desktop"),className:M("gvp-toggle-btn",y==="desktop"&&"is-active"),"aria-label":"Desktop view","aria-pressed":y==="desktop",children:t.jsx(de,{})}),t.jsx("div",{className:"gvp-toggle-divider"}),t.jsx("button",{type:"button",onClick:()=>V("mobile"),className:M("gvp-toggle-btn",y==="mobile"&&"is-active"),"aria-label":"Mobile view","aria-pressed":y==="mobile",children:t.jsx(he,{})})]})}),c&&t.jsx("button",{type:"button",onClick:c,className:"gvp-close","aria-label":"Close",children:t.jsx(pe,{})}),ue&&t.jsx(Ae,{tracks:k,activeIndex:A>=0?A:k[0].index,onSelect:Z}),!S&&I&&t.jsx("button",{type:"button",className:"gvp-click-layer",onClick:()=>void W(),"aria-label":T?"Pause":"Play"}),!S&&!I&&!T&&t.jsx("div",{className:"gvp-play-wrap",children:t.jsxs("button",{type:"button",onClick:()=>void W(),onMouseEnter:()=>E(!0),onMouseLeave:()=>E(!1),className:"gvp-play","aria-label":"Play",children:[t.jsx(me,{}),d&&C&&t.jsx("span",{className:"gvp-tooltip",role:"tooltip",children:d})]})}),!S&&I&&t.jsx(Se,{video:te,isPlaying:T,container:Q,onTogglePlay:()=>void W()}),!S&&!I&&t.jsx("div",{className:"gvp-bottom-fade"})]})};exports.ReactVideoPlayer=$e;exports.parseYouTubeId=J;exports.parseYouTubeStart=X;exports.youTubeEmbedUrl=G;
3
3
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs","../src/HLSPlayer.tsx","../src/utils/icons.tsx","../src/utils/youtube.ts","../src/VideoPlayerWrapper.tsx"],"sourcesContent":["function r(e){var t,f,n=\"\";if(\"string\"==typeof e||\"number\"==typeof e)n+=e;else if(\"object\"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=\" \"),n+=f)}else for(f in e)e[f]&&(n&&(n+=\" \"),n+=f);return n}export function clsx(){for(var e,t,f=0,n=\"\",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=\" \"),n+=t);return n}export default clsx;","\"use client\";\n\nimport Hls from \"hls.js\";\nimport React, { useEffect, useImperativeHandle, useRef } from \"react\";\nimport type { HLSPlayerProps } from \"./types\";\n\nexport const HLSPlayer = React.forwardRef<HTMLVideoElement, HLSPlayerProps>(\n ({ src, hlsConfig, isHls, autoPlay, children, ...videoProps }, forwardedRef) => {\n const internalRef = useRef<HTMLVideoElement | null>(null);\n const hlsRef = useRef<Hls | null>(null);\n\n useImperativeHandle(forwardedRef, () => internalRef.current as HTMLVideoElement);\n\n const canUseHlsJs = globalThis.window !== undefined && Hls.isSupported();\n const shouldUseHls =\n Boolean(isHls) || (canUseHlsJs && typeof src === \"string\" && src.endsWith(\".m3u8\"));\n\n useEffect(() => {\n if (!src) return;\n const videoEl = internalRef.current;\n if (!videoEl) return;\n\n // Tries to start playback once the stream is ready. Browsers only\n // honor sound-on autoplay after a user gesture, so this is best\n // effort — silently swallows the NotAllowedError when blocked.\n const tryAutoPlay = () => {\n if (!autoPlay) return;\n videoEl.play().catch(() => {\n /* autoplay blocked; user gesture required */\n });\n };\n\n // destroy previous\n if (hlsRef.current) {\n hlsRef.current.destroy();\n hlsRef.current = null;\n }\n\n // reset video element\n videoEl.pause();\n videoEl.removeAttribute(\"src\");\n while (videoEl.firstChild) videoEl.firstChild.remove();\n\n if (shouldUseHls) {\n const hls = new Hls(hlsConfig);\n hlsRef.current = hls;\n\n hls.attachMedia(videoEl);\n hls.loadSource(src);\n\n hls.on(Hls.Events.MANIFEST_PARSED, tryAutoPlay);\n hls.on(Hls.Events.ERROR, (_evt, data) => {\n if (data.fatal) {\n hls.destroy();\n hlsRef.current = null;\n }\n });\n } else {\n // native playback\n videoEl.src = src;\n videoEl.load();\n // For a plain <video>, we wait for metadata so play() has\n // something to start.\n videoEl.addEventListener(\"loadedmetadata\", tryAutoPlay, { once: true });\n }\n\n return () => {\n videoEl.removeEventListener(\"loadedmetadata\", tryAutoPlay);\n if (hlsRef.current) {\n hlsRef.current.destroy();\n hlsRef.current = null;\n }\n videoEl.pause();\n videoEl.removeAttribute(\"src\");\n while (videoEl.firstChild) videoEl.firstChild.remove();\n videoEl.load();\n };\n }, [src, shouldUseHls, hlsConfig, autoPlay]);\n\n // Captions are the consumer's responsibility — pass <track> elements as children.\n // NOSONAR: typescript:S4084\n return (\n <video ref={internalRef} {...videoProps}>\n {children}\n </video>\n );\n }\n);\n\nHLSPlayer.displayName = \"HLSPlayer\";\nexport default HLSPlayer;\n","\"use client\";\n\nimport React from \"react\";\n\ntype IconProps = { className?: string };\n\nconst baseClass = \"gvp-icon\";\nconst cls = (extra?: string) => (extra ? `${baseClass} ${extra}` : baseClass);\n\nexport const IconDesktop: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M14 2H10C6.72077 2 5.08116 2 3.91891 2.81382C3.48891 3.1149 3.1149 3.48891 2.81382 3.91891C2 5.08116 2 6.72077 2 10C2 13.2792 2 14.9188 2.81382 16.0811C3.1149 16.5111 3.48891 16.8851 3.91891 17.1862C5.08116 18 6.72077 18 10 18H14C17.2792 18 18.9188 18 20.0811 17.1862C20.5111 16.8851 20.8851 16.5111 21.1862 16.0811C22 14.9188 22 13.2792 22 10C22 6.72077 22 5.08116 21.1862 3.91891C20.8851 3.48891 20.5111 3.1149 20.0811 2.81382C18.9188 2 17.2792 2 14 2Z\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n <path\n d=\"M11 15H13\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M14.5 22L14.1845 21.5811C13.4733 20.6369 13.2969 19.1944 13.7468 18M9.5 22L9.8155 21.5811C10.5267 20.6369 10.7031 19.1944 10.2532 18\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n <path d=\"M7 22H17\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n </svg>\n);\n\nexport const IconMobile: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M5 9C5 5.70017 5 4.05025 6.02513 3.02513C7.05025 2 8.70017 2 12 2C15.2998 2 16.9497 2 17.9749 3.02513C19 4.05025 19 5.70017 19 9V15C19 18.2998 19 19.9497 17.9749 20.9749C16.9497 22 15.2998 22 12 22C8.70017 22 7.05025 22 6.02513 20.9749C5 19.9497 5 18.2998 5 15V9Z\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n <path\n d=\"M11 19H13\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M9 2L9.089 2.53402C9.28188 3.69129 9.37832 4.26993 9.77519 4.62204C10.1892 4.98934 10.7761 5 12 5C13.2239 5 13.8108 4.98934 14.2248 4.62204C14.6217 4.26993 14.7181 3.69129 14.911 2.53402L15 2\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinejoin=\"round\"\n />\n </svg>\n);\n\nexport const IconX: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6.94994 5.53594L12.1929 0.292938C12.5834 -0.0975275 13.2165 -0.0975279 13.6069 0.292938C13.9974 0.683403 13.9974 1.31647 13.6069 1.70694L8.36394 6.94994L13.6069 12.1929C13.9974 12.5834 13.9974 13.2165 13.6069 13.6069C13.2165 13.9974 12.5834 13.9974 12.1929 13.6069L6.94994 8.36394L1.70694 13.6069C1.31647 13.9974 0.683403 13.9974 0.292938 13.6069C-0.0975279 13.2165 -0.0975277 12.5834 0.292938 12.1929L5.53594 6.94994L0.292938 1.70694C-0.0975279 1.31647 -0.0975279 0.683403 0.292938 0.292938C0.683403 -0.0975279 1.31647 -0.0975277 1.70694 0.292938L6.94994 5.53594Z\"\n fill=\"currentColor\"\n />\n </svg>\n);\n\nexport const IconPlay: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"22\"\n height=\"22\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M5.3335 11.45V4.54997C5.3335 4.36108 5.40016 4.20275 5.5335 4.07497C5.66683 3.94719 5.82238 3.8833 6.00016 3.8833C6.05572 3.8833 6.11405 3.89163 6.17516 3.9083C6.23627 3.92497 6.29461 3.94997 6.35016 3.9833L11.7835 7.4333C11.8835 7.49997 11.9585 7.5833 12.0085 7.6833C12.0585 7.7833 12.0835 7.88886 12.0835 7.99997C12.0835 8.11108 12.0585 8.21663 12.0085 8.31663C11.9585 8.41663 11.8835 8.49997 11.7835 8.56663L6.35016 12.0166C6.29461 12.05 6.23627 12.075 6.17516 12.0916C6.11405 12.1083 6.05572 12.1166 6.00016 12.1166C5.82238 12.1166 5.66683 12.0527 5.5335 11.925C5.40016 11.7972 5.3335 11.6389 5.3335 11.45Z\"\n fill=\"currentColor\"\n />\n </svg>\n);\n","/**\n * Extract a YouTube video ID from any common URL form, or `null` if the URL\n * isn't a YouTube link.\n *\n * Recognised:\n * - https://www.youtube.com/watch?v=ID\n * - https://youtube.com/watch?v=ID&t=42\n * - https://youtu.be/ID\n * - https://youtu.be/ID?t=42\n * - https://www.youtube.com/embed/ID\n * - https://www.youtube.com/shorts/ID\n * - https://music.youtube.com/watch?v=ID\n * - bare 11-character video IDs\n */\nexport function parseYouTubeId(input: string): string | null {\n if (!input) return null;\n\n // Bare 11-char ID (YouTube IDs are [A-Za-z0-9_-]{11}).\n if (/^[A-Za-z0-9_-]{11}$/.test(input)) return input;\n\n let url: URL;\n try {\n url = new URL(input);\n } catch {\n return null;\n }\n\n const host = url.hostname.replace(/^www\\./, \"\");\n\n if (host === \"youtu.be\") {\n const id = url.pathname.slice(1).split(\"/\")[0];\n return /^[A-Za-z0-9_-]{11}$/.test(id) ? id : null;\n }\n\n if (\n host === \"youtube.com\" ||\n host === \"m.youtube.com\" ||\n host === \"music.youtube.com\" ||\n host === \"youtube-nocookie.com\"\n ) {\n // /watch?v=ID\n const v = url.searchParams.get(\"v\");\n if (v && /^[A-Za-z0-9_-]{11}$/.test(v)) return v;\n\n // /embed/ID, /shorts/ID, /v/ID, /live/ID\n const m = /^\\/(?:embed|shorts|v|live)\\/([A-Za-z0-9_-]{11})/.exec(\n url.pathname\n );\n if (m) return m[1];\n }\n\n return null;\n}\n\n/** Extract a `t`/`start` timestamp (in seconds) from a YouTube URL, if present. */\nexport function parseYouTubeStart(input: string): number | null {\n try {\n const url = new URL(input);\n const t = url.searchParams.get(\"t\") ?? url.searchParams.get(\"start\");\n if (!t) return null;\n // Supports \"90\", \"90s\", \"1m30s\", \"1h2m3s\".\n if (/^\\d+s?$/.test(t)) return Number.parseInt(t, 10);\n const m = /^(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?$/.exec(t);\n if (m) {\n const h = Number.parseInt(m[1] ?? \"0\", 10);\n const min = Number.parseInt(m[2] ?? \"0\", 10);\n const s = Number.parseInt(m[3] ?? \"0\", 10);\n const total = h * 3600 + min * 60 + s;\n return total > 0 ? total : null;\n }\n } catch {\n /* not a URL */\n }\n return null;\n}\n\nexport interface YouTubeEmbedOptions {\n /** Start playback immediately. Forces `mute` on, since browsers block sound-on autoplay. */\n autoPlay?: boolean;\n /** Mute the player. */\n muted?: boolean;\n /** Loop the video. */\n loop?: boolean;\n /** Show YouTube's player controls. Defaults to `true`. */\n controls?: boolean;\n /** Start offset in seconds. */\n startSeconds?: number | null;\n}\n\n/**\n * Build a privacy-enhanced YouTube embed URL from a video ID and player options.\n *\n * Notes on YouTube's quirks:\n * - `autoplay=1` only takes effect if `mute=1` is also set (browser policy).\n * - single-video loop requires `loop=1` **and** `playlist=<id>`.\n */\nexport function youTubeEmbedUrl(id: string, opts: YouTubeEmbedOptions = {}): string {\n const { autoPlay = false, muted = true, loop = false, controls = true, startSeconds } = opts;\n\n const params = new URLSearchParams({\n rel: \"0\",\n modestbranding: \"1\",\n playsinline: \"1\",\n controls: controls ? \"1\" : \"0\",\n });\n\n if (autoPlay) {\n params.set(\"autoplay\", \"1\");\n params.set(\"mute\", \"1\"); // required for autoplay to actually fire\n } else if (muted) {\n params.set(\"mute\", \"1\");\n }\n\n if (loop) {\n params.set(\"loop\", \"1\");\n params.set(\"playlist\", id); // YouTube needs this for single-video loop\n }\n\n if (startSeconds && startSeconds > 0) {\n params.set(\"start\", String(startSeconds));\n }\n\n return `https://www.youtube-nocookie.com/embed/${id}?${params.toString()}`;\n}\n","\"use client\";\n\nimport clsx from \"clsx\";\nimport React, { useCallback, useMemo, useRef, useState } from \"react\";\nimport { HLSPlayer } from \"./HLSPlayer\";\nimport type { DeviceMode, VideoPlayerWrapperProps } from \"./types\";\nimport { IconDesktop, IconMobile, IconPlay, IconX } from \"./utils/icons\";\nimport { parseYouTubeId, parseYouTubeStart, youTubeEmbedUrl } from \"./utils/youtube\";\n\nexport const VideoPlayerWrapper: React.FC<VideoPlayerWrapperProps> = ({\n src,\n poster,\n showDeviceToggle = true,\n defaultDevice = \"desktop\",\n hoverPlay = false,\n tooltipText,\n onClose,\n className = \"\",\n muted = true,\n loop = false,\n controls = false,\n autoPlay = false,\n frameMaxWidth: customFrameMaxWidth,\n aspectRatio: customAspectRatio,\n hlsConfig,\n children,\n}) => {\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const playPromiseRef = useRef<Promise<void> | null>(null);\n\n const [device, setDevice] = useState<DeviceMode>(defaultDevice);\n const [isPlaying, setIsPlaying] = useState(false);\n const [showTooltip, setShowTooltip] = useState(false);\n\n const youTubeId = useMemo(() => parseYouTubeId(src), [src]);\n const isYouTube = youTubeId !== null;\n\n const aspectRatio = useMemo(() => {\n return device === \"mobile\"\n ? (customAspectRatio?.mobile ?? \"9/16\")\n : (customAspectRatio?.desktop ?? \"16/9\");\n }, [device, customAspectRatio]);\n\n const frameMaxWidth = useMemo(() => {\n return device === \"mobile\"\n ? (customFrameMaxWidth?.mobile ?? \"420px\")\n : (customFrameMaxWidth?.desktop ?? \"960px\");\n }, [device, customFrameMaxWidth]);\n\n const youTubeSrc = useMemo(\n () =>\n youTubeId\n ? youTubeEmbedUrl(youTubeId, {\n autoPlay,\n muted,\n loop,\n controls,\n startSeconds: parseYouTubeStart(src),\n })\n : null,\n [youTubeId, src, autoPlay, muted, loop, controls]\n );\n\n const safePause = useCallback(async () => {\n const el = videoRef.current;\n if (!el) return;\n if (playPromiseRef.current) {\n try {\n await playPromiseRef.current;\n } catch {\n /* play was interrupted; nothing to await */\n }\n }\n el.pause();\n }, []);\n\n const safePlay = useCallback(async () => {\n const el = videoRef.current;\n if (!el) return;\n try {\n if (el.readyState < 2) el.load();\n const p = el.play();\n playPromiseRef.current = p;\n await p;\n setIsPlaying(true);\n } catch {\n setIsPlaying(false);\n } finally {\n playPromiseRef.current = null;\n }\n }, []);\n\n const hoverStart = useCallback(() => {\n if (!hoverPlay || isYouTube) return;\n void safePlay();\n }, [hoverPlay, isYouTube, safePlay]);\n\n const hoverStop = useCallback(() => {\n if (!hoverPlay || isYouTube) return;\n void safePause().then(() => setIsPlaying(false));\n }, [hoverPlay, isYouTube, safePause]);\n\n const togglePlay = useCallback(async () => {\n const el = videoRef.current;\n if (!el) return;\n if (el.paused) {\n await safePlay();\n } else {\n await safePause();\n setIsPlaying(false);\n }\n }, [safePlay, safePause]);\n\n return (\n // The mouse handlers are a progressive enhancement (hoverPlay + tooltip).\n // Keyboard/click users reach the same actions via the inner <button> elements,\n // so the outer container is intentionally non-interactive at the role level.\n // NOSONAR: typescript:S6848\n <div\n className={clsx(\"gvp-root\", className)}\n style={{ width: frameMaxWidth, aspectRatio }}\n onMouseEnter={() => {\n setShowTooltip(true);\n hoverStart();\n }}\n onMouseLeave={() => {\n setShowTooltip(false);\n hoverStop();\n }}\n >\n {isYouTube ? (\n <iframe\n className=\"gvp-video gvp-youtube\"\n src={youTubeSrc ?? undefined}\n title=\"YouTube video player\"\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n allowFullScreen\n referrerPolicy=\"strict-origin-when-cross-origin\"\n />\n ) : (\n <HLSPlayer\n ref={videoRef}\n src={src}\n poster={poster}\n muted={muted}\n loop={loop}\n playsInline\n preload=\"metadata\"\n controls={controls}\n autoPlay={autoPlay}\n hlsConfig={hlsConfig}\n className=\"gvp-video\"\n onPlay={() => setIsPlaying(true)}\n onPause={() => setIsPlaying(false)}\n >\n {children}\n </HLSPlayer>\n )}\n\n {!isYouTube && <div className=\"gvp-vignette\" />}\n\n {showDeviceToggle && (\n <div className=\"gvp-toggle\">\n <div className=\"gvp-toggle-pill\">\n <button\n type=\"button\"\n onClick={() => setDevice(\"desktop\")}\n className={clsx(\n \"gvp-toggle-btn\",\n device === \"desktop\" && \"is-active\"\n )}\n aria-label=\"Desktop view\"\n aria-pressed={device === \"desktop\"}\n >\n <IconDesktop />\n </button>\n\n <div className=\"gvp-toggle-divider\" />\n\n <button\n type=\"button\"\n onClick={() => setDevice(\"mobile\")}\n className={clsx(\n \"gvp-toggle-btn\",\n device === \"mobile\" && \"is-active\"\n )}\n aria-label=\"Mobile view\"\n aria-pressed={device === \"mobile\"}\n >\n <IconMobile />\n </button>\n </div>\n </div>\n )}\n\n {onClose && (\n <button\n type=\"button\"\n onClick={onClose}\n className=\"gvp-close\"\n aria-label=\"Close\"\n >\n <IconX />\n </button>\n )}\n\n {!isYouTube && !isPlaying && (\n <div className=\"gvp-play-wrap\">\n <button\n type=\"button\"\n onClick={() => void togglePlay()}\n onMouseEnter={() => setShowTooltip(true)}\n onMouseLeave={() => setShowTooltip(false)}\n className=\"gvp-play\"\n aria-label=\"Play\"\n >\n <IconPlay />\n {tooltipText && showTooltip && (\n <span className=\"gvp-tooltip\" role=\"tooltip\">\n {tooltipText}\n </span>\n )}\n </button>\n </div>\n )}\n\n {!isYouTube && <div className=\"gvp-bottom-fade\" />}\n </div>\n );\n};\n\nexport default VideoPlayerWrapper;\n"],"names":["r","t","f","n","o","clsx","HLSPlayer","React","src","hlsConfig","isHls","autoPlay","children","videoProps","forwardedRef","internalRef","useRef","hlsRef","useImperativeHandle","canUseHlsJs","Hls","shouldUseHls","useEffect","videoEl","tryAutoPlay","hls","_evt","data","baseClass","cls","extra","IconDesktop","className","jsxs","jsx","IconMobile","IconX","IconPlay","parseYouTubeId","input","url","host","id","v","m","parseYouTubeStart","h","min","s","total","youTubeEmbedUrl","opts","muted","loop","controls","startSeconds","params","VideoPlayerWrapper","poster","showDeviceToggle","defaultDevice","hoverPlay","tooltipText","onClose","customFrameMaxWidth","customAspectRatio","videoRef","playPromiseRef","device","setDevice","useState","isPlaying","setIsPlaying","showTooltip","setShowTooltip","youTubeId","useMemo","isYouTube","aspectRatio","frameMaxWidth","youTubeSrc","safePause","useCallback","el","safePlay","p","hoverStart","hoverStop","togglePlay"],"mappings":"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","x_google_ignoreList":[0]}
1
+ {"version":3,"file":"index.cjs","sources":["../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs","../src/utils/icons.tsx","../src/utils/format-time.ts","../src/components/ControlBar.tsx","../src/utils/audio-tracks.ts","../src/HLSPlayer.tsx","../src/utils/youtube.ts","../src/VideoPlayerWrapper.tsx"],"sourcesContent":["function r(e){var t,f,n=\"\";if(\"string\"==typeof e||\"number\"==typeof e)n+=e;else if(\"object\"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=\" \"),n+=f)}else for(f in e)e[f]&&(n&&(n+=\" \"),n+=f);return n}export function clsx(){for(var e,t,f=0,n=\"\",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=\" \"),n+=t);return n}export default clsx;","\"use client\";\n\nimport React from \"react\";\n\ntype IconProps = { className?: string };\n\nconst baseClass = \"gvp-icon\";\nconst cls = (extra?: string) => (extra ? `${baseClass} ${extra}` : baseClass);\n\nexport const IconDesktop: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M14 2H10C6.72077 2 5.08116 2 3.91891 2.81382C3.48891 3.1149 3.1149 3.48891 2.81382 3.91891C2 5.08116 2 6.72077 2 10C2 13.2792 2 14.9188 2.81382 16.0811C3.1149 16.5111 3.48891 16.8851 3.91891 17.1862C5.08116 18 6.72077 18 10 18H14C17.2792 18 18.9188 18 20.0811 17.1862C20.5111 16.8851 20.8851 16.5111 21.1862 16.0811C22 14.9188 22 13.2792 22 10C22 6.72077 22 5.08116 21.1862 3.91891C20.8851 3.48891 20.5111 3.1149 20.0811 2.81382C18.9188 2 17.2792 2 14 2Z\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n <path\n d=\"M11 15H13\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M14.5 22L14.1845 21.5811C13.4733 20.6369 13.2969 19.1944 13.7468 18M9.5 22L9.8155 21.5811C10.5267 20.6369 10.7031 19.1944 10.2532 18\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n <path d=\"M7 22H17\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n </svg>\n);\n\nexport const IconMobile: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M5 9C5 5.70017 5 4.05025 6.02513 3.02513C7.05025 2 8.70017 2 12 2C15.2998 2 16.9497 2 17.9749 3.02513C19 4.05025 19 5.70017 19 9V15C19 18.2998 19 19.9497 17.9749 20.9749C16.9497 22 15.2998 22 12 22C8.70017 22 7.05025 22 6.02513 20.9749C5 19.9497 5 18.2998 5 15V9Z\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n <path\n d=\"M11 19H13\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M9 2L9.089 2.53402C9.28188 3.69129 9.37832 4.26993 9.77519 4.62204C10.1892 4.98934 10.7761 5 12 5C13.2239 5 13.8108 4.98934 14.2248 4.62204C14.6217 4.26993 14.7181 3.69129 14.911 2.53402L15 2\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinejoin=\"round\"\n />\n </svg>\n);\n\nexport const IconX: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6.94994 5.53594L12.1929 0.292938C12.5834 -0.0975275 13.2165 -0.0975279 13.6069 0.292938C13.9974 0.683403 13.9974 1.31647 13.6069 1.70694L8.36394 6.94994L13.6069 12.1929C13.9974 12.5834 13.9974 13.2165 13.6069 13.6069C13.2165 13.9974 12.5834 13.9974 12.1929 13.6069L6.94994 8.36394L1.70694 13.6069C1.31647 13.9974 0.683403 13.9974 0.292938 13.6069C-0.0975279 13.2165 -0.0975277 12.5834 0.292938 12.1929L5.53594 6.94994L0.292938 1.70694C-0.0975279 1.31647 -0.0975279 0.683403 0.292938 0.292938C0.683403 -0.0975279 1.31647 -0.0975277 1.70694 0.292938L6.94994 5.53594Z\"\n fill=\"currentColor\"\n />\n </svg>\n);\n\nexport const IconPlay: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"22\"\n height=\"22\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M5.3335 11.45V4.54997C5.3335 4.36108 5.40016 4.20275 5.5335 4.07497C5.66683 3.94719 5.82238 3.8833 6.00016 3.8833C6.05572 3.8833 6.11405 3.89163 6.17516 3.9083C6.23627 3.92497 6.29461 3.94997 6.35016 3.9833L11.7835 7.4333C11.8835 7.49997 11.9585 7.5833 12.0085 7.6833C12.0585 7.7833 12.0835 7.88886 12.0835 7.99997C12.0835 8.11108 12.0585 8.21663 12.0085 8.31663C11.9585 8.41663 11.8835 8.49997 11.7835 8.56663L6.35016 12.0166C6.29461 12.05 6.23627 12.075 6.17516 12.0916C6.11405 12.1083 6.05572 12.1166 6.00016 12.1166C5.82238 12.1166 5.66683 12.0527 5.5335 11.925C5.40016 11.7972 5.3335 11.6389 5.3335 11.45Z\"\n fill=\"currentColor\"\n />\n </svg>\n);\n\nexport const IconAudio: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M3 14V10C3 9.44772 3.44772 9 4 9H6.58579C6.851 9 7.10536 8.89464 7.29289 8.70711L11.2929 4.70711C11.9229 4.07714 13 4.52331 13 5.41421V18.5858C13 19.4767 11.9229 19.9229 11.2929 19.2929L7.29289 15.2929C7.10536 15.1054 6.851 15 6.58579 15H4C3.44772 15 3 14.5523 3 14Z\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M16.5 8C17.5 9 18 10.5 18 12C18 13.5 17.5 15 16.5 16M19 5.5C20.5 7 21.5 9.5 21.5 12C21.5 14.5 20.5 17 19 18.5\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n);\n\nexport const IconPlaySolid: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M7 5.5V18.5C7 19.2659 7.84856 19.7261 8.4899 19.3071L19.0801 12.4014C19.6644 12.0204 19.6644 11.9796 19.0801 11.5986L8.4899 4.69288C7.84856 4.27388 7 4.73408 7 5.5Z\"\n fill=\"currentColor\"\n />\n </svg>\n);\n\nexport const IconPause: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" fill=\"currentColor\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" fill=\"currentColor\" />\n </svg>\n);\n\nexport const IconVolumeHigh: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M3 14V10C3 9.44772 3.44772 9 4 9H6.58579C6.851 9 7.10536 8.89464 7.29289 8.70711L11.2929 4.70711C11.9229 4.07714 13 4.52331 13 5.41421V18.5858C13 19.4767 11.9229 19.9229 11.2929 19.2929L7.29289 15.2929C7.10536 15.1054 6.851 15 6.58579 15H4C3.44772 15 3 14.5523 3 14Z\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M16.5 8C17.5 9 18 10.5 18 12C18 13.5 17.5 15 16.5 16M19 5.5C20.5 7 21.5 9.5 21.5 12C21.5 14.5 20.5 17 19 18.5\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n);\n\nexport const IconVolumeLow: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M3 14V10C3 9.44772 3.44772 9 4 9H6.58579C6.851 9 7.10536 8.89464 7.29289 8.70711L11.2929 4.70711C11.9229 4.07714 13 4.52331 13 5.41421V18.5858C13 19.4767 11.9229 19.9229 11.2929 19.2929L7.29289 15.2929C7.10536 15.1054 6.851 15 6.58579 15H4C3.44772 15 3 14.5523 3 14Z\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M16.5 8C17.5 9 18 10.5 18 12C18 13.5 17.5 15 16.5 16\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n);\n\nexport const IconVolumeMuted: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M3 14V10C3 9.44772 3.44772 9 4 9H6.58579C6.851 9 7.10536 8.89464 7.29289 8.70711L11.2929 4.70711C11.9229 4.07714 13 4.52331 13 5.41421V18.5858C13 19.4767 11.9229 19.9229 11.2929 19.2929L7.29289 15.2929C7.10536 15.1054 6.851 15 6.58579 15H4C3.44772 15 3 14.5523 3 14Z\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M16 9L22 15M22 9L16 15\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n);\n\nexport const IconFullscreen: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M4 9V4H9M15 4H20V9M20 15V20H15M9 20H4V15\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n);\n\nexport const IconFullscreenExit: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M9 4V9H4M15 9V4H20M15 20V15H20M9 15H4V20\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n);\n\nexport const IconPiP: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"2\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <rect x=\"12\" y=\"12\" width=\"8\" height=\"5\" rx=\"1\" fill=\"currentColor\" />\n </svg>\n);\n\nexport const IconCaptions: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect x=\"2\" y=\"5\" width=\"20\" height=\"14\" rx=\"3\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <path d=\"M7 12H11M13 12H17M7 15H9M11 15H13M15 15H17\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n </svg>\n);\n\nexport const IconSpeed: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2Z\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n />\n <path\n d=\"M12 8V12L14.5 14.5\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n);\n\nexport const IconChevron: React.FC<IconProps> = ({ className }) => (\n <svg\n className={cls(className)}\n width=\"12\"\n height=\"12\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M6 9L12 15L18 9\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n);\n","/**\n * Format a duration in seconds as `M:SS` or `H:MM:SS`. NaN/Infinity (live\n * streams, unloaded media) become `\"--:--\"`.\n */\nexport function formatTime(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 0) return \"--:--\";\n const total = Math.floor(seconds);\n const h = Math.floor(total / 3600);\n const m = Math.floor((total % 3600) / 60);\n const s = total % 60;\n const ss = s.toString().padStart(2, \"0\");\n if (h > 0) {\n const mm = m.toString().padStart(2, \"0\");\n return `${h}:${mm}:${ss}`;\n }\n return `${m}:${ss}`;\n}\n","\"use client\";\n\nimport clsx from \"clsx\";\nimport React, {\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport {\n IconCaptions,\n IconFullscreen,\n IconFullscreenExit,\n IconPause,\n IconPiP,\n IconPlaySolid,\n IconSpeed,\n IconVolumeHigh,\n IconVolumeLow,\n IconVolumeMuted,\n} from \"../utils/icons\";\nimport { formatTime } from \"../utils/format-time\";\n\n/**\n * On iOS, `HTMLVideoElement.volume` is read-only (hardware volume only),\n * so the slider has no effect. We detect iOS and show only the mute toggle.\n * iPad on iPadOS 13+ reports as Mac but with maxTouchPoints > 1.\n */\nconst isIOS = (() => {\n if (typeof navigator === \"undefined\") return false;\n const ua = navigator.userAgent;\n return (\n /iPad|iPhone|iPod/.test(ua) ||\n (ua.includes(\"Mac\") && navigator.maxTouchPoints > 1)\n );\n})();\n\n/**\n * iPhone (not iPad) has no Fullscreen API for arbitrary elements — only the\n * video itself via `webkitEnterFullscreen()`. Goes to Apple's native player,\n * which means our custom controls disappear in fullscreen on iPhone.\n */\nconst isIPhone = (() => {\n if (typeof navigator === \"undefined\") return false;\n return /iPhone|iPod/.test(navigator.userAgent);\n})();\n\nconst AUTO_HIDE_MS = 3000;\n\ninterface ControlBarProps {\n /** The underlying video element. The bar reads time/duration/buffered from it. */\n video: HTMLVideoElement | null;\n /** Whether the underlying video is currently playing. Drives auto-hide + icon. */\n isPlaying: boolean;\n /** Container element that receives mouse/touch events. Usually `.gvp-root`. */\n container: HTMLElement | null;\n /** Toggle play/pause. The wrapper owns the safePlay/safePause logic. */\n onTogglePlay: () => void;\n}\n\n/** Light wrapper over the cross-vendor Fullscreen API. */\nconst fullscreenApi = {\n isSupported(): boolean {\n if (typeof document === \"undefined\") return false;\n return Boolean(\n document.fullscreenEnabled || document.webkitFullscreenEnabled\n );\n },\n element(): Element | null {\n if (typeof document === \"undefined\") return null;\n return (\n document.fullscreenElement ??\n document.webkitFullscreenElement ??\n null\n );\n },\n request(el: HTMLElement): Promise<void> {\n const r = el.requestFullscreen ?? el.webkitRequestFullscreen;\n return r ? r.call(el) : Promise.reject(new Error(\"Fullscreen not supported\"));\n },\n exit(): Promise<void> {\n const e = document.exitFullscreen ?? document.webkitExitFullscreen;\n return e ? e.call(document) : Promise.reject(new Error(\"Fullscreen not supported\"));\n },\n onChange(cb: () => void): () => void {\n document.addEventListener(\"fullscreenchange\", cb);\n document.addEventListener(\"webkitfullscreenchange\", cb);\n return () => {\n document.removeEventListener(\"fullscreenchange\", cb);\n document.removeEventListener(\"webkitfullscreenchange\", cb);\n };\n },\n};\n\n/**\n * Custom control bar. Renders only when `controls=\"custom\"` is set on the\n * wrapper. Pinned to the bottom of the player frame; auto-hides 3s after the\n * last interaction during playback. Always visible while paused.\n */\nexport const ControlBar: React.FC<ControlBarProps> = ({\n video,\n isPlaying,\n container,\n onTogglePlay,\n}) => {\n const [visible, setVisible] = useState(true);\n const hideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearHideTimer = useCallback(() => {\n if (hideTimerRef.current !== null) {\n clearTimeout(hideTimerRef.current);\n hideTimerRef.current = null;\n }\n }, []);\n\n const scheduleHide = useCallback(() => {\n clearHideTimer();\n if (!isPlaying) return;\n hideTimerRef.current = setTimeout(() => setVisible(false), AUTO_HIDE_MS);\n }, [clearHideTimer, isPlaying]);\n\n const showAndScheduleHide = useCallback(() => {\n setVisible(true);\n scheduleHide();\n }, [scheduleHide]);\n\n useEffect(() => {\n if (!isPlaying) {\n clearHideTimer();\n setVisible(true);\n return;\n }\n scheduleHide();\n return clearHideTimer;\n }, [isPlaying, scheduleHide, clearHideTimer]);\n\n useEffect(() => {\n if (!container) return;\n const handler = () => showAndScheduleHide();\n container.addEventListener(\"mousemove\", handler);\n container.addEventListener(\"touchstart\", handler);\n container.addEventListener(\"focusin\", handler);\n return () => {\n container.removeEventListener(\"mousemove\", handler);\n container.removeEventListener(\"touchstart\", handler);\n container.removeEventListener(\"focusin\", handler);\n };\n }, [container, showAndScheduleHide]);\n\n // Keyboard shortcuts — active when focus is inside the player container.\n // Space/k: play/pause ←/→: seek ±5s ↑/↓: volume ±10% m: mute f: fullscreen p: PiP\n useEffect(() => {\n if (!container || !video) return;\n const onKey = (e: KeyboardEvent) => {\n if (!container.contains(document.activeElement)) return;\n // Don't steal shortcuts from inputs inside the player (e.g. seek range).\n const tag = (e.target as HTMLElement).tagName;\n if (tag === \"INPUT\" && e.key !== \" \") return;\n switch (e.key) {\n case \" \":\n case \"k\":\n e.preventDefault();\n onTogglePlay();\n showAndScheduleHide();\n break;\n case \"ArrowLeft\":\n e.preventDefault();\n video.currentTime = Math.max(0, video.currentTime - 5);\n showAndScheduleHide();\n break;\n case \"ArrowRight\":\n e.preventDefault();\n video.currentTime = Math.min(video.duration || 0, video.currentTime + 5);\n showAndScheduleHide();\n break;\n case \"ArrowUp\":\n e.preventDefault();\n video.volume = Math.min(1, video.volume + 0.1);\n if (video.muted) video.muted = false;\n showAndScheduleHide();\n break;\n case \"ArrowDown\":\n e.preventDefault();\n video.volume = Math.max(0, video.volume - 0.1);\n showAndScheduleHide();\n break;\n case \"m\":\n video.muted = !video.muted;\n showAndScheduleHide();\n break;\n case \"f\":\n if (fullscreenApi.element() === container) {\n fullscreenApi.exit().catch(() => { /* ignore */ });\n } else {\n fullscreenApi.request(container).catch(() => { /* ignore */ });\n }\n showAndScheduleHide();\n break;\n case \"p\":\n if (document.pictureInPictureElement) {\n document.exitPictureInPicture().catch(() => { /* ignore */ });\n } else if (document.pictureInPictureEnabled) {\n video.requestPictureInPicture().catch(() => { /* ignore */ });\n }\n showAndScheduleHide();\n break;\n }\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [container, video, onTogglePlay, showAndScheduleHide]);\n\n return (\n <div\n role=\"toolbar\"\n aria-label=\"Video controls\"\n className={clsx(\"gvp-controls\", !visible && \"is-hidden\")}\n onMouseEnter={clearHideTimer}\n onMouseLeave={scheduleHide}\n >\n <div className=\"gvp-controls-row\">\n <PlayPauseButton isPlaying={isPlaying} onToggle={onTogglePlay} />\n <SeekBar video={video} />\n <TimeDisplay video={video} />\n <SpeedButton video={video} />\n <CaptionsButton video={video} />\n <VolumeControl video={video} />\n <PiPButton video={video} />\n <FullscreenButton container={container} video={video} />\n </div>\n </div>\n );\n};\n\nexport default ControlBar;\n\n/* ───── PlayPauseButton ──────────────────────────────────────────────── */\n\nconst PlayPauseButton: React.FC<{ isPlaying: boolean; onToggle: () => void }> = ({\n isPlaying,\n onToggle,\n}) => (\n <button\n type=\"button\"\n className=\"gvp-ctrl-btn\"\n aria-label={isPlaying ? \"Pause\" : \"Play\"}\n aria-pressed={isPlaying}\n onClick={onToggle}\n >\n {isPlaying ? <IconPause /> : <IconPlaySolid />}\n </button>\n);\n\n/* ───── SeekBar ──────────────────────────────────────────────────────── */\n\n/**\n * Subscribes to the video's time/duration/buffered events and renders a scrub\n * bar with a buffered track behind the played track. Dragging the handle seeks\n * the video live (`currentTime` is assigned on every input).\n */\nconst SeekBar: React.FC<{ video: HTMLVideoElement | null }> = ({ video }) => {\n const [currentTime, setCurrentTime] = useState(0);\n const [duration, setDuration] = useState(0);\n const [buffered, setBuffered] = useState(0);\n\n useEffect(() => {\n if (!video) return;\n\n const onTime = () => setCurrentTime(video.currentTime);\n const onDuration = () => setDuration(Number.isFinite(video.duration) ? video.duration : 0);\n const onProgress = () => {\n const ranges = video.buffered;\n if (!ranges || ranges.length === 0) {\n setBuffered(0);\n return;\n }\n // Find the buffered range that includes currentTime; fall back to\n // the last range so we still show progress for the segment ahead.\n let end = ranges.end(ranges.length - 1);\n for (let i = 0; i < ranges.length; i++) {\n if (ranges.start(i) <= video.currentTime && video.currentTime <= ranges.end(i)) {\n end = ranges.end(i);\n break;\n }\n }\n setBuffered(end);\n };\n\n onTime();\n onDuration();\n onProgress();\n\n video.addEventListener(\"timeupdate\", onTime);\n video.addEventListener(\"durationchange\", onDuration);\n video.addEventListener(\"loadedmetadata\", onDuration);\n video.addEventListener(\"progress\", onProgress);\n video.addEventListener(\"timeupdate\", onProgress);\n return () => {\n video.removeEventListener(\"timeupdate\", onTime);\n video.removeEventListener(\"durationchange\", onDuration);\n video.removeEventListener(\"loadedmetadata\", onDuration);\n video.removeEventListener(\"progress\", onProgress);\n video.removeEventListener(\"timeupdate\", onProgress);\n };\n }, [video]);\n\n const seekable = duration > 0;\n const progressPct = seekable ? (currentTime / duration) * 100 : 0;\n const bufferedPct = seekable ? (buffered / duration) * 100 : 0;\n\n const onInput = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (!video || !seekable) return;\n const t = (Number(e.target.value) / 100) * duration;\n video.currentTime = t;\n setCurrentTime(t);\n };\n\n return (\n <div className=\"gvp-seek\">\n <div className=\"gvp-seek-track\">\n <div\n className=\"gvp-seek-buffered\"\n style={{ width: `${bufferedPct}%` }}\n />\n <div\n className=\"gvp-seek-progress\"\n style={{ width: `${progressPct}%` }}\n />\n </div>\n <input\n type=\"range\"\n className=\"gvp-seek-input\"\n min={0}\n max={100}\n step={0.1}\n value={progressPct}\n onChange={onInput}\n disabled={!seekable}\n aria-label=\"Seek\"\n aria-valuemin={0}\n aria-valuemax={Math.floor(duration)}\n aria-valuenow={Math.floor(currentTime)}\n />\n </div>\n );\n};\n\n/* ───── TimeDisplay ──────────────────────────────────────────────────── */\n\nconst TimeDisplay: React.FC<{ video: HTMLVideoElement | null }> = ({ video }) => {\n const [currentTime, setCurrentTime] = useState(0);\n const [duration, setDuration] = useState(0);\n\n useEffect(() => {\n if (!video) return;\n const onTime = () => setCurrentTime(video.currentTime);\n const onDuration = () => setDuration(Number.isFinite(video.duration) ? video.duration : 0);\n onTime();\n onDuration();\n video.addEventListener(\"timeupdate\", onTime);\n video.addEventListener(\"durationchange\", onDuration);\n video.addEventListener(\"loadedmetadata\", onDuration);\n return () => {\n video.removeEventListener(\"timeupdate\", onTime);\n video.removeEventListener(\"durationchange\", onDuration);\n video.removeEventListener(\"loadedmetadata\", onDuration);\n };\n }, [video]);\n\n return (\n <span className=\"gvp-time\" aria-live=\"off\">\n {formatTime(currentTime)} / {formatTime(duration)}\n </span>\n );\n};\n\n/* ───── VolumeControl ────────────────────────────────────────────────── */\n\n/**\n * Mute toggle + volume slider. On iOS the slider is hidden (the platform\n * blocks programmatic volume changes), and only the mute toggle is rendered.\n */\nconst VolumeControl: React.FC<{ video: HTMLVideoElement | null }> = ({ video }) => {\n const [volume, setVolume] = useState(1);\n const [muted, setMuted] = useState(false);\n\n useEffect(() => {\n if (!video) return;\n const onChange = () => {\n setVolume(video.volume);\n setMuted(video.muted);\n };\n onChange();\n video.addEventListener(\"volumechange\", onChange);\n return () => video.removeEventListener(\"volumechange\", onChange);\n }, [video]);\n\n const toggleMute = () => {\n if (!video) return;\n video.muted = !video.muted;\n };\n\n const onInput = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (!video) return;\n const v = Number(e.target.value) / 100;\n video.volume = v;\n // Drag the slider above zero → automatically unmute (matches YouTube\n // and the browser native controls).\n if (v > 0 && video.muted) video.muted = false;\n };\n\n let Icon = IconVolumeHigh;\n if (muted || volume === 0) Icon = IconVolumeMuted;\n else if (volume < 0.5) Icon = IconVolumeLow;\n\n const effective = muted ? 0 : Math.round(volume * 100);\n\n return (\n <div className={clsx(\"gvp-volume\", !isIOS && \"is-expandable\")}>\n <button\n type=\"button\"\n className=\"gvp-ctrl-btn\"\n aria-label={muted ? \"Unmute\" : \"Mute\"}\n aria-pressed={muted}\n onClick={toggleMute}\n >\n <Icon />\n </button>\n {!isIOS && (\n <div className=\"gvp-volume-slider-wrap\">\n <div className=\"gvp-volume-track\">\n <div\n className=\"gvp-volume-fill\"\n style={{ width: `${effective}%` }}\n />\n </div>\n <input\n type=\"range\"\n className=\"gvp-volume-input\"\n min={0}\n max={100}\n step={1}\n value={effective}\n onChange={onInput}\n aria-label=\"Volume\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={effective}\n />\n </div>\n )}\n </div>\n );\n};\n\n/* ───── SpeedButton ─────────────────────────────────────────────────── */\n\nconst SPEED_OPTIONS = [0.5, 0.75, 1, 1.25, 1.5, 2];\n\nconst SpeedButton: React.FC<{ video: HTMLVideoElement | null }> = ({ video }) => {\n const [speed, setSpeed] = useState(1);\n const [open, setOpen] = useState(false);\n const rootRef = useRef<HTMLDivElement | null>(null);\n\n useEffect(() => {\n if (!video) return;\n const onRate = () => setSpeed(video.playbackRate);\n onRate();\n video.addEventListener(\"ratechange\", onRate);\n return () => video.removeEventListener(\"ratechange\", onRate);\n }, [video]);\n\n useEffect(() => {\n if (!open) return;\n const onDocClick = (e: MouseEvent) => {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n const onEsc = (e: KeyboardEvent) => { if (e.key === \"Escape\") setOpen(false); };\n document.addEventListener(\"mousedown\", onDocClick);\n document.addEventListener(\"keydown\", onEsc);\n return () => {\n document.removeEventListener(\"mousedown\", onDocClick);\n document.removeEventListener(\"keydown\", onEsc);\n };\n }, [open]);\n\n const pick = (rate: number) => {\n if (video) video.playbackRate = rate;\n setSpeed(rate);\n setOpen(false);\n };\n\n const label = speed === 1 ? \"1×\" : `${speed}×`;\n\n return (\n <div className=\"gvp-speed\" ref={rootRef}>\n <button\n type=\"button\"\n className=\"gvp-ctrl-btn gvp-speed-btn\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-label={`Playback speed: ${label}`}\n onClick={() => setOpen((v) => !v)}\n >\n <IconSpeed />\n <span className=\"gvp-speed-label\">{label}</span>\n </button>\n {open && (\n // NOSONAR: typescript:S6819\n <ul className=\"gvp-speed-menu\" role=\"listbox\" aria-label=\"Playback speed\">\n {SPEED_OPTIONS.map((rate) => (\n <li key={rate}>\n <button\n type=\"button\"\n role=\"option\"\n aria-selected={rate === speed}\n className={clsx(\"gvp-speed-menu-item\", rate === speed && \"is-active\")}\n onClick={() => pick(rate)}\n >\n {rate === 1 ? \"Normal\" : `${rate}×`}\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n};\n\n/* ───── CaptionsButton ──────────────────────────────────────────────── */\n\ninterface TextTrackItem {\n index: number;\n label: string;\n language: string;\n}\n\nconst CaptionsButton: React.FC<{ video: HTMLVideoElement | null }> = ({ video }) => {\n const [tracks, setTracks] = useState<TextTrackItem[]>([]);\n const [activeIndex, setActiveIndex] = useState<number>(-1); // -1 = Off\n const [open, setOpen] = useState(false);\n const rootRef = useRef<HTMLDivElement | null>(null);\n\n // Snapshot the text tracks list and derive the active one from track.mode.\n // TextTrackList has indexed access but is not a true iterable in all envs,\n // so we use index loops rather than for-of. NOSONAR: typescript:S4138\n const snapshot = useCallback((tl: TextTrackList) => {\n const items: TextTrackItem[] = [];\n for (let i = 0; i < tl.length; i++) {\n const t = tl[i];\n if (t.kind === \"subtitles\" || t.kind === \"captions\") {\n items.push({ index: i, label: t.label || t.language || `Track ${i + 1}`, language: t.language });\n }\n }\n setTracks(items);\n let active = -1;\n for (let i = 0; i < tl.length; i++) {\n if (tl[i].mode === \"showing\") { active = i; break; }\n }\n setActiveIndex(active);\n }, []);\n\n useEffect(() => {\n if (!video) { setTracks([]); setActiveIndex(-1); return; }\n const tl = video.textTracks;\n snapshot(tl);\n const onChange = () => snapshot(tl);\n // jsdom's TextTrackList lacks addEventListener — guard for it so the\n // component is safe in test/SSR-ish environments.\n if (typeof tl.addEventListener !== \"function\") return;\n tl.addEventListener(\"addtrack\", onChange);\n tl.addEventListener(\"removetrack\", onChange);\n tl.addEventListener(\"change\", onChange);\n return () => {\n tl.removeEventListener(\"addtrack\", onChange);\n tl.removeEventListener(\"removetrack\", onChange);\n tl.removeEventListener(\"change\", onChange);\n };\n }, [video, snapshot]);\n\n useEffect(() => {\n if (!open) return;\n const onDocClick = (e: MouseEvent) => {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);\n };\n const onEsc = (e: KeyboardEvent) => { if (e.key === \"Escape\") setOpen(false); };\n document.addEventListener(\"mousedown\", onDocClick);\n document.addEventListener(\"keydown\", onEsc);\n return () => {\n document.removeEventListener(\"mousedown\", onDocClick);\n document.removeEventListener(\"keydown\", onEsc);\n };\n }, [open]);\n\n const pick = (index: number) => {\n if (!video) return;\n const tl = video.textTracks;\n for (let i = 0; i < tl.length; i++) {\n tl[i].mode = i === index ? \"showing\" : \"hidden\";\n }\n setActiveIndex(index);\n setOpen(false);\n };\n\n const pickOff = () => {\n if (!video) return;\n const tl = video.textTracks;\n for (let i = 0; i < tl.length; i++) tl[i].mode = \"hidden\";\n setActiveIndex(-1);\n setOpen(false);\n };\n\n if (tracks.length === 0) return null;\n\n return (\n <div className=\"gvp-captions\" ref={rootRef}>\n <button\n type=\"button\"\n className={clsx(\"gvp-ctrl-btn\", activeIndex >= 0 && \"is-active\")}\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-label=\"Captions\"\n aria-pressed={activeIndex >= 0}\n onClick={() => setOpen((v) => !v)}\n >\n <IconCaptions />\n </button>\n {open && (\n // NOSONAR: typescript:S6819\n <ul className=\"gvp-captions-menu\" role=\"listbox\" aria-label=\"Captions\">\n <li>\n <button\n type=\"button\"\n role=\"option\"\n aria-selected={activeIndex === -1}\n className={clsx(\"gvp-captions-menu-item\", activeIndex === -1 && \"is-active\")}\n onClick={pickOff}\n >\n Off\n </button>\n </li>\n {tracks.map((t) => (\n <li key={t.index}>\n <button\n type=\"button\"\n role=\"option\"\n aria-selected={t.index === activeIndex}\n className={clsx(\"gvp-captions-menu-item\", t.index === activeIndex && \"is-active\")}\n onClick={() => pick(t.index)}\n >\n {t.label}\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n};\n\n/* ───── PiPButton ───────────────────────────────────────────────────── */\n\nconst PiPButton: React.FC<{ video: HTMLVideoElement | null }> = ({ video }) => {\n const [isPiP, setIsPiP] = useState(false);\n\n useEffect(() => {\n if (!video) return;\n const onEnter = () => setIsPiP(true);\n const onLeave = () => setIsPiP(false);\n video.addEventListener(\"enterpictureinpicture\", onEnter);\n video.addEventListener(\"leavepictureinpicture\", onLeave);\n return () => {\n video.removeEventListener(\"enterpictureinpicture\", onEnter);\n video.removeEventListener(\"leavepictureinpicture\", onLeave);\n };\n }, [video]);\n\n if (!document.pictureInPictureEnabled) return null;\n\n const toggle = () => {\n if (document.pictureInPictureElement) {\n document.exitPictureInPicture().catch(() => { /* ignore */ });\n } else {\n video?.requestPictureInPicture().catch(() => { /* ignore */ });\n }\n };\n\n return (\n <button\n type=\"button\"\n className={clsx(\"gvp-ctrl-btn\", isPiP && \"is-active\")}\n aria-label={isPiP ? \"Exit picture-in-picture\" : \"Picture-in-picture\"}\n aria-pressed={isPiP}\n onClick={toggle}\n >\n <IconPiP />\n </button>\n );\n};\n\n/* ───── FullscreenButton ────────────────────────────────────────────── */\n\n/**\n * Fullscreens the player container so custom controls stay visible inside.\n * On iPhone, falls back to `video.webkitEnterFullscreen()` which goes to\n * Apple's native fullscreen player (no JS-driven controls in that state).\n */\nconst FullscreenButton: React.FC<{\n container: HTMLElement | null;\n video: HTMLVideoElement | null;\n}> = ({ container, video }) => {\n const [isFs, setIsFs] = useState(false);\n\n useEffect(() => {\n // iPhone uses the native video fullscreen — there's no container-fs\n // event we can listen to that meaningfully reflects state. Treat the\n // button as a fire-and-forget command on iPhone.\n if (isIPhone) return;\n const update = () => setIsFs(fullscreenApi.element() === container);\n update();\n return fullscreenApi.onChange(update);\n }, [container]);\n\n // Hide the button entirely if the platform supports neither path.\n const supported =\n fullscreenApi.isSupported() ||\n (isIPhone &&\n video !== null &&\n typeof video.webkitEnterFullscreen === \"function\");\n\n if (!supported) return null;\n\n const toggle = () => {\n if (isIPhone) {\n video?.webkitEnterFullscreen?.();\n return;\n }\n if (isFs) {\n fullscreenApi.exit().catch(() => {\n /* ignore */\n });\n } else if (container) {\n fullscreenApi.request(container).catch(() => {\n /* ignore */\n });\n }\n };\n\n const Icon = isFs ? IconFullscreenExit : IconFullscreen;\n const label = isFs ? \"Exit fullscreen\" : \"Enter fullscreen\";\n\n return (\n <button\n type=\"button\"\n className=\"gvp-ctrl-btn\"\n aria-label={label}\n aria-pressed={isFs}\n onClick={toggle}\n >\n <Icon />\n </button>\n );\n};\n","/**\n * A normalized audio-track descriptor, independent of whether the source plays\n * through hls.js or the native `<video>` element.\n */\nexport interface AudioTrackInfo {\n /** Stable index into the player's track list. */\n index: number;\n /** Human-readable label, e.g. \"English\", \"Español\", or a fallback. */\n label: string;\n /** BCP-47-ish language code if the source provides one. */\n lang?: string;\n}\n\n/** Build a display label for a track, falling back to language code or \"Audio N\". */\nexport function audioTrackLabel(\n name: string | undefined,\n lang: string | undefined,\n index: number\n): string {\n const trimmed = name?.trim();\n if (trimmed) return trimmed;\n if (lang) {\n try {\n const dn = new Intl.DisplayNames(undefined, { type: \"language\" });\n const resolved = dn.of(lang);\n if (resolved && resolved !== lang) return resolved;\n } catch {\n /* Intl.DisplayNames unavailable or bad code */\n }\n return lang;\n }\n return `Audio ${index + 1}`;\n}\n","\"use client\";\n\nimport Hls from \"hls.js\";\nimport React, { useEffect, useImperativeHandle, useRef } from \"react\";\nimport type { HLSPlayerProps } from \"./types\";\nimport { audioTrackLabel, type AudioTrackInfo } from \"./utils/audio-tracks\";\n\ninterface InternalHLSPlayerProps extends HLSPlayerProps {\n /** Called whenever the available audio tracks change (after load / level switch). */\n onAudioTracks?: (tracks: AudioTrackInfo[]) => void;\n /** Index of the audio track to activate. `-1` / undefined means \"leave at source default\". */\n audioTrackIndex?: number;\n}\n\nexport const HLSPlayer = React.forwardRef<HTMLVideoElement, InternalHLSPlayerProps>(\n (\n {\n src,\n hlsConfig,\n isHls,\n autoPlay,\n children,\n onAudioTracks,\n audioTrackIndex,\n ...videoProps\n },\n forwardedRef\n ) => {\n const internalRef = useRef<HTMLVideoElement | null>(null);\n const hlsRef = useRef<Hls | null>(null);\n const onAudioTracksRef = useRef(onAudioTracks);\n onAudioTracksRef.current = onAudioTracks;\n\n useImperativeHandle(forwardedRef, () => internalRef.current as HTMLVideoElement);\n\n const canUseHlsJs = globalThis.window !== undefined && Hls.isSupported();\n const shouldUseHls =\n Boolean(isHls) || (canUseHlsJs && typeof src === \"string\" && src.endsWith(\".m3u8\"));\n\n // Load / tear down the source.\n useEffect(() => {\n if (!src) return;\n const videoEl = internalRef.current;\n if (!videoEl) return;\n\n const emitTracks = (tracks: AudioTrackInfo[]) => {\n onAudioTracksRef.current?.(tracks);\n };\n\n // Tries to start playback once the stream is ready. Browsers only\n // honor sound-on autoplay after a user gesture, so this is best\n // effort — silently swallows the NotAllowedError when blocked.\n const tryAutoPlay = () => {\n if (!autoPlay) return;\n videoEl.play().catch(() => {\n /* autoplay blocked; user gesture required */\n });\n };\n\n if (hlsRef.current) {\n hlsRef.current.destroy();\n hlsRef.current = null;\n }\n videoEl.pause();\n videoEl.removeAttribute(\"src\");\n while (videoEl.firstChild) videoEl.firstChild.remove();\n emitTracks([]);\n\n let detachNative: (() => void) | undefined;\n\n if (shouldUseHls) {\n const hls = new Hls(hlsConfig);\n hlsRef.current = hls;\n\n const reportHlsTracks = () => {\n const list = hls.audioTracks.map((t, i) => ({\n index: i,\n label: audioTrackLabel(t.name, t.lang, i),\n lang: t.lang || undefined,\n }));\n emitTracks(list);\n };\n\n hls.on(Hls.Events.MANIFEST_PARSED, tryAutoPlay);\n hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, reportHlsTracks);\n hls.on(Hls.Events.AUDIO_TRACK_SWITCHED, reportHlsTracks);\n hls.on(Hls.Events.ERROR, (_evt, data) => {\n if (data.fatal) {\n hls.destroy();\n hlsRef.current = null;\n }\n });\n\n hls.attachMedia(videoEl);\n hls.loadSource(src);\n } else {\n videoEl.src = src;\n videoEl.load();\n // For a plain <video>, wait for metadata so play() has\n // something to start.\n videoEl.addEventListener(\"loadedmetadata\", tryAutoPlay, { once: true });\n\n // Native AudioTrackList (Safari, some Chromium). Most browsers\n // don't implement this, so the list stays empty and the\n // wrapper renders no switcher — no dead UI.\n const at = videoEl.audioTracks;\n if (at) {\n const reportNativeTracks = () => {\n const list: AudioTrackInfo[] = [];\n for (let i = 0; i < at.length; i++) {\n const t = at[i];\n list.push({\n index: i,\n label: audioTrackLabel(t.label, t.language, i),\n lang: t.language || undefined,\n });\n }\n emitTracks(list);\n };\n at.addEventListener?.(\"addtrack\", reportNativeTracks);\n at.addEventListener?.(\"removetrack\", reportNativeTracks);\n at.addEventListener?.(\"change\", reportNativeTracks);\n if (at.length > 0) reportNativeTracks();\n detachNative = () => {\n at.removeEventListener?.(\"addtrack\", reportNativeTracks);\n at.removeEventListener?.(\"removetrack\", reportNativeTracks);\n at.removeEventListener?.(\"change\", reportNativeTracks);\n };\n }\n }\n\n return () => {\n videoEl.removeEventListener(\"loadedmetadata\", tryAutoPlay);\n detachNative?.();\n if (hlsRef.current) {\n hlsRef.current.destroy();\n hlsRef.current = null;\n }\n videoEl.pause();\n videoEl.removeAttribute(\"src\");\n while (videoEl.firstChild) videoEl.firstChild.remove();\n videoEl.load();\n emitTracks([]);\n };\n }, [src, shouldUseHls, hlsConfig, autoPlay]);\n\n // Apply the requested audio track.\n useEffect(() => {\n if (audioTrackIndex == null || audioTrackIndex < 0) return;\n const hls = hlsRef.current;\n if (hls) {\n if (hls.audioTrack !== audioTrackIndex) hls.audioTrack = audioTrackIndex;\n return;\n }\n const at = internalRef.current?.audioTracks;\n if (at) {\n for (let i = 0; i < at.length; i++) {\n at[i].enabled = i === audioTrackIndex;\n }\n }\n }, [audioTrackIndex]);\n\n return (\n // Captions are the consumer's responsibility — pass <track> elements as children.\n // NOSONAR: typescript:S4084\n <video ref={internalRef} {...videoProps}>\n {children}\n </video>\n );\n }\n);\n\nHLSPlayer.displayName = \"HLSPlayer\";\nexport default HLSPlayer;\n\n/**\n * Minimal structural types for the DOM `AudioTrackList` API — not part of the\n * default `lib.dom` typings, and only implemented in Safari + some Chromium.\n */\ninterface AudioTrackLike {\n label: string;\n language: string;\n enabled: boolean;\n}\ninterface AudioTrackListLike {\n readonly length: number;\n [index: number]: AudioTrackLike;\n addEventListener?: (type: string, listener: () => void) => void;\n removeEventListener?: (type: string, listener: () => void) => void;\n}\ndeclare global {\n interface HTMLMediaElement {\n readonly audioTracks?: AudioTrackListLike;\n }\n interface HTMLVideoElement {\n /** iOS Safari only — opens Apple's native fullscreen player. */\n webkitEnterFullscreen?(): void;\n }\n interface Document {\n readonly webkitFullscreenElement?: Element | null;\n readonly webkitFullscreenEnabled?: boolean;\n webkitExitFullscreen?(): Promise<void>;\n }\n interface HTMLElement {\n webkitRequestFullscreen?(): Promise<void>;\n }\n}\n","/**\n * Extract a YouTube video ID from any common URL form, or `null` if the URL\n * isn't a YouTube link.\n *\n * Recognised:\n * - https://www.youtube.com/watch?v=ID\n * - https://youtube.com/watch?v=ID&t=42\n * - https://youtu.be/ID\n * - https://youtu.be/ID?t=42\n * - https://www.youtube.com/embed/ID\n * - https://www.youtube.com/shorts/ID\n * - https://music.youtube.com/watch?v=ID\n * - bare 11-character video IDs\n */\nexport function parseYouTubeId(input: string): string | null {\n if (!input) return null;\n\n // Bare 11-char ID (YouTube IDs are [A-Za-z0-9_-]{11}).\n if (/^[A-Za-z0-9_-]{11}$/.test(input)) return input;\n\n let url: URL;\n try {\n url = new URL(input);\n } catch {\n return null;\n }\n\n const host = url.hostname.replace(/^www\\./, \"\");\n\n if (host === \"youtu.be\") {\n const id = url.pathname.slice(1).split(\"/\")[0];\n return /^[A-Za-z0-9_-]{11}$/.test(id) ? id : null;\n }\n\n if (\n host === \"youtube.com\" ||\n host === \"m.youtube.com\" ||\n host === \"music.youtube.com\" ||\n host === \"youtube-nocookie.com\"\n ) {\n // /watch?v=ID\n const v = url.searchParams.get(\"v\");\n if (v && /^[A-Za-z0-9_-]{11}$/.test(v)) return v;\n\n // /embed/ID, /shorts/ID, /v/ID, /live/ID\n const m = /^\\/(?:embed|shorts|v|live)\\/([A-Za-z0-9_-]{11})/.exec(\n url.pathname\n );\n if (m) return m[1];\n }\n\n return null;\n}\n\n/** Extract a `t`/`start` timestamp (in seconds) from a YouTube URL, if present. */\nexport function parseYouTubeStart(input: string): number | null {\n try {\n const url = new URL(input);\n const t = url.searchParams.get(\"t\") ?? url.searchParams.get(\"start\");\n if (!t) return null;\n // Supports \"90\", \"90s\", \"1m30s\", \"1h2m3s\".\n if (/^\\d+s?$/.test(t)) return Number.parseInt(t, 10);\n const m = /^(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?$/.exec(t);\n if (m) {\n const h = Number.parseInt(m[1] ?? \"0\", 10);\n const min = Number.parseInt(m[2] ?? \"0\", 10);\n const s = Number.parseInt(m[3] ?? \"0\", 10);\n const total = h * 3600 + min * 60 + s;\n return total > 0 ? total : null;\n }\n } catch {\n /* not a URL */\n }\n return null;\n}\n\nexport interface YouTubeEmbedOptions {\n /** Start playback immediately. Forces `mute` on, since browsers block sound-on autoplay. */\n autoPlay?: boolean;\n /** Mute the player. */\n muted?: boolean;\n /** Loop the video. */\n loop?: boolean;\n /** Show YouTube's player controls. Defaults to `true`. */\n controls?: boolean;\n /** Start offset in seconds. */\n startSeconds?: number | null;\n}\n\n/**\n * Build a privacy-enhanced YouTube embed URL from a video ID and player options.\n *\n * Notes on YouTube's quirks:\n * - `autoplay=1` only takes effect if `mute=1` is also set (browser policy).\n * - single-video loop requires `loop=1` **and** `playlist=<id>`.\n */\nexport function youTubeEmbedUrl(id: string, opts: YouTubeEmbedOptions = {}): string {\n const { autoPlay = false, muted = true, loop = false, controls = true, startSeconds } = opts;\n\n const params = new URLSearchParams({\n rel: \"0\",\n modestbranding: \"1\",\n playsinline: \"1\",\n controls: controls ? \"1\" : \"0\",\n });\n\n if (autoPlay) {\n params.set(\"autoplay\", \"1\");\n params.set(\"mute\", \"1\"); // required for autoplay to actually fire\n } else if (muted) {\n params.set(\"mute\", \"1\");\n }\n\n if (loop) {\n params.set(\"loop\", \"1\");\n params.set(\"playlist\", id); // YouTube needs this for single-video loop\n }\n\n if (startSeconds && startSeconds > 0) {\n params.set(\"start\", String(startSeconds));\n }\n\n return `https://www.youtube-nocookie.com/embed/${id}?${params.toString()}`;\n}\n","\"use client\";\n\nimport clsx from \"clsx\";\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { ControlBar } from \"./components/ControlBar\";\nimport { HLSPlayer } from \"./HLSPlayer\";\nimport type { DeviceMode, VideoPlayerWrapperProps } from \"./types\";\nimport { IconAudio, IconChevron, IconDesktop, IconMobile, IconPlay, IconX } from \"./utils/icons\";\nimport type { AudioTrackInfo } from \"./utils/audio-tracks\";\nimport { parseYouTubeId, parseYouTubeStart, youTubeEmbedUrl } from \"./utils/youtube\";\n\ninterface AudioTrackSwitcherProps {\n tracks: AudioTrackInfo[];\n activeIndex: number;\n onSelect: (index: number) => void;\n}\n\nconst AudioTrackSwitcher: React.FC<AudioTrackSwitcherProps> = ({\n tracks,\n activeIndex,\n onSelect,\n}) => {\n const [open, setOpen] = useState(false);\n const rootRef = useRef<HTMLDivElement | null>(null);\n\n useEffect(() => {\n if (!open) return;\n const onDocClick = (e: MouseEvent) => {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n const onEsc = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(\"mousedown\", onDocClick);\n document.addEventListener(\"keydown\", onEsc);\n return () => {\n document.removeEventListener(\"mousedown\", onDocClick);\n document.removeEventListener(\"keydown\", onEsc);\n };\n }, [open]);\n\n const active =\n tracks.find((t) => t.index === activeIndex) ?? tracks[0];\n\n return (\n <div className=\"gvp-audio\" ref={rootRef}>\n <button\n type=\"button\"\n className=\"gvp-audio-btn\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-label=\"Audio track\"\n onClick={() => setOpen((v) => !v)}\n >\n <IconAudio />\n <span className=\"gvp-audio-label\">{active?.label}</span>\n <IconChevron />\n </button>\n {open && (\n // A native <select> can't be styled to sit inside the player overlay\n // (it renders the OS dropdown chrome), so this is a custom listbox with\n // full keyboard + ARIA support.\n // NOSONAR: typescript:S6819\n <ul className=\"gvp-audio-menu\" role=\"listbox\" aria-label=\"Audio tracks\">\n {tracks.map((t) => (\n <li key={t.index}>\n <button\n type=\"button\"\n role=\"option\"\n aria-selected={t.index === activeIndex}\n className={clsx(\n \"gvp-audio-menu-item\",\n t.index === activeIndex && \"is-active\"\n )}\n onClick={() => {\n onSelect(t.index);\n setOpen(false);\n }}\n >\n {t.label}\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n};\n\nexport const VideoPlayerWrapper: React.FC<VideoPlayerWrapperProps> = ({\n src,\n poster,\n showDeviceToggle = true,\n defaultDevice = \"desktop\",\n hoverPlay = false,\n tooltipText,\n onClose,\n className = \"\",\n muted = true,\n loop = false,\n controls = true,\n autoPlay = false,\n frameMaxWidth: customFrameMaxWidth,\n aspectRatio: customAspectRatio,\n hlsConfig,\n children,\n}) => {\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const playPromiseRef = useRef<Promise<void> | null>(null);\n\n const [device, setDevice] = useState<DeviceMode>(defaultDevice);\n const [isPlaying, setIsPlaying] = useState(false);\n const [showTooltip, setShowTooltip] = useState(false);\n const [audioTracks, setAudioTracks] = useState<AudioTrackInfo[]>([]);\n const [audioTrackIndex, setAudioTrackIndex] = useState<number>(-1);\n // Tracked as state (not ref) so the ControlBar re-renders once these are\n // bound; plain refs don't trigger updates.\n const [rootEl, setRootEl] = useState<HTMLDivElement | null>(null);\n const [videoEl, setVideoEl] = useState<HTMLVideoElement | null>(null);\n\n // Callback ref that captures the video element in both `videoRef` (for the\n // play/pause logic that doesn't need re-renders) and `videoEl` state (for\n // the ControlBar's seek/time listeners that do).\n const setVideoRef = useCallback((node: HTMLVideoElement | null) => {\n videoRef.current = node;\n setVideoEl(node);\n }, []);\n\n // `true` and `\"custom\"` both mean the branded control bar; `\"native\"`\n // means the browser's own controls; `false` means none.\n const isCustomControls = controls === true || controls === \"custom\";\n const isNativeControls = controls === \"native\";\n\n const youTubeId = useMemo(() => parseYouTubeId(src), [src]);\n const isYouTube = youTubeId !== null;\n\n const aspectRatio = useMemo(() => {\n return device === \"mobile\"\n ? (customAspectRatio?.mobile ?? \"9/16\")\n : (customAspectRatio?.desktop ?? \"16/9\");\n }, [device, customAspectRatio]);\n\n const frameMaxWidth = useMemo(() => {\n return device === \"mobile\"\n ? (customFrameMaxWidth?.mobile ?? \"420px\")\n : (customFrameMaxWidth?.desktop ?? \"960px\");\n }, [device, customFrameMaxWidth]);\n\n const youTubeSrc = useMemo(\n () =>\n youTubeId\n ? youTubeEmbedUrl(youTubeId, {\n autoPlay,\n muted,\n loop,\n // Custom controls can't drive an iframe, so YouTube falls\n // back to its own controls in that mode.\n controls: isCustomControls ? true : isNativeControls,\n startSeconds: parseYouTubeStart(src),\n })\n : null,\n [youTubeId, src, autoPlay, muted, loop, isCustomControls, isNativeControls]\n );\n\n const handleAudioTracks = useCallback((tracks: AudioTrackInfo[]) => {\n setAudioTracks(tracks);\n // Reset selection whenever the list changes (new source / level switch).\n setAudioTrackIndex(-1);\n }, []);\n\n const safePause = useCallback(async () => {\n const el = videoRef.current;\n if (!el) return;\n if (playPromiseRef.current) {\n try {\n await playPromiseRef.current;\n } catch {\n /* play was interrupted; nothing to await */\n }\n }\n el.pause();\n }, []);\n\n const safePlay = useCallback(async () => {\n const el = videoRef.current;\n if (!el) return;\n try {\n if (el.readyState < 2) el.load();\n const p = el.play();\n playPromiseRef.current = p;\n await p;\n setIsPlaying(true);\n } catch {\n setIsPlaying(false);\n } finally {\n playPromiseRef.current = null;\n }\n }, []);\n\n const hoverStart = useCallback(() => {\n if (!hoverPlay || isYouTube) return;\n void safePlay();\n }, [hoverPlay, isYouTube, safePlay]);\n\n const hoverStop = useCallback(() => {\n if (!hoverPlay || isYouTube) return;\n void safePause().then(() => setIsPlaying(false));\n }, [hoverPlay, isYouTube, safePause]);\n\n const togglePlay = useCallback(async () => {\n const el = videoRef.current;\n if (!el) return;\n if (el.paused) {\n await safePlay();\n } else {\n await safePause();\n setIsPlaying(false);\n }\n }, [safePlay, safePause]);\n\n const showAudioSwitcher = !isYouTube && audioTracks.length > 1;\n\n return (\n // The mouse handlers are a progressive enhancement (hoverPlay + tooltip).\n // Keyboard/click users reach the same actions via the inner <button> elements,\n // so the outer container is intentionally non-interactive at the role level.\n // NOSONAR: typescript:S6848\n <div\n ref={setRootEl}\n className={clsx(\"gvp-root\", className)}\n style={{ width: frameMaxWidth, aspectRatio }}\n onMouseEnter={() => {\n setShowTooltip(true);\n hoverStart();\n }}\n onMouseLeave={() => {\n setShowTooltip(false);\n hoverStop();\n }}\n >\n {isYouTube ? (\n <iframe\n className=\"gvp-video gvp-youtube\"\n src={youTubeSrc ?? undefined}\n title=\"YouTube video player\"\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\"\n allowFullScreen\n referrerPolicy=\"strict-origin-when-cross-origin\"\n />\n ) : (\n <HLSPlayer\n ref={setVideoRef}\n src={src}\n poster={poster}\n muted={muted}\n loop={loop}\n playsInline\n preload=\"metadata\"\n controls={isNativeControls}\n autoPlay={autoPlay}\n hlsConfig={hlsConfig}\n className=\"gvp-video\"\n onPlay={() => setIsPlaying(true)}\n onPause={() => setIsPlaying(false)}\n onAudioTracks={handleAudioTracks}\n audioTrackIndex={audioTrackIndex}\n >\n {children}\n </HLSPlayer>\n )}\n\n {!isYouTube && <div className=\"gvp-vignette\" />}\n\n {showDeviceToggle && (\n <div className=\"gvp-toggle\">\n <div className=\"gvp-toggle-pill\">\n <button\n type=\"button\"\n onClick={() => setDevice(\"desktop\")}\n className={clsx(\n \"gvp-toggle-btn\",\n device === \"desktop\" && \"is-active\"\n )}\n aria-label=\"Desktop view\"\n aria-pressed={device === \"desktop\"}\n >\n <IconDesktop />\n </button>\n\n <div className=\"gvp-toggle-divider\" />\n\n <button\n type=\"button\"\n onClick={() => setDevice(\"mobile\")}\n className={clsx(\n \"gvp-toggle-btn\",\n device === \"mobile\" && \"is-active\"\n )}\n aria-label=\"Mobile view\"\n aria-pressed={device === \"mobile\"}\n >\n <IconMobile />\n </button>\n </div>\n </div>\n )}\n\n {onClose && (\n <button\n type=\"button\"\n onClick={onClose}\n className=\"gvp-close\"\n aria-label=\"Close\"\n >\n <IconX />\n </button>\n )}\n\n {showAudioSwitcher && (\n <AudioTrackSwitcher\n tracks={audioTracks}\n activeIndex={audioTrackIndex >= 0 ? audioTrackIndex : audioTracks[0].index}\n onSelect={setAudioTrackIndex}\n />\n )}\n\n {/* Click-to-toggle layer: any tap on the video area plays/pauses.\n Only active in custom-controls mode — for native controls we\n stay out of the browser's way. */}\n {!isYouTube && isCustomControls && (\n <button\n type=\"button\"\n className=\"gvp-click-layer\"\n onClick={() => void togglePlay()}\n aria-label={isPlaying ? \"Pause\" : \"Play\"}\n />\n )}\n\n {!isYouTube && !isCustomControls && !isPlaying && (\n <div className=\"gvp-play-wrap\">\n <button\n type=\"button\"\n onClick={() => void togglePlay()}\n onMouseEnter={() => setShowTooltip(true)}\n onMouseLeave={() => setShowTooltip(false)}\n className=\"gvp-play\"\n aria-label=\"Play\"\n >\n <IconPlay />\n {tooltipText && showTooltip && (\n <span className=\"gvp-tooltip\" role=\"tooltip\">\n {tooltipText}\n </span>\n )}\n </button>\n </div>\n )}\n\n {!isYouTube && isCustomControls && (\n <ControlBar\n video={videoEl}\n isPlaying={isPlaying}\n container={rootEl}\n onTogglePlay={() => void togglePlay()}\n />\n )}\n\n {!isYouTube && !isCustomControls && <div className=\"gvp-bottom-fade\" />}\n </div>\n );\n};\n\nexport default VideoPlayerWrapper;\n"],"names":["r","t","f","n","o","clsx","baseClass","cls","extra","IconDesktop","className","jsxs","jsx","IconMobile","IconX","IconPlay","IconAudio","IconPlaySolid","IconPause","IconVolumeHigh","IconVolumeLow","IconVolumeMuted","IconFullscreen","IconFullscreenExit","IconPiP","IconCaptions","IconSpeed","IconChevron","formatTime","seconds","total","h","m","ss","mm","isIOS","ua","isIPhone","AUTO_HIDE_MS","fullscreenApi","el","cb","ControlBar","video","isPlaying","container","onTogglePlay","visible","setVisible","useState","hideTimerRef","useRef","clearHideTimer","useCallback","scheduleHide","showAndScheduleHide","useEffect","handler","onKey","e","PlayPauseButton","SeekBar","TimeDisplay","SpeedButton","CaptionsButton","VolumeControl","PiPButton","FullscreenButton","onToggle","currentTime","setCurrentTime","duration","setDuration","buffered","setBuffered","onTime","onDuration","onProgress","ranges","end","i","seekable","progressPct","bufferedPct","onInput","volume","setVolume","muted","setMuted","onChange","toggleMute","v","Icon","effective","SPEED_OPTIONS","speed","setSpeed","open","setOpen","rootRef","onRate","onDocClick","onEsc","pick","rate","label","tracks","setTracks","activeIndex","setActiveIndex","snapshot","tl","items","active","index","pickOff","isPiP","setIsPiP","onEnter","onLeave","toggle","isFs","setIsFs","update","_a","audioTrackLabel","name","lang","trimmed","resolved","HLSPlayer","React","src","hlsConfig","isHls","autoPlay","children","onAudioTracks","audioTrackIndex","videoProps","forwardedRef","internalRef","hlsRef","onAudioTracksRef","useImperativeHandle","canUseHlsJs","Hls","shouldUseHls","videoEl","emitTracks","tryAutoPlay","detachNative","hls","reportHlsTracks","list","_evt","data","at","reportNativeTracks","_b","_c","parseYouTubeId","input","url","host","id","parseYouTubeStart","min","s","youTubeEmbedUrl","opts","loop","controls","startSeconds","params","AudioTrackSwitcher","onSelect","VideoPlayerWrapper","poster","showDeviceToggle","defaultDevice","hoverPlay","tooltipText","onClose","customFrameMaxWidth","customAspectRatio","videoRef","playPromiseRef","device","setDevice","setIsPlaying","showTooltip","setShowTooltip","audioTracks","setAudioTracks","setAudioTrackIndex","rootEl","setRootEl","setVideoEl","setVideoRef","node","isCustomControls","isNativeControls","youTubeId","useMemo","isYouTube","aspectRatio","frameMaxWidth","youTubeSrc","handleAudioTracks","safePause","safePlay","p","hoverStart","hoverStop","togglePlay","showAudioSwitcher"],"mappings":"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","x_google_ignoreList":[0]}
package/dist/index.d.ts CHANGED
@@ -3,6 +3,19 @@ import { HlsConfig } from 'hls.js';
3
3
 
4
4
  declare type AspectRatio = `${number}/${number}`;
5
5
 
6
+ /**
7
+ * A normalized audio-track descriptor, independent of whether the source plays
8
+ * through hls.js or the native `<video>` element.
9
+ */
10
+ export declare interface AudioTrackInfo {
11
+ /** Stable index into the player's track list. */
12
+ index: number;
13
+ /** Human-readable label, e.g. "English", "Español", or a fallback. */
14
+ label: string;
15
+ /** BCP-47-ish language code if the source provides one. */
16
+ lang?: string;
17
+ }
18
+
6
19
  declare type DeviceMode = "desktop" | "mobile";
7
20
 
8
21
  /**
@@ -47,8 +60,15 @@ export declare interface ReactVideoPlayerProps {
47
60
  muted?: boolean;
48
61
  /** Loop playback. Defaults to `false`. */
49
62
  loop?: boolean;
50
- /** Show native browser controls. Defaults to `false`. */
51
- controls?: boolean;
63
+ /**
64
+ * Controls mode.
65
+ * - `true` (default) / `"custom"`: branded custom control bar at the
66
+ * bottom of the frame, consistent across browsers/OS. Auto-hides
67
+ * during playback.
68
+ * - `"native"`: native browser controls (look varies by browser/OS).
69
+ * - `false`: no controls — just the centered play overlay.
70
+ */
71
+ controls?: boolean | "custom" | "native";
52
72
  /**
53
73
  * Start playback as soon as the source loads. Defaults to `false`.
54
74
  * Browsers block sound-on autoplay, so this only autoplays when `muted`
@@ -100,3 +120,23 @@ declare interface YouTubeEmbedOptions {
100
120
  export declare function youTubeEmbedUrl(id: string, opts?: YouTubeEmbedOptions): string;
101
121
 
102
122
  export { }
123
+
124
+
125
+ declare global {
126
+ interface HTMLMediaElement {
127
+ readonly audioTracks?: AudioTrackListLike;
128
+ }
129
+ interface HTMLVideoElement {
130
+ /** iOS Safari only — opens Apple's native fullscreen player. */
131
+ webkitEnterFullscreen?(): void;
132
+ }
133
+ interface Document {
134
+ readonly webkitFullscreenElement?: Element | null;
135
+ readonly webkitFullscreenEnabled?: boolean;
136
+ webkitExitFullscreen?(): Promise<void>;
137
+ }
138
+ interface HTMLElement {
139
+ webkitRequestFullscreen?(): Promise<void>;
140
+ }
141
+ }
142
+