@@ -162,8 +162,8 @@ export default function Home() {
{` `}
@@ -180,8 +180,8 @@ export default function Home() {
Supported by all modern browsers (97%+ coverage).
@@ -190,8 +190,8 @@ export default function Home() {
{` `}
@@ -215,16 +215,16 @@ export default function Home() {
ensures proper spacing is reserved.
View Code
{` `}
@@ -240,21 +240,21 @@ export default function Home() {
a standard cover image, use playlistCoverId to specify a video ID for the thumbnail.
View Code
{` `}
@@ -273,19 +273,19 @@ export default function Home() {
You can pass any valid YouTube URL parameters this way.
View Code
{` `}
@@ -302,11 +302,11 @@ export default function Home() {
Includes automatic noscript fallback for crawlers.
{` `}
@@ -335,11 +335,11 @@ export default function Home() {
Custom Aspect Ratio
Change the aspect ratio from the default 16:9 using aspectWidth and aspectHeight.
- Useful for older videos in 4:3 format or custom video dimensions.
+ Useful for older videos in 4:3 format or custom video dimensions. This video has the 4:3 aspect ratio.
@@ -348,8 +348,8 @@ export default function Home() {
{` `}
@@ -373,8 +373,8 @@ export default function Home() {
instead of the default "Watch".
@@ -383,8 +383,8 @@ export default function Home() {
{` `}
@@ -397,13 +397,50 @@ export default function Home() {
)
@@ -449,8 +486,8 @@ function PlayerControlExample() {
{isPlaying ? '⏸ Pause' : '▶ Play'}
{
const timestamp = new Date().toLocaleTimeString();
@@ -536,6 +588,9 @@ function EventsExample() {
const timestamp = new Date().toLocaleTimeString();
console.log(`✅ Event fired: ${eventName}`, data || '(no data)');
+ // Mark this event as fired
+ setFiredEvents(prev => ({ ...prev, [eventName]: true }));
+
setEvents(prev => [{
name: eventName,
data: data,
@@ -663,7 +718,13 @@ function EventsExample() {
Interactive Events Demo 🎉 NEW in v3.0+
- Events are first-class citizens in v3.0+! All event handlers require enableJsApi={'{'}true{'}'}.
+ Events are first-class citizens in v3.0+! All event handlers require:
+
+
+ enableJsApi={'{'}true{'}'} - Enables YouTube's JavaScript API
+ ref={'{'}yourRef{'}'} - A React ref is REQUIRED for events to work (the component uses it to communicate with YouTube's iframe)
+
+
Play the video below and watch the live event log to see all available events in action.
The event log below only captures events from this specific video embed .
@@ -796,7 +857,7 @@ function EventsExample() {
Video Info
ID: {playerInfo.videoId}
- {playerInfo.duration && <> Duration: {playerInfo.duration}s>}
+ {playerInfo.title && <> Title: {playerInfo.title}>}
)}
@@ -804,8 +865,9 @@ function EventsExample() {
{/* Video Player */}
+ {/* Event Status Tracker */}
+
+
📋 Event Status Tracker
+
+ Interact with the video to fire different events. Green checkmarks ✅ indicate events that have fired at least once.
+
+
+ {[
+ { name: 'onIframeAdded', label: 'Iframe Added', description: 'Fires when iframe is added to DOM' },
+ { name: 'onReady', label: 'Player Ready', description: 'Player initialized successfully' },
+ { name: 'onStateChange', label: 'State Change', description: 'Player state changed' },
+ { name: 'onPlay', label: 'Play', description: 'Video started playing' },
+ { name: 'onPause', label: 'Pause', description: 'Video was paused' },
+ { name: 'onEnd', label: 'End', description: 'Video finished playing' },
+ { name: 'onBuffering', label: 'Buffering', description: 'Video is buffering' },
+ { name: 'onError', label: 'Error', description: 'An error occurred' },
+ { name: 'onPlaybackRateChange', label: 'Playback Rate', description: 'Speed changed (use ⚙️ settings)' },
+ { name: 'onPlaybackQualityChange', label: 'Quality Change', description: 'Quality changed (use ⚙️ settings)' }
+ ].map(event => (
+
+
+
+ {firedEvents[event.name] ? '✅' : '⏸️'}
+
+
+ {event.label}
+
+
+
+ {event.description}
+
+
+ ))}
+
+
+
{/* Event Log */}
View Code - All Event Handlers (with Console Debugging)
-{`import { useState, useCallback } from 'react';
+{`import { useState, useCallback, useRef } from 'react';
import LiteYouTubeEmbed from 'react-lite-youtube-embed';
function EventsExample() {
+ const ytRef = useRef(null); // ⚠️ CRITICAL: ref is REQUIRED for events!
const [events, setEvents] = useState([]);
const [currentState, setCurrentState] = useState('Not Started');
@@ -913,9 +1029,10 @@ function EventsExample() {
{/* Current State: {currentState} */}
{
@@ -998,6 +1115,7 @@ function EventsExample() {
Problem: Events are not firing at all
+ 🚨 MOST COMMON: Missing ref: You MUST pass a ref to the component! Without it, the component cannot communicate with YouTube. Example: ref={'{'}useRef(null){'}'}
Forgot enableJsApi: Ensure enableJsApi={'{'}true{'}'} is set on the component
YouTube blocked: Check if ad blockers, firewalls, or network policies block YouTube
CORS/iframe restrictions: Some browsers block third-party iframes in certain contexts
diff --git a/dist/index.es.js b/dist/index.es.js
index 32ecc5f..2fe1338 100644
--- a/dist/index.es.js
+++ b/dist/index.es.js
@@ -1,5 +1,5 @@
/**
-* @ibrahimcesar/react-lite-youtube-embed v3.1.0
+* @ibrahimcesar/react-lite-youtube-embed v3.2.0
* git+https://github.com/ibrahimcesar/react-lite-youtube-embed.git
*
* Copyright (c) Ibrahim Cesar and project contributors.
@@ -9,9 +9,9 @@
*
* Author site: https://ibrahimcesar.cloud
*/
-import { jsxs as b, Fragment as k, jsx as c } from "react/jsx-runtime";
-import * as o from "react";
-import { useState as Q, useEffect as q } from "react";
+import { jsxs as y, Fragment as E, jsx as c } from "react/jsx-runtime";
+import * as l from "react";
+import { useState as G, useEffect as q } from "react";
const z = {
default: 120,
mqdefault: 320,
@@ -19,12 +19,12 @@ const z = {
sddefault: 640,
maxresdefault: 1280
}, K = (e, t, u, s = "maxresdefault") => {
- const [a, r] = Q("");
+ const [a, r] = G("");
return q(() => {
- const l = `https://img.youtube.com/${t}/${e}/${s}.${u}`, f = `https://img.youtube.com/${t}/${e}/hqdefault.${u}`, i = z[s], d = new Image();
+ const o = `https://img.youtube.com/${t}/${e}/${s}.${u}`, h = `https://img.youtube.com/${t}/${e}/hqdefault.${u}`, i = z[s], d = new Image();
d.onload = () => {
- d.width < i ? r(f) : r(l);
- }, d.onerror = () => r(f), d.src = l;
+ d.width < i ? r(h) : r(o);
+ }, d.onerror = () => r(h), d.src = o;
}, [e, t, u, s]), a;
};
var X = /* @__PURE__ */ ((e) => (e[e.UNSTARTED = -1] = "UNSTARTED", e[e.ENDED = 0] = "ENDED", e[e.PLAYING = 1] = "PLAYING", e[e.PAUSED = 2] = "PAUSED", e[e.BUFFERING = 3] = "BUFFERING", e[e.CUED = 5] = "CUED", e))(X || {}), Z = /* @__PURE__ */ ((e) => (e[e.INVALID_PARAM = 2] = "INVALID_PARAM", e[e.HTML5_ERROR = 5] = "HTML5_ERROR", e[e.VIDEO_NOT_FOUND = 100] = "VIDEO_NOT_FOUND", e[e.NOT_EMBEDDABLE = 101] = "NOT_EMBEDDABLE", e[e.NOT_EMBEDDABLE_DISGUISED = 150] = "NOT_EMBEDDABLE_DISGUISED", e))(Z || {});
@@ -43,59 +43,60 @@ function p(e, t, u, s, a) {
return JSON.stringify(r);
}
function ee(e, t) {
- const [u, s] = o.useState(!1), [a, r] = o.useState(e.alwaysLoadIframe || !1), l = encodeURIComponent(e.id), f = typeof e.playlistCoverId == "string" ? encodeURIComponent(e.playlistCoverId) : null, i = e.title, d = e.poster || "hqdefault", U = e.announce || "Watch", T = e.alwaysLoadIframe ? e.autoplay && e.muted : !0, C = o.useMemo(() => {
+ const [u, s] = l.useState(!1), [a, r] = l.useState(e.alwaysLoadIframe || !1), o = encodeURIComponent(e.id), h = typeof e.playlistCoverId == "string" ? encodeURIComponent(e.playlistCoverId) : null, i = e.title, d = e.poster || "hqdefault", N = e.announce || "Watch", U = e.alwaysLoadIframe ? e.autoplay && e.muted : !0, C = l.useMemo(() => {
const v = new URLSearchParams({
...e.muted ? { mute: "1" } : {},
- ...T ? { autoplay: "1" } : {},
+ ...U ? { autoplay: "1" } : {},
...e.enableJsApi ? { enablejsapi: "1" } : {},
- ...e.playlist ? { list: l } : {}
+ ...e.enableJsApi && typeof window < "u" ? { origin: window.location.origin } : {},
+ ...e.playlist ? { list: o } : {}
});
return e.params && new URLSearchParams(
e.params.startsWith("&") ? e.params.slice(1) : e.params
- ).forEach((g, w) => {
- v.append(w, g);
+ ).forEach((w, k) => {
+ v.append(k, w);
}), v;
}, [
e.muted,
- T,
+ U,
e.enableJsApi,
e.playlist,
- l,
+ o,
e.params
- ]), h = o.useMemo(
+ ]), b = l.useMemo(
() => e.cookie ? "https://www.youtube.com" : "https://www.youtube-nocookie.com",
[e.cookie]
- ), _ = o.useMemo(
- () => e.playlist ? `${h}/embed/videoseries?${C.toString()}` : `${h}/embed/${l}?${C.toString()}`,
- [e.playlist, h, l, C]
- ), M = !e.thumbnail && !e.playlist && d === "maxresdefault", D = e.webp ? "webp" : "jpg", I = e.webp ? "vi_webp" : "vi", A = M ? K(e.id, I, D, d) : null, y = o.useMemo(
- () => e.thumbnail || A || `https://i.ytimg.com/${I}/${e.playlist ? f : l}/${d}.${D}`,
+ ), M = l.useMemo(
+ () => e.playlist ? `${b}/embed/videoseries?${C.toString()}` : `${b}/embed/${o}?${C.toString()}`,
+ [e.playlist, b, o, C]
+ ), _ = !e.thumbnail && !e.playlist && d === "maxresdefault", D = e.webp ? "webp" : "jpg", I = e.webp ? "vi_webp" : "vi", A = _ ? K(e.id, I, D, d) : null, g = l.useMemo(
+ () => e.thumbnail || A || `https://i.ytimg.com/${I}/${e.playlist ? h : o}/${d}.${D}`,
[
e.thumbnail,
A,
I,
e.playlist,
- f,
- l,
+ h,
+ o,
d,
D
]
- ), B = e.activatedClass || "lyt-activated", W = e.adNetwork || !1, j = e.aspectHeight || 9, x = e.aspectWidth || 16, P = e.iframeClass || "", F = e.playerClass || "lty-playbtn", Y = e.wrapperClass || "yt-lite", O = o.useCallback(
+ ), P = e.activatedClass || "lyt-activated", W = e.adNetwork || !1, B = e.aspectHeight || 9, j = e.aspectWidth || 16, x = e.iframeClass || "", F = e.playerClass || "lty-playbtn", Q = e.wrapperClass || "yt-lite", S = l.useCallback(
e.onIframeAdded || function() {
},
[e.onIframeAdded]
- ), H = e.rel ? "prefetch" : "preload", V = e.containerElement || "article", G = e.noscriptFallback !== !1, J = () => {
+ ), V = e.rel ? "prefetch" : "preload", Y = e.containerElement || "article", H = e.noscriptFallback !== !1, J = () => {
u || s(!0);
- }, S = () => {
+ }, O = () => {
a || r(!0);
};
- return o.useEffect(() => {
- a && (O(), e.focusOnLoad && typeof t == "object" && t?.current && t.current.focus());
- }, [a, O, e.focusOnLoad, t]), o.useEffect(() => {
+ return l.useEffect(() => {
+ a && (S(), e.focusOnLoad && typeof t == "object" && t?.current && t.current.focus());
+ }, [a, S, e.focusOnLoad, t]), l.useEffect(() => {
if (!a || !e.enableJsApi || !(e.onReady || e.onStateChange || e.onError || e.onPlay || e.onPause || e.onEnd || e.onBuffering || e.onPlaybackRateChange || e.onPlaybackQualityChange))
return;
- let $ = !1, g = !1;
- const w = (m) => {
+ let R = !1, w = !1;
+ const k = (m) => {
if (m.origin !== "https://www.youtube.com" && m.origin !== "https://www.youtube-nocookie.com")
return;
let n;
@@ -106,19 +107,46 @@ function ee(e, t) {
}
switch (n.event) {
case "onReady":
- $ || ($ = !0, e.onReady && e.onReady({
+ R || (R = !0, e.onReady && e.onReady({
videoId: e.id,
title: i
}));
break;
+ case "infoDelivery":
+ if (n.info?.playerState !== void 0) {
+ const f = n.info.playerState;
+ switch (e.onStateChange && e.onStateChange({
+ state: f,
+ currentTime: n.info.currentTime,
+ duration: n.info.duration
+ }), f) {
+ case 1:
+ e.onPlay?.();
+ break;
+ case 2:
+ e.onPause?.();
+ break;
+ case 0:
+ e.onEnd?.(), e.stopOnEnd && typeof t == "object" && t?.current?.contentWindow && t.current.contentWindow.postMessage(
+ '{"event":"command","func":"stopVideo","args":""}',
+ "*"
+ );
+ break;
+ case 3:
+ e.onBuffering?.();
+ break;
+ }
+ }
+ n.info?.playbackRate !== void 0 && e.onPlaybackRateChange?.(n.info.playbackRate), n.info?.playbackQuality !== void 0 && e.onPlaybackQualityChange?.(n.info.playbackQuality);
+ break;
case "onStateChange":
if (n.info?.playerState !== void 0) {
- const E = n.info.playerState;
+ const f = n.info.playerState;
switch (e.onStateChange && e.onStateChange({
- state: E,
+ state: f,
currentTime: n.info.currentTime,
duration: n.info.duration
- }), E) {
+ }), f) {
case 1:
e.onPlay?.();
break;
@@ -139,8 +167,8 @@ function ee(e, t) {
break;
case "onError":
if (n.info && "errorCode" in n.info) {
- const E = n.info.errorCode;
- e.onError && e.onError(E);
+ const f = n.info.errorCode;
+ e.onError && e.onError(f);
}
break;
case "onPlaybackRateChange":
@@ -151,22 +179,23 @@ function ee(e, t) {
break;
}
};
- window.addEventListener("message", w);
- const L = [], N = () => {
+ window.addEventListener("message", k);
+ const T = [], $ = () => {
typeof t == "object" && t?.current?.contentWindow && t.current.contentWindow.postMessage(
- '{"event":"listening","id":"' + l + '"}',
+ '{"event":"listening","id":"' + o + '"}',
"*"
);
- }, R = () => {
- if (g) return;
- g = !0, N(), [100, 300, 600, 1200, 2400].forEach((n) => {
- L.push(setTimeout(N, n));
+ }, L = () => {
+ if (w)
+ return;
+ w = !0, $(), [100, 300, 600, 1200, 2400].forEach((n) => {
+ T.push(setTimeout($, n));
});
};
- return typeof t == "object" && t?.current ? (t.current.addEventListener("load", R), t.current.contentDocument?.readyState === "complete" && R()) : [200, 500, 1e3, 2e3, 3e3].forEach((n) => {
- L.push(setTimeout(N, n));
+ return typeof t == "object" && t?.current ? (t.current.addEventListener("load", L), t.current.contentDocument?.readyState === "complete" && L()) : [200, 500, 1e3, 2e3, 3e3].forEach((n) => {
+ T.push(setTimeout($, n));
}), () => {
- window.removeEventListener("message", w), L.forEach(clearTimeout), typeof t == "object" && t?.current && t.current.removeEventListener("load", R);
+ window.removeEventListener("message", k), T.forEach(clearTimeout), typeof t == "object" && t?.current && t.current.removeEventListener("load", L);
};
}, [
a,
@@ -182,15 +211,15 @@ function ee(e, t) {
e.onPlaybackQualityChange,
e.stopOnEnd,
e.id,
- l,
+ o,
i,
t
- ]), /* @__PURE__ */ b(k, { children: [
- !e.lazyLoad && /* @__PURE__ */ c("link", { rel: H, href: y, as: "image" }),
- /* @__PURE__ */ c(k, { children: u && /* @__PURE__ */ b(k, { children: [
- /* @__PURE__ */ c("link", { rel: "preconnect", href: h }),
+ ]), /* @__PURE__ */ y(E, { children: [
+ !e.lazyLoad && /* @__PURE__ */ c("link", { rel: V, href: g, as: "image" }),
+ /* @__PURE__ */ c(E, { children: u && /* @__PURE__ */ y(E, { children: [
+ /* @__PURE__ */ c("link", { rel: "preconnect", href: b }),
/* @__PURE__ */ c("link", { rel: "preconnect", href: "https://www.google.com" }),
- W && /* @__PURE__ */ b(k, { children: [
+ W && /* @__PURE__ */ y(E, { children: [
/* @__PURE__ */ c("link", { rel: "preconnect", href: "https://static.doubleclick.net" }),
/* @__PURE__ */ c(
"link",
@@ -209,14 +238,14 @@ function ee(e, t) {
__html: p(
e.id,
i,
- y,
- h,
+ g,
+ b,
e.seo
)
}
}
),
- G && !e.playlist && /* @__PURE__ */ c("noscript", { children: /* @__PURE__ */ b(
+ H && !e.playlist && /* @__PURE__ */ c("noscript", { children: /* @__PURE__ */ y(
"a",
{
href: `https://www.youtube.com/watch?v=${e.id}`,
@@ -228,25 +257,25 @@ function ee(e, t) {
]
}
) }),
- /* @__PURE__ */ b(
- V,
+ /* @__PURE__ */ y(
+ Y,
{
onPointerOver: J,
- onClick: S,
- className: `${Y} ${a ? B : ""}`,
+ onClick: O,
+ className: `${Q} ${a ? P : ""}`,
"data-title": i,
role: a ? void 0 : "img",
"aria-label": a ? void 0 : `${i} - YouTube video preview`,
style: {
- ...!e.lazyLoad && { backgroundImage: `url(${y})` },
- "--aspect-ratio": `${j / x * 100}%`,
+ ...!e.lazyLoad && { backgroundImage: `url(${g})` },
+ "--aspect-ratio": `${B / j * 100}%`,
...e.style || {}
},
children: [
e.lazyLoad && !a && /* @__PURE__ */ c(
"img",
{
- src: y,
+ src: g,
alt: `${i} - YouTube thumbnail`,
className: "lty-thumbnail",
loading: "lazy"
@@ -257,24 +286,24 @@ function ee(e, t) {
{
type: "button",
className: F,
- "aria-label": `${U} ${i}`,
+ "aria-label": `${N} ${i}`,
"aria-hidden": a || void 0,
tabIndex: a ? -1 : 0,
- onClick: S,
- children: /* @__PURE__ */ c("span", { className: "lty-visually-hidden", children: U })
+ onClick: O,
+ children: /* @__PURE__ */ c("span", { className: "lty-visually-hidden", children: N })
}
),
a && /* @__PURE__ */ c(
"iframe",
{
ref: t,
- className: P,
+ className: x,
title: i,
width: "560",
height: "315",
allow: "accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",
allowFullScreen: !0,
- src: _,
+ src: M,
referrerPolicy: e.referrerPolicy || "strict-origin-when-cross-origin"
}
)
@@ -283,7 +312,7 @@ function ee(e, t) {
)
] });
}
-const ne = o.forwardRef(
+const ne = l.forwardRef(
ee
);
export {
diff --git a/dist/index.es.js.map b/dist/index.es.js.map
index abda671..34c3db2 100644
--- a/dist/index.es.js.map
+++ b/dist/index.es.js.map
@@ -1 +1 @@
-{"version":3,"file":"index.es.js","sources":["../src/lib/useYoutubeThumbnail.tsx","../src/lib/index.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\n\nexport type imgResolution =\n | \"default\"\n | \"mqdefault\"\n | \"hqdefault\"\n | \"sddefault\"\n | \"maxresdefault\";\n\nconst expectedWidths: Record = {\n default: 120,\n mqdefault: 320,\n hqdefault: 480,\n sddefault: 640,\n maxresdefault: 1280,\n};\n\nexport const useYoutubeThumbnail = (\n videoId: string,\n vi: string,\n format: string,\n imageRes: imgResolution = \"maxresdefault\"\n) => {\n const [url, setUrl] = useState(\"\");\n\n useEffect(() => {\n const testUrl = `https://img.youtube.com/${vi}/${videoId}/${imageRes}.${format}`;\n const fallbackUrl = `https://img.youtube.com/${vi}/${videoId}/hqdefault.${format}`;\n\n const expectedWidth = expectedWidths[imageRes];\n\n const img = new Image();\n img.onload = () => {\n if (img.width < expectedWidth) {\n setUrl(fallbackUrl);\n } else {\n setUrl(testUrl);\n }\n };\n img.onerror = () => setUrl(fallbackUrl);\n img.src = testUrl;\n }, [videoId, vi, format, imageRes]);\n\n return url;\n};\n\nexport default useYoutubeThumbnail;\n","import * as React from \"react\";\nimport useYoutubeThumbnail from \"./useYoutubeThumbnail\";\nimport { imgResolution } from \"./useYoutubeThumbnail\";\n\n// Re-export types for public API\nexport type { imgResolution };\n\n/**\n * YouTube Player State constants\n * @see https://developers.google.com/youtube/iframe_api_reference#onStateChange\n */\nexport enum PlayerState {\n UNSTARTED = -1,\n ENDED = 0,\n PLAYING = 1,\n PAUSED = 2,\n BUFFERING = 3,\n CUED = 5,\n}\n\n/**\n * YouTube Player Error codes\n * @see https://developers.google.com/youtube/iframe_api_reference#onError\n */\nexport enum PlayerError {\n INVALID_PARAM = 2,\n HTML5_ERROR = 5,\n VIDEO_NOT_FOUND = 100,\n NOT_EMBEDDABLE = 101,\n NOT_EMBEDDABLE_DISGUISED = 150,\n}\n\n/**\n * YouTube Player Event data structure\n * Represents the data received from YouTube's postMessage API\n */\nexport interface YouTubeEvent {\n info?: {\n playerState?: PlayerState;\n currentTime?: number;\n duration?: number;\n videoData?: {\n video_id: string;\n title: string;\n };\n playbackRate?: number;\n playbackQuality?: string;\n };\n}\n\n/**\n * Event handler for player state changes\n */\nexport interface PlayerStateChangeEvent {\n state: PlayerState;\n currentTime?: number;\n duration?: number;\n}\n\n/**\n * Event handler for when player is ready\n */\nexport interface PlayerReadyEvent {\n videoId: string;\n title: string;\n}\n\n/**\n * SEO metadata for YouTube video following schema.org VideoObject structure.\n * See: https://developers.google.com/search/docs/appearance/structured-data/video\n *\n * All fields are optional but providing them improves search engine discoverability\n * and enables rich results (video carousels, thumbnails in search results).\n *\n * Use the provided `scripts/fetch-youtube-metadata.sh` helper to easily retrieve\n * this data from YouTube's API.\n */\nexport interface VideoSEO {\n /**\n * The title of the video. If not provided, falls back to the component's `title` prop.\n * @example \"What's new in Material Design for the web\"\n */\n name?: string;\n\n /**\n * A description of the video content.\n * Recommended: 50-160 characters for optimal search result display.\n * @example \"Learn about the latest Material Design updates presented at Chrome Dev Summit 2019\"\n */\n description?: string;\n\n /**\n * ISO 8601 date when the video was uploaded to YouTube.\n * @example \"2019-11-11T08:00:00Z\" or \"2019-11-11\"\n */\n uploadDate?: string;\n\n /**\n * ISO 8601 duration format. Required for video rich results.\n * Format: PT#H#M#S where # is the number of hours, minutes, seconds\n * @example \"PT1M33S\" (1 minute 33 seconds)\n * @example \"PT15M\" (15 minutes)\n * @example \"PT1H30M\" (1 hour 30 minutes)\n */\n duration?: string;\n\n /**\n * Custom thumbnail URL. If not provided, auto-generated from video ID.\n * Recommended: At least 1200px wide for best quality in search results.\n * @example \"https://i.ytimg.com/vi/VIDEO_ID/maxresdefault.jpg\"\n */\n thumbnailUrl?: string;\n\n /**\n * Direct URL to watch the video. Auto-generated if not provided.\n * @example \"https://www.youtube.com/watch?v=L2vS_050c-M\"\n */\n contentUrl?: string;\n\n /**\n * The embed URL. Auto-generated from video ID if not provided.\n * @example \"https://www.youtube.com/embed/L2vS_050c-M\"\n */\n embedUrl?: string;\n}\n\nexport interface LiteYouTubeProps {\n announce?: string;\n id: string;\n title: string;\n activatedClass?: string;\n adNetwork?: boolean;\n aspectHeight?: number;\n aspectWidth?: number;\n iframeClass?: string;\n /** @deprecated Use cookie prop instead */\n noCookie?: boolean;\n cookie?: boolean;\n enableJsApi?: boolean;\n alwaysLoadIframe?: boolean;\n params?: string;\n playerClass?: string;\n playlist?: boolean;\n playlistCoverId?: string;\n poster?: imgResolution;\n webp?: boolean;\n wrapperClass?: string;\n onIframeAdded?: () => void;\n muted?: boolean;\n autoplay?: boolean;\n thumbnail?: string;\n rel?: string;\n containerElement?: keyof React.JSX.IntrinsicElements;\n style?: React.CSSProperties;\n focusOnLoad?: boolean;\n referrerPolicy?: React.HTMLAttributeReferrerPolicy;\n /**\n * Enable lazy loading for thumbnail image.\n * Uses native browser lazy loading to defer offscreen images.\n * Improves Lighthouse scores and reduces bandwidth for below-fold videos.\n * @default false\n */\n lazyLoad?: boolean;\n /**\n * Stop video and return to thumbnail when playback ends.\n * Prevents YouTube from showing related videos. Requires enableJsApi.\n * @default false\n */\n stopOnEnd?: boolean;\n /**\n * SEO metadata for search engines. Enables rich results and better discoverability.\n * Provides structured data following schema.org VideoObject specification.\n * @see https://developers.google.com/search/docs/appearance/structured-data/video\n */\n seo?: VideoSEO;\n /**\n * Include noscript fallback link for accessibility and search crawlers.\n * When true, adds a direct YouTube link inside tags.\n * @default true\n */\n noscriptFallback?: boolean;\n\n // ==================== Player Event Handlers ====================\n\n /**\n * Fires when the player is ready and API is available.\n * This is the first event to fire and indicates it's safe to call player methods.\n * @param event - Contains video ID and title\n * @example\n * onReady={(event) => console.log('Player ready for:', event.videoId)}\n */\n onReady?: (event: PlayerReadyEvent) => void;\n\n /**\n * Fires when the player's state changes.\n * Use this for comprehensive state tracking (play, pause, end, buffering, etc.)\n * @param event - Contains state, current time, and duration\n * @example\n * onStateChange={(event) => {\n * if (event.state === PlayerState.PLAYING) {\n * analytics.track('video_play');\n * }\n * }}\n */\n onStateChange?: (event: PlayerStateChangeEvent) => void;\n\n /**\n * Fires when the player encounters an error.\n * Use this for graceful error handling and user feedback.\n * @param errorCode - YouTube error code (see PlayerError enum)\n * @example\n * onError={(code) => {\n * if (code === PlayerError.VIDEO_NOT_FOUND) {\n * showErrorMessage('Video not available');\n * }\n * }}\n */\n onError?: (errorCode: PlayerError) => void;\n\n // ==================== Convenience Event Handlers ====================\n\n /**\n * Fires when the video starts playing.\n * Convenience wrapper for onStateChange with PlayerState.PLAYING.\n * @example\n * onPlay={() => analytics.track('video_play')}\n */\n onPlay?: () => void;\n\n /**\n * Fires when the video is paused.\n * Convenience wrapper for onStateChange with PlayerState.PAUSED.\n * @example\n * onPause={() => analytics.track('video_pause')}\n */\n onPause?: () => void;\n\n /**\n * Fires when the video ends.\n * Convenience wrapper for onStateChange with PlayerState.ENDED.\n * Useful for loading next video in a playlist.\n * @example\n * onEnd={() => loadNextVideo()}\n */\n onEnd?: () => void;\n\n /**\n * Fires when the video is buffering.\n * Convenience wrapper for onStateChange with PlayerState.BUFFERING.\n * @example\n * onBuffering={() => showLoadingSpinner()}\n */\n onBuffering?: () => void;\n\n // ==================== Advanced Event Handlers ====================\n\n /**\n * Fires when the playback rate (speed) changes.\n * Common values: 0.25, 0.5, 1, 1.5, 2\n * @param playbackRate - The new playback rate\n * @example\n * onPlaybackRateChange={(rate) => console.log('Speed:', rate + 'x')}\n */\n onPlaybackRateChange?: (playbackRate: number) => void;\n\n /**\n * Fires when the video quality changes.\n * Common values: \"small\" (240p), \"medium\" (360p), \"large\" (480p), \"hd720\", \"hd1080\"\n * @param quality - The new quality level\n * @example\n * onPlaybackQualityChange={(quality) => {\n * analytics.track('quality_change', { quality });\n * }}\n */\n onPlaybackQualityChange?: (quality: string) => void;\n}\n\n/**\n * Generates JSON-LD structured data for VideoObject schema.\n * @see https://schema.org/VideoObject\n * @see https://developers.google.com/search/docs/appearance/structured-data/video\n */\nfunction generateVideoStructuredData(\n videoId: string,\n title: string,\n posterUrl: string,\n ytUrl: string,\n seo?: VideoSEO\n): string {\n const structuredData = {\n \"@context\": \"https://schema.org\",\n \"@type\": \"VideoObject\",\n name: seo?.name || title,\n thumbnailUrl: [seo?.thumbnailUrl || posterUrl],\n embedUrl: seo?.embedUrl || `${ytUrl}/embed/${videoId}`,\n contentUrl: seo?.contentUrl || `https://www.youtube.com/watch?v=${videoId}`,\n ...(seo?.description && { description: seo.description }),\n ...(seo?.uploadDate && { uploadDate: seo.uploadDate }),\n ...(seo?.duration && { duration: seo.duration }),\n };\n\n return JSON.stringify(structuredData);\n}\n\nfunction LiteYouTubeEmbedComponent(\n props: LiteYouTubeProps,\n ref: React.Ref\n) {\n const [preconnected, setPreconnected] = React.useState(false);\n const [iframe, setIframe] = React.useState(props.alwaysLoadIframe || false);\n const videoId = encodeURIComponent(props.id);\n const videoPlaylistCoverId =\n typeof props.playlistCoverId === \"string\"\n ? encodeURIComponent(props.playlistCoverId)\n : null;\n const videoTitle = props.title;\n const posterImp = props.poster || \"hqdefault\";\n const announceWatch = props.announce || \"Watch\";\n\n const shouldAddAutoplayParam = props.alwaysLoadIframe\n ? props.autoplay && props.muted\n : true; // When the iframe is not loaded immediately, the video should play as soon as its loaded (which happens when the button is clicked)\n\n // Iframe Parameters - memoized to avoid recreating URLSearchParams on every render\n const iframeParams = React.useMemo(() => {\n const params = new URLSearchParams({\n ...(props.muted ? { mute: \"1\" } : {}),\n ...(shouldAddAutoplayParam ? { autoplay: \"1\" } : {}),\n ...(props.enableJsApi ? { enablejsapi: \"1\" } : {}),\n ...(props.playlist ? { list: videoId } : {}),\n });\n\n // parse props.params into individual search parameters and append them to params\n if (props.params) {\n const additionalParams = new URLSearchParams(\n props.params.startsWith(\"&\") ? props.params.slice(1) : props.params\n );\n additionalParams.forEach((value, key) => {\n params.append(key, value);\n });\n }\n\n return params;\n }, [\n props.muted,\n shouldAddAutoplayParam,\n props.enableJsApi,\n props.playlist,\n videoId,\n props.params,\n ]);\n\n const ytUrl = React.useMemo(\n () =>\n props.cookie\n ? \"https://www.youtube.com\"\n : \"https://www.youtube-nocookie.com\",\n [props.cookie]\n );\n\n const iframeSrc = React.useMemo(\n () =>\n !props.playlist\n ? `${ytUrl}/embed/${videoId}?${iframeParams.toString()}`\n : `${ytUrl}/embed/videoseries?${iframeParams.toString()}`,\n [props.playlist, ytUrl, videoId, iframeParams]\n );\n\n const useDynamicThumbnail =\n !props.thumbnail && !props.playlist && posterImp === \"maxresdefault\";\n\n const format = props.webp ? \"webp\" : \"jpg\";\n const vi = props.webp ? \"vi_webp\" : \"vi\";\n\n const dynamicThumbnailUrl = useDynamicThumbnail\n ? useYoutubeThumbnail(props.id, vi, format, posterImp)\n : null;\n\n const posterUrl = React.useMemo(\n () =>\n props.thumbnail ||\n dynamicThumbnailUrl ||\n `https://i.ytimg.com/${vi}/${\n props.playlist ? videoPlaylistCoverId : videoId\n }/${posterImp}.${format}`,\n [\n props.thumbnail,\n dynamicThumbnailUrl,\n vi,\n props.playlist,\n videoPlaylistCoverId,\n videoId,\n posterImp,\n format,\n ]\n );\n\n const activatedClassImp = props.activatedClass || \"lyt-activated\";\n const adNetworkImp = props.adNetwork || false;\n const aspectHeight = props.aspectHeight || 9;\n const aspectWidth = props.aspectWidth || 16;\n const iframeClassImp = props.iframeClass || \"\";\n const playerClassImp = props.playerClass || \"lty-playbtn\";\n const wrapperClassImp = props.wrapperClass || \"yt-lite\";\n const onIframeAdded = React.useCallback(\n props.onIframeAdded || function () {},\n [props.onIframeAdded]\n );\n const rel = props.rel ? \"prefetch\" : \"preload\";\n const ContainerElement = props.containerElement || \"article\";\n const includeNoscriptFallback = props.noscriptFallback !== false; // Default to true\n\n const warmConnections = () => {\n if (preconnected) return;\n setPreconnected(true);\n };\n\n const addIframe = () => {\n if (iframe) return;\n setIframe(true);\n };\n\n React.useEffect(() => {\n if (iframe) {\n onIframeAdded();\n\n // Focus iframe if focusOnLoad is enabled and ref is available\n if (props.focusOnLoad && typeof ref === \"object\" && ref?.current) {\n ref.current.focus();\n }\n }\n }, [iframe, onIframeAdded, props.focusOnLoad, ref]);\n\n // Set up postMessage listener for YouTube player events\n React.useEffect(() => {\n // Only set up listener if iframe is loaded and we have event handlers\n if (!iframe || !props.enableJsApi) {\n return;\n }\n\n const hasEventHandlers =\n props.onReady ||\n props.onStateChange ||\n props.onError ||\n props.onPlay ||\n props.onPause ||\n props.onEnd ||\n props.onBuffering ||\n props.onPlaybackRateChange ||\n props.onPlaybackQualityChange;\n\n if (!hasEventHandlers) {\n return;\n }\n\n let isReady = false;\n let iframeLoaded = false;\n\n const handleMessage = (event: MessageEvent) => {\n // Verify origin is from YouTube\n if (\n event.origin !== \"https://www.youtube.com\" &&\n event.origin !== \"https://www.youtube-nocookie.com\"\n ) {\n return;\n }\n\n let data: { event?: string; info?: YouTubeEvent[\"info\"] };\n try {\n data =\n typeof event.data === \"string\" ? JSON.parse(event.data) : event.data;\n } catch {\n return; // Invalid JSON, ignore\n }\n\n // Handle different YouTube events\n switch (data.event) {\n case \"onReady\":\n if (!isReady) {\n isReady = true;\n if (props.onReady) {\n props.onReady({\n videoId: props.id,\n title: videoTitle,\n });\n }\n }\n break;\n\n case \"onStateChange\":\n if (data.info?.playerState !== undefined) {\n const state = data.info.playerState as PlayerState;\n\n // Call main onStateChange handler\n if (props.onStateChange) {\n props.onStateChange({\n state,\n currentTime: data.info.currentTime,\n duration: data.info.duration,\n });\n }\n\n // Call convenience handlers\n switch (state) {\n case PlayerState.PLAYING:\n props.onPlay?.();\n break;\n case PlayerState.PAUSED:\n props.onPause?.();\n break;\n case PlayerState.ENDED:\n props.onEnd?.();\n // Stop video to return to thumbnail and prevent related videos\n if (\n props.stopOnEnd &&\n typeof ref === \"object\" &&\n ref?.current?.contentWindow\n ) {\n ref.current.contentWindow.postMessage(\n '{\"event\":\"command\",\"func\":\"stopVideo\",\"args\":\"\"}',\n \"*\"\n );\n }\n break;\n case PlayerState.BUFFERING:\n props.onBuffering?.();\n break;\n }\n }\n break;\n\n case \"onError\":\n if (data.info && \"errorCode\" in data.info) {\n const errorCode = (data.info as { errorCode: number }).errorCode;\n if (props.onError) {\n props.onError(errorCode as PlayerError);\n }\n }\n break;\n\n case \"onPlaybackRateChange\":\n if (data.info?.playbackRate !== undefined) {\n props.onPlaybackRateChange?.(data.info.playbackRate);\n }\n break;\n\n case \"onPlaybackQualityChange\":\n if (data.info?.playbackQuality !== undefined) {\n props.onPlaybackQualityChange?.(data.info.playbackQuality);\n }\n break;\n }\n };\n\n window.addEventListener(\"message\", handleMessage);\n\n // Request iframe to send events by posting \"listening\" message\n // This tells YouTube player to start sending events\n const timeouts: ReturnType[] = [];\n const attemptListen = () => {\n if (typeof ref === \"object\" && ref?.current?.contentWindow) {\n ref.current.contentWindow.postMessage(\n '{\"event\":\"listening\",\"id\":\"' + videoId + '\"}',\n \"*\"\n );\n }\n };\n\n // Strategy: Wait for iframe load event, then send listening message\n // This is more reliable than arbitrary delays\n const handleIframeLoad = () => {\n if (iframeLoaded) return;\n iframeLoaded = true;\n\n // Send initial listening message immediately when iframe loads\n attemptListen();\n\n // Also retry with delays as fallback for slower YouTube API initialization\n // YouTube's player API needs additional time to initialize even after iframe loads\n const delays = [100, 300, 600, 1200, 2400];\n delays.forEach((delay) => {\n timeouts.push(setTimeout(attemptListen, delay));\n });\n };\n\n // Attach load event listener to iframe\n if (typeof ref === \"object\" && ref?.current) {\n ref.current.addEventListener(\"load\", handleIframeLoad);\n\n // If iframe is already loaded, trigger immediately\n // This handles race condition where load event fired before listener attached\n if (ref.current.contentDocument?.readyState === \"complete\") {\n handleIframeLoad();\n }\n } else {\n // Fallback: If ref not ready, use longer delays\n const fallbackDelays = [200, 500, 1000, 2000, 3000];\n fallbackDelays.forEach((delay) => {\n timeouts.push(setTimeout(attemptListen, delay));\n });\n }\n\n return () => {\n window.removeEventListener(\"message\", handleMessage);\n timeouts.forEach(clearTimeout);\n\n // Clean up iframe load listener\n if (typeof ref === \"object\" && ref?.current) {\n ref.current.removeEventListener(\"load\", handleIframeLoad);\n }\n };\n }, [\n iframe,\n props.enableJsApi,\n props.onReady,\n props.onStateChange,\n props.onError,\n props.onPlay,\n props.onPause,\n props.onEnd,\n props.onBuffering,\n props.onPlaybackRateChange,\n props.onPlaybackQualityChange,\n props.stopOnEnd,\n props.id,\n videoId,\n videoTitle,\n ref,\n ]);\n\n return (\n <>\n {!props.lazyLoad && }\n <>\n {preconnected && (\n <>\n \n \n {adNetworkImp && (\n <>\n \n \n >\n )}\n >\n )}\n >\n {/* SEO: JSON-LD Structured Data for VideoObject */}\n {props.seo && !props.playlist && (\n \n )}\n {/* SEO: Noscript fallback for accessibility and crawlers */}\n {includeNoscriptFallback && !props.playlist && (\n \n \n Watch "{videoTitle}" on YouTube\n \n \n )}\n \n {props.lazyLoad && !iframe && (\n \n )}\n \n {announceWatch} \n \n {iframe && (\n \n )}\n \n >\n );\n}\n\nexport default React.forwardRef(\n LiteYouTubeEmbedComponent\n);\n"],"names":["expectedWidths","useYoutubeThumbnail","videoId","vi","format","imageRes","url","setUrl","useState","useEffect","testUrl","fallbackUrl","expectedWidth","img","PlayerState","PlayerError","generateVideoStructuredData","title","posterUrl","ytUrl","seo","structuredData","LiteYouTubeEmbedComponent","props","ref","preconnected","setPreconnected","React","iframe","setIframe","videoPlaylistCoverId","videoTitle","posterImp","announceWatch","shouldAddAutoplayParam","iframeParams","params","value","key","iframeSrc","useDynamicThumbnail","dynamicThumbnailUrl","activatedClassImp","adNetworkImp","aspectHeight","aspectWidth","iframeClassImp","playerClassImp","wrapperClassImp","onIframeAdded","rel","ContainerElement","includeNoscriptFallback","warmConnections","addIframe","isReady","iframeLoaded","handleMessage","event","data","state","errorCode","timeouts","attemptListen","handleIframeLoad","delay","jsxs","Fragment","jsx","index"],"mappings":";;;AASA,MAAMA,IAAgD;AAAA,EACpD,SAAS;AAAA,EACT,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,eAAe;AACjB,GAEaC,IAAsB,CACjCC,GACAC,GACAC,GACAC,IAA0B,oBACvB;AACH,QAAM,CAACC,GAAKC,CAAM,IAAIC,EAAS,EAAE;AAEjC,SAAAC,EAAU,MAAM;AACd,UAAMC,IAAU,2BAA2BP,CAAE,IAAID,CAAO,IAAIG,CAAQ,IAAID,CAAM,IACxEO,IAAc,2BAA2BR,CAAE,IAAID,CAAO,cAAcE,CAAM,IAE1EQ,IAAgBZ,EAAeK,CAAQ,GAEvCQ,IAAM,IAAI,MAAA;AAChB,IAAAA,EAAI,SAAS,MAAM;AACjB,MAAIA,EAAI,QAAQD,IACdL,EAAOI,CAAW,IAElBJ,EAAOG,CAAO;AAAA,IAElB,GACAG,EAAI,UAAU,MAAMN,EAAOI,CAAW,GACtCE,EAAI,MAAMH;AAAA,EACZ,GAAG,CAACR,GAASC,GAAIC,GAAQC,CAAQ,CAAC,GAE3BC;AACT;ACjCO,IAAKQ,sBAAAA,OACVA,EAAAA,EAAA,YAAY,EAAA,IAAZ,aACAA,EAAAA,EAAA,QAAQ,CAAA,IAAR,SACAA,EAAAA,EAAA,UAAU,CAAA,IAAV,WACAA,EAAAA,EAAA,SAAS,CAAA,IAAT,UACAA,EAAAA,EAAA,YAAY,CAAA,IAAZ,aACAA,EAAAA,EAAA,OAAO,CAAA,IAAP,QANUA,IAAAA,KAAA,CAAA,CAAA,GAaAC,sBAAAA,OACVA,EAAAA,EAAA,gBAAgB,CAAA,IAAhB,iBACAA,EAAAA,EAAA,cAAc,CAAA,IAAd,eACAA,EAAAA,EAAA,kBAAkB,GAAA,IAAlB,mBACAA,EAAAA,EAAA,iBAAiB,GAAA,IAAjB,kBACAA,EAAAA,EAAA,2BAA2B,GAAA,IAA3B,4BALUA,IAAAA,KAAA,CAAA,CAAA;AAkQZ,SAASC,EACPd,GACAe,GACAC,GACAC,GACAC,GACQ;AACR,QAAMC,IAAiB;AAAA,IACrB,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,MAAMD,GAAK,QAAQH;AAAA,IACnB,cAAc,CAACG,GAAK,gBAAgBF,CAAS;AAAA,IAC7C,UAAUE,GAAK,YAAY,GAAGD,CAAK,UAAUjB,CAAO;AAAA,IACpD,YAAYkB,GAAK,cAAc,mCAAmClB,CAAO;AAAA,IACzE,GAAIkB,GAAK,eAAe,EAAE,aAAaA,EAAI,YAAA;AAAA,IAC3C,GAAIA,GAAK,cAAc,EAAE,YAAYA,EAAI,WAAA;AAAA,IACzC,GAAIA,GAAK,YAAY,EAAE,UAAUA,EAAI,SAAA;AAAA,EAAS;AAGhD,SAAO,KAAK,UAAUC,CAAc;AACtC;AAEA,SAASC,GACPC,GACAC,GACA;AACA,QAAM,CAACC,GAAcC,CAAe,IAAIC,EAAM,SAAS,EAAK,GACtD,CAACC,GAAQC,CAAS,IAAIF,EAAM,SAASJ,EAAM,oBAAoB,EAAK,GACpErB,IAAU,mBAAmBqB,EAAM,EAAE,GACrCO,IACJ,OAAOP,EAAM,mBAAoB,WAC7B,mBAAmBA,EAAM,eAAe,IACxC,MACAQ,IAAaR,EAAM,OACnBS,IAAYT,EAAM,UAAU,aAC5BU,IAAgBV,EAAM,YAAY,SAElCW,IAAyBX,EAAM,mBACjCA,EAAM,YAAYA,EAAM,QACxB,IAGEY,IAAeR,EAAM,QAAQ,MAAM;AACvC,UAAMS,IAAS,IAAI,gBAAgB;AAAA,MACjC,GAAIb,EAAM,QAAQ,EAAE,MAAM,IAAA,IAAQ,CAAA;AAAA,MAClC,GAAIW,IAAyB,EAAE,UAAU,IAAA,IAAQ,CAAA;AAAA,MACjD,GAAIX,EAAM,cAAc,EAAE,aAAa,IAAA,IAAQ,CAAA;AAAA,MAC/C,GAAIA,EAAM,WAAW,EAAE,MAAMrB,EAAA,IAAY,CAAA;AAAA,IAAC,CAC3C;AAGD,WAAIqB,EAAM,UACiB,IAAI;AAAA,MAC3BA,EAAM,OAAO,WAAW,GAAG,IAAIA,EAAM,OAAO,MAAM,CAAC,IAAIA,EAAM;AAAA,IAAA,EAE9C,QAAQ,CAACc,GAAOC,MAAQ;AACvC,MAAAF,EAAO,OAAOE,GAAKD,CAAK;AAAA,IAC1B,CAAC,GAGID;AAAA,EACT,GAAG;AAAA,IACDb,EAAM;AAAA,IACNW;AAAA,IACAX,EAAM;AAAA,IACNA,EAAM;AAAA,IACNrB;AAAA,IACAqB,EAAM;AAAA,EAAA,CACP,GAEKJ,IAAQQ,EAAM;AAAA,IAClB,MACEJ,EAAM,SACF,4BACA;AAAA,IACN,CAACA,EAAM,MAAM;AAAA,EAAA,GAGTgB,IAAYZ,EAAM;AAAA,IACtB,MACGJ,EAAM,WAEH,GAAGJ,CAAK,sBAAsBgB,EAAa,UAAU,KADrD,GAAGhB,CAAK,UAAUjB,CAAO,IAAIiC,EAAa,SAAA,CAAU;AAAA,IAE1D,CAACZ,EAAM,UAAUJ,GAAOjB,GAASiC,CAAY;AAAA,EAAA,GAGzCK,IACJ,CAACjB,EAAM,aAAa,CAACA,EAAM,YAAYS,MAAc,iBAEjD5B,IAASmB,EAAM,OAAO,SAAS,OAC/BpB,IAAKoB,EAAM,OAAO,YAAY,MAE9BkB,IAAsBD,IACxBvC,EAAoBsB,EAAM,IAAIpB,GAAIC,GAAQ4B,CAAS,IACnD,MAEEd,IAAYS,EAAM;AAAA,IACtB,MACEJ,EAAM,aACNkB,KACA,uBAAuBtC,CAAE,IACvBoB,EAAM,WAAWO,IAAuB5B,CAC1C,IAAI8B,CAAS,IAAI5B,CAAM;AAAA,IACzB;AAAA,MACEmB,EAAM;AAAA,MACNkB;AAAA,MACAtC;AAAA,MACAoB,EAAM;AAAA,MACNO;AAAA,MACA5B;AAAA,MACA8B;AAAA,MACA5B;AAAA,IAAA;AAAA,EACF,GAGIsC,IAAoBnB,EAAM,kBAAkB,iBAC5CoB,IAAepB,EAAM,aAAa,IAClCqB,IAAerB,EAAM,gBAAgB,GACrCsB,IAActB,EAAM,eAAe,IACnCuB,IAAiBvB,EAAM,eAAe,IACtCwB,IAAiBxB,EAAM,eAAe,eACtCyB,IAAkBzB,EAAM,gBAAgB,WACxC0B,IAAgBtB,EAAM;AAAA,IAC1BJ,EAAM,iBAAiB,WAAY;AAAA,IAAC;AAAA,IACpC,CAACA,EAAM,aAAa;AAAA,EAAA,GAEhB2B,IAAM3B,EAAM,MAAM,aAAa,WAC/B4B,IAAmB5B,EAAM,oBAAoB,WAC7C6B,IAA0B7B,EAAM,qBAAqB,IAErD8B,IAAkB,MAAM;AAC5B,IAAI5B,KACJC,EAAgB,EAAI;AAAA,EACtB,GAEM4B,IAAY,MAAM;AACtB,IAAI1B,KACJC,EAAU,EAAI;AAAA,EAChB;AAEA,SAAAF,EAAM,UAAU,MAAM;AACpB,IAAIC,MACFqB,EAAA,GAGI1B,EAAM,eAAe,OAAOC,KAAQ,YAAYA,GAAK,WACvDA,EAAI,QAAQ,MAAA;AAAA,EAGlB,GAAG,CAACI,GAAQqB,GAAe1B,EAAM,aAAaC,CAAG,CAAC,GAGlDG,EAAM,UAAU,MAAM;AAiBpB,QAfI,CAACC,KAAU,CAACL,EAAM,eAelB,EAVFA,EAAM,WACNA,EAAM,iBACNA,EAAM,WACNA,EAAM,UACNA,EAAM,WACNA,EAAM,SACNA,EAAM,eACNA,EAAM,wBACNA,EAAM;AAGN;AAGF,QAAIgC,IAAU,IACVC,IAAe;AAEnB,UAAMC,IAAgB,CAACC,MAAwB;AAE7C,UACEA,EAAM,WAAW,6BACjBA,EAAM,WAAW;AAEjB;AAGF,UAAIC;AACJ,UAAI;AACF,QAAAA,IACE,OAAOD,EAAM,QAAS,WAAW,KAAK,MAAMA,EAAM,IAAI,IAAIA,EAAM;AAAA,MACpE,QAAQ;AACN;AAAA,MACF;AAGA,cAAQC,EAAK,OAAA;AAAA,QACX,KAAK;AACH,UAAKJ,MACHA,IAAU,IACNhC,EAAM,WACRA,EAAM,QAAQ;AAAA,YACZ,SAASA,EAAM;AAAA,YACf,OAAOQ;AAAA,UAAA,CACR;AAGL;AAAA,QAEF,KAAK;AACH,cAAI4B,EAAK,MAAM,gBAAgB,QAAW;AACxC,kBAAMC,IAAQD,EAAK,KAAK;AAYxB,oBATIpC,EAAM,iBACRA,EAAM,cAAc;AAAA,cAClB,OAAAqC;AAAA,cACA,aAAaD,EAAK,KAAK;AAAA,cACvB,UAAUA,EAAK,KAAK;AAAA,YAAA,CACrB,GAIKC,GAAA;AAAA,cACN,KAAK;AACH,gBAAArC,EAAM,SAAA;AACN;AAAA,cACF,KAAK;AACH,gBAAAA,EAAM,UAAA;AACN;AAAA,cACF,KAAK;AACH,gBAAAA,EAAM,QAAA,GAGJA,EAAM,aACN,OAAOC,KAAQ,YACfA,GAAK,SAAS,iBAEdA,EAAI,QAAQ,cAAc;AAAA,kBACxB;AAAA,kBACA;AAAA,gBAAA;AAGJ;AAAA,cACF,KAAK;AACH,gBAAAD,EAAM,cAAA;AACN;AAAA,YAAA;AAAA,UAEN;AACA;AAAA,QAEF,KAAK;AACH,cAAIoC,EAAK,QAAQ,eAAeA,EAAK,MAAM;AACzC,kBAAME,IAAaF,EAAK,KAA+B;AACvD,YAAIpC,EAAM,WACRA,EAAM,QAAQsC,CAAwB;AAAA,UAE1C;AACA;AAAA,QAEF,KAAK;AACH,UAAIF,EAAK,MAAM,iBAAiB,UAC9BpC,EAAM,uBAAuBoC,EAAK,KAAK,YAAY;AAErD;AAAA,QAEF,KAAK;AACH,UAAIA,EAAK,MAAM,oBAAoB,UACjCpC,EAAM,0BAA0BoC,EAAK,KAAK,eAAe;AAE3D;AAAA,MAAA;AAAA,IAEN;AAEA,WAAO,iBAAiB,WAAWF,CAAa;AAIhD,UAAMK,IAA4C,CAAA,GAC5CC,IAAgB,MAAM;AAC1B,MAAI,OAAOvC,KAAQ,YAAYA,GAAK,SAAS,iBAC3CA,EAAI,QAAQ,cAAc;AAAA,QACxB,gCAAgCtB,IAAU;AAAA,QAC1C;AAAA,MAAA;AAAA,IAGN,GAIM8D,IAAmB,MAAM;AAC7B,UAAIR,EAAc;AAClB,MAAAA,IAAe,IAGfO,EAAA,GAIe,CAAC,KAAK,KAAK,KAAK,MAAM,IAAI,EAClC,QAAQ,CAACE,MAAU;AACxB,QAAAH,EAAS,KAAK,WAAWC,GAAeE,CAAK,CAAC;AAAA,MAChD,CAAC;AAAA,IACH;AAGA,WAAI,OAAOzC,KAAQ,YAAYA,GAAK,WAClCA,EAAI,QAAQ,iBAAiB,QAAQwC,CAAgB,GAIjDxC,EAAI,QAAQ,iBAAiB,eAAe,cAC9CwC,EAAA,KAIqB,CAAC,KAAK,KAAK,KAAM,KAAM,GAAI,EACnC,QAAQ,CAACC,MAAU;AAChC,MAAAH,EAAS,KAAK,WAAWC,GAAeE,CAAK,CAAC;AAAA,IAChD,CAAC,GAGI,MAAM;AACX,aAAO,oBAAoB,WAAWR,CAAa,GACnDK,EAAS,QAAQ,YAAY,GAGzB,OAAOtC,KAAQ,YAAYA,GAAK,WAClCA,EAAI,QAAQ,oBAAoB,QAAQwC,CAAgB;AAAA,IAE5D;AAAA,EACF,GAAG;AAAA,IACDpC;AAAA,IACAL,EAAM;AAAA,IACNA,EAAM;AAAA,IACNA,EAAM;AAAA,IACNA,EAAM;AAAA,IACNA,EAAM;AAAA,IACNA,EAAM;AAAA,IACNA,EAAM;AAAA,IACNA,EAAM;AAAA,IACNA,EAAM;AAAA,IACNA,EAAM;AAAA,IACNA,EAAM;AAAA,IACNA,EAAM;AAAA,IACNrB;AAAA,IACA6B;AAAA,IACAP;AAAA,EAAA,CACD,GAGC,gBAAA0C,EAAAC,GAAA,EACG,UAAA;AAAA,IAAA,CAAC5C,EAAM,YAAY,gBAAA6C,EAAC,QAAA,EAAK,KAAAlB,GAAU,MAAMhC,GAAW,IAAG,SAAQ;AAAA,IAChE,gBAAAkD,EAAAD,GAAA,EACG,eACC,gBAAAD,EAAAC,GAAA,EACE,UAAA;AAAA,MAAA,gBAAAC,EAAC,QAAA,EAAK,KAAI,cAAa,MAAMjD,GAAO;AAAA,MACpC,gBAAAiD,EAAC,QAAA,EAAK,KAAI,cAAa,MAAK,0BAAyB;AAAA,MACpDzB,KACC,gBAAAuB,EAAAC,GAAA,EACE,UAAA;AAAA,QAAA,gBAAAC,EAAC,QAAA,EAAK,KAAI,cAAa,MAAK,kCAAiC;AAAA,QAC7D,gBAAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,KAAI;AAAA,YACJ,MAAK;AAAA,UAAA;AAAA,QAAA;AAAA,MACP,EAAA,CACF;AAAA,IAAA,EAAA,CAEJ,EAAA,CAEJ;AAAA,IAEC7C,EAAM,OAAO,CAACA,EAAM,YACnB,gBAAA6C;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,yBAAyB;AAAA,UACvB,QAAQpD;AAAA,YACNO,EAAM;AAAA,YACNQ;AAAA,YACAb;AAAA,YACAC;AAAA,YACAI,EAAM;AAAA,UAAA;AAAA,QACR;AAAA,MACF;AAAA,IAAA;AAAA,IAIH6B,KAA2B,CAAC7B,EAAM,8BAChC,YAAA,EACC,UAAA,gBAAA2C;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAM,mCAAmC3C,EAAM,EAAE;AAAA,QACjD,cAAY,SAASQ,CAAU;AAAA,QAChC,UAAA;AAAA,UAAA;AAAA,UACcA;AAAA,UAAW;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA,GAE5B;AAAA,IAEF,gBAAAmC;AAAA,MAACf;AAAA,MAAA;AAAA,QACC,eAAeE;AAAA,QACf,SAASC;AAAA,QACT,WAAW,GAAGN,CAAe,IAAIpB,IAASc,IAAoB,EAAE;AAAA,QAChE,cAAYX;AAAA,QACZ,MAAOH,IAAiB,SAAR;AAAA,QAChB,cACGA,IAAmD,SAA1C,GAAGG,CAAU;AAAA,QAEzB,OACE;AAAA,UACE,GAAI,CAACR,EAAM,YAAY,EAAE,iBAAiB,OAAOL,CAAS,IAAA;AAAA,UAC1D,kBAAkB,GAAI0B,IAAeC,IAAe,GAAG;AAAA,UACvD,GAAItB,EAAM,SAAS,CAAA;AAAA,QAAC;AAAA,QAIvB,UAAA;AAAA,UAAAA,EAAM,YAAY,CAACK,KAClB,gBAAAwC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,KAAKlD;AAAA,cACL,KAAK,GAAGa,CAAU;AAAA,cAClB,WAAU;AAAA,cACV,SAAQ;AAAA,YAAA;AAAA,UAAA;AAAA,UAGZ,gBAAAqC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAWrB;AAAA,cACX,cAAY,GAAGd,CAAa,IAAIF,CAAU;AAAA,cAC1C,eAAaH,KAAU;AAAA,cACvB,UAAUA,IAAS,KAAK;AAAA,cACxB,SAAS0B;AAAA,cAET,UAAA,gBAAAc,EAAC,QAAA,EAAK,WAAU,uBAAuB,UAAAnC,EAAA,CAAc;AAAA,YAAA;AAAA,UAAA;AAAA,UAEtDL,KACC,gBAAAwC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,KAAA5C;AAAA,cACA,WAAWsB;AAAA,cACX,OAAOf;AAAA,cACP,OAAM;AAAA,cACN,QAAO;AAAA,cACP,OAAM;AAAA,cACN,iBAAe;AAAA,cACf,KAAKQ;AAAA,cACL,gBACGhB,EAAM,kBACL;AAAA,YAAA;AAAA,UAAA;AAAA,QAEL;AAAA,MAAA;AAAA,IAAA;AAAA,EAEL,GACF;AAEJ;AAEA,MAAA8C,KAAe1C,EAAM;AAAA,EACnBL;AACF;"}
\ No newline at end of file
+{"version":3,"file":"index.es.js","sources":["../src/lib/useYoutubeThumbnail.tsx","../src/lib/index.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\n\nexport type imgResolution =\n | \"default\"\n | \"mqdefault\"\n | \"hqdefault\"\n | \"sddefault\"\n | \"maxresdefault\";\n\nconst expectedWidths: Record = {\n default: 120,\n mqdefault: 320,\n hqdefault: 480,\n sddefault: 640,\n maxresdefault: 1280,\n};\n\nexport const useYoutubeThumbnail = (\n videoId: string,\n vi: string,\n format: string,\n imageRes: imgResolution = \"maxresdefault\"\n) => {\n const [url, setUrl] = useState(\"\");\n\n useEffect(() => {\n const testUrl = `https://img.youtube.com/${vi}/${videoId}/${imageRes}.${format}`;\n const fallbackUrl = `https://img.youtube.com/${vi}/${videoId}/hqdefault.${format}`;\n\n const expectedWidth = expectedWidths[imageRes];\n\n const img = new Image();\n img.onload = () => {\n if (img.width < expectedWidth) {\n setUrl(fallbackUrl);\n } else {\n setUrl(testUrl);\n }\n };\n img.onerror = () => setUrl(fallbackUrl);\n img.src = testUrl;\n }, [videoId, vi, format, imageRes]);\n\n return url;\n};\n\nexport default useYoutubeThumbnail;\n","import * as React from \"react\";\nimport useYoutubeThumbnail from \"./useYoutubeThumbnail\";\nimport { imgResolution } from \"./useYoutubeThumbnail\";\n\n// Re-export types for public API\nexport type { imgResolution };\n\n/**\n * YouTube Player State constants\n * @see https://developers.google.com/youtube/iframe_api_reference#onStateChange\n */\nexport enum PlayerState {\n UNSTARTED = -1,\n ENDED = 0,\n PLAYING = 1,\n PAUSED = 2,\n BUFFERING = 3,\n CUED = 5,\n}\n\n/**\n * YouTube Player Error codes\n * @see https://developers.google.com/youtube/iframe_api_reference#onError\n */\nexport enum PlayerError {\n INVALID_PARAM = 2,\n HTML5_ERROR = 5,\n VIDEO_NOT_FOUND = 100,\n NOT_EMBEDDABLE = 101,\n NOT_EMBEDDABLE_DISGUISED = 150,\n}\n\n/**\n * YouTube Player Event data structure\n * Represents the data received from YouTube's postMessage API\n */\nexport interface YouTubeEvent {\n info?: {\n playerState?: PlayerState;\n currentTime?: number;\n duration?: number;\n videoData?: {\n video_id: string;\n title: string;\n };\n playbackRate?: number;\n playbackQuality?: string;\n };\n}\n\n/**\n * Event handler for player state changes\n */\nexport interface PlayerStateChangeEvent {\n state: PlayerState;\n currentTime?: number;\n duration?: number;\n}\n\n/**\n * Event handler for when player is ready\n */\nexport interface PlayerReadyEvent {\n videoId: string;\n title: string;\n}\n\n/**\n * SEO metadata for YouTube video following schema.org VideoObject structure.\n * See: https://developers.google.com/search/docs/appearance/structured-data/video\n *\n * All fields are optional but providing them improves search engine discoverability\n * and enables rich results (video carousels, thumbnails in search results).\n *\n * Use the provided `scripts/fetch-youtube-metadata.sh` helper to easily retrieve\n * this data from YouTube's API.\n */\nexport interface VideoSEO {\n /**\n * The title of the video. If not provided, falls back to the component's `title` prop.\n * @example \"What's new in Material Design for the web\"\n */\n name?: string;\n\n /**\n * A description of the video content.\n * Recommended: 50-160 characters for optimal search result display.\n * @example \"Learn about the latest Material Design updates presented at Chrome Dev Summit 2019\"\n */\n description?: string;\n\n /**\n * ISO 8601 date when the video was uploaded to YouTube.\n * @example \"2019-11-11T08:00:00Z\" or \"2019-11-11\"\n */\n uploadDate?: string;\n\n /**\n * ISO 8601 duration format. Required for video rich results.\n * Format: PT#H#M#S where # is the number of hours, minutes, seconds\n * @example \"PT1M33S\" (1 minute 33 seconds)\n * @example \"PT15M\" (15 minutes)\n * @example \"PT1H30M\" (1 hour 30 minutes)\n */\n duration?: string;\n\n /**\n * Custom thumbnail URL. If not provided, auto-generated from video ID.\n * Recommended: At least 1200px wide for best quality in search results.\n * @example \"https://i.ytimg.com/vi/VIDEO_ID/maxresdefault.jpg\"\n */\n thumbnailUrl?: string;\n\n /**\n * Direct URL to watch the video. Auto-generated if not provided.\n * @example \"https://www.youtube.com/watch?v=L2vS_050c-M\"\n */\n contentUrl?: string;\n\n /**\n * The embed URL. Auto-generated from video ID if not provided.\n * @example \"https://www.youtube.com/embed/L2vS_050c-M\"\n */\n embedUrl?: string;\n}\n\nexport interface LiteYouTubeProps {\n announce?: string;\n id: string;\n title: string;\n activatedClass?: string;\n adNetwork?: boolean;\n aspectHeight?: number;\n aspectWidth?: number;\n iframeClass?: string;\n /** @deprecated Use cookie prop instead */\n noCookie?: boolean;\n cookie?: boolean;\n enableJsApi?: boolean;\n alwaysLoadIframe?: boolean;\n params?: string;\n playerClass?: string;\n playlist?: boolean;\n playlistCoverId?: string;\n poster?: imgResolution;\n webp?: boolean;\n wrapperClass?: string;\n onIframeAdded?: () => void;\n muted?: boolean;\n autoplay?: boolean;\n thumbnail?: string;\n rel?: string;\n containerElement?: keyof React.JSX.IntrinsicElements;\n style?: React.CSSProperties;\n focusOnLoad?: boolean;\n referrerPolicy?: React.HTMLAttributeReferrerPolicy;\n /**\n * Enable lazy loading for thumbnail image.\n * Uses native browser lazy loading to defer offscreen images.\n * Improves Lighthouse scores and reduces bandwidth for below-fold videos.\n * @default false\n */\n lazyLoad?: boolean;\n /**\n * Stop video and return to thumbnail when playback ends.\n * Prevents YouTube from showing related videos. Requires enableJsApi.\n * @default false\n */\n stopOnEnd?: boolean;\n /**\n * SEO metadata for search engines. Enables rich results and better discoverability.\n * Provides structured data following schema.org VideoObject specification.\n * @see https://developers.google.com/search/docs/appearance/structured-data/video\n */\n seo?: VideoSEO;\n /**\n * Include noscript fallback link for accessibility and search crawlers.\n * When true, adds a direct YouTube link inside tags.\n * @default true\n */\n noscriptFallback?: boolean;\n\n // ==================== Player Event Handlers ====================\n\n /**\n * Fires when the player is ready and API is available.\n * This is the first event to fire and indicates it's safe to call player methods.\n * @param event - Contains video ID and title\n * @example\n * onReady={(event) => console.log('Player ready for:', event.videoId)}\n */\n onReady?: (event: PlayerReadyEvent) => void;\n\n /**\n * Fires when the player's state changes.\n * Use this for comprehensive state tracking (play, pause, end, buffering, etc.)\n * @param event - Contains state, current time, and duration\n * @example\n * onStateChange={(event) => {\n * if (event.state === PlayerState.PLAYING) {\n * analytics.track('video_play');\n * }\n * }}\n */\n onStateChange?: (event: PlayerStateChangeEvent) => void;\n\n /**\n * Fires when the player encounters an error.\n * Use this for graceful error handling and user feedback.\n * @param errorCode - YouTube error code (see PlayerError enum)\n * @example\n * onError={(code) => {\n * if (code === PlayerError.VIDEO_NOT_FOUND) {\n * showErrorMessage('Video not available');\n * }\n * }}\n */\n onError?: (errorCode: PlayerError) => void;\n\n // ==================== Convenience Event Handlers ====================\n\n /**\n * Fires when the video starts playing.\n * Convenience wrapper for onStateChange with PlayerState.PLAYING.\n * @example\n * onPlay={() => analytics.track('video_play')}\n */\n onPlay?: () => void;\n\n /**\n * Fires when the video is paused.\n * Convenience wrapper for onStateChange with PlayerState.PAUSED.\n * @example\n * onPause={() => analytics.track('video_pause')}\n */\n onPause?: () => void;\n\n /**\n * Fires when the video ends.\n * Convenience wrapper for onStateChange with PlayerState.ENDED.\n * Useful for loading next video in a playlist.\n * @example\n * onEnd={() => loadNextVideo()}\n */\n onEnd?: () => void;\n\n /**\n * Fires when the video is buffering.\n * Convenience wrapper for onStateChange with PlayerState.BUFFERING.\n * @example\n * onBuffering={() => showLoadingSpinner()}\n */\n onBuffering?: () => void;\n\n // ==================== Advanced Event Handlers ====================\n\n /**\n * Fires when the playback rate (speed) changes.\n * Common values: 0.25, 0.5, 1, 1.5, 2\n * @param playbackRate - The new playback rate\n * @example\n * onPlaybackRateChange={(rate) => console.log('Speed:', rate + 'x')}\n */\n onPlaybackRateChange?: (playbackRate: number) => void;\n\n /**\n * Fires when the video quality changes.\n * Common values: \"small\" (240p), \"medium\" (360p), \"large\" (480p), \"hd720\", \"hd1080\"\n * @param quality - The new quality level\n * @example\n * onPlaybackQualityChange={(quality) => {\n * analytics.track('quality_change', { quality });\n * }}\n */\n onPlaybackQualityChange?: (quality: string) => void;\n}\n\n/**\n * Generates JSON-LD structured data for VideoObject schema.\n * @see https://schema.org/VideoObject\n * @see https://developers.google.com/search/docs/appearance/structured-data/video\n */\nfunction generateVideoStructuredData(\n videoId: string,\n title: string,\n posterUrl: string,\n ytUrl: string,\n seo?: VideoSEO\n): string {\n const structuredData = {\n \"@context\": \"https://schema.org\",\n \"@type\": \"VideoObject\",\n name: seo?.name || title,\n thumbnailUrl: [seo?.thumbnailUrl || posterUrl],\n embedUrl: seo?.embedUrl || `${ytUrl}/embed/${videoId}`,\n contentUrl: seo?.contentUrl || `https://www.youtube.com/watch?v=${videoId}`,\n ...(seo?.description && { description: seo.description }),\n ...(seo?.uploadDate && { uploadDate: seo.uploadDate }),\n ...(seo?.duration && { duration: seo.duration }),\n };\n\n return JSON.stringify(structuredData);\n}\n\nfunction LiteYouTubeEmbedComponent(\n props: LiteYouTubeProps,\n ref: React.Ref\n) {\n const [preconnected, setPreconnected] = React.useState(false);\n const [iframe, setIframe] = React.useState(props.alwaysLoadIframe || false);\n const videoId = encodeURIComponent(props.id);\n const videoPlaylistCoverId =\n typeof props.playlistCoverId === \"string\"\n ? encodeURIComponent(props.playlistCoverId)\n : null;\n const videoTitle = props.title;\n const posterImp = props.poster || \"hqdefault\";\n const announceWatch = props.announce || \"Watch\";\n\n const shouldAddAutoplayParam = props.alwaysLoadIframe\n ? props.autoplay && props.muted\n : true; // When the iframe is not loaded immediately, the video should play as soon as its loaded (which happens when the button is clicked)\n\n // Iframe Parameters - memoized to avoid recreating URLSearchParams on every render\n const iframeParams = React.useMemo(() => {\n const params = new URLSearchParams({\n ...(props.muted ? { mute: \"1\" } : {}),\n ...(shouldAddAutoplayParam ? { autoplay: \"1\" } : {}),\n ...(props.enableJsApi ? { enablejsapi: \"1\" } : {}),\n ...(props.enableJsApi && typeof window !== 'undefined' ? { origin: window.location.origin } : {}),\n ...(props.playlist ? { list: videoId } : {}),\n });\n\n // parse props.params into individual search parameters and append them to params\n if (props.params) {\n const additionalParams = new URLSearchParams(\n props.params.startsWith(\"&\") ? props.params.slice(1) : props.params\n );\n additionalParams.forEach((value, key) => {\n params.append(key, value);\n });\n }\n\n return params;\n }, [\n props.muted,\n shouldAddAutoplayParam,\n props.enableJsApi,\n props.playlist,\n videoId,\n props.params,\n ]);\n\n const ytUrl = React.useMemo(\n () =>\n props.cookie\n ? \"https://www.youtube.com\"\n : \"https://www.youtube-nocookie.com\",\n [props.cookie]\n );\n\n const iframeSrc = React.useMemo(\n () =>\n !props.playlist\n ? `${ytUrl}/embed/${videoId}?${iframeParams.toString()}`\n : `${ytUrl}/embed/videoseries?${iframeParams.toString()}`,\n [props.playlist, ytUrl, videoId, iframeParams]\n );\n\n const useDynamicThumbnail =\n !props.thumbnail && !props.playlist && posterImp === \"maxresdefault\";\n\n const format = props.webp ? \"webp\" : \"jpg\";\n const vi = props.webp ? \"vi_webp\" : \"vi\";\n\n const dynamicThumbnailUrl = useDynamicThumbnail\n ? useYoutubeThumbnail(props.id, vi, format, posterImp)\n : null;\n\n const posterUrl = React.useMemo(\n () =>\n props.thumbnail ||\n dynamicThumbnailUrl ||\n `https://i.ytimg.com/${vi}/${\n props.playlist ? videoPlaylistCoverId : videoId\n }/${posterImp}.${format}`,\n [\n props.thumbnail,\n dynamicThumbnailUrl,\n vi,\n props.playlist,\n videoPlaylistCoverId,\n videoId,\n posterImp,\n format,\n ]\n );\n\n const activatedClassImp = props.activatedClass || \"lyt-activated\";\n const adNetworkImp = props.adNetwork || false;\n const aspectHeight = props.aspectHeight || 9;\n const aspectWidth = props.aspectWidth || 16;\n const iframeClassImp = props.iframeClass || \"\";\n const playerClassImp = props.playerClass || \"lty-playbtn\";\n const wrapperClassImp = props.wrapperClass || \"yt-lite\";\n const onIframeAdded = React.useCallback(\n props.onIframeAdded || function () {},\n [props.onIframeAdded]\n );\n const rel = props.rel ? \"prefetch\" : \"preload\";\n const ContainerElement = props.containerElement || \"article\";\n const includeNoscriptFallback = props.noscriptFallback !== false; // Default to true\n\n const warmConnections = () => {\n if (preconnected) return;\n setPreconnected(true);\n };\n\n const addIframe = () => {\n if (iframe) return;\n setIframe(true);\n };\n\n React.useEffect(() => {\n if (iframe) {\n onIframeAdded();\n\n // Focus iframe if focusOnLoad is enabled and ref is available\n if (props.focusOnLoad && typeof ref === \"object\" && ref?.current) {\n ref.current.focus();\n }\n }\n }, [iframe, onIframeAdded, props.focusOnLoad, ref]);\n\n // Set up postMessage listener for YouTube player events\n React.useEffect(() => {\n // Only set up listener if iframe is loaded and we have event handlers\n if (!iframe || !props.enableJsApi) {\n return;\n }\n\n const hasEventHandlers =\n props.onReady ||\n props.onStateChange ||\n props.onError ||\n props.onPlay ||\n props.onPause ||\n props.onEnd ||\n props.onBuffering ||\n props.onPlaybackRateChange ||\n props.onPlaybackQualityChange;\n\n if (!hasEventHandlers) {\n return;\n }\n\n let isReady = false;\n let iframeLoaded = false;\n\n const handleMessage = (event: MessageEvent) => {\n // Verify origin is from YouTube\n if (\n event.origin !== \"https://www.youtube.com\" &&\n event.origin !== \"https://www.youtube-nocookie.com\"\n ) {\n return;\n }\n\n let data: { event?: string; info?: YouTubeEvent[\"info\"] };\n try {\n data =\n typeof event.data === \"string\" ? JSON.parse(event.data) : event.data;\n } catch {\n return; // Invalid JSON, ignore\n }\n\n // Handle different YouTube events\n switch (data.event) {\n case \"onReady\":\n if (!isReady) {\n isReady = true;\n if (props.onReady) {\n props.onReady({\n videoId: props.id,\n title: videoTitle,\n });\n }\n }\n break;\n\n case \"infoDelivery\":\n // YouTube's postMessage API sends state changes via infoDelivery events\n // This is the primary mechanism for receiving state change notifications\n if (data.info?.playerState !== undefined) {\n const state = data.info.playerState as PlayerState;\n\n // Call main onStateChange handler\n if (props.onStateChange) {\n props.onStateChange({\n state,\n currentTime: data.info.currentTime,\n duration: data.info.duration,\n });\n }\n\n // Call convenience handlers\n switch (state) {\n case PlayerState.PLAYING:\n props.onPlay?.();\n break;\n case PlayerState.PAUSED:\n props.onPause?.();\n break;\n case PlayerState.ENDED:\n props.onEnd?.();\n // Stop video to return to thumbnail and prevent related videos\n if (\n props.stopOnEnd &&\n typeof ref === \"object\" &&\n ref?.current?.contentWindow\n ) {\n ref.current.contentWindow.postMessage(\n '{\"event\":\"command\",\"func\":\"stopVideo\",\"args\":\"\"}',\n \"*\"\n );\n }\n break;\n case PlayerState.BUFFERING:\n props.onBuffering?.();\n break;\n }\n }\n\n // Handle playback rate changes\n if (data.info?.playbackRate !== undefined) {\n props.onPlaybackRateChange?.(data.info.playbackRate);\n }\n\n // Handle playback quality changes\n if (data.info?.playbackQuality !== undefined) {\n props.onPlaybackQualityChange?.(data.info.playbackQuality);\n }\n break;\n\n case \"onStateChange\":\n // Fallback: YouTube may send dedicated onStateChange events in some cases\n // However, infoDelivery (above) is the primary mechanism observed\n if (data.info?.playerState !== undefined) {\n const state = data.info.playerState as PlayerState;\n\n // Call main onStateChange handler\n if (props.onStateChange) {\n props.onStateChange({\n state,\n currentTime: data.info.currentTime,\n duration: data.info.duration,\n });\n }\n\n // Call convenience handlers\n switch (state) {\n case PlayerState.PLAYING:\n props.onPlay?.();\n break;\n case PlayerState.PAUSED:\n props.onPause?.();\n break;\n case PlayerState.ENDED:\n props.onEnd?.();\n // Stop video to return to thumbnail and prevent related videos\n if (\n props.stopOnEnd &&\n typeof ref === \"object\" &&\n ref?.current?.contentWindow\n ) {\n ref.current.contentWindow.postMessage(\n '{\"event\":\"command\",\"func\":\"stopVideo\",\"args\":\"\"}',\n \"*\"\n );\n }\n break;\n case PlayerState.BUFFERING:\n props.onBuffering?.();\n break;\n }\n }\n break;\n\n case \"onError\":\n if (data.info && \"errorCode\" in data.info) {\n const errorCode = (data.info as { errorCode: number }).errorCode;\n if (props.onError) {\n props.onError(errorCode as PlayerError);\n }\n }\n break;\n\n case \"onPlaybackRateChange\":\n if (data.info?.playbackRate !== undefined) {\n props.onPlaybackRateChange?.(data.info.playbackRate);\n }\n break;\n\n case \"onPlaybackQualityChange\":\n if (data.info?.playbackQuality !== undefined) {\n props.onPlaybackQualityChange?.(data.info.playbackQuality);\n }\n break;\n }\n };\n\n window.addEventListener(\"message\", handleMessage);\n\n // Request iframe to send events by posting \"listening\" message\n // This tells YouTube player to start sending events\n const timeouts: ReturnType[] = [];\n const attemptListen = () => {\n if (typeof ref === \"object\" && ref?.current?.contentWindow) {\n ref.current.contentWindow.postMessage(\n '{\"event\":\"listening\",\"id\":\"' + videoId + '\"}',\n \"*\"\n );\n }\n };\n\n // Strategy: Wait for iframe load event, then send listening message\n // This is more reliable than arbitrary delays\n const handleIframeLoad = () => {\n if (iframeLoaded) {\n return;\n }\n iframeLoaded = true;\n\n // Send initial listening message immediately when iframe loads\n attemptListen();\n\n // Also retry with delays as fallback for slower YouTube API initialization\n // YouTube's player API needs additional time to initialize even after iframe loads\n const delays = [100, 300, 600, 1200, 2400];\n delays.forEach((delay) => {\n timeouts.push(setTimeout(attemptListen, delay));\n });\n };\n\n // Attach load event listener to iframe\n if (typeof ref === \"object\" && ref?.current) {\n ref.current.addEventListener(\"load\", handleIframeLoad);\n\n // If iframe is already loaded, trigger immediately\n // This handles race condition where load event fired before listener attached\n if (ref.current.contentDocument?.readyState === \"complete\") {\n handleIframeLoad();\n }\n } else {\n // Fallback: If ref not ready, use longer delays\n const fallbackDelays = [200, 500, 1000, 2000, 3000];\n fallbackDelays.forEach((delay) => {\n timeouts.push(setTimeout(attemptListen, delay));\n });\n }\n\n return () => {\n window.removeEventListener(\"message\", handleMessage);\n timeouts.forEach(clearTimeout);\n\n // Clean up iframe load listener\n if (typeof ref === \"object\" && ref?.current) {\n ref.current.removeEventListener(\"load\", handleIframeLoad);\n }\n };\n }, [\n iframe,\n props.enableJsApi,\n props.onReady,\n props.onStateChange,\n props.onError,\n props.onPlay,\n props.onPause,\n props.onEnd,\n props.onBuffering,\n props.onPlaybackRateChange,\n props.onPlaybackQualityChange,\n props.stopOnEnd,\n props.id,\n videoId,\n videoTitle,\n ref,\n ]);\n\n return (\n <>\n {!props.lazyLoad && }\n <>\n {preconnected && (\n <>\n \n \n {adNetworkImp && (\n <>\n \n \n >\n )}\n >\n )}\n >\n {/* SEO: JSON-LD Structured Data for VideoObject */}\n {props.seo && !props.playlist && (\n \n )}\n {/* SEO: Noscript fallback for accessibility and crawlers */}\n {includeNoscriptFallback && !props.playlist && (\n \n \n Watch "{videoTitle}" on YouTube\n \n \n )}\n \n {props.lazyLoad && !iframe && (\n \n )}\n \n {announceWatch} \n \n {iframe && (\n \n )}\n \n >\n );\n}\n\nexport default React.forwardRef(\n LiteYouTubeEmbedComponent\n);\n"],"names":["expectedWidths","useYoutubeThumbnail","videoId","vi","format","imageRes","url","setUrl","useState","useEffect","testUrl","fallbackUrl","expectedWidth","img","PlayerState","PlayerError","generateVideoStructuredData","title","posterUrl","ytUrl","seo","structuredData","LiteYouTubeEmbedComponent","props","ref","preconnected","setPreconnected","React","iframe","setIframe","videoPlaylistCoverId","videoTitle","posterImp","announceWatch","shouldAddAutoplayParam","iframeParams","params","value","key","iframeSrc","useDynamicThumbnail","dynamicThumbnailUrl","activatedClassImp","adNetworkImp","aspectHeight","aspectWidth","iframeClassImp","playerClassImp","wrapperClassImp","onIframeAdded","rel","ContainerElement","includeNoscriptFallback","warmConnections","addIframe","isReady","iframeLoaded","handleMessage","event","data","state","errorCode","timeouts","attemptListen","handleIframeLoad","delay","jsxs","Fragment","jsx","index"],"mappings":"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"}
\ No newline at end of file
diff --git a/dist/index.js b/dist/index.js
index 0ce9302..b2e4906 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -1,5 +1,5 @@
/**
-* @ibrahimcesar/react-lite-youtube-embed v3.1.0
+* @ibrahimcesar/react-lite-youtube-embed v3.2.0
* git+https://github.com/ibrahimcesar/react-lite-youtube-embed.git
*
* Copyright (c) Ibrahim Cesar and project contributors.
@@ -9,5 +9,5 @@
*
* Author site: https://ibrahimcesar.cloud
*/
-"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const n=require("react/jsx-runtime"),N=require("react");function Q(e){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const c in e)if(c!=="default"){const s=Object.getOwnPropertyDescriptor(e,c);Object.defineProperty(t,c,s.get?s:{enumerable:!0,get:()=>e[c]})}}return t.default=e,Object.freeze(t)}const u=Q(N),z={default:120,mqdefault:320,hqdefault:480,sddefault:640,maxresdefault:1280},K=(e,t,c,s="maxresdefault")=>{const[a,r]=N.useState("");return N.useEffect(()=>{const o=`https://img.youtube.com/${t}/${e}/${s}.${c}`,f=`https://img.youtube.com/${t}/${e}/hqdefault.${c}`,l=z[s],d=new Image;d.onload=()=>{d.widthr(f),d.src=o},[e,t,c,s]),a};var O=(e=>(e[e.UNSTARTED=-1]="UNSTARTED",e[e.ENDED=0]="ENDED",e[e.PLAYING=1]="PLAYING",e[e.PAUSED=2]="PAUSED",e[e.BUFFERING=3]="BUFFERING",e[e.CUED=5]="CUED",e))(O||{}),A=(e=>(e[e.INVALID_PARAM=2]="INVALID_PARAM",e[e.HTML5_ERROR=5]="HTML5_ERROR",e[e.VIDEO_NOT_FOUND=100]="VIDEO_NOT_FOUND",e[e.NOT_EMBEDDABLE=101]="NOT_EMBEDDABLE",e[e.NOT_EMBEDDABLE_DISGUISED=150]="NOT_EMBEDDABLE_DISGUISED",e))(A||{});function X(e,t,c,s,a){const r={"@context":"https://schema.org","@type":"VideoObject",name:a?.name||t,thumbnailUrl:[a?.thumbnailUrl||c],embedUrl:a?.embedUrl||`${s}/embed/${e}`,contentUrl:a?.contentUrl||`https://www.youtube.com/watch?v=${e}`,...a?.description&&{description:a.description},...a?.uploadDate&&{uploadDate:a.uploadDate},...a?.duration&&{duration:a.duration}};return JSON.stringify(r)}function Z(e,t){const[c,s]=u.useState(!1),[a,r]=u.useState(e.alwaysLoadIframe||!1),o=encodeURIComponent(e.id),f=typeof e.playlistCoverId=="string"?encodeURIComponent(e.playlistCoverId):null,l=e.title,d=e.poster||"hqdefault",T=e.announce||"Watch",$=e.alwaysLoadIframe?e.autoplay&&e.muted:!0,E=u.useMemo(()=>{const C=new URLSearchParams({...e.muted?{mute:"1"}:{},...$?{autoplay:"1"}:{},...e.enableJsApi?{enablejsapi:"1"}:{},...e.playlist?{list:o}:{}});return e.params&&new URLSearchParams(e.params.startsWith("&")?e.params.slice(1):e.params).forEach((y,g)=>{C.append(g,y)}),C},[e.muted,$,e.enableJsApi,e.playlist,o,e.params]),h=u.useMemo(()=>e.cookie?"https://www.youtube.com":"https://www.youtube-nocookie.com",[e.cookie]),S=u.useMemo(()=>e.playlist?`${h}/embed/videoseries?${E.toString()}`:`${h}/embed/${o}?${E.toString()}`,[e.playlist,h,o,E]),_=!e.thumbnail&&!e.playlist&&d==="maxresdefault",k=e.webp?"webp":"jpg",D=e.webp?"vi_webp":"vi",L=_?K(e.id,D,k,d):null,b=u.useMemo(()=>e.thumbnail||L||`https://i.ytimg.com/${D}/${e.playlist?f:o}/${d}.${k}`,[e.thumbnail,L,D,e.playlist,f,o,d,k]),M=e.activatedClass||"lyt-activated",P=e.adNetwork||!1,B=e.aspectHeight||9,F=e.aspectWidth||16,W=e.iframeClass||"",Y=e.playerClass||"lty-playbtn",H=e.wrapperClass||"yt-lite",U=u.useCallback(e.onIframeAdded||function(){},[e.onIframeAdded]),V=e.rel?"prefetch":"preload",q=e.containerElement||"article",G=e.noscriptFallback!==!1,J=()=>{c||s(!0)},x=()=>{a||r(!0)};return u.useEffect(()=>{a&&(U(),e.focusOnLoad&&typeof t=="object"&&t?.current&&t.current.focus())},[a,U,e.focusOnLoad,t]),u.useEffect(()=>{if(!a||!e.enableJsApi||!(e.onReady||e.onStateChange||e.onError||e.onPlay||e.onPause||e.onEnd||e.onBuffering||e.onPlaybackRateChange||e.onPlaybackQualityChange))return;let I=!1,y=!1;const g=m=>{if(m.origin!=="https://www.youtube.com"&&m.origin!=="https://www.youtube-nocookie.com")return;let i;try{i=typeof m.data=="string"?JSON.parse(m.data):m.data}catch{return}switch(i.event){case"onReady":I||(I=!0,e.onReady&&e.onReady({videoId:e.id,title:l}));break;case"onStateChange":if(i.info?.playerState!==void 0){const w=i.info.playerState;switch(e.onStateChange&&e.onStateChange({state:w,currentTime:i.info.currentTime,duration:i.info.duration}),w){case 1:e.onPlay?.();break;case 2:e.onPause?.();break;case 0:e.onEnd?.(),e.stopOnEnd&&typeof t=="object"&&t?.current?.contentWindow&&t.current.contentWindow.postMessage('{"event":"command","func":"stopVideo","args":""}',"*");break;case 3:e.onBuffering?.();break}}break;case"onError":if(i.info&&"errorCode"in i.info){const w=i.info.errorCode;e.onError&&e.onError(w)}break;case"onPlaybackRateChange":i.info?.playbackRate!==void 0&&e.onPlaybackRateChange?.(i.info.playbackRate);break;case"onPlaybackQualityChange":i.info?.playbackQuality!==void 0&&e.onPlaybackQualityChange?.(i.info.playbackQuality);break}};window.addEventListener("message",g);const v=[],j=()=>{typeof t=="object"&&t?.current?.contentWindow&&t.current.contentWindow.postMessage('{"event":"listening","id":"'+o+'"}',"*")},R=()=>{if(y)return;y=!0,j(),[100,300,600,1200,2400].forEach(i=>{v.push(setTimeout(j,i))})};return typeof t=="object"&&t?.current?(t.current.addEventListener("load",R),t.current.contentDocument?.readyState==="complete"&&R()):[200,500,1e3,2e3,3e3].forEach(i=>{v.push(setTimeout(j,i))}),()=>{window.removeEventListener("message",g),v.forEach(clearTimeout),typeof t=="object"&&t?.current&&t.current.removeEventListener("load",R)}},[a,e.enableJsApi,e.onReady,e.onStateChange,e.onError,e.onPlay,e.onPause,e.onEnd,e.onBuffering,e.onPlaybackRateChange,e.onPlaybackQualityChange,e.stopOnEnd,e.id,o,l,t]),n.jsxs(n.Fragment,{children:[!e.lazyLoad&&n.jsx("link",{rel:V,href:b,as:"image"}),n.jsx(n.Fragment,{children:c&&n.jsxs(n.Fragment,{children:[n.jsx("link",{rel:"preconnect",href:h}),n.jsx("link",{rel:"preconnect",href:"https://www.google.com"}),P&&n.jsxs(n.Fragment,{children:[n.jsx("link",{rel:"preconnect",href:"https://static.doubleclick.net"}),n.jsx("link",{rel:"preconnect",href:"https://googleads.g.doubleclick.net"})]})]})}),e.seo&&!e.playlist&&n.jsx("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:X(e.id,l,b,h,e.seo)}}),G&&!e.playlist&&n.jsx("noscript",{children:n.jsxs("a",{href:`https://www.youtube.com/watch?v=${e.id}`,"aria-label":`Watch ${l} on YouTube`,children:['Watch "',l,'" on YouTube']})}),n.jsxs(q,{onPointerOver:J,onClick:x,className:`${H} ${a?M:""}`,"data-title":l,role:a?void 0:"img","aria-label":a?void 0:`${l} - YouTube video preview`,style:{...!e.lazyLoad&&{backgroundImage:`url(${b})`},"--aspect-ratio":`${B/F*100}%`,...e.style||{}},children:[e.lazyLoad&&!a&&n.jsx("img",{src:b,alt:`${l} - YouTube thumbnail`,className:"lty-thumbnail",loading:"lazy"}),n.jsx("button",{type:"button",className:Y,"aria-label":`${T} ${l}`,"aria-hidden":a||void 0,tabIndex:a?-1:0,onClick:x,children:n.jsx("span",{className:"lty-visually-hidden",children:T})}),a&&n.jsx("iframe",{ref:t,className:W,title:l,width:"560",height:"315",allow:"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",allowFullScreen:!0,src:S,referrerPolicy:e.referrerPolicy||"strict-origin-when-cross-origin"})]})]})}const p=u.forwardRef(Z);exports.PlayerError=A;exports.PlayerState=O;exports.default=p;
+"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const i=require("react/jsx-runtime"),T=require("react");function G(e){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const c in e)if(c!=="default"){const d=Object.getOwnPropertyDescriptor(e,c);Object.defineProperty(t,c,d.get?d:{enumerable:!0,get:()=>e[c]})}}return t.default=e,Object.freeze(t)}const u=G(T),z={default:120,mqdefault:320,hqdefault:480,sddefault:640,maxresdefault:1280},K=(e,t,c,d="maxresdefault")=>{const[a,r]=T.useState("");return T.useEffect(()=>{const l=`https://img.youtube.com/${t}/${e}/${d}.${c}`,b=`https://img.youtube.com/${t}/${e}/hqdefault.${c}`,o=z[d],s=new Image;s.onload=()=>{s.widthr(b),s.src=l},[e,t,c,d]),a};var S=(e=>(e[e.UNSTARTED=-1]="UNSTARTED",e[e.ENDED=0]="ENDED",e[e.PLAYING=1]="PLAYING",e[e.PAUSED=2]="PAUSED",e[e.BUFFERING=3]="BUFFERING",e[e.CUED=5]="CUED",e))(S||{}),x=(e=>(e[e.INVALID_PARAM=2]="INVALID_PARAM",e[e.HTML5_ERROR=5]="HTML5_ERROR",e[e.VIDEO_NOT_FOUND=100]="VIDEO_NOT_FOUND",e[e.NOT_EMBEDDABLE=101]="NOT_EMBEDDABLE",e[e.NOT_EMBEDDABLE_DISGUISED=150]="NOT_EMBEDDABLE_DISGUISED",e))(x||{});function X(e,t,c,d,a){const r={"@context":"https://schema.org","@type":"VideoObject",name:a?.name||t,thumbnailUrl:[a?.thumbnailUrl||c],embedUrl:a?.embedUrl||`${d}/embed/${e}`,contentUrl:a?.contentUrl||`https://www.youtube.com/watch?v=${e}`,...a?.description&&{description:a.description},...a?.uploadDate&&{uploadDate:a.uploadDate},...a?.duration&&{duration:a.duration}};return JSON.stringify(r)}function Z(e,t){const[c,d]=u.useState(!1),[a,r]=u.useState(e.alwaysLoadIframe||!1),l=encodeURIComponent(e.id),b=typeof e.playlistCoverId=="string"?encodeURIComponent(e.playlistCoverId):null,o=e.title,s=e.poster||"hqdefault",N=e.announce||"Watch",$=e.alwaysLoadIframe?e.autoplay&&e.muted:!0,k=u.useMemo(()=>{const D=new URLSearchParams({...e.muted?{mute:"1"}:{},...$?{autoplay:"1"}:{},...e.enableJsApi?{enablejsapi:"1"}:{},...e.enableJsApi&&typeof window<"u"?{origin:window.location.origin}:{},...e.playlist?{list:l}:{}});return e.params&&new URLSearchParams(e.params.startsWith("&")?e.params.slice(1):e.params).forEach((g,w)=>{D.append(w,g)}),D},[e.muted,$,e.enableJsApi,e.playlist,l,e.params]),h=u.useMemo(()=>e.cookie?"https://www.youtube.com":"https://www.youtube-nocookie.com",[e.cookie]),A=u.useMemo(()=>e.playlist?`${h}/embed/videoseries?${k.toString()}`:`${h}/embed/${l}?${k.toString()}`,[e.playlist,h,l,k]),_=!e.thumbnail&&!e.playlist&&s==="maxresdefault",E=e.webp?"webp":"jpg",C=e.webp?"vi_webp":"vi",L=_?K(e.id,C,E,s):null,y=u.useMemo(()=>e.thumbnail||L||`https://i.ytimg.com/${C}/${e.playlist?b:l}/${s}.${E}`,[e.thumbnail,L,C,e.playlist,b,l,s,E]),M=e.activatedClass||"lyt-activated",P=e.adNetwork||!1,W=e.aspectHeight||9,B=e.aspectWidth||16,F=e.iframeClass||"",Q=e.playerClass||"lty-playbtn",V=e.wrapperClass||"yt-lite",U=u.useCallback(e.onIframeAdded||function(){},[e.onIframeAdded]),Y=e.rel?"prefetch":"preload",H=e.containerElement||"article",J=e.noscriptFallback!==!1,q=()=>{c||d(!0)},O=()=>{a||r(!0)};return u.useEffect(()=>{a&&(U(),e.focusOnLoad&&typeof t=="object"&&t?.current&&t.current.focus())},[a,U,e.focusOnLoad,t]),u.useEffect(()=>{if(!a||!e.enableJsApi||!(e.onReady||e.onStateChange||e.onError||e.onPlay||e.onPause||e.onEnd||e.onBuffering||e.onPlaybackRateChange||e.onPlaybackQualityChange))return;let v=!1,g=!1;const w=m=>{if(m.origin!=="https://www.youtube.com"&&m.origin!=="https://www.youtube-nocookie.com")return;let n;try{n=typeof m.data=="string"?JSON.parse(m.data):m.data}catch{return}switch(n.event){case"onReady":v||(v=!0,e.onReady&&e.onReady({videoId:e.id,title:o}));break;case"infoDelivery":if(n.info?.playerState!==void 0){const f=n.info.playerState;switch(e.onStateChange&&e.onStateChange({state:f,currentTime:n.info.currentTime,duration:n.info.duration}),f){case 1:e.onPlay?.();break;case 2:e.onPause?.();break;case 0:e.onEnd?.(),e.stopOnEnd&&typeof t=="object"&&t?.current?.contentWindow&&t.current.contentWindow.postMessage('{"event":"command","func":"stopVideo","args":""}',"*");break;case 3:e.onBuffering?.();break}}n.info?.playbackRate!==void 0&&e.onPlaybackRateChange?.(n.info.playbackRate),n.info?.playbackQuality!==void 0&&e.onPlaybackQualityChange?.(n.info.playbackQuality);break;case"onStateChange":if(n.info?.playerState!==void 0){const f=n.info.playerState;switch(e.onStateChange&&e.onStateChange({state:f,currentTime:n.info.currentTime,duration:n.info.duration}),f){case 1:e.onPlay?.();break;case 2:e.onPause?.();break;case 0:e.onEnd?.(),e.stopOnEnd&&typeof t=="object"&&t?.current?.contentWindow&&t.current.contentWindow.postMessage('{"event":"command","func":"stopVideo","args":""}',"*");break;case 3:e.onBuffering?.();break}}break;case"onError":if(n.info&&"errorCode"in n.info){const f=n.info.errorCode;e.onError&&e.onError(f)}break;case"onPlaybackRateChange":n.info?.playbackRate!==void 0&&e.onPlaybackRateChange?.(n.info.playbackRate);break;case"onPlaybackQualityChange":n.info?.playbackQuality!==void 0&&e.onPlaybackQualityChange?.(n.info.playbackQuality);break}};window.addEventListener("message",w);const I=[],j=()=>{typeof t=="object"&&t?.current?.contentWindow&&t.current.contentWindow.postMessage('{"event":"listening","id":"'+l+'"}',"*")},R=()=>{if(g)return;g=!0,j(),[100,300,600,1200,2400].forEach(n=>{I.push(setTimeout(j,n))})};return typeof t=="object"&&t?.current?(t.current.addEventListener("load",R),t.current.contentDocument?.readyState==="complete"&&R()):[200,500,1e3,2e3,3e3].forEach(n=>{I.push(setTimeout(j,n))}),()=>{window.removeEventListener("message",w),I.forEach(clearTimeout),typeof t=="object"&&t?.current&&t.current.removeEventListener("load",R)}},[a,e.enableJsApi,e.onReady,e.onStateChange,e.onError,e.onPlay,e.onPause,e.onEnd,e.onBuffering,e.onPlaybackRateChange,e.onPlaybackQualityChange,e.stopOnEnd,e.id,l,o,t]),i.jsxs(i.Fragment,{children:[!e.lazyLoad&&i.jsx("link",{rel:Y,href:y,as:"image"}),i.jsx(i.Fragment,{children:c&&i.jsxs(i.Fragment,{children:[i.jsx("link",{rel:"preconnect",href:h}),i.jsx("link",{rel:"preconnect",href:"https://www.google.com"}),P&&i.jsxs(i.Fragment,{children:[i.jsx("link",{rel:"preconnect",href:"https://static.doubleclick.net"}),i.jsx("link",{rel:"preconnect",href:"https://googleads.g.doubleclick.net"})]})]})}),e.seo&&!e.playlist&&i.jsx("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:X(e.id,o,y,h,e.seo)}}),J&&!e.playlist&&i.jsx("noscript",{children:i.jsxs("a",{href:`https://www.youtube.com/watch?v=${e.id}`,"aria-label":`Watch ${o} on YouTube`,children:['Watch "',o,'" on YouTube']})}),i.jsxs(H,{onPointerOver:q,onClick:O,className:`${V} ${a?M:""}`,"data-title":o,role:a?void 0:"img","aria-label":a?void 0:`${o} - YouTube video preview`,style:{...!e.lazyLoad&&{backgroundImage:`url(${y})`},"--aspect-ratio":`${W/B*100}%`,...e.style||{}},children:[e.lazyLoad&&!a&&i.jsx("img",{src:y,alt:`${o} - YouTube thumbnail`,className:"lty-thumbnail",loading:"lazy"}),i.jsx("button",{type:"button",className:Q,"aria-label":`${N} ${o}`,"aria-hidden":a||void 0,tabIndex:a?-1:0,onClick:O,children:i.jsx("span",{className:"lty-visually-hidden",children:N})}),a&&i.jsx("iframe",{ref:t,className:F,title:o,width:"560",height:"315",allow:"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture",allowFullScreen:!0,src:A,referrerPolicy:e.referrerPolicy||"strict-origin-when-cross-origin"})]})]})}const p=u.forwardRef(Z);exports.PlayerError=x;exports.PlayerState=S;exports.default=p;
//# sourceMappingURL=index.js.map
diff --git a/dist/index.js.map b/dist/index.js.map
index e1b585a..0dda008 100644
--- a/dist/index.js.map
+++ b/dist/index.js.map
@@ -1 +1 @@
-{"version":3,"file":"index.js","sources":["../src/lib/useYoutubeThumbnail.tsx","../src/lib/index.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\n\nexport type imgResolution =\n | \"default\"\n | \"mqdefault\"\n | \"hqdefault\"\n | \"sddefault\"\n | \"maxresdefault\";\n\nconst expectedWidths: Record = {\n default: 120,\n mqdefault: 320,\n hqdefault: 480,\n sddefault: 640,\n maxresdefault: 1280,\n};\n\nexport const useYoutubeThumbnail = (\n videoId: string,\n vi: string,\n format: string,\n imageRes: imgResolution = \"maxresdefault\"\n) => {\n const [url, setUrl] = useState(\"\");\n\n useEffect(() => {\n const testUrl = `https://img.youtube.com/${vi}/${videoId}/${imageRes}.${format}`;\n const fallbackUrl = `https://img.youtube.com/${vi}/${videoId}/hqdefault.${format}`;\n\n const expectedWidth = expectedWidths[imageRes];\n\n const img = new Image();\n img.onload = () => {\n if (img.width < expectedWidth) {\n setUrl(fallbackUrl);\n } else {\n setUrl(testUrl);\n }\n };\n img.onerror = () => setUrl(fallbackUrl);\n img.src = testUrl;\n }, [videoId, vi, format, imageRes]);\n\n return url;\n};\n\nexport default useYoutubeThumbnail;\n","import * as React from \"react\";\nimport useYoutubeThumbnail from \"./useYoutubeThumbnail\";\nimport { imgResolution } from \"./useYoutubeThumbnail\";\n\n// Re-export types for public API\nexport type { imgResolution };\n\n/**\n * YouTube Player State constants\n * @see https://developers.google.com/youtube/iframe_api_reference#onStateChange\n */\nexport enum PlayerState {\n UNSTARTED = -1,\n ENDED = 0,\n PLAYING = 1,\n PAUSED = 2,\n BUFFERING = 3,\n CUED = 5,\n}\n\n/**\n * YouTube Player Error codes\n * @see https://developers.google.com/youtube/iframe_api_reference#onError\n */\nexport enum PlayerError {\n INVALID_PARAM = 2,\n HTML5_ERROR = 5,\n VIDEO_NOT_FOUND = 100,\n NOT_EMBEDDABLE = 101,\n NOT_EMBEDDABLE_DISGUISED = 150,\n}\n\n/**\n * YouTube Player Event data structure\n * Represents the data received from YouTube's postMessage API\n */\nexport interface YouTubeEvent {\n info?: {\n playerState?: PlayerState;\n currentTime?: number;\n duration?: number;\n videoData?: {\n video_id: string;\n title: string;\n };\n playbackRate?: number;\n playbackQuality?: string;\n };\n}\n\n/**\n * Event handler for player state changes\n */\nexport interface PlayerStateChangeEvent {\n state: PlayerState;\n currentTime?: number;\n duration?: number;\n}\n\n/**\n * Event handler for when player is ready\n */\nexport interface PlayerReadyEvent {\n videoId: string;\n title: string;\n}\n\n/**\n * SEO metadata for YouTube video following schema.org VideoObject structure.\n * See: https://developers.google.com/search/docs/appearance/structured-data/video\n *\n * All fields are optional but providing them improves search engine discoverability\n * and enables rich results (video carousels, thumbnails in search results).\n *\n * Use the provided `scripts/fetch-youtube-metadata.sh` helper to easily retrieve\n * this data from YouTube's API.\n */\nexport interface VideoSEO {\n /**\n * The title of the video. If not provided, falls back to the component's `title` prop.\n * @example \"What's new in Material Design for the web\"\n */\n name?: string;\n\n /**\n * A description of the video content.\n * Recommended: 50-160 characters for optimal search result display.\n * @example \"Learn about the latest Material Design updates presented at Chrome Dev Summit 2019\"\n */\n description?: string;\n\n /**\n * ISO 8601 date when the video was uploaded to YouTube.\n * @example \"2019-11-11T08:00:00Z\" or \"2019-11-11\"\n */\n uploadDate?: string;\n\n /**\n * ISO 8601 duration format. Required for video rich results.\n * Format: PT#H#M#S where # is the number of hours, minutes, seconds\n * @example \"PT1M33S\" (1 minute 33 seconds)\n * @example \"PT15M\" (15 minutes)\n * @example \"PT1H30M\" (1 hour 30 minutes)\n */\n duration?: string;\n\n /**\n * Custom thumbnail URL. If not provided, auto-generated from video ID.\n * Recommended: At least 1200px wide for best quality in search results.\n * @example \"https://i.ytimg.com/vi/VIDEO_ID/maxresdefault.jpg\"\n */\n thumbnailUrl?: string;\n\n /**\n * Direct URL to watch the video. Auto-generated if not provided.\n * @example \"https://www.youtube.com/watch?v=L2vS_050c-M\"\n */\n contentUrl?: string;\n\n /**\n * The embed URL. Auto-generated from video ID if not provided.\n * @example \"https://www.youtube.com/embed/L2vS_050c-M\"\n */\n embedUrl?: string;\n}\n\nexport interface LiteYouTubeProps {\n announce?: string;\n id: string;\n title: string;\n activatedClass?: string;\n adNetwork?: boolean;\n aspectHeight?: number;\n aspectWidth?: number;\n iframeClass?: string;\n /** @deprecated Use cookie prop instead */\n noCookie?: boolean;\n cookie?: boolean;\n enableJsApi?: boolean;\n alwaysLoadIframe?: boolean;\n params?: string;\n playerClass?: string;\n playlist?: boolean;\n playlistCoverId?: string;\n poster?: imgResolution;\n webp?: boolean;\n wrapperClass?: string;\n onIframeAdded?: () => void;\n muted?: boolean;\n autoplay?: boolean;\n thumbnail?: string;\n rel?: string;\n containerElement?: keyof React.JSX.IntrinsicElements;\n style?: React.CSSProperties;\n focusOnLoad?: boolean;\n referrerPolicy?: React.HTMLAttributeReferrerPolicy;\n /**\n * Enable lazy loading for thumbnail image.\n * Uses native browser lazy loading to defer offscreen images.\n * Improves Lighthouse scores and reduces bandwidth for below-fold videos.\n * @default false\n */\n lazyLoad?: boolean;\n /**\n * Stop video and return to thumbnail when playback ends.\n * Prevents YouTube from showing related videos. Requires enableJsApi.\n * @default false\n */\n stopOnEnd?: boolean;\n /**\n * SEO metadata for search engines. Enables rich results and better discoverability.\n * Provides structured data following schema.org VideoObject specification.\n * @see https://developers.google.com/search/docs/appearance/structured-data/video\n */\n seo?: VideoSEO;\n /**\n * Include noscript fallback link for accessibility and search crawlers.\n * When true, adds a direct YouTube link inside tags.\n * @default true\n */\n noscriptFallback?: boolean;\n\n // ==================== Player Event Handlers ====================\n\n /**\n * Fires when the player is ready and API is available.\n * This is the first event to fire and indicates it's safe to call player methods.\n * @param event - Contains video ID and title\n * @example\n * onReady={(event) => console.log('Player ready for:', event.videoId)}\n */\n onReady?: (event: PlayerReadyEvent) => void;\n\n /**\n * Fires when the player's state changes.\n * Use this for comprehensive state tracking (play, pause, end, buffering, etc.)\n * @param event - Contains state, current time, and duration\n * @example\n * onStateChange={(event) => {\n * if (event.state === PlayerState.PLAYING) {\n * analytics.track('video_play');\n * }\n * }}\n */\n onStateChange?: (event: PlayerStateChangeEvent) => void;\n\n /**\n * Fires when the player encounters an error.\n * Use this for graceful error handling and user feedback.\n * @param errorCode - YouTube error code (see PlayerError enum)\n * @example\n * onError={(code) => {\n * if (code === PlayerError.VIDEO_NOT_FOUND) {\n * showErrorMessage('Video not available');\n * }\n * }}\n */\n onError?: (errorCode: PlayerError) => void;\n\n // ==================== Convenience Event Handlers ====================\n\n /**\n * Fires when the video starts playing.\n * Convenience wrapper for onStateChange with PlayerState.PLAYING.\n * @example\n * onPlay={() => analytics.track('video_play')}\n */\n onPlay?: () => void;\n\n /**\n * Fires when the video is paused.\n * Convenience wrapper for onStateChange with PlayerState.PAUSED.\n * @example\n * onPause={() => analytics.track('video_pause')}\n */\n onPause?: () => void;\n\n /**\n * Fires when the video ends.\n * Convenience wrapper for onStateChange with PlayerState.ENDED.\n * Useful for loading next video in a playlist.\n * @example\n * onEnd={() => loadNextVideo()}\n */\n onEnd?: () => void;\n\n /**\n * Fires when the video is buffering.\n * Convenience wrapper for onStateChange with PlayerState.BUFFERING.\n * @example\n * onBuffering={() => showLoadingSpinner()}\n */\n onBuffering?: () => void;\n\n // ==================== Advanced Event Handlers ====================\n\n /**\n * Fires when the playback rate (speed) changes.\n * Common values: 0.25, 0.5, 1, 1.5, 2\n * @param playbackRate - The new playback rate\n * @example\n * onPlaybackRateChange={(rate) => console.log('Speed:', rate + 'x')}\n */\n onPlaybackRateChange?: (playbackRate: number) => void;\n\n /**\n * Fires when the video quality changes.\n * Common values: \"small\" (240p), \"medium\" (360p), \"large\" (480p), \"hd720\", \"hd1080\"\n * @param quality - The new quality level\n * @example\n * onPlaybackQualityChange={(quality) => {\n * analytics.track('quality_change', { quality });\n * }}\n */\n onPlaybackQualityChange?: (quality: string) => void;\n}\n\n/**\n * Generates JSON-LD structured data for VideoObject schema.\n * @see https://schema.org/VideoObject\n * @see https://developers.google.com/search/docs/appearance/structured-data/video\n */\nfunction generateVideoStructuredData(\n videoId: string,\n title: string,\n posterUrl: string,\n ytUrl: string,\n seo?: VideoSEO\n): string {\n const structuredData = {\n \"@context\": \"https://schema.org\",\n \"@type\": \"VideoObject\",\n name: seo?.name || title,\n thumbnailUrl: [seo?.thumbnailUrl || posterUrl],\n embedUrl: seo?.embedUrl || `${ytUrl}/embed/${videoId}`,\n contentUrl: seo?.contentUrl || `https://www.youtube.com/watch?v=${videoId}`,\n ...(seo?.description && { description: seo.description }),\n ...(seo?.uploadDate && { uploadDate: seo.uploadDate }),\n ...(seo?.duration && { duration: seo.duration }),\n };\n\n return JSON.stringify(structuredData);\n}\n\nfunction LiteYouTubeEmbedComponent(\n props: LiteYouTubeProps,\n ref: React.Ref\n) {\n const [preconnected, setPreconnected] = React.useState(false);\n const [iframe, setIframe] = React.useState(props.alwaysLoadIframe || false);\n const videoId = encodeURIComponent(props.id);\n const videoPlaylistCoverId =\n typeof props.playlistCoverId === \"string\"\n ? encodeURIComponent(props.playlistCoverId)\n : null;\n const videoTitle = props.title;\n const posterImp = props.poster || \"hqdefault\";\n const announceWatch = props.announce || \"Watch\";\n\n const shouldAddAutoplayParam = props.alwaysLoadIframe\n ? props.autoplay && props.muted\n : true; // When the iframe is not loaded immediately, the video should play as soon as its loaded (which happens when the button is clicked)\n\n // Iframe Parameters - memoized to avoid recreating URLSearchParams on every render\n const iframeParams = React.useMemo(() => {\n const params = new URLSearchParams({\n ...(props.muted ? { mute: \"1\" } : {}),\n ...(shouldAddAutoplayParam ? { autoplay: \"1\" } : {}),\n ...(props.enableJsApi ? { enablejsapi: \"1\" } : {}),\n ...(props.playlist ? { list: videoId } : {}),\n });\n\n // parse props.params into individual search parameters and append them to params\n if (props.params) {\n const additionalParams = new URLSearchParams(\n props.params.startsWith(\"&\") ? props.params.slice(1) : props.params\n );\n additionalParams.forEach((value, key) => {\n params.append(key, value);\n });\n }\n\n return params;\n }, [\n props.muted,\n shouldAddAutoplayParam,\n props.enableJsApi,\n props.playlist,\n videoId,\n props.params,\n ]);\n\n const ytUrl = React.useMemo(\n () =>\n props.cookie\n ? \"https://www.youtube.com\"\n : \"https://www.youtube-nocookie.com\",\n [props.cookie]\n );\n\n const iframeSrc = React.useMemo(\n () =>\n !props.playlist\n ? `${ytUrl}/embed/${videoId}?${iframeParams.toString()}`\n : `${ytUrl}/embed/videoseries?${iframeParams.toString()}`,\n [props.playlist, ytUrl, videoId, iframeParams]\n );\n\n const useDynamicThumbnail =\n !props.thumbnail && !props.playlist && posterImp === \"maxresdefault\";\n\n const format = props.webp ? \"webp\" : \"jpg\";\n const vi = props.webp ? \"vi_webp\" : \"vi\";\n\n const dynamicThumbnailUrl = useDynamicThumbnail\n ? useYoutubeThumbnail(props.id, vi, format, posterImp)\n : null;\n\n const posterUrl = React.useMemo(\n () =>\n props.thumbnail ||\n dynamicThumbnailUrl ||\n `https://i.ytimg.com/${vi}/${\n props.playlist ? videoPlaylistCoverId : videoId\n }/${posterImp}.${format}`,\n [\n props.thumbnail,\n dynamicThumbnailUrl,\n vi,\n props.playlist,\n videoPlaylistCoverId,\n videoId,\n posterImp,\n format,\n ]\n );\n\n const activatedClassImp = props.activatedClass || \"lyt-activated\";\n const adNetworkImp = props.adNetwork || false;\n const aspectHeight = props.aspectHeight || 9;\n const aspectWidth = props.aspectWidth || 16;\n const iframeClassImp = props.iframeClass || \"\";\n const playerClassImp = props.playerClass || \"lty-playbtn\";\n const wrapperClassImp = props.wrapperClass || \"yt-lite\";\n const onIframeAdded = React.useCallback(\n props.onIframeAdded || function () {},\n [props.onIframeAdded]\n );\n const rel = props.rel ? \"prefetch\" : \"preload\";\n const ContainerElement = props.containerElement || \"article\";\n const includeNoscriptFallback = props.noscriptFallback !== false; // Default to true\n\n const warmConnections = () => {\n if (preconnected) return;\n setPreconnected(true);\n };\n\n const addIframe = () => {\n if (iframe) return;\n setIframe(true);\n };\n\n React.useEffect(() => {\n if (iframe) {\n onIframeAdded();\n\n // Focus iframe if focusOnLoad is enabled and ref is available\n if (props.focusOnLoad && typeof ref === \"object\" && ref?.current) {\n ref.current.focus();\n }\n }\n }, [iframe, onIframeAdded, props.focusOnLoad, ref]);\n\n // Set up postMessage listener for YouTube player events\n React.useEffect(() => {\n // Only set up listener if iframe is loaded and we have event handlers\n if (!iframe || !props.enableJsApi) {\n return;\n }\n\n const hasEventHandlers =\n props.onReady ||\n props.onStateChange ||\n props.onError ||\n props.onPlay ||\n props.onPause ||\n props.onEnd ||\n props.onBuffering ||\n props.onPlaybackRateChange ||\n props.onPlaybackQualityChange;\n\n if (!hasEventHandlers) {\n return;\n }\n\n let isReady = false;\n let iframeLoaded = false;\n\n const handleMessage = (event: MessageEvent) => {\n // Verify origin is from YouTube\n if (\n event.origin !== \"https://www.youtube.com\" &&\n event.origin !== \"https://www.youtube-nocookie.com\"\n ) {\n return;\n }\n\n let data: { event?: string; info?: YouTubeEvent[\"info\"] };\n try {\n data =\n typeof event.data === \"string\" ? JSON.parse(event.data) : event.data;\n } catch {\n return; // Invalid JSON, ignore\n }\n\n // Handle different YouTube events\n switch (data.event) {\n case \"onReady\":\n if (!isReady) {\n isReady = true;\n if (props.onReady) {\n props.onReady({\n videoId: props.id,\n title: videoTitle,\n });\n }\n }\n break;\n\n case \"onStateChange\":\n if (data.info?.playerState !== undefined) {\n const state = data.info.playerState as PlayerState;\n\n // Call main onStateChange handler\n if (props.onStateChange) {\n props.onStateChange({\n state,\n currentTime: data.info.currentTime,\n duration: data.info.duration,\n });\n }\n\n // Call convenience handlers\n switch (state) {\n case PlayerState.PLAYING:\n props.onPlay?.();\n break;\n case PlayerState.PAUSED:\n props.onPause?.();\n break;\n case PlayerState.ENDED:\n props.onEnd?.();\n // Stop video to return to thumbnail and prevent related videos\n if (\n props.stopOnEnd &&\n typeof ref === \"object\" &&\n ref?.current?.contentWindow\n ) {\n ref.current.contentWindow.postMessage(\n '{\"event\":\"command\",\"func\":\"stopVideo\",\"args\":\"\"}',\n \"*\"\n );\n }\n break;\n case PlayerState.BUFFERING:\n props.onBuffering?.();\n break;\n }\n }\n break;\n\n case \"onError\":\n if (data.info && \"errorCode\" in data.info) {\n const errorCode = (data.info as { errorCode: number }).errorCode;\n if (props.onError) {\n props.onError(errorCode as PlayerError);\n }\n }\n break;\n\n case \"onPlaybackRateChange\":\n if (data.info?.playbackRate !== undefined) {\n props.onPlaybackRateChange?.(data.info.playbackRate);\n }\n break;\n\n case \"onPlaybackQualityChange\":\n if (data.info?.playbackQuality !== undefined) {\n props.onPlaybackQualityChange?.(data.info.playbackQuality);\n }\n break;\n }\n };\n\n window.addEventListener(\"message\", handleMessage);\n\n // Request iframe to send events by posting \"listening\" message\n // This tells YouTube player to start sending events\n const timeouts: ReturnType[] = [];\n const attemptListen = () => {\n if (typeof ref === \"object\" && ref?.current?.contentWindow) {\n ref.current.contentWindow.postMessage(\n '{\"event\":\"listening\",\"id\":\"' + videoId + '\"}',\n \"*\"\n );\n }\n };\n\n // Strategy: Wait for iframe load event, then send listening message\n // This is more reliable than arbitrary delays\n const handleIframeLoad = () => {\n if (iframeLoaded) return;\n iframeLoaded = true;\n\n // Send initial listening message immediately when iframe loads\n attemptListen();\n\n // Also retry with delays as fallback for slower YouTube API initialization\n // YouTube's player API needs additional time to initialize even after iframe loads\n const delays = [100, 300, 600, 1200, 2400];\n delays.forEach((delay) => {\n timeouts.push(setTimeout(attemptListen, delay));\n });\n };\n\n // Attach load event listener to iframe\n if (typeof ref === \"object\" && ref?.current) {\n ref.current.addEventListener(\"load\", handleIframeLoad);\n\n // If iframe is already loaded, trigger immediately\n // This handles race condition where load event fired before listener attached\n if (ref.current.contentDocument?.readyState === \"complete\") {\n handleIframeLoad();\n }\n } else {\n // Fallback: If ref not ready, use longer delays\n const fallbackDelays = [200, 500, 1000, 2000, 3000];\n fallbackDelays.forEach((delay) => {\n timeouts.push(setTimeout(attemptListen, delay));\n });\n }\n\n return () => {\n window.removeEventListener(\"message\", handleMessage);\n timeouts.forEach(clearTimeout);\n\n // Clean up iframe load listener\n if (typeof ref === \"object\" && ref?.current) {\n ref.current.removeEventListener(\"load\", handleIframeLoad);\n }\n };\n }, [\n iframe,\n props.enableJsApi,\n props.onReady,\n props.onStateChange,\n props.onError,\n props.onPlay,\n props.onPause,\n props.onEnd,\n props.onBuffering,\n props.onPlaybackRateChange,\n props.onPlaybackQualityChange,\n props.stopOnEnd,\n props.id,\n videoId,\n videoTitle,\n ref,\n ]);\n\n return (\n <>\n {!props.lazyLoad && }\n <>\n {preconnected && (\n <>\n \n \n {adNetworkImp && (\n <>\n \n \n >\n )}\n >\n )}\n >\n {/* SEO: JSON-LD Structured Data for VideoObject */}\n {props.seo && !props.playlist && (\n \n )}\n {/* SEO: Noscript fallback for accessibility and crawlers */}\n {includeNoscriptFallback && !props.playlist && (\n \n \n Watch "{videoTitle}" on YouTube\n \n \n )}\n \n {props.lazyLoad && !iframe && (\n \n )}\n \n {announceWatch} \n \n {iframe && (\n \n )}\n \n >\n );\n}\n\nexport default React.forwardRef(\n LiteYouTubeEmbedComponent\n);\n"],"names":["expectedWidths","useYoutubeThumbnail","videoId","vi","format","imageRes","url","setUrl","useState","useEffect","testUrl","fallbackUrl","expectedWidth","img","PlayerState","PlayerError","generateVideoStructuredData","title","posterUrl","ytUrl","seo","structuredData","LiteYouTubeEmbedComponent","props","ref","preconnected","setPreconnected","React","iframe","setIframe","videoPlaylistCoverId","videoTitle","posterImp","announceWatch","shouldAddAutoplayParam","iframeParams","params","value","key","iframeSrc","useDynamicThumbnail","dynamicThumbnailUrl","activatedClassImp","adNetworkImp","aspectHeight","aspectWidth","iframeClassImp","playerClassImp","wrapperClassImp","onIframeAdded","rel","ContainerElement","includeNoscriptFallback","warmConnections","addIframe","isReady","iframeLoaded","handleMessage","event","data","state","errorCode","timeouts","attemptListen","handleIframeLoad","delay","jsxs","Fragment","jsx","index"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"index.js","sources":["../src/lib/useYoutubeThumbnail.tsx","../src/lib/index.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\n\nexport type imgResolution =\n | \"default\"\n | \"mqdefault\"\n | \"hqdefault\"\n | \"sddefault\"\n | \"maxresdefault\";\n\nconst expectedWidths: Record = {\n default: 120,\n mqdefault: 320,\n hqdefault: 480,\n sddefault: 640,\n maxresdefault: 1280,\n};\n\nexport const useYoutubeThumbnail = (\n videoId: string,\n vi: string,\n format: string,\n imageRes: imgResolution = \"maxresdefault\"\n) => {\n const [url, setUrl] = useState(\"\");\n\n useEffect(() => {\n const testUrl = `https://img.youtube.com/${vi}/${videoId}/${imageRes}.${format}`;\n const fallbackUrl = `https://img.youtube.com/${vi}/${videoId}/hqdefault.${format}`;\n\n const expectedWidth = expectedWidths[imageRes];\n\n const img = new Image();\n img.onload = () => {\n if (img.width < expectedWidth) {\n setUrl(fallbackUrl);\n } else {\n setUrl(testUrl);\n }\n };\n img.onerror = () => setUrl(fallbackUrl);\n img.src = testUrl;\n }, [videoId, vi, format, imageRes]);\n\n return url;\n};\n\nexport default useYoutubeThumbnail;\n","import * as React from \"react\";\nimport useYoutubeThumbnail from \"./useYoutubeThumbnail\";\nimport { imgResolution } from \"./useYoutubeThumbnail\";\n\n// Re-export types for public API\nexport type { imgResolution };\n\n/**\n * YouTube Player State constants\n * @see https://developers.google.com/youtube/iframe_api_reference#onStateChange\n */\nexport enum PlayerState {\n UNSTARTED = -1,\n ENDED = 0,\n PLAYING = 1,\n PAUSED = 2,\n BUFFERING = 3,\n CUED = 5,\n}\n\n/**\n * YouTube Player Error codes\n * @see https://developers.google.com/youtube/iframe_api_reference#onError\n */\nexport enum PlayerError {\n INVALID_PARAM = 2,\n HTML5_ERROR = 5,\n VIDEO_NOT_FOUND = 100,\n NOT_EMBEDDABLE = 101,\n NOT_EMBEDDABLE_DISGUISED = 150,\n}\n\n/**\n * YouTube Player Event data structure\n * Represents the data received from YouTube's postMessage API\n */\nexport interface YouTubeEvent {\n info?: {\n playerState?: PlayerState;\n currentTime?: number;\n duration?: number;\n videoData?: {\n video_id: string;\n title: string;\n };\n playbackRate?: number;\n playbackQuality?: string;\n };\n}\n\n/**\n * Event handler for player state changes\n */\nexport interface PlayerStateChangeEvent {\n state: PlayerState;\n currentTime?: number;\n duration?: number;\n}\n\n/**\n * Event handler for when player is ready\n */\nexport interface PlayerReadyEvent {\n videoId: string;\n title: string;\n}\n\n/**\n * SEO metadata for YouTube video following schema.org VideoObject structure.\n * See: https://developers.google.com/search/docs/appearance/structured-data/video\n *\n * All fields are optional but providing them improves search engine discoverability\n * and enables rich results (video carousels, thumbnails in search results).\n *\n * Use the provided `scripts/fetch-youtube-metadata.sh` helper to easily retrieve\n * this data from YouTube's API.\n */\nexport interface VideoSEO {\n /**\n * The title of the video. If not provided, falls back to the component's `title` prop.\n * @example \"What's new in Material Design for the web\"\n */\n name?: string;\n\n /**\n * A description of the video content.\n * Recommended: 50-160 characters for optimal search result display.\n * @example \"Learn about the latest Material Design updates presented at Chrome Dev Summit 2019\"\n */\n description?: string;\n\n /**\n * ISO 8601 date when the video was uploaded to YouTube.\n * @example \"2019-11-11T08:00:00Z\" or \"2019-11-11\"\n */\n uploadDate?: string;\n\n /**\n * ISO 8601 duration format. Required for video rich results.\n * Format: PT#H#M#S where # is the number of hours, minutes, seconds\n * @example \"PT1M33S\" (1 minute 33 seconds)\n * @example \"PT15M\" (15 minutes)\n * @example \"PT1H30M\" (1 hour 30 minutes)\n */\n duration?: string;\n\n /**\n * Custom thumbnail URL. If not provided, auto-generated from video ID.\n * Recommended: At least 1200px wide for best quality in search results.\n * @example \"https://i.ytimg.com/vi/VIDEO_ID/maxresdefault.jpg\"\n */\n thumbnailUrl?: string;\n\n /**\n * Direct URL to watch the video. Auto-generated if not provided.\n * @example \"https://www.youtube.com/watch?v=L2vS_050c-M\"\n */\n contentUrl?: string;\n\n /**\n * The embed URL. Auto-generated from video ID if not provided.\n * @example \"https://www.youtube.com/embed/L2vS_050c-M\"\n */\n embedUrl?: string;\n}\n\nexport interface LiteYouTubeProps {\n announce?: string;\n id: string;\n title: string;\n activatedClass?: string;\n adNetwork?: boolean;\n aspectHeight?: number;\n aspectWidth?: number;\n iframeClass?: string;\n /** @deprecated Use cookie prop instead */\n noCookie?: boolean;\n cookie?: boolean;\n enableJsApi?: boolean;\n alwaysLoadIframe?: boolean;\n params?: string;\n playerClass?: string;\n playlist?: boolean;\n playlistCoverId?: string;\n poster?: imgResolution;\n webp?: boolean;\n wrapperClass?: string;\n onIframeAdded?: () => void;\n muted?: boolean;\n autoplay?: boolean;\n thumbnail?: string;\n rel?: string;\n containerElement?: keyof React.JSX.IntrinsicElements;\n style?: React.CSSProperties;\n focusOnLoad?: boolean;\n referrerPolicy?: React.HTMLAttributeReferrerPolicy;\n /**\n * Enable lazy loading for thumbnail image.\n * Uses native browser lazy loading to defer offscreen images.\n * Improves Lighthouse scores and reduces bandwidth for below-fold videos.\n * @default false\n */\n lazyLoad?: boolean;\n /**\n * Stop video and return to thumbnail when playback ends.\n * Prevents YouTube from showing related videos. Requires enableJsApi.\n * @default false\n */\n stopOnEnd?: boolean;\n /**\n * SEO metadata for search engines. Enables rich results and better discoverability.\n * Provides structured data following schema.org VideoObject specification.\n * @see https://developers.google.com/search/docs/appearance/structured-data/video\n */\n seo?: VideoSEO;\n /**\n * Include noscript fallback link for accessibility and search crawlers.\n * When true, adds a direct YouTube link inside tags.\n * @default true\n */\n noscriptFallback?: boolean;\n\n // ==================== Player Event Handlers ====================\n\n /**\n * Fires when the player is ready and API is available.\n * This is the first event to fire and indicates it's safe to call player methods.\n * @param event - Contains video ID and title\n * @example\n * onReady={(event) => console.log('Player ready for:', event.videoId)}\n */\n onReady?: (event: PlayerReadyEvent) => void;\n\n /**\n * Fires when the player's state changes.\n * Use this for comprehensive state tracking (play, pause, end, buffering, etc.)\n * @param event - Contains state, current time, and duration\n * @example\n * onStateChange={(event) => {\n * if (event.state === PlayerState.PLAYING) {\n * analytics.track('video_play');\n * }\n * }}\n */\n onStateChange?: (event: PlayerStateChangeEvent) => void;\n\n /**\n * Fires when the player encounters an error.\n * Use this for graceful error handling and user feedback.\n * @param errorCode - YouTube error code (see PlayerError enum)\n * @example\n * onError={(code) => {\n * if (code === PlayerError.VIDEO_NOT_FOUND) {\n * showErrorMessage('Video not available');\n * }\n * }}\n */\n onError?: (errorCode: PlayerError) => void;\n\n // ==================== Convenience Event Handlers ====================\n\n /**\n * Fires when the video starts playing.\n * Convenience wrapper for onStateChange with PlayerState.PLAYING.\n * @example\n * onPlay={() => analytics.track('video_play')}\n */\n onPlay?: () => void;\n\n /**\n * Fires when the video is paused.\n * Convenience wrapper for onStateChange with PlayerState.PAUSED.\n * @example\n * onPause={() => analytics.track('video_pause')}\n */\n onPause?: () => void;\n\n /**\n * Fires when the video ends.\n * Convenience wrapper for onStateChange with PlayerState.ENDED.\n * Useful for loading next video in a playlist.\n * @example\n * onEnd={() => loadNextVideo()}\n */\n onEnd?: () => void;\n\n /**\n * Fires when the video is buffering.\n * Convenience wrapper for onStateChange with PlayerState.BUFFERING.\n * @example\n * onBuffering={() => showLoadingSpinner()}\n */\n onBuffering?: () => void;\n\n // ==================== Advanced Event Handlers ====================\n\n /**\n * Fires when the playback rate (speed) changes.\n * Common values: 0.25, 0.5, 1, 1.5, 2\n * @param playbackRate - The new playback rate\n * @example\n * onPlaybackRateChange={(rate) => console.log('Speed:', rate + 'x')}\n */\n onPlaybackRateChange?: (playbackRate: number) => void;\n\n /**\n * Fires when the video quality changes.\n * Common values: \"small\" (240p), \"medium\" (360p), \"large\" (480p), \"hd720\", \"hd1080\"\n * @param quality - The new quality level\n * @example\n * onPlaybackQualityChange={(quality) => {\n * analytics.track('quality_change', { quality });\n * }}\n */\n onPlaybackQualityChange?: (quality: string) => void;\n}\n\n/**\n * Generates JSON-LD structured data for VideoObject schema.\n * @see https://schema.org/VideoObject\n * @see https://developers.google.com/search/docs/appearance/structured-data/video\n */\nfunction generateVideoStructuredData(\n videoId: string,\n title: string,\n posterUrl: string,\n ytUrl: string,\n seo?: VideoSEO\n): string {\n const structuredData = {\n \"@context\": \"https://schema.org\",\n \"@type\": \"VideoObject\",\n name: seo?.name || title,\n thumbnailUrl: [seo?.thumbnailUrl || posterUrl],\n embedUrl: seo?.embedUrl || `${ytUrl}/embed/${videoId}`,\n contentUrl: seo?.contentUrl || `https://www.youtube.com/watch?v=${videoId}`,\n ...(seo?.description && { description: seo.description }),\n ...(seo?.uploadDate && { uploadDate: seo.uploadDate }),\n ...(seo?.duration && { duration: seo.duration }),\n };\n\n return JSON.stringify(structuredData);\n}\n\nfunction LiteYouTubeEmbedComponent(\n props: LiteYouTubeProps,\n ref: React.Ref\n) {\n const [preconnected, setPreconnected] = React.useState(false);\n const [iframe, setIframe] = React.useState(props.alwaysLoadIframe || false);\n const videoId = encodeURIComponent(props.id);\n const videoPlaylistCoverId =\n typeof props.playlistCoverId === \"string\"\n ? encodeURIComponent(props.playlistCoverId)\n : null;\n const videoTitle = props.title;\n const posterImp = props.poster || \"hqdefault\";\n const announceWatch = props.announce || \"Watch\";\n\n const shouldAddAutoplayParam = props.alwaysLoadIframe\n ? props.autoplay && props.muted\n : true; // When the iframe is not loaded immediately, the video should play as soon as its loaded (which happens when the button is clicked)\n\n // Iframe Parameters - memoized to avoid recreating URLSearchParams on every render\n const iframeParams = React.useMemo(() => {\n const params = new URLSearchParams({\n ...(props.muted ? { mute: \"1\" } : {}),\n ...(shouldAddAutoplayParam ? { autoplay: \"1\" } : {}),\n ...(props.enableJsApi ? { enablejsapi: \"1\" } : {}),\n ...(props.enableJsApi && typeof window !== 'undefined' ? { origin: window.location.origin } : {}),\n ...(props.playlist ? { list: videoId } : {}),\n });\n\n // parse props.params into individual search parameters and append them to params\n if (props.params) {\n const additionalParams = new URLSearchParams(\n props.params.startsWith(\"&\") ? props.params.slice(1) : props.params\n );\n additionalParams.forEach((value, key) => {\n params.append(key, value);\n });\n }\n\n return params;\n }, [\n props.muted,\n shouldAddAutoplayParam,\n props.enableJsApi,\n props.playlist,\n videoId,\n props.params,\n ]);\n\n const ytUrl = React.useMemo(\n () =>\n props.cookie\n ? \"https://www.youtube.com\"\n : \"https://www.youtube-nocookie.com\",\n [props.cookie]\n );\n\n const iframeSrc = React.useMemo(\n () =>\n !props.playlist\n ? `${ytUrl}/embed/${videoId}?${iframeParams.toString()}`\n : `${ytUrl}/embed/videoseries?${iframeParams.toString()}`,\n [props.playlist, ytUrl, videoId, iframeParams]\n );\n\n const useDynamicThumbnail =\n !props.thumbnail && !props.playlist && posterImp === \"maxresdefault\";\n\n const format = props.webp ? \"webp\" : \"jpg\";\n const vi = props.webp ? \"vi_webp\" : \"vi\";\n\n const dynamicThumbnailUrl = useDynamicThumbnail\n ? useYoutubeThumbnail(props.id, vi, format, posterImp)\n : null;\n\n const posterUrl = React.useMemo(\n () =>\n props.thumbnail ||\n dynamicThumbnailUrl ||\n `https://i.ytimg.com/${vi}/${\n props.playlist ? videoPlaylistCoverId : videoId\n }/${posterImp}.${format}`,\n [\n props.thumbnail,\n dynamicThumbnailUrl,\n vi,\n props.playlist,\n videoPlaylistCoverId,\n videoId,\n posterImp,\n format,\n ]\n );\n\n const activatedClassImp = props.activatedClass || \"lyt-activated\";\n const adNetworkImp = props.adNetwork || false;\n const aspectHeight = props.aspectHeight || 9;\n const aspectWidth = props.aspectWidth || 16;\n const iframeClassImp = props.iframeClass || \"\";\n const playerClassImp = props.playerClass || \"lty-playbtn\";\n const wrapperClassImp = props.wrapperClass || \"yt-lite\";\n const onIframeAdded = React.useCallback(\n props.onIframeAdded || function () {},\n [props.onIframeAdded]\n );\n const rel = props.rel ? \"prefetch\" : \"preload\";\n const ContainerElement = props.containerElement || \"article\";\n const includeNoscriptFallback = props.noscriptFallback !== false; // Default to true\n\n const warmConnections = () => {\n if (preconnected) return;\n setPreconnected(true);\n };\n\n const addIframe = () => {\n if (iframe) return;\n setIframe(true);\n };\n\n React.useEffect(() => {\n if (iframe) {\n onIframeAdded();\n\n // Focus iframe if focusOnLoad is enabled and ref is available\n if (props.focusOnLoad && typeof ref === \"object\" && ref?.current) {\n ref.current.focus();\n }\n }\n }, [iframe, onIframeAdded, props.focusOnLoad, ref]);\n\n // Set up postMessage listener for YouTube player events\n React.useEffect(() => {\n // Only set up listener if iframe is loaded and we have event handlers\n if (!iframe || !props.enableJsApi) {\n return;\n }\n\n const hasEventHandlers =\n props.onReady ||\n props.onStateChange ||\n props.onError ||\n props.onPlay ||\n props.onPause ||\n props.onEnd ||\n props.onBuffering ||\n props.onPlaybackRateChange ||\n props.onPlaybackQualityChange;\n\n if (!hasEventHandlers) {\n return;\n }\n\n let isReady = false;\n let iframeLoaded = false;\n\n const handleMessage = (event: MessageEvent) => {\n // Verify origin is from YouTube\n if (\n event.origin !== \"https://www.youtube.com\" &&\n event.origin !== \"https://www.youtube-nocookie.com\"\n ) {\n return;\n }\n\n let data: { event?: string; info?: YouTubeEvent[\"info\"] };\n try {\n data =\n typeof event.data === \"string\" ? JSON.parse(event.data) : event.data;\n } catch {\n return; // Invalid JSON, ignore\n }\n\n // Handle different YouTube events\n switch (data.event) {\n case \"onReady\":\n if (!isReady) {\n isReady = true;\n if (props.onReady) {\n props.onReady({\n videoId: props.id,\n title: videoTitle,\n });\n }\n }\n break;\n\n case \"infoDelivery\":\n // YouTube's postMessage API sends state changes via infoDelivery events\n // This is the primary mechanism for receiving state change notifications\n if (data.info?.playerState !== undefined) {\n const state = data.info.playerState as PlayerState;\n\n // Call main onStateChange handler\n if (props.onStateChange) {\n props.onStateChange({\n state,\n currentTime: data.info.currentTime,\n duration: data.info.duration,\n });\n }\n\n // Call convenience handlers\n switch (state) {\n case PlayerState.PLAYING:\n props.onPlay?.();\n break;\n case PlayerState.PAUSED:\n props.onPause?.();\n break;\n case PlayerState.ENDED:\n props.onEnd?.();\n // Stop video to return to thumbnail and prevent related videos\n if (\n props.stopOnEnd &&\n typeof ref === \"object\" &&\n ref?.current?.contentWindow\n ) {\n ref.current.contentWindow.postMessage(\n '{\"event\":\"command\",\"func\":\"stopVideo\",\"args\":\"\"}',\n \"*\"\n );\n }\n break;\n case PlayerState.BUFFERING:\n props.onBuffering?.();\n break;\n }\n }\n\n // Handle playback rate changes\n if (data.info?.playbackRate !== undefined) {\n props.onPlaybackRateChange?.(data.info.playbackRate);\n }\n\n // Handle playback quality changes\n if (data.info?.playbackQuality !== undefined) {\n props.onPlaybackQualityChange?.(data.info.playbackQuality);\n }\n break;\n\n case \"onStateChange\":\n // Fallback: YouTube may send dedicated onStateChange events in some cases\n // However, infoDelivery (above) is the primary mechanism observed\n if (data.info?.playerState !== undefined) {\n const state = data.info.playerState as PlayerState;\n\n // Call main onStateChange handler\n if (props.onStateChange) {\n props.onStateChange({\n state,\n currentTime: data.info.currentTime,\n duration: data.info.duration,\n });\n }\n\n // Call convenience handlers\n switch (state) {\n case PlayerState.PLAYING:\n props.onPlay?.();\n break;\n case PlayerState.PAUSED:\n props.onPause?.();\n break;\n case PlayerState.ENDED:\n props.onEnd?.();\n // Stop video to return to thumbnail and prevent related videos\n if (\n props.stopOnEnd &&\n typeof ref === \"object\" &&\n ref?.current?.contentWindow\n ) {\n ref.current.contentWindow.postMessage(\n '{\"event\":\"command\",\"func\":\"stopVideo\",\"args\":\"\"}',\n \"*\"\n );\n }\n break;\n case PlayerState.BUFFERING:\n props.onBuffering?.();\n break;\n }\n }\n break;\n\n case \"onError\":\n if (data.info && \"errorCode\" in data.info) {\n const errorCode = (data.info as { errorCode: number }).errorCode;\n if (props.onError) {\n props.onError(errorCode as PlayerError);\n }\n }\n break;\n\n case \"onPlaybackRateChange\":\n if (data.info?.playbackRate !== undefined) {\n props.onPlaybackRateChange?.(data.info.playbackRate);\n }\n break;\n\n case \"onPlaybackQualityChange\":\n if (data.info?.playbackQuality !== undefined) {\n props.onPlaybackQualityChange?.(data.info.playbackQuality);\n }\n break;\n }\n };\n\n window.addEventListener(\"message\", handleMessage);\n\n // Request iframe to send events by posting \"listening\" message\n // This tells YouTube player to start sending events\n const timeouts: ReturnType[] = [];\n const attemptListen = () => {\n if (typeof ref === \"object\" && ref?.current?.contentWindow) {\n ref.current.contentWindow.postMessage(\n '{\"event\":\"listening\",\"id\":\"' + videoId + '\"}',\n \"*\"\n );\n }\n };\n\n // Strategy: Wait for iframe load event, then send listening message\n // This is more reliable than arbitrary delays\n const handleIframeLoad = () => {\n if (iframeLoaded) {\n return;\n }\n iframeLoaded = true;\n\n // Send initial listening message immediately when iframe loads\n attemptListen();\n\n // Also retry with delays as fallback for slower YouTube API initialization\n // YouTube's player API needs additional time to initialize even after iframe loads\n const delays = [100, 300, 600, 1200, 2400];\n delays.forEach((delay) => {\n timeouts.push(setTimeout(attemptListen, delay));\n });\n };\n\n // Attach load event listener to iframe\n if (typeof ref === \"object\" && ref?.current) {\n ref.current.addEventListener(\"load\", handleIframeLoad);\n\n // If iframe is already loaded, trigger immediately\n // This handles race condition where load event fired before listener attached\n if (ref.current.contentDocument?.readyState === \"complete\") {\n handleIframeLoad();\n }\n } else {\n // Fallback: If ref not ready, use longer delays\n const fallbackDelays = [200, 500, 1000, 2000, 3000];\n fallbackDelays.forEach((delay) => {\n timeouts.push(setTimeout(attemptListen, delay));\n });\n }\n\n return () => {\n window.removeEventListener(\"message\", handleMessage);\n timeouts.forEach(clearTimeout);\n\n // Clean up iframe load listener\n if (typeof ref === \"object\" && ref?.current) {\n ref.current.removeEventListener(\"load\", handleIframeLoad);\n }\n };\n }, [\n iframe,\n props.enableJsApi,\n props.onReady,\n props.onStateChange,\n props.onError,\n props.onPlay,\n props.onPause,\n props.onEnd,\n props.onBuffering,\n props.onPlaybackRateChange,\n props.onPlaybackQualityChange,\n props.stopOnEnd,\n props.id,\n videoId,\n videoTitle,\n ref,\n ]);\n\n return (\n <>\n {!props.lazyLoad && }\n <>\n {preconnected && (\n <>\n \n \n {adNetworkImp && (\n <>\n \n \n >\n )}\n >\n )}\n >\n {/* SEO: JSON-LD Structured Data for VideoObject */}\n {props.seo && !props.playlist && (\n \n )}\n {/* SEO: Noscript fallback for accessibility and crawlers */}\n {includeNoscriptFallback && !props.playlist && (\n \n \n Watch "{videoTitle}" on YouTube\n \n \n )}\n \n {props.lazyLoad && !iframe && (\n \n )}\n \n {announceWatch} \n \n {iframe && (\n \n )}\n \n >\n );\n}\n\nexport default React.forwardRef(\n LiteYouTubeEmbedComponent\n);\n"],"names":["expectedWidths","useYoutubeThumbnail","videoId","vi","format","imageRes","url","setUrl","useState","useEffect","testUrl","fallbackUrl","expectedWidth","img","PlayerState","PlayerError","generateVideoStructuredData","title","posterUrl","ytUrl","seo","structuredData","LiteYouTubeEmbedComponent","props","ref","preconnected","setPreconnected","React","iframe","setIframe","videoPlaylistCoverId","videoTitle","posterImp","announceWatch","shouldAddAutoplayParam","iframeParams","params","value","key","iframeSrc","useDynamicThumbnail","dynamicThumbnailUrl","activatedClassImp","adNetworkImp","aspectHeight","aspectWidth","iframeClassImp","playerClassImp","wrapperClassImp","onIframeAdded","rel","ContainerElement","includeNoscriptFallback","warmConnections","addIframe","isReady","iframeLoaded","handleMessage","event","data","state","errorCode","timeouts","attemptListen","handleIframeLoad","delay","jsxs","Fragment","jsx","index"],"mappings":"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"}
\ No newline at end of file
diff --git a/src/lib/index.tsx b/src/lib/index.tsx
index cc8bf3d..f4c8d1a 100644
--- a/src/lib/index.tsx
+++ b/src/lib/index.tsx
@@ -327,6 +327,7 @@ function LiteYouTubeEmbedComponent(
...(props.muted ? { mute: "1" } : {}),
...(shouldAddAutoplayParam ? { autoplay: "1" } : {}),
...(props.enableJsApi ? { enablejsapi: "1" } : {}),
+ ...(props.enableJsApi && typeof window !== 'undefined' ? { origin: window.location.origin } : {}),
...(props.playlist ? { list: videoId } : {}),
});
@@ -487,7 +488,63 @@ function LiteYouTubeEmbedComponent(
}
break;
+ case "infoDelivery":
+ // YouTube's postMessage API sends state changes via infoDelivery events
+ // This is the primary mechanism for receiving state change notifications
+ if (data.info?.playerState !== undefined) {
+ const state = data.info.playerState as PlayerState;
+
+ // Call main onStateChange handler
+ if (props.onStateChange) {
+ props.onStateChange({
+ state,
+ currentTime: data.info.currentTime,
+ duration: data.info.duration,
+ });
+ }
+
+ // Call convenience handlers
+ switch (state) {
+ case PlayerState.PLAYING:
+ props.onPlay?.();
+ break;
+ case PlayerState.PAUSED:
+ props.onPause?.();
+ break;
+ case PlayerState.ENDED:
+ props.onEnd?.();
+ // Stop video to return to thumbnail and prevent related videos
+ if (
+ props.stopOnEnd &&
+ typeof ref === "object" &&
+ ref?.current?.contentWindow
+ ) {
+ ref.current.contentWindow.postMessage(
+ '{"event":"command","func":"stopVideo","args":""}',
+ "*"
+ );
+ }
+ break;
+ case PlayerState.BUFFERING:
+ props.onBuffering?.();
+ break;
+ }
+ }
+
+ // Handle playback rate changes
+ if (data.info?.playbackRate !== undefined) {
+ props.onPlaybackRateChange?.(data.info.playbackRate);
+ }
+
+ // Handle playback quality changes
+ if (data.info?.playbackQuality !== undefined) {
+ props.onPlaybackQualityChange?.(data.info.playbackQuality);
+ }
+ break;
+
case "onStateChange":
+ // Fallback: YouTube may send dedicated onStateChange events in some cases
+ // However, infoDelivery (above) is the primary mechanism observed
if (data.info?.playerState !== undefined) {
const state = data.info.playerState as PlayerState;
@@ -569,7 +626,9 @@ function LiteYouTubeEmbedComponent(
// Strategy: Wait for iframe load event, then send listening message
// This is more reliable than arbitrary delays
const handleIframeLoad = () => {
- if (iframeLoaded) return;
+ if (iframeLoaded) {
+ return;
+ }
iframeLoaded = true;
// Send initial listening message immediately when iframe loads