@videojs/react 10.0.0-beta.29 → 10.0.0-beta.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/dist/default/index.js +2 -1
  2. package/dist/default/media/shaka-video/index.js +2 -0
  3. package/dist/default/media/shaka-video/media.js +21 -0
  4. package/dist/default/media/shaka-video/media.js.map +1 -0
  5. package/dist/default/presets/audio/minimal-skin.css +1 -3
  6. package/dist/default/presets/audio/skin.css +1 -3
  7. package/dist/default/presets/live-audio/minimal-skin.css +1 -3
  8. package/dist/default/presets/live-audio/skin.css +1 -3
  9. package/dist/default/presets/live-video/minimal-skin.css +2 -31
  10. package/dist/default/presets/live-video/minimal-skin.js +2 -6
  11. package/dist/default/presets/live-video/minimal-skin.js.map +1 -1
  12. package/dist/default/presets/live-video/minimal-skin.tailwind.js +2 -6
  13. package/dist/default/presets/live-video/minimal-skin.tailwind.js.map +1 -1
  14. package/dist/default/presets/live-video/skin.css +2 -33
  15. package/dist/default/presets/live-video/skin.js +2 -6
  16. package/dist/default/presets/live-video/skin.js.map +1 -1
  17. package/dist/default/presets/live-video/skin.tailwind.js +2 -6
  18. package/dist/default/presets/live-video/skin.tailwind.js.map +1 -1
  19. package/dist/default/presets/video/minimal-skin.css +2 -31
  20. package/dist/default/presets/video/minimal-skin.js +2 -6
  21. package/dist/default/presets/video/minimal-skin.js.map +1 -1
  22. package/dist/default/presets/video/minimal-skin.tailwind.js +2 -6
  23. package/dist/default/presets/video/minimal-skin.tailwind.js.map +1 -1
  24. package/dist/default/presets/video/skin.css +2 -33
  25. package/dist/default/presets/video/skin.js +2 -6
  26. package/dist/default/presets/video/skin.js.map +1 -1
  27. package/dist/default/presets/video/skin.tailwind.js +2 -6
  28. package/dist/default/presets/video/skin.tailwind.js.map +1 -1
  29. package/dist/default/skins/dist/default/default/tailwind/components/button.js +1 -1
  30. package/dist/default/skins/dist/default/default/tailwind/components/button.js.map +1 -1
  31. package/dist/default/skins/dist/default/default/tailwind/components/menu.js +2 -2
  32. package/dist/default/skins/dist/default/default/tailwind/components/menu.js.map +1 -1
  33. package/dist/default/skins/dist/default/default/tailwind/components/poster.js +1 -7
  34. package/dist/default/skins/dist/default/default/tailwind/components/poster.js.map +1 -1
  35. package/dist/default/skins/dist/default/default/tailwind/video.tailwind.js +1 -10
  36. package/dist/default/skins/dist/default/default/tailwind/video.tailwind.js.map +1 -1
  37. package/dist/default/skins/dist/default/minimal/tailwind/components/button.js +1 -1
  38. package/dist/default/skins/dist/default/minimal/tailwind/components/button.js.map +1 -1
  39. package/dist/default/skins/dist/default/minimal/tailwind/components/menu.js +2 -2
  40. package/dist/default/skins/dist/default/minimal/tailwind/components/menu.js.map +1 -1
  41. package/dist/default/skins/dist/default/minimal/tailwind/components/poster.js +1 -7
  42. package/dist/default/skins/dist/default/minimal/tailwind/components/poster.js.map +1 -1
  43. package/dist/default/skins/dist/default/minimal/tailwind/video.tailwind.js +1 -10
  44. package/dist/default/skins/dist/default/minimal/tailwind/video.tailwind.js.map +1 -1
  45. package/dist/default/ui/thumbnail/thumbnail.js +18 -1
  46. package/dist/default/ui/thumbnail/thumbnail.js.map +1 -1
  47. package/dist/default/ui/title/index.js +2 -0
  48. package/dist/default/ui/title/title.js +44 -0
  49. package/dist/default/ui/title/title.js.map +1 -0
  50. package/dist/dev/index.d.ts +3 -2
  51. package/dist/dev/index.js +2 -1
  52. package/dist/dev/media/shaka-video/index.d.ts +2 -0
  53. package/dist/dev/media/shaka-video/index.js +2 -0
  54. package/dist/dev/media/shaka-video/media.d.ts +13 -0
  55. package/dist/dev/media/shaka-video/media.d.ts.map +1 -0
  56. package/dist/dev/media/shaka-video/media.js +21 -0
  57. package/dist/dev/media/shaka-video/media.js.map +1 -0
  58. package/dist/dev/presets/audio/minimal-skin.css +1 -3
  59. package/dist/dev/presets/audio/skin.css +1 -3
  60. package/dist/dev/presets/live-audio/minimal-skin.css +1 -3
  61. package/dist/dev/presets/live-audio/skin.css +1 -3
  62. package/dist/dev/presets/live-video/minimal-skin.css +2 -31
  63. package/dist/dev/presets/live-video/minimal-skin.js +2 -6
  64. package/dist/dev/presets/live-video/minimal-skin.js.map +1 -1
  65. package/dist/dev/presets/live-video/minimal-skin.tailwind.js +2 -6
  66. package/dist/dev/presets/live-video/minimal-skin.tailwind.js.map +1 -1
  67. package/dist/dev/presets/live-video/skin.css +2 -33
  68. package/dist/dev/presets/live-video/skin.js +2 -6
  69. package/dist/dev/presets/live-video/skin.js.map +1 -1
  70. package/dist/dev/presets/live-video/skin.tailwind.js +2 -6
  71. package/dist/dev/presets/live-video/skin.tailwind.js.map +1 -1
  72. package/dist/dev/presets/types.d.ts +0 -2
  73. package/dist/dev/presets/types.d.ts.map +1 -1
  74. package/dist/dev/presets/video/minimal-skin.css +2 -31
  75. package/dist/dev/presets/video/minimal-skin.js +2 -6
  76. package/dist/dev/presets/video/minimal-skin.js.map +1 -1
  77. package/dist/dev/presets/video/minimal-skin.tailwind.js +2 -6
  78. package/dist/dev/presets/video/minimal-skin.tailwind.js.map +1 -1
  79. package/dist/dev/presets/video/skin.css +2 -33
  80. package/dist/dev/presets/video/skin.js +2 -6
  81. package/dist/dev/presets/video/skin.js.map +1 -1
  82. package/dist/dev/presets/video/skin.tailwind.js +2 -6
  83. package/dist/dev/presets/video/skin.tailwind.js.map +1 -1
  84. package/dist/dev/skins/dist/default/default/tailwind/components/button.js +1 -1
  85. package/dist/dev/skins/dist/default/default/tailwind/components/button.js.map +1 -1
  86. package/dist/dev/skins/dist/default/default/tailwind/components/menu.js +2 -2
  87. package/dist/dev/skins/dist/default/default/tailwind/components/menu.js.map +1 -1
  88. package/dist/dev/skins/dist/default/default/tailwind/components/poster.js +1 -7
  89. package/dist/dev/skins/dist/default/default/tailwind/components/poster.js.map +1 -1
  90. package/dist/dev/skins/dist/default/default/tailwind/video.tailwind.js +1 -10
  91. package/dist/dev/skins/dist/default/default/tailwind/video.tailwind.js.map +1 -1
  92. package/dist/dev/skins/dist/default/minimal/tailwind/components/button.js +1 -1
  93. package/dist/dev/skins/dist/default/minimal/tailwind/components/button.js.map +1 -1
  94. package/dist/dev/skins/dist/default/minimal/tailwind/components/menu.js +2 -2
  95. package/dist/dev/skins/dist/default/minimal/tailwind/components/menu.js.map +1 -1
  96. package/dist/dev/skins/dist/default/minimal/tailwind/components/poster.js +1 -7
  97. package/dist/dev/skins/dist/default/minimal/tailwind/components/poster.js.map +1 -1
  98. package/dist/dev/skins/dist/default/minimal/tailwind/video.tailwind.js +1 -10
  99. package/dist/dev/skins/dist/default/minimal/tailwind/video.tailwind.js.map +1 -1
  100. package/dist/dev/ui/thumbnail/thumbnail.d.ts.map +1 -1
  101. package/dist/dev/ui/thumbnail/thumbnail.js +18 -1
  102. package/dist/dev/ui/thumbnail/thumbnail.js.map +1 -1
  103. package/dist/dev/ui/title/index.d.ts +2 -0
  104. package/dist/dev/ui/title/index.js +2 -0
  105. package/dist/dev/ui/title/title.d.ts +27 -0
  106. package/dist/dev/ui/title/title.d.ts.map +1 -0
  107. package/dist/dev/ui/title/title.js +47 -0
  108. package/dist/dev/ui/title/title.js.map +1 -0
  109. package/docs/README.md +1 -1
  110. package/docs/concepts/presets.md +4 -4
  111. package/docs/concepts/skins.md +8 -48
  112. package/docs/how-to/add-a-poster-placeholder.md +133 -0
  113. package/docs/how-to/customize-skins.md +10 -92
  114. package/docs/how-to/migrate-from-mux-player.md +14 -8
  115. package/docs/llms.txt +3 -0
  116. package/docs/reference/feature-fullscreen.md +9 -8
  117. package/docs/reference/feature-metadata.md +129 -0
  118. package/docs/reference/feature-orientation-lock.md +94 -8
  119. package/docs/reference/feature-text-tracks.md +10 -0
  120. package/docs/reference/menu.md +0 -1
  121. package/docs/reference/thumbnail.md +4 -0
  122. package/docs/reference/title.md +208 -0
  123. package/package.json +8 -8
@@ -34,20 +34,11 @@ const container = (isShadowDOM) => cn(container$1, "group/skin", "bg-black overf
34
34
  "[&_video::-webkit-media-text-track-container]:scale-98",
35
35
  "[&_video::-webkit-media-text-track-container]:z-1",
36
36
  "[&_video::-webkit-media-text-track-container]:font-[inherit]"
37
- ] : [], !isShadowDOM ? [
38
- "before:absolute before:inset-0 before:pointer-events-none",
39
- "before:[background-image:var(--media-poster-placeholder,none)]",
40
- "before:bg-no-repeat",
41
- "before:[background-position:var(--media-object-position,center)]",
42
- "before:[background-size:var(--media-object-fit,contain)]",
43
- "before:opacity-0 before:[filter:blur(var(--media-poster-placeholder-blur,20px))]",
44
- "before:transition-opacity before:duration-250",
45
- "has-[img[data-visible]:not([data-loaded])]:before:opacity-100"
46
37
  ] : [], "[&:fullscreen]:[--container-border-radius:0]", {
47
38
  "[&:fullscreen_video]:object-contain": !isShadowDOM,
48
39
  "[&:fullscreen_::slotted(video)]:object-contain": isShadowDOM
49
40
  });
50
- const controls = cn(controls$1, "absolute bottom-1 inset-x-1", "[--base-side-offset:5] [--base-boundary-offset:1]", "[--volume-mask:linear-gradient(to_right,transparent_10%,black_25%,black_100%)]", "gap-x-2 flex-wrap rounded-[--spacing(3)] group/controls", "text-white z-20", "peer-data-open/error:hidden", "ease-(--controls-transition-timing-function)", "duration-[calc(var(--controls-transition-duration)/2)]", "not-data-visible:duration-(--controls-transition-duration)", "pointer-fine:will-change-[translate,filter,opacity]", "pointer-fine:transition-[translate,filter,opacity]", "pointer-coarse:will-change-[translate,opacity]", "pointer-coarse:transition-[translate,opacity]", "not-data-visible:opacity-0 not-data-visible:pointer-events-none", "motion-safe:not-data-visible:translate-y-full", "pointer-fine:motion-safe:not-data-visible:blur-sm", "@2xl/media-root:flex-nowrap @2xl/media-root:[--controls-padding:2] @2xl/media-root:[--base-side-offset:2]");
41
+ const controls = cn(controls$1, "absolute bottom-1 inset-x-1", "[--base-side-offset:5] [--base-boundary-offset:1]", "[--volume-mask:linear-gradient(to_right,transparent_10%,black_25%,black_100%)]", "gap-x-2 flex-wrap rounded-[--spacing(3)] group/controls", "text-white z-20", "peer-data-open/error:hidden", "ease-(--controls-transition-timing-function)", "duration-[calc(var(--controls-transition-duration)/2)]", "not-data-visible:duration-(--controls-transition-duration)", "pointer-fine:transition-[translate,filter,opacity]", "pointer-coarse:transition-[translate,opacity]", "not-data-visible:opacity-0 not-data-visible:pointer-events-none", "motion-safe:not-data-visible:translate-y-full", "pointer-fine:motion-safe:not-data-visible:blur-sm", "@2xl/media-root:flex-nowrap @2xl/media-root:[--controls-padding:2] @2xl/media-root:[--base-side-offset:2]");
51
42
  const volumeMaskTarget = cn("[mask-image:var(--volume-mask-image,none)]", "[mask-repeat:no-repeat]", "[mask-position:var(--volume-mask-position,100%_0)]", "[mask-size:var(--volume-mask-size,200%_100%)]", "[transition:mask-position_50ms_ease-out]");
52
43
  const buttonGroupStart = cn(buttonGroup, "flex-1 @2xl/media-root:flex-none");
53
44
  const buttonGroupEnd = cn(buttonGroup, volumeMaskTarget, "flex-1 justify-end @2xl/media-root:flex-none", "group-has-[[data-volume-level][aria-expanded=true]]/controls:@max-2xl/media-root:[--volume-mask-image:var(--volume-mask)]", "group-has-[[data-volume-level][aria-expanded=true]]/controls:@max-2xl/media-root:[--volume-mask-position:0_0]", "group-has-[[data-volume-level][aria-expanded=true]]/controls:@max-2xl/media-root:[--volume-mask-size:400%_100%]");
@@ -1 +1 @@
1
- {"version":3,"file":"video.tailwind.js","names":[],"sources":["../../../../../../../../skins/dist/default/minimal/tailwind/video.tailwind.js"],"sourcesContent":["import { container as container$1 } from \"./components/container.js\";\nimport { controls as controls$1 } from \"./components/controls.js\";\nimport { error as error$1 } from \"./components/error.js\";\nimport { popup as popup$1 } from \"./components/popup.js\";\nimport { slider as slider$1 } from \"./components/slider.js\";\nimport { iconState } from \"../../shared/tailwind/icon-state.js\";\nimport { badge } from \"./components/badge.js\";\nimport { bufferingIndicator } from \"./components/buffering.js\";\nimport { button } from \"./components/button.js\";\nimport { buttonGroup } from \"./components/button-group.js\";\nimport { icon, iconContainer, iconFlipped, iconHidden } from \"./components/icon.js\";\nimport { menu as menu$1 } from \"./components/menu.js\";\nimport { playbackRate } from \"./components/playback-rate.js\";\nimport { seek } from \"./components/seek.js\";\nimport { time as time$1 } from \"./components/time.js\";\nimport { thumbnail as thumbnail$1 } from \"./components/thumbnail.js\";\nimport { inputIndicatorOverlay } from \"./components/input-indicator-overlay.js\";\nimport { overlay } from \"./components/overlay.js\";\nimport { poster } from \"./components/poster.js\";\nimport { seekIndicator } from \"./components/seek-indicator.js\";\nimport { statusIndicator } from \"./components/status-indicator.js\";\nimport { volumeIndicator } from \"./components/volume-indicator.js\";\nimport { cn } from \"@videojs/utils/style\";\n//#region src/minimal/tailwind/video.tailwind.ts\nconst container = (isShadowDOM) => cn(container$1, \"group/skin\", \"bg-black overflow-clip\", \"after:absolute after:pointer-events-none after:rounded-[inherit] after:z-10\", \"[&:fullscreen]:after:hidden\", \"after:inset-0 after:ring-1 after:ring-inset after:ring-(color:--border-color)\", {\n\t\"[&_::slotted(video)]:block [&_::slotted(video)]:w-full [&_::slotted(video)]:h-full [&_::slotted(video)]:rounded-(--container-border-radius) [&_::slotted(video)]:[object-fit:var(--media-object-fit,cover)] [&_::slotted(video)]:[object-position:var(--media-object-position,center)]\": isShadowDOM,\n\t\"[&_video]:block [&_video]:w-full [&_video]:h-full [&_video]:rounded-[inherit] [&_video]:[object-fit:var(--media-object-fit,contain)] [&_video]:[object-position:var(--media-object-position,center)]\": !isShadowDOM\n}, \"[--default-accent-color:oklch(1_0_0)]\", \"[--border-color:light-dark(oklch(0_0_0/0.15),oklch(1_0_0/0.15))]\", \"[--focus-ring-color:light-dark(oklch(0_0_0),oklch(1_0_0))]\", \"**:[--focus-ring-color:oklch(1_0_0)]\", \"[--container-border-radius:var(--media-border-radius,0.75rem)]\", \"[--media-video-border-radius:var(--container-border-radius)]\", \"[--controls-background-color:transparent]\", \"[--controls-transition-duration:100ms]\", \"[--controls-transition-timing-function:ease-out]\", \"[--error-dialog-transition-duration:150ms]\", \"[--error-dialog-transition-delay:100ms]\", \"[--error-dialog-transition-timing-function:ease-out]\", \"[--popup-transition-duration:100ms]\", \"[--popup-transition-timing-function:ease-out]\", \"[--popover-backdrop-filter:blur(16px)_saturate(1.5)]\", \"[--popover-background-color:oklch(0_0_0/0.5)]\", \"[--popover-border-color:oklch(1_0_0/0.1)]\", \"[--tooltip-backdrop-filter:var(--popover-backdrop-filter)]\", \"[--tooltip-background-color:var(--popover-background-color)]\", \"[--tooltip-border-color:var(--popover-border-color)]\", \"[--tooltip-text-color:currentColor]\", \"min-[1280px]:[&:fullscreen]:[--scale:1.25]\", \"min-[1536px]:[&:fullscreen]:[--scale:1.5]\", \"min-[1920px]:[&:fullscreen]:[--scale:1.75]\", \"motion-reduce:[--error-dialog-transition-duration:50ms]\", \"motion-reduce:[--error-dialog-transition-delay:0ms]\", \"motion-reduce:[--popup-transition-duration:0ms]\", \"[@media(prefers-reduced-transparency:reduce)]:[--controls-background-color:oklch(0_0_0)]\", \"contrast-more:[--controls-background-color:oklch(0_0_0)]\", \"[@media(prefers-reduced-transparency:reduce)]:[--tooltip-background-color:oklch(0_0_0)]\", \"contrast-more:[--tooltip-background-color:oklch(0_0_0)]\", \"pointer-fine:has-[[data-controls]:not([data-visible])]:[--controls-transition-duration:300ms]\", \"pointer-coarse:has-[[data-controls]:not([data-visible])]:[--controls-transition-duration:150ms]\", \"motion-reduce:has-[[data-controls]:not([data-visible])]:[--controls-transition-duration:50ms]\", \"[--media-caption-track-y:--spacing(-2)]\", \"[--media-caption-track-delay:25ms]\", \"[--media-caption-track-duration:var(--controls-transition-duration)]\", \"has-[[data-controls][data-visible]]:[--media-caption-track-y:--spacing(-18)]\", \"@2xl/media-root:has-[[data-controls][data-visible]]:*:[--media-caption-track-y:--spacing(-12)]\", !isShadowDOM ? [\n\t\"[&_video::-webkit-media-text-track-container]:transition-[translate]\",\n\t\"[&_video::-webkit-media-text-track-container]:duration-(--media-caption-track-duration)\",\n\t\"[&_video::-webkit-media-text-track-container]:ease-out\",\n\t\"[&_video::-webkit-media-text-track-container]:delay-(--media-caption-track-delay)\",\n\t\"[&_video::-webkit-media-text-track-container]:translate-y-(--media-caption-track-y)\",\n\t\"[&_video::-webkit-media-text-track-container]:scale-98\",\n\t\"[&_video::-webkit-media-text-track-container]:z-1\",\n\t\"[&_video::-webkit-media-text-track-container]:font-[inherit]\"\n] : [], !isShadowDOM ? [\n\t\"before:absolute before:inset-0 before:pointer-events-none\",\n\t\"before:[background-image:var(--media-poster-placeholder,none)]\",\n\t\"before:bg-no-repeat\",\n\t\"before:[background-position:var(--media-object-position,center)]\",\n\t\"before:[background-size:var(--media-object-fit,contain)]\",\n\t\"before:opacity-0 before:[filter:blur(var(--media-poster-placeholder-blur,20px))]\",\n\t\"before:transition-opacity before:duration-250\",\n\t\"has-[img[data-visible]:not([data-loaded])]:before:opacity-100\"\n] : [], \"[&:fullscreen]:[--container-border-radius:0]\", {\n\t\"[&:fullscreen_video]:object-contain\": !isShadowDOM,\n\t\"[&:fullscreen_::slotted(video)]:object-contain\": isShadowDOM\n});\nconst controls = cn(controls$1, \"absolute bottom-1 inset-x-1\", \"[--base-side-offset:5] [--base-boundary-offset:1]\", \"[--volume-mask:linear-gradient(to_right,transparent_10%,black_25%,black_100%)]\", \"gap-x-2 flex-wrap rounded-[--spacing(3)] group/controls\", \"text-white z-20\", \"peer-data-open/error:hidden\", \"ease-(--controls-transition-timing-function)\", \"duration-[calc(var(--controls-transition-duration)/2)]\", \"not-data-visible:duration-(--controls-transition-duration)\", \"pointer-fine:will-change-[translate,filter,opacity]\", \"pointer-fine:transition-[translate,filter,opacity]\", \"pointer-coarse:will-change-[translate,opacity]\", \"pointer-coarse:transition-[translate,opacity]\", \"not-data-visible:opacity-0 not-data-visible:pointer-events-none\", \"motion-safe:not-data-visible:translate-y-full\", \"pointer-fine:motion-safe:not-data-visible:blur-sm\", \"@2xl/media-root:flex-nowrap @2xl/media-root:[--controls-padding:2] @2xl/media-root:[--base-side-offset:2]\");\nconst volumeMaskTarget = cn(\"[mask-image:var(--volume-mask-image,none)]\", \"[mask-repeat:no-repeat]\", \"[mask-position:var(--volume-mask-position,100%_0)]\", \"[mask-size:var(--volume-mask-size,200%_100%)]\", \"[transition:mask-position_50ms_ease-out]\");\nconst buttonGroupStart = cn(buttonGroup, \"flex-1 @2xl/media-root:flex-none\");\nconst buttonGroupEnd = cn(buttonGroup, volumeMaskTarget, \"flex-1 justify-end @2xl/media-root:flex-none\", \"group-has-[[data-volume-level][aria-expanded=true]]/controls:@max-2xl/media-root:[--volume-mask-image:var(--volume-mask)]\", \"group-has-[[data-volume-level][aria-expanded=true]]/controls:@max-2xl/media-root:[--volume-mask-position:0_0]\", \"group-has-[[data-volume-level][aria-expanded=true]]/controls:@max-2xl/media-root:[--volume-mask-size:400%_100%]\");\nconst spacer = \"grow\";\nconst time = {\n\t...time$1,\n\tcontrols: cn(time$1.controls, volumeMaskTarget, \"[--slider-height:--spacing(5)] grow-0 shrink-0 basis-full order-[-1] px-1.5\", \"@2xl/media-root:[--slider-height:--spacing(8)] @2xl/media-root:grow @2xl/media-root:shrink @2xl/media-root:basis-0 @2xl/media-root:order-[unset]\", \"group-has-[[data-volume-level][aria-expanded=true]]/controls:@2xl/media-root:[--volume-mask-image:var(--volume-mask)]\", \"group-has-[[data-volume-level][aria-expanded=true]]/controls:@2xl/media-root:[--volume-mask-position:0_0]\")\n};\nconst error = {\n\t...error$1,\n\troot: cn(error$1.root, \"pointer-events-none outline-none\"),\n\tdialog: cn(error$1.dialog, \"pointer-events-auto w-full max-w-64 p-4 rounded-none\"),\n\tcontent: cn(error$1.content, \"p-0 py-1.5\"),\n\ttitle: \"text-(length:--font-size-medium)\"\n};\nconst thumbnail = thumbnail$1;\nconst slider = {\n\t...slider$1,\n\ttrack: cn(slider$1.track, \"ring-1 ring-black/5\"),\n\tpreview: cn(slider$1.preview, \"[--preview-end-inset:calc(100cqi-100%)]\", \"[--preview-left:clamp(calc(var(--max-size)/2),var(--media-slider-pointer),calc(100%-var(--max-size)/2+var(--preview-end-inset)))]\", \"@2xl/media-root:[--preview-left:var(--media-slider-pointer)]\")\n};\nconst popup = {\n\t...popup$1,\n\tvolume: cn(popup$1.popover, \"p-0 bg-transparent\", \"@max-2xl/media-root:[--media-popover-side-offset:--spacing(3)]\")\n};\nconst menu = {\n\t...menu$1,\n\troot: menu$1.root,\n\tsettings: menu$1.settings\n};\n//#endregion\nexport { badge, bufferingIndicator, button, buttonGroup, buttonGroupEnd, buttonGroupStart, container, controls, error, icon, iconContainer, iconFlipped, iconHidden, iconState, inputIndicatorOverlay, menu, overlay, playbackRate, popup, poster, seek, seekIndicator, slider, spacer, statusIndicator, thumbnail, time, volumeIndicator };\n\n//# sourceMappingURL=video.tailwind.js.map"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAM,aAAa,gBAAgB,GAAG,aAAa,cAAc,0BAA0B,+EAA+E,+BAA+B,iFAAiF;CACzR,0RAA0R;CAC1R,wMAAwM,CAAC;AAC1M,GAAG,yCAAyC,oEAAoE,8DAA8D,wCAAwC,kEAAkE,gEAAgE,6CAA6C,0CAA0C,oDAAoD,8CAA8C,2CAA2C,wDAAwD,uCAAuC,iDAAiD,wDAAwD,iDAAiD,6CAA6C,8DAA8D,gEAAgE,wDAAwD,uCAAuC,8CAA8C,6CAA6C,8CAA8C,2DAA2D,uDAAuD,mDAAmD,4FAA4F,4DAA4D,2FAA2F,2DAA2D,iGAAiG,mGAAmG,iGAAiG,2CAA2C,sCAAsC,wEAAwE,gFAAgF,kGAAkG,CAAC,cAAc;CACzxE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD,IAAI,CAAC,GAAG,CAAC,cAAc;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD,IAAI,CAAC,GAAG,gDAAgD;CACvD,uCAAuC,CAAC;CACxC,kDAAkD;AACnD,CAAC;AACD,MAAM,WAAW,GAAG,YAAY,+BAA+B,qDAAqD,kFAAkF,2DAA2D,mBAAmB,+BAA+B,gDAAgD,0DAA0D,8DAA8D,uDAAuD,sDAAsD,kDAAkD,iDAAiD,mEAAmE,iDAAiD,qDAAqD,2GAA2G;AAC/7B,MAAM,mBAAmB,GAAG,8CAA8C,2BAA2B,sDAAsD,iDAAiD,0CAA0C;AACtP,MAAM,mBAAmB,GAAG,aAAa,kCAAkC;AAC3E,MAAM,iBAAiB,GAAG,aAAa,kBAAkB,gDAAgD,6HAA6H,iHAAiH,iHAAiH;AACxc,MAAM,SAAS;AACf,MAAM,OAAO;CACZ,GAAG;CACH,UAAU,GAAG,OAAO,UAAU,kBAAkB,+EAA+E,oJAAoJ,yHAAyH,2GAA2G;AACxf;AACA,MAAM,QAAQ;CACb,GAAG;CACH,MAAM,GAAG,QAAQ,MAAM,kCAAkC;CACzD,QAAQ,GAAG,QAAQ,QAAQ,sDAAsD;CACjF,SAAS,GAAG,QAAQ,SAAS,YAAY;CACzC,OAAO;AACR;AACA,MAAM,YAAY;AAClB,MAAM,SAAS;CACd,GAAG;CACH,OAAO,GAAG,SAAS,OAAO,qBAAqB;CAC/C,SAAS,GAAG,SAAS,SAAS,2CAA2C,qIAAqI,8DAA8D;AAC7Q;AACA,MAAM,QAAQ;CACb,GAAG;CACH,QAAQ,GAAG,QAAQ,SAAS,sBAAsB,gEAAgE;AACnH;AACA,MAAM,OAAO;CACZ,GAAG;CACH,MAAM,OAAO;CACb,UAAU,OAAO;AAClB"}
1
+ {"version":3,"file":"video.tailwind.js","names":[],"sources":["../../../../../../../../skins/dist/default/minimal/tailwind/video.tailwind.js"],"sourcesContent":["import { container as container$1 } from \"./components/container.js\";\nimport { controls as controls$1 } from \"./components/controls.js\";\nimport { error as error$1 } from \"./components/error.js\";\nimport { popup as popup$1 } from \"./components/popup.js\";\nimport { slider as slider$1 } from \"./components/slider.js\";\nimport { iconState } from \"../../shared/tailwind/icon-state.js\";\nimport { badge } from \"./components/badge.js\";\nimport { bufferingIndicator } from \"./components/buffering.js\";\nimport { button } from \"./components/button.js\";\nimport { buttonGroup } from \"./components/button-group.js\";\nimport { icon, iconContainer, iconFlipped, iconHidden } from \"./components/icon.js\";\nimport { menu as menu$1 } from \"./components/menu.js\";\nimport { playbackRate } from \"./components/playback-rate.js\";\nimport { seek } from \"./components/seek.js\";\nimport { time as time$1 } from \"./components/time.js\";\nimport { thumbnail as thumbnail$1 } from \"./components/thumbnail.js\";\nimport { inputIndicatorOverlay } from \"./components/input-indicator-overlay.js\";\nimport { overlay } from \"./components/overlay.js\";\nimport { poster } from \"./components/poster.js\";\nimport { seekIndicator } from \"./components/seek-indicator.js\";\nimport { statusIndicator } from \"./components/status-indicator.js\";\nimport { volumeIndicator } from \"./components/volume-indicator.js\";\nimport { cn } from \"@videojs/utils/style\";\n//#region src/minimal/tailwind/video.tailwind.ts\nconst container = (isShadowDOM) => cn(container$1, \"group/skin\", \"bg-black overflow-clip\", \"after:absolute after:pointer-events-none after:rounded-[inherit] after:z-10\", \"[&:fullscreen]:after:hidden\", \"after:inset-0 after:ring-1 after:ring-inset after:ring-(color:--border-color)\", {\n\t\"[&_::slotted(video)]:block [&_::slotted(video)]:w-full [&_::slotted(video)]:h-full [&_::slotted(video)]:rounded-(--container-border-radius) [&_::slotted(video)]:[object-fit:var(--media-object-fit,cover)] [&_::slotted(video)]:[object-position:var(--media-object-position,center)]\": isShadowDOM,\n\t\"[&_video]:block [&_video]:w-full [&_video]:h-full [&_video]:rounded-[inherit] [&_video]:[object-fit:var(--media-object-fit,contain)] [&_video]:[object-position:var(--media-object-position,center)]\": !isShadowDOM\n}, \"[--default-accent-color:oklch(1_0_0)]\", \"[--border-color:light-dark(oklch(0_0_0/0.15),oklch(1_0_0/0.15))]\", \"[--focus-ring-color:light-dark(oklch(0_0_0),oklch(1_0_0))]\", \"**:[--focus-ring-color:oklch(1_0_0)]\", \"[--container-border-radius:var(--media-border-radius,0.75rem)]\", \"[--media-video-border-radius:var(--container-border-radius)]\", \"[--controls-background-color:transparent]\", \"[--controls-transition-duration:100ms]\", \"[--controls-transition-timing-function:ease-out]\", \"[--error-dialog-transition-duration:150ms]\", \"[--error-dialog-transition-delay:100ms]\", \"[--error-dialog-transition-timing-function:ease-out]\", \"[--popup-transition-duration:100ms]\", \"[--popup-transition-timing-function:ease-out]\", \"[--popover-backdrop-filter:blur(16px)_saturate(1.5)]\", \"[--popover-background-color:oklch(0_0_0/0.5)]\", \"[--popover-border-color:oklch(1_0_0/0.1)]\", \"[--tooltip-backdrop-filter:var(--popover-backdrop-filter)]\", \"[--tooltip-background-color:var(--popover-background-color)]\", \"[--tooltip-border-color:var(--popover-border-color)]\", \"[--tooltip-text-color:currentColor]\", \"min-[1280px]:[&:fullscreen]:[--scale:1.25]\", \"min-[1536px]:[&:fullscreen]:[--scale:1.5]\", \"min-[1920px]:[&:fullscreen]:[--scale:1.75]\", \"motion-reduce:[--error-dialog-transition-duration:50ms]\", \"motion-reduce:[--error-dialog-transition-delay:0ms]\", \"motion-reduce:[--popup-transition-duration:0ms]\", \"[@media(prefers-reduced-transparency:reduce)]:[--controls-background-color:oklch(0_0_0)]\", \"contrast-more:[--controls-background-color:oklch(0_0_0)]\", \"[@media(prefers-reduced-transparency:reduce)]:[--tooltip-background-color:oklch(0_0_0)]\", \"contrast-more:[--tooltip-background-color:oklch(0_0_0)]\", \"pointer-fine:has-[[data-controls]:not([data-visible])]:[--controls-transition-duration:300ms]\", \"pointer-coarse:has-[[data-controls]:not([data-visible])]:[--controls-transition-duration:150ms]\", \"motion-reduce:has-[[data-controls]:not([data-visible])]:[--controls-transition-duration:50ms]\", \"[--media-caption-track-y:--spacing(-2)]\", \"[--media-caption-track-delay:25ms]\", \"[--media-caption-track-duration:var(--controls-transition-duration)]\", \"has-[[data-controls][data-visible]]:[--media-caption-track-y:--spacing(-18)]\", \"@2xl/media-root:has-[[data-controls][data-visible]]:*:[--media-caption-track-y:--spacing(-12)]\", !isShadowDOM ? [\n\t\"[&_video::-webkit-media-text-track-container]:transition-[translate]\",\n\t\"[&_video::-webkit-media-text-track-container]:duration-(--media-caption-track-duration)\",\n\t\"[&_video::-webkit-media-text-track-container]:ease-out\",\n\t\"[&_video::-webkit-media-text-track-container]:delay-(--media-caption-track-delay)\",\n\t\"[&_video::-webkit-media-text-track-container]:translate-y-(--media-caption-track-y)\",\n\t\"[&_video::-webkit-media-text-track-container]:scale-98\",\n\t\"[&_video::-webkit-media-text-track-container]:z-1\",\n\t\"[&_video::-webkit-media-text-track-container]:font-[inherit]\"\n] : [], \"[&:fullscreen]:[--container-border-radius:0]\", {\n\t\"[&:fullscreen_video]:object-contain\": !isShadowDOM,\n\t\"[&:fullscreen_::slotted(video)]:object-contain\": isShadowDOM\n});\nconst controls = cn(controls$1, \"absolute bottom-1 inset-x-1\", \"[--base-side-offset:5] [--base-boundary-offset:1]\", \"[--volume-mask:linear-gradient(to_right,transparent_10%,black_25%,black_100%)]\", \"gap-x-2 flex-wrap rounded-[--spacing(3)] group/controls\", \"text-white z-20\", \"peer-data-open/error:hidden\", \"ease-(--controls-transition-timing-function)\", \"duration-[calc(var(--controls-transition-duration)/2)]\", \"not-data-visible:duration-(--controls-transition-duration)\", \"pointer-fine:transition-[translate,filter,opacity]\", \"pointer-coarse:transition-[translate,opacity]\", \"not-data-visible:opacity-0 not-data-visible:pointer-events-none\", \"motion-safe:not-data-visible:translate-y-full\", \"pointer-fine:motion-safe:not-data-visible:blur-sm\", \"@2xl/media-root:flex-nowrap @2xl/media-root:[--controls-padding:2] @2xl/media-root:[--base-side-offset:2]\");\nconst volumeMaskTarget = cn(\"[mask-image:var(--volume-mask-image,none)]\", \"[mask-repeat:no-repeat]\", \"[mask-position:var(--volume-mask-position,100%_0)]\", \"[mask-size:var(--volume-mask-size,200%_100%)]\", \"[transition:mask-position_50ms_ease-out]\");\nconst buttonGroupStart = cn(buttonGroup, \"flex-1 @2xl/media-root:flex-none\");\nconst buttonGroupEnd = cn(buttonGroup, volumeMaskTarget, \"flex-1 justify-end @2xl/media-root:flex-none\", \"group-has-[[data-volume-level][aria-expanded=true]]/controls:@max-2xl/media-root:[--volume-mask-image:var(--volume-mask)]\", \"group-has-[[data-volume-level][aria-expanded=true]]/controls:@max-2xl/media-root:[--volume-mask-position:0_0]\", \"group-has-[[data-volume-level][aria-expanded=true]]/controls:@max-2xl/media-root:[--volume-mask-size:400%_100%]\");\nconst spacer = \"grow\";\nconst time = {\n\t...time$1,\n\tcontrols: cn(time$1.controls, volumeMaskTarget, \"[--slider-height:--spacing(5)] grow-0 shrink-0 basis-full order-[-1] px-1.5\", \"@2xl/media-root:[--slider-height:--spacing(8)] @2xl/media-root:grow @2xl/media-root:shrink @2xl/media-root:basis-0 @2xl/media-root:order-[unset]\", \"group-has-[[data-volume-level][aria-expanded=true]]/controls:@2xl/media-root:[--volume-mask-image:var(--volume-mask)]\", \"group-has-[[data-volume-level][aria-expanded=true]]/controls:@2xl/media-root:[--volume-mask-position:0_0]\")\n};\nconst error = {\n\t...error$1,\n\troot: cn(error$1.root, \"pointer-events-none outline-none\"),\n\tdialog: cn(error$1.dialog, \"pointer-events-auto w-full max-w-64 p-4 rounded-none\"),\n\tcontent: cn(error$1.content, \"p-0 py-1.5\"),\n\ttitle: \"text-(length:--font-size-medium)\"\n};\nconst thumbnail = thumbnail$1;\nconst slider = {\n\t...slider$1,\n\ttrack: cn(slider$1.track, \"ring-1 ring-black/5\"),\n\tpreview: cn(slider$1.preview, \"[--preview-end-inset:calc(100cqi-100%)]\", \"[--preview-left:clamp(calc(var(--max-size)/2),var(--media-slider-pointer),calc(100%-var(--max-size)/2+var(--preview-end-inset)))]\", \"@2xl/media-root:[--preview-left:var(--media-slider-pointer)]\")\n};\nconst popup = {\n\t...popup$1,\n\tvolume: cn(popup$1.popover, \"p-0 bg-transparent\", \"@max-2xl/media-root:[--media-popover-side-offset:--spacing(3)]\")\n};\nconst menu = {\n\t...menu$1,\n\troot: menu$1.root,\n\tsettings: menu$1.settings\n};\n//#endregion\nexport { badge, bufferingIndicator, button, buttonGroup, buttonGroupEnd, buttonGroupStart, container, controls, error, icon, iconContainer, iconFlipped, iconHidden, iconState, inputIndicatorOverlay, menu, overlay, playbackRate, popup, poster, seek, seekIndicator, slider, spacer, statusIndicator, thumbnail, time, volumeIndicator };\n\n//# sourceMappingURL=video.tailwind.js.map"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAM,aAAa,gBAAgB,GAAG,aAAa,cAAc,0BAA0B,+EAA+E,+BAA+B,iFAAiF;CACzR,0RAA0R;CAC1R,wMAAwM,CAAC;AAC1M,GAAG,yCAAyC,oEAAoE,8DAA8D,wCAAwC,kEAAkE,gEAAgE,6CAA6C,0CAA0C,oDAAoD,8CAA8C,2CAA2C,wDAAwD,uCAAuC,iDAAiD,wDAAwD,iDAAiD,6CAA6C,8DAA8D,gEAAgE,wDAAwD,uCAAuC,8CAA8C,6CAA6C,8CAA8C,2DAA2D,uDAAuD,mDAAmD,4FAA4F,4DAA4D,2FAA2F,2DAA2D,iGAAiG,mGAAmG,iGAAiG,2CAA2C,sCAAsC,wEAAwE,gFAAgF,kGAAkG,CAAC,cAAc;CACzxE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD,IAAI,CAAC,GAAG,gDAAgD;CACvD,uCAAuC,CAAC;CACxC,kDAAkD;AACnD,CAAC;AACD,MAAM,WAAW,GAAG,YAAY,+BAA+B,qDAAqD,kFAAkF,2DAA2D,mBAAmB,+BAA+B,gDAAgD,0DAA0D,8DAA8D,sDAAsD,iDAAiD,mEAAmE,iDAAiD,qDAAqD,2GAA2G;AACt1B,MAAM,mBAAmB,GAAG,8CAA8C,2BAA2B,sDAAsD,iDAAiD,0CAA0C;AACtP,MAAM,mBAAmB,GAAG,aAAa,kCAAkC;AAC3E,MAAM,iBAAiB,GAAG,aAAa,kBAAkB,gDAAgD,6HAA6H,iHAAiH,iHAAiH;AACxc,MAAM,SAAS;AACf,MAAM,OAAO;CACZ,GAAG;CACH,UAAU,GAAG,OAAO,UAAU,kBAAkB,+EAA+E,oJAAoJ,yHAAyH,2GAA2G;AACxf;AACA,MAAM,QAAQ;CACb,GAAG;CACH,MAAM,GAAG,QAAQ,MAAM,kCAAkC;CACzD,QAAQ,GAAG,QAAQ,QAAQ,sDAAsD;CACjF,SAAS,GAAG,QAAQ,SAAS,YAAY;CACzC,OAAO;AACR;AACA,MAAM,YAAY;AAClB,MAAM,SAAS;CACd,GAAG;CACH,OAAO,GAAG,SAAS,OAAO,qBAAqB;CAC/C,SAAS,GAAG,SAAS,SAAS,2CAA2C,qIAAqI,8DAA8D;AAC7Q;AACA,MAAM,QAAQ;CACb,GAAG;CACH,QAAQ,GAAG,QAAQ,SAAS,sBAAsB,gEAAgE;AACnH;AACA,MAAM,OAAO;CACZ,GAAG;CACH,MAAM,OAAO;CACb,UAAU,OAAO;AAClB"}
@@ -1 +1 @@
1
- {"version":3,"file":"thumbnail.d.ts","names":[],"sources":["../../../../src/ui/thumbnail/thumbnail.tsx"],"mappings":";;;UAgBiB,uBAAuB,wBAAwB,cAAc,QAAQ,cAAc;;EAElG,aAAa;;cAGF,2BAAS,0BAAA,KAAA,yCAAA,cAAA;kBAsGL;OACH,QAAQ;OACR,QAAQ,cAAc"}
1
+ {"version":3,"file":"thumbnail.d.ts","names":[],"sources":["../../../../src/ui/thumbnail/thumbnail.tsx"],"mappings":";;;UAkBiB,uBAAuB,wBAAwB,cAAc,QAAQ,cAAc;;EAElG,aAAa;;cAwBF,2BAAS,0BAAA,KAAA,yCAAA,cAAA;kBAwGL;OACH,QAAQ;OACR,QAAQ,cAAc"}
@@ -4,8 +4,24 @@ import { renderElement } from "../../utils/use-render.js";
4
4
  import { createThumbnail, selectTextTrack } from "@videojs/core/dom";
5
5
  import { forwardRef, useMemo, useRef, useState } from "react";
6
6
  import { jsx } from "react/jsx-runtime";
7
+ import { isNull, isUndefined } from "@videojs/utils/predicate";
7
8
  import { ThumbnailCore, ThumbnailDataAttrs, mapCuesToThumbnails } from "@videojs/core";
8
9
  //#region src/ui/thumbnail/thumbnail.tsx
10
+ /**
11
+ * Leaving `crossOrigin` unset means "follow the media element", so thumbnails
12
+ * keep working on a CORS-enabled player without a skin having to thread a prop
13
+ * through. `null` opts out and fetches the sprites no-CORS. `''` is passed
14
+ * straight through, since the CORS-settings attribute reads it as Anonymous.
15
+ *
16
+ * Only the `<track>` path inherits: `thumbnails` passed directly may point at a
17
+ * host that has nothing to do with the media element.
18
+ */
19
+ function resolveCrossOrigin(explicit, external, inherited) {
20
+ if (isNull(explicit)) return void 0;
21
+ if (!isUndefined(explicit)) return explicit;
22
+ if (external?.length) return void 0;
23
+ return inherited ?? void 0;
24
+ }
9
25
  const Thumbnail = forwardRef(function Thumbnail(componentProps, forwardedRef) {
10
26
  const { render, className, style, time = 0, thumbnails: externalThumbnails, crossOrigin, loading, fetchPriority, ...elementProps } = componentProps;
11
27
  const [core] = useState(() => new ThumbnailCore());
@@ -28,6 +44,7 @@ const Thumbnail = forwardRef(function Thumbnail(componentProps, forwardedRef) {
28
44
  thumbnails,
29
45
  time
30
46
  ]);
47
+ const resolvedCrossOrigin = resolveCrossOrigin(crossOrigin, externalThumbnails, textTrack?.thumbnailTrackCrossOrigin);
31
48
  handle.updateSrc(thumbnail?.url);
32
49
  const state = core.getState(handle.loading, handle.error, thumbnail);
33
50
  let containerStyle = { overflow: "hidden" };
@@ -67,7 +84,7 @@ const Thumbnail = forwardRef(function Thumbnail(componentProps, forwardedRef) {
67
84
  "aria-hidden": "true",
68
85
  decoding: "async",
69
86
  src: thumbnail?.url,
70
- crossOrigin: crossOrigin === "" || crossOrigin === null ? void 0 : crossOrigin,
87
+ crossOrigin: resolvedCrossOrigin,
71
88
  loading,
72
89
  style: imgStyle,
73
90
  fetchPriority
@@ -1 +1 @@
1
- {"version":3,"file":"thumbnail.js","names":[],"sources":["../../../../src/ui/thumbnail/thumbnail.tsx"],"sourcesContent":["import {\n mapCuesToThumbnails,\n ThumbnailCore,\n ThumbnailDataAttrs,\n type ThumbnailFetchPriority,\n type ThumbnailImage,\n} from '@videojs/core';\nimport { createThumbnail, selectTextTrack } from '@videojs/core/dom';\nimport type { CSSProperties } from 'react';\nimport { forwardRef, useMemo, useRef, useState } from 'react';\n\nimport { useOptionalPlayer } from '../../player/context';\nimport type { UIComponentProps } from '../../utils/types';\nimport { useDestroy } from '../../utils/use-destroy';\nimport { renderElement } from '../../utils/use-render';\n\nexport interface ThumbnailProps extends UIComponentProps<'div', ThumbnailCore.State>, ThumbnailCore.Props {\n /** Pre-parsed thumbnail images — bypasses the automatic `<track>` detection. */\n thumbnails?: ThumbnailImage[] | undefined;\n}\n\nexport const Thumbnail = forwardRef<HTMLDivElement, ThumbnailProps>(function Thumbnail(componentProps, forwardedRef) {\n const {\n render,\n className,\n style,\n time = 0,\n thumbnails: externalThumbnails,\n crossOrigin,\n loading,\n fetchPriority,\n ...elementProps\n } = componentProps;\n\n const [core] = useState(() => new ThumbnailCore());\n const divRef = useRef<HTMLDivElement>(null);\n const imgRef = useRef<HTMLImageElement>(null);\n const textTrack = useOptionalPlayer(selectTextTrack);\n\n // Force re-render when the handle's state changes (img load/error, resize).\n const [, setRenderToken] = useState(0);\n\n const [handle] = useState(() =>\n createThumbnail({\n getContainer: () => divRef.current,\n getImg: () => imgRef.current,\n onStateChange: () => setRenderToken((n) => n + 1),\n })\n );\n\n useDestroy(handle, () => handle.connect());\n\n // Resolve thumbnails: external prop takes priority over auto <track> path.\n const thumbnails = useMemo(() => {\n if (externalThumbnails && externalThumbnails.length > 0) return externalThumbnails;\n return textTrack && textTrack.thumbnailCues.length > 0\n ? mapCuesToThumbnails(textTrack.thumbnailCues, textTrack.thumbnailTrackSrc ?? undefined)\n : [];\n }, [externalThumbnails, textTrack]);\n\n const thumbnail = useMemo(() => core.findActiveThumbnail(thumbnails, time), [core, thumbnails, time]);\n\n // Track src changes via the handle.\n handle.updateSrc(thumbnail?.url);\n\n const state = core.getState(handle.loading, handle.error, thumbnail);\n\n // Compute styles declaratively from resize result.\n let containerStyle: CSSProperties = { overflow: 'hidden' };\n let imgStyle: CSSProperties | undefined;\n\n if (thumbnail && handle.naturalWidth && handle.naturalHeight) {\n const constraints = handle.readConstraints();\n const result = core.resize(thumbnail, handle.naturalWidth, handle.naturalHeight, constraints);\n\n if (result) {\n containerStyle = {\n overflow: 'hidden',\n width: result.containerWidth,\n height: result.containerHeight,\n };\n imgStyle = {\n width: result.imageWidth,\n height: result.imageHeight,\n maxWidth: 'none',\n transform:\n result.offsetX || result.offsetY ? `translate(-${result.offsetX}px, -${result.offsetY}px)` : undefined,\n };\n }\n }\n\n return renderElement(\n 'div',\n { render, className, style },\n {\n state,\n stateAttrMap: ThumbnailDataAttrs,\n ref: [forwardedRef, divRef],\n props: [\n core.getAttrs(state),\n { style: containerStyle },\n elementProps,\n {\n children: (\n <img\n ref={imgRef}\n alt=\"\"\n aria-hidden=\"true\"\n decoding=\"async\"\n src={thumbnail?.url}\n crossOrigin={crossOrigin === '' || crossOrigin === null ? undefined : crossOrigin}\n loading={loading}\n style={imgStyle}\n // React's types omit `| undefined` from fetchPriority — cast to satisfy exactOptionalPropertyTypes.\n fetchPriority={fetchPriority as ThumbnailFetchPriority}\n />\n ),\n },\n ],\n }\n );\n});\n\nexport namespace Thumbnail {\n export type Props = ThumbnailProps;\n export type State = ThumbnailCore.State;\n}\n"],"mappings":";;;;;;;;AAqBA,MAAa,YAAY,WAA2C,SAAS,UAAU,gBAAgB,cAAc;CACnH,MAAM,EACJ,QACA,WACA,OACA,OAAO,GACP,YAAY,oBACZ,aACA,SACA,eACA,GAAG,iBACD;CAEJ,MAAM,CAAC,QAAQ,eAAe,IAAI,cAAc,CAAC;CACjD,MAAM,SAAS,OAAuB,IAAI;CAC1C,MAAM,SAAS,OAAyB,IAAI;CAC5C,MAAM,YAAY,kBAAkB,eAAe;CAGnD,MAAM,GAAG,kBAAkB,SAAS,CAAC;CAErC,MAAM,CAAC,UAAU,eACf,gBAAgB;EACd,oBAAoB,OAAO;EAC3B,cAAc,OAAO;EACrB,qBAAqB,gBAAgB,MAAM,IAAI,CAAC;CAClD,CAAC,CACH;CAEA,WAAW,cAAc,OAAO,QAAQ,CAAC;CAGzC,MAAM,aAAa,cAAc;EAC/B,IAAI,sBAAsB,mBAAmB,SAAS,GAAG,OAAO;EAChE,OAAO,aAAa,UAAU,cAAc,SAAS,IACjD,oBAAoB,UAAU,eAAe,UAAU,qBAAqB,KAAA,CAAS,IACrF,CAAC;CACP,GAAG,CAAC,oBAAoB,SAAS,CAAC;CAElC,MAAM,YAAY,cAAc,KAAK,oBAAoB,YAAY,IAAI,GAAG;EAAC;EAAM;EAAY;CAAI,CAAC;CAGpG,OAAO,UAAU,WAAW,GAAG;CAE/B,MAAM,QAAQ,KAAK,SAAS,OAAO,SAAS,OAAO,OAAO,SAAS;CAGnE,IAAI,iBAAgC,EAAE,UAAU,SAAS;CACzD,IAAI;CAEJ,IAAI,aAAa,OAAO,gBAAgB,OAAO,eAAe;EAC5D,MAAM,cAAc,OAAO,gBAAgB;EAC3C,MAAM,SAAS,KAAK,OAAO,WAAW,OAAO,cAAc,OAAO,eAAe,WAAW;EAE5F,IAAI,QAAQ;GACV,iBAAiB;IACf,UAAU;IACV,OAAO,OAAO;IACd,QAAQ,OAAO;GACjB;GACA,WAAW;IACT,OAAO,OAAO;IACd,QAAQ,OAAO;IACf,UAAU;IACV,WACE,OAAO,WAAW,OAAO,UAAU,cAAc,OAAO,QAAQ,OAAO,OAAO,QAAQ,OAAO,KAAA;GACjG;EACF;CACF;CAEA,OAAO,cACL,OACA;EAAE;EAAQ;EAAW;CAAM,GAC3B;EACE;EACA,cAAc;EACd,KAAK,CAAC,cAAc,MAAM;EAC1B,OAAO;GACL,KAAK,SAAS,KAAK;GACnB,EAAE,OAAO,eAAe;GACxB;GACA,EACE,UACE,oBAAC,OAAD;IACE,KAAK;IACL,KAAI;IACJ,eAAY;IACZ,UAAS;IACT,KAAK,WAAW;IAChB,aAAa,gBAAgB,MAAM,gBAAgB,OAAO,KAAA,IAAY;IAC7D;IACT,OAAO;IAEQ;GAChB,CAAA,EAEL;EACF;CACF,CACF;AACF,CAAC"}
1
+ {"version":3,"file":"thumbnail.js","names":[],"sources":["../../../../src/ui/thumbnail/thumbnail.tsx"],"sourcesContent":["import {\n mapCuesToThumbnails,\n ThumbnailCore,\n ThumbnailDataAttrs,\n type ThumbnailFetchPriority,\n type ThumbnailImage,\n} from '@videojs/core';\nimport { createThumbnail, selectTextTrack } from '@videojs/core/dom';\nimport type { MediaTextTrackState } from '@videojs/media';\nimport { isNull, isUndefined } from '@videojs/utils/predicate';\nimport type { CSSProperties } from 'react';\nimport { forwardRef, useMemo, useRef, useState } from 'react';\n\nimport { useOptionalPlayer } from '../../player/context';\nimport type { UIComponentProps } from '../../utils/types';\nimport { useDestroy } from '../../utils/use-destroy';\nimport { renderElement } from '../../utils/use-render';\n\nexport interface ThumbnailProps extends UIComponentProps<'div', ThumbnailCore.State>, ThumbnailCore.Props {\n /** Pre-parsed thumbnail images — bypasses the automatic `<track>` detection. */\n thumbnails?: ThumbnailImage[] | undefined;\n}\n\n/**\n * Leaving `crossOrigin` unset means \"follow the media element\", so thumbnails\n * keep working on a CORS-enabled player without a skin having to thread a prop\n * through. `null` opts out and fetches the sprites no-CORS. `''` is passed\n * straight through, since the CORS-settings attribute reads it as Anonymous.\n *\n * Only the `<track>` path inherits: `thumbnails` passed directly may point at a\n * host that has nothing to do with the media element.\n */\nfunction resolveCrossOrigin(\n explicit: ThumbnailCore.Props['crossOrigin'],\n external: ThumbnailImage[] | undefined,\n inherited: MediaTextTrackState['thumbnailTrackCrossOrigin'] | undefined\n) {\n if (isNull(explicit)) return undefined;\n if (!isUndefined(explicit)) return explicit;\n if (external?.length) return undefined;\n\n return inherited ?? undefined;\n}\n\nexport const Thumbnail = forwardRef<HTMLDivElement, ThumbnailProps>(function Thumbnail(componentProps, forwardedRef) {\n const {\n render,\n className,\n style,\n time = 0,\n thumbnails: externalThumbnails,\n crossOrigin,\n loading,\n fetchPriority,\n ...elementProps\n } = componentProps;\n\n const [core] = useState(() => new ThumbnailCore());\n const divRef = useRef<HTMLDivElement>(null);\n const imgRef = useRef<HTMLImageElement>(null);\n const textTrack = useOptionalPlayer(selectTextTrack);\n\n // Force re-render when the handle's state changes (img load/error, resize).\n const [, setRenderToken] = useState(0);\n\n const [handle] = useState(() =>\n createThumbnail({\n getContainer: () => divRef.current,\n getImg: () => imgRef.current,\n onStateChange: () => setRenderToken((n) => n + 1),\n })\n );\n\n useDestroy(handle, () => handle.connect());\n\n // Resolve thumbnails: external prop takes priority over auto <track> path.\n const thumbnails = useMemo(() => {\n if (externalThumbnails && externalThumbnails.length > 0) return externalThumbnails;\n return textTrack && textTrack.thumbnailCues.length > 0\n ? mapCuesToThumbnails(textTrack.thumbnailCues, textTrack.thumbnailTrackSrc ?? undefined)\n : [];\n }, [externalThumbnails, textTrack]);\n\n const thumbnail = useMemo(() => core.findActiveThumbnail(thumbnails, time), [core, thumbnails, time]);\n\n const resolvedCrossOrigin = resolveCrossOrigin(crossOrigin, externalThumbnails, textTrack?.thumbnailTrackCrossOrigin);\n\n // Track src changes via the handle.\n handle.updateSrc(thumbnail?.url);\n\n const state = core.getState(handle.loading, handle.error, thumbnail);\n\n // Compute styles declaratively from resize result.\n let containerStyle: CSSProperties = { overflow: 'hidden' };\n let imgStyle: CSSProperties | undefined;\n\n if (thumbnail && handle.naturalWidth && handle.naturalHeight) {\n const constraints = handle.readConstraints();\n const result = core.resize(thumbnail, handle.naturalWidth, handle.naturalHeight, constraints);\n\n if (result) {\n containerStyle = {\n overflow: 'hidden',\n width: result.containerWidth,\n height: result.containerHeight,\n };\n imgStyle = {\n width: result.imageWidth,\n height: result.imageHeight,\n maxWidth: 'none',\n transform:\n result.offsetX || result.offsetY ? `translate(-${result.offsetX}px, -${result.offsetY}px)` : undefined,\n };\n }\n }\n\n return renderElement(\n 'div',\n { render, className, style },\n {\n state,\n stateAttrMap: ThumbnailDataAttrs,\n ref: [forwardedRef, divRef],\n props: [\n core.getAttrs(state),\n { style: containerStyle },\n elementProps,\n {\n children: (\n <img\n ref={imgRef}\n alt=\"\"\n aria-hidden=\"true\"\n decoding=\"async\"\n src={thumbnail?.url}\n crossOrigin={resolvedCrossOrigin}\n loading={loading}\n style={imgStyle}\n // React's types omit `| undefined` from fetchPriority — cast to satisfy exactOptionalPropertyTypes.\n fetchPriority={fetchPriority as ThumbnailFetchPriority}\n />\n ),\n },\n ],\n }\n );\n});\n\nexport namespace Thumbnail {\n export type Props = ThumbnailProps;\n export type State = ThumbnailCore.State;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAgCA,SAAS,mBACP,UACA,UACA,WACA;CACA,IAAI,OAAO,QAAQ,GAAG,OAAO,KAAA;CAC7B,IAAI,CAAC,YAAY,QAAQ,GAAG,OAAO;CACnC,IAAI,UAAU,QAAQ,OAAO,KAAA;CAE7B,OAAO,aAAa,KAAA;AACtB;AAEA,MAAa,YAAY,WAA2C,SAAS,UAAU,gBAAgB,cAAc;CACnH,MAAM,EACJ,QACA,WACA,OACA,OAAO,GACP,YAAY,oBACZ,aACA,SACA,eACA,GAAG,iBACD;CAEJ,MAAM,CAAC,QAAQ,eAAe,IAAI,cAAc,CAAC;CACjD,MAAM,SAAS,OAAuB,IAAI;CAC1C,MAAM,SAAS,OAAyB,IAAI;CAC5C,MAAM,YAAY,kBAAkB,eAAe;CAGnD,MAAM,GAAG,kBAAkB,SAAS,CAAC;CAErC,MAAM,CAAC,UAAU,eACf,gBAAgB;EACd,oBAAoB,OAAO;EAC3B,cAAc,OAAO;EACrB,qBAAqB,gBAAgB,MAAM,IAAI,CAAC;CAClD,CAAC,CACH;CAEA,WAAW,cAAc,OAAO,QAAQ,CAAC;CAGzC,MAAM,aAAa,cAAc;EAC/B,IAAI,sBAAsB,mBAAmB,SAAS,GAAG,OAAO;EAChE,OAAO,aAAa,UAAU,cAAc,SAAS,IACjD,oBAAoB,UAAU,eAAe,UAAU,qBAAqB,KAAA,CAAS,IACrF,CAAC;CACP,GAAG,CAAC,oBAAoB,SAAS,CAAC;CAElC,MAAM,YAAY,cAAc,KAAK,oBAAoB,YAAY,IAAI,GAAG;EAAC;EAAM;EAAY;CAAI,CAAC;CAEpG,MAAM,sBAAsB,mBAAmB,aAAa,oBAAoB,WAAW,yBAAyB;CAGpH,OAAO,UAAU,WAAW,GAAG;CAE/B,MAAM,QAAQ,KAAK,SAAS,OAAO,SAAS,OAAO,OAAO,SAAS;CAGnE,IAAI,iBAAgC,EAAE,UAAU,SAAS;CACzD,IAAI;CAEJ,IAAI,aAAa,OAAO,gBAAgB,OAAO,eAAe;EAC5D,MAAM,cAAc,OAAO,gBAAgB;EAC3C,MAAM,SAAS,KAAK,OAAO,WAAW,OAAO,cAAc,OAAO,eAAe,WAAW;EAE5F,IAAI,QAAQ;GACV,iBAAiB;IACf,UAAU;IACV,OAAO,OAAO;IACd,QAAQ,OAAO;GACjB;GACA,WAAW;IACT,OAAO,OAAO;IACd,QAAQ,OAAO;IACf,UAAU;IACV,WACE,OAAO,WAAW,OAAO,UAAU,cAAc,OAAO,QAAQ,OAAO,OAAO,QAAQ,OAAO,KAAA;GACjG;EACF;CACF;CAEA,OAAO,cACL,OACA;EAAE;EAAQ;EAAW;CAAM,GAC3B;EACE;EACA,cAAc;EACd,KAAK,CAAC,cAAc,MAAM;EAC1B,OAAO;GACL,KAAK,SAAS,KAAK;GACnB,EAAE,OAAO,eAAe;GACxB;GACA,EACE,UACE,oBAAC,OAAD;IACE,KAAK;IACL,KAAI;IACJ,eAAY;IACZ,UAAS;IACT,KAAK,WAAW;IAChB,aAAa;IACJ;IACT,OAAO;IAEQ;GAChB,CAAA,EAEL;EACF;CACF,CACF;AACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { Title, TitleProps } from "./title.js";
2
+ export { Title, type TitleProps };
@@ -0,0 +1,2 @@
1
+ import { Title } from "./title.js";
2
+ export { Title };
@@ -0,0 +1,27 @@
1
+ import { UIComponentProps } from "../../utils/types.js";
2
+ import { TitleCore } from "@videojs/core";
3
+ //#region src/ui/title/title.d.ts
4
+ interface TitleProps extends Omit<UIComponentProps<'span', TitleCore.State>, 'children'> {}
5
+ /**
6
+ * Displays the resolved content title.
7
+ *
8
+ * The component owns its text content. Set the title through the player's
9
+ * `title` prop rather than by passing children.
10
+ *
11
+ * Renders nothing when no title resolves.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * <Title />
16
+ *
17
+ * <Title className="title" />
18
+ * ```
19
+ */
20
+ declare const Title: import("react").ForwardRefExoticComponent<Omit<TitleProps, "ref"> & import("react").RefAttributes<HTMLSpanElement>>;
21
+ declare namespace Title {
22
+ type Props = TitleProps;
23
+ type State = TitleCore.State;
24
+ }
25
+ //#endregion
26
+ export { Title, TitleProps };
27
+ //# sourceMappingURL=title.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"title.d.ts","names":[],"sources":["../../../../src/ui/title/title.tsx"],"mappings":";;;UAWiB,mBAAmB,KAAK,yBAAyB,UAAU;;;;;;;;;;;;;;;;cAiB/D,uBAAK,0BAAA,KAAA,qCAAA,cAAA;kBA+BD;OACH,QAAQ;OACR,QAAQ,UAAU"}
@@ -0,0 +1,47 @@
1
+ "use client";
2
+ import { usePlayer } from "../../player/context.js";
3
+ import { renderElement } from "../../utils/use-render.js";
4
+ import { logMissingFeature, selectMetadata } from "@videojs/core/dom";
5
+ import { forwardRef, useState } from "react";
6
+ import { TitleCore, TitleDataAttrs } from "@videojs/core";
7
+ //#region src/ui/title/title.tsx
8
+ /**
9
+ * Displays the resolved content title.
10
+ *
11
+ * The component owns its text content. Set the title through the player's
12
+ * `title` prop rather than by passing children.
13
+ *
14
+ * Renders nothing when no title resolves.
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * <Title />
19
+ *
20
+ * <Title className="title" />
21
+ * ```
22
+ */
23
+ const Title = forwardRef(function Title(componentProps, forwardedRef) {
24
+ const { render, className, style, ...elementProps } = componentProps;
25
+ const metadata = usePlayer(selectMetadata);
26
+ const [core] = useState(() => new TitleCore());
27
+ if (!metadata) {
28
+ logMissingFeature("Title", "metadata");
29
+ return null;
30
+ }
31
+ const state = core.getState(metadata);
32
+ if (state.hidden) return null;
33
+ return renderElement("span", {
34
+ render,
35
+ className,
36
+ style
37
+ }, {
38
+ state,
39
+ stateAttrMap: TitleDataAttrs,
40
+ ref: [forwardedRef],
41
+ props: [{ children: state.title }, elementProps]
42
+ });
43
+ });
44
+ //#endregion
45
+ export { Title };
46
+
47
+ //# sourceMappingURL=title.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"title.js","names":[],"sources":["../../../../src/ui/title/title.tsx"],"sourcesContent":["'use client';\n\nimport { TitleCore, TitleDataAttrs } from '@videojs/core';\nimport { logMissingFeature, selectMetadata } from '@videojs/core/dom';\nimport type { ForwardedRef } from 'react';\nimport { forwardRef, useState } from 'react';\n\nimport { usePlayer } from '../../player/context';\nimport type { UIComponentProps } from '../../utils/types';\nimport { renderElement } from '../../utils/use-render';\n\nexport interface TitleProps extends Omit<UIComponentProps<'span', TitleCore.State>, 'children'> {}\n\n/**\n * Displays the resolved content title.\n *\n * The component owns its text content. Set the title through the player's\n * `title` prop rather than by passing children.\n *\n * Renders nothing when no title resolves.\n *\n * @example\n * ```tsx\n * <Title />\n *\n * <Title className=\"title\" />\n * ```\n */\nexport const Title = forwardRef(function Title(\n componentProps: TitleProps,\n forwardedRef: ForwardedRef<HTMLSpanElement>\n) {\n const { render, className, style, ...elementProps } = componentProps;\n\n const metadata = usePlayer(selectMetadata);\n\n const [core] = useState(() => new TitleCore());\n\n if (!metadata) {\n if (__DEV__) logMissingFeature('Title', 'metadata');\n return null;\n }\n\n const state = core.getState(metadata);\n\n if (state.hidden) return null;\n\n return renderElement(\n 'span',\n { render, className, style },\n {\n state,\n stateAttrMap: TitleDataAttrs,\n ref: [forwardedRef],\n props: [{ children: state.title }, elementProps],\n }\n );\n});\n\nexport namespace Title {\n export type Props = TitleProps;\n export type State = TitleCore.State;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA4BA,MAAa,QAAQ,WAAW,SAAS,MACvC,gBACA,cACA;CACA,MAAM,EAAE,QAAQ,WAAW,OAAO,GAAG,iBAAiB;CAEtD,MAAM,WAAW,UAAU,cAAc;CAEzC,MAAM,CAAC,QAAQ,eAAe,IAAI,UAAU,CAAC;CAE7C,IAAI,CAAC,UAAU;EACA,kBAAkB,SAAS,UAAU;EAClD,OAAO;CACT;CAEA,MAAM,QAAQ,KAAK,SAAS,QAAQ;CAEpC,IAAI,MAAM,QAAQ,OAAO;CAEzB,OAAO,cACL,QACA;EAAE;EAAQ;EAAW;CAAM,GAC3B;EACE;EACA,cAAc;EACd,KAAK,CAAC,YAAY;EAClB,OAAO,CAAC,EAAE,UAAU,MAAM,MAAM,GAAG,YAAY;CACjD,CACF;AACF,CAAC"}
package/docs/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @videojs/react documentation
2
2
 
3
- Bundled markdown documentation for `@videojs/react` v10.0.0-beta.29.
3
+ Bundled markdown documentation for `@videojs/react` v10.0.0-beta.30.
4
4
 
5
5
  Start at [`./llms.txt`](./llms.txt) — it's the structured index of every page in this directory.
6
6
 
@@ -44,7 +44,7 @@ Feature bundle
44
44
 
45
45
  Features
46
46
 
47
- [playback](../reference/feature-playback.md), [playbackRate](../reference/feature-playback-rate.md), [quality](../reference/feature-quality.md), [audioTrack](../reference/feature-audio-track.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [fullscreen](../reference/feature-fullscreen.md), [pip](../reference/feature-pip.md), [remotePlayback](../reference/feature-remote-playback.md), [controls](../reference/feature-controls.md), [textTrack](../reference/feature-text-tracks.md), [error](../reference/feature-error.md), metadata
47
+ [playback](../reference/feature-playback.md), [playbackRate](../reference/feature-playback-rate.md), [quality](../reference/feature-quality.md), [audioTrack](../reference/feature-audio-track.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [fullscreen](../reference/feature-fullscreen.md), [pip](../reference/feature-pip.md), [remotePlayback](../reference/feature-remote-playback.md), [controls](../reference/feature-controls.md), [textTrack](../reference/feature-text-tracks.md), [error](../reference/feature-error.md), [metadata](../reference/feature-metadata.md)
48
48
 
49
49
  Skins
50
50
 
@@ -72,7 +72,7 @@ Feature bundle
72
72
 
73
73
  Features
74
74
 
75
- [playback](../reference/feature-playback.md), [playbackRate](../reference/feature-playback-rate.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [error](../reference/feature-error.md), metadata
75
+ [playback](../reference/feature-playback.md), [playbackRate](../reference/feature-playback-rate.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [error](../reference/feature-error.md), [metadata](../reference/feature-metadata.md)
76
76
 
77
77
  Skins
78
78
 
@@ -124,7 +124,7 @@ Feature bundle
124
124
 
125
125
  Features
126
126
 
127
- [playback](../reference/feature-playback.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [error](../reference/feature-error.md), [live](../reference/feature-live.md), metadata
127
+ [playback](../reference/feature-playback.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [error](../reference/feature-error.md), [live](../reference/feature-live.md), [metadata](../reference/feature-metadata.md)
128
128
 
129
129
  Skins
130
130
 
@@ -152,7 +152,7 @@ Feature bundle
152
152
 
153
153
  Features
154
154
 
155
- [playback](../reference/feature-playback.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [fullscreen](../reference/feature-fullscreen.md), [pip](../reference/feature-pip.md), [remotePlayback](../reference/feature-remote-playback.md), [controls](../reference/feature-controls.md), [textTrack](../reference/feature-text-tracks.md), [error](../reference/feature-error.md), [live](../reference/feature-live.md), metadata
155
+ [playback](../reference/feature-playback.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [fullscreen](../reference/feature-fullscreen.md), [pip](../reference/feature-pip.md), [remotePlayback](../reference/feature-remote-playback.md), [controls](../reference/feature-controls.md), [textTrack](../reference/feature-text-tracks.md), [error](../reference/feature-error.md), [live](../reference/feature-live.md), [metadata](../reference/feature-metadata.md)
156
156
 
157
157
  Skins
158
158
 
@@ -354,7 +354,6 @@ export interface VideoPlayerProps {
354
354
  style?: CSSProperties;
355
355
  className?: string;
356
356
  renderPoster?: Poster.Props['render'];
357
- placeholder?: string | undefined;
358
357
  }
359
358
 
360
359
  /**
@@ -366,18 +365,10 @@ export interface VideoPlayerProps {
366
365
  * />
367
366
  * ```
368
367
  */
369
- export function VideoPlayer({ src, poster, className, renderPoster, placeholder, style, ...rest }: VideoPlayerProps): ReactNode {
370
- const containerStyle = placeholder
371
- ? ({ '--media-poster-placeholder': `url(${placeholder})`, ...style } as CSSProperties)
372
- : style;
373
-
368
+ export function VideoPlayer({ src, poster, className, renderPoster, style, ...rest }: VideoPlayerProps): ReactNode {
374
369
  return (
375
370
  <Player poster={poster}>
376
- <Container
377
- className={`media-default-skin media-default-skin--video ${className ?? ''}`}
378
- style={containerStyle}
379
- {...rest}
380
- >
371
+ <Container className={`media-default-skin media-default-skin--video ${className ?? ''}`} style={style} {...rest}>
381
372
  <Video src={src} playsInline />
382
373
 
383
374
  <Poster render={renderPoster} />
@@ -1066,7 +1057,7 @@ export const { Player } = createPlayer({ features: videoFeatures });
1066
1057
  @media (prefers-reduced-motion: reduce) {
1067
1058
  .media-default-skin .media-button {
1068
1059
  scale: 1;
1069
- transition-property: background-color, color, outline-offset;
1060
+ transition-property: background-color, color;
1070
1061
  will-change: auto;
1071
1062
  }
1072
1063
  }
@@ -1111,8 +1102,7 @@ export const { Player } = createPlayer({ features: videoFeatures });
1111
1102
  transition: opacity 0.25s;
1112
1103
  }
1113
1104
  .media-default-skin media-poster:not([data-visible]),
1114
- .media-default-skin > img:not([data-visible]),
1115
- .media-default-skin > img[data-visible]:not([data-loaded]) {
1105
+ .media-default-skin > img:not([data-visible]) {
1116
1106
  opacity: 0;
1117
1107
  }
1118
1108
  .media-default-skin media-poster ::slotted(img),
@@ -1131,30 +1121,6 @@ export const { Player } = createPlayer({ features: videoFeatures });
1131
1121
  border-radius: inherit;
1132
1122
  }
1133
1123
 
1134
- /* Blurred placeholder: HTML path targets media-poster::before, React path targets the skin container::before */
1135
- .media-default-skin media-poster::before,
1136
- .media-default-skin::before {
1137
- position: absolute;
1138
- inset: 0;
1139
- pointer-events: none;
1140
- content: "";
1141
- background-image: var(--media-poster-placeholder, none);
1142
- background-repeat: no-repeat;
1143
- background-position: var(--media-object-position, center);
1144
- background-size: var(--media-object-fit, contain);
1145
- filter: blur(var(--media-poster-placeholder-blur, 20px));
1146
- }
1147
-
1148
- /* React path: hidden by default, transitions in while poster loads */
1149
- .media-default-skin::before {
1150
- opacity: 0;
1151
- transition: opacity 0.25s;
1152
- }
1153
- /* Show placeholder while the poster is visible but not yet loaded */
1154
- .media-default-skin:has(img[data-visible]:not([data-loaded]))::before {
1155
- opacity: 1;
1156
- }
1157
-
1158
1124
  .media-default-skin:fullscreen media-poster ::slotted(img),
1159
1125
  .media-default-skin:fullscreen media-poster img,
1160
1126
  .media-default-skin:fullscreen > img {
@@ -1759,7 +1725,6 @@ export const { Player } = createPlayer({ features: videoFeatures });
1759
1725
  transition-timing-function: ease-out;
1760
1726
  transition-duration: var(--menu-transition-duration);
1761
1727
  transition-property: translate, filter;
1762
- will-change: translate, filter;
1763
1728
 
1764
1729
  &:where([data-starting-style], [data-ending-style]) {
1765
1730
  overflow: hidden;
@@ -1943,7 +1908,6 @@ export const { Player } = createPlayer({ features: videoFeatures });
1943
1908
  transition:
1944
1909
  translate var(--menu-transition-duration) ease-out,
1945
1910
  filter var(--menu-transition-duration) ease-out;
1946
- will-change: translate, filter;
1947
1911
  }
1948
1912
 
1949
1913
  &[data-submenu-expanded="true"] > :not([data-submenu]) {
@@ -2497,12 +2461,10 @@ export const { Player } = createPlayer({ features: videoFeatures });
2497
2461
 
2498
2462
  @media (pointer: fine) {
2499
2463
  transition-property: filter, opacity, scale, translate;
2500
- will-change: filter, opacity, scale, translate;
2501
2464
  }
2502
2465
 
2503
2466
  @media (pointer: coarse) {
2504
2467
  transition-property: opacity, scale, translate;
2505
- will-change: opacity, scale, translate;
2506
2468
  }
2507
2469
  }
2508
2470
 
@@ -2587,12 +2549,10 @@ export const { Player } = createPlayer({ features: videoFeatures });
2587
2549
 
2588
2550
  @media (pointer: fine) {
2589
2551
  transition-property: filter, opacity, scale, translate;
2590
- will-change: filter, opacity, scale, translate;
2591
2552
  }
2592
2553
 
2593
2554
  @media (pointer: coarse) {
2594
2555
  transition-property: opacity, scale, translate;
2595
- will-change: opacity, scale, translate;
2596
2556
  }
2597
2557
 
2598
2558
  @container media-root (width > 42rem) {
@@ -2641,7 +2601,7 @@ Feature bundle
2641
2601
 
2642
2602
  Features
2643
2603
 
2644
- [playback](../reference/feature-playback.md), [playbackRate](../reference/feature-playback-rate.md), [quality](../reference/feature-quality.md), [audioTrack](../reference/feature-audio-track.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [fullscreen](../reference/feature-fullscreen.md), [pip](../reference/feature-pip.md), [remotePlayback](../reference/feature-remote-playback.md), [controls](../reference/feature-controls.md), [textTrack](../reference/feature-text-tracks.md), [error](../reference/feature-error.md), metadata
2604
+ [playback](../reference/feature-playback.md), [playbackRate](../reference/feature-playback-rate.md), [quality](../reference/feature-quality.md), [audioTrack](../reference/feature-audio-track.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [fullscreen](../reference/feature-fullscreen.md), [pip](../reference/feature-pip.md), [remotePlayback](../reference/feature-remote-playback.md), [controls](../reference/feature-controls.md), [textTrack](../reference/feature-text-tracks.md), [error](../reference/feature-error.md), [metadata](../reference/feature-metadata.md)
2645
2605
 
2646
2606
  Skins
2647
2607
 
@@ -2669,7 +2629,7 @@ Feature bundle
2669
2629
 
2670
2630
  Features
2671
2631
 
2672
- [playback](../reference/feature-playback.md), [playbackRate](../reference/feature-playback-rate.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [error](../reference/feature-error.md), metadata
2632
+ [playback](../reference/feature-playback.md), [playbackRate](../reference/feature-playback-rate.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [error](../reference/feature-error.md), [metadata](../reference/feature-metadata.md)
2673
2633
 
2674
2634
  Skins
2675
2635
 
@@ -2721,7 +2681,7 @@ Feature bundle
2721
2681
 
2722
2682
  Features
2723
2683
 
2724
- [playback](../reference/feature-playback.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [error](../reference/feature-error.md), [live](../reference/feature-live.md), metadata
2684
+ [playback](../reference/feature-playback.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [error](../reference/feature-error.md), [live](../reference/feature-live.md), [metadata](../reference/feature-metadata.md)
2725
2685
 
2726
2686
  Skins
2727
2687
 
@@ -2749,7 +2709,7 @@ Feature bundle
2749
2709
 
2750
2710
  Features
2751
2711
 
2752
- [playback](../reference/feature-playback.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [fullscreen](../reference/feature-fullscreen.md), [pip](../reference/feature-pip.md), [remotePlayback](../reference/feature-remote-playback.md), [controls](../reference/feature-controls.md), [textTrack](../reference/feature-text-tracks.md), [error](../reference/feature-error.md), [live](../reference/feature-live.md), metadata
2712
+ [playback](../reference/feature-playback.md), [volume](../reference/feature-volume.md), [time](../reference/feature-time.md), [source](../reference/feature-source.md), [buffer](../reference/feature-buffer.md), [fullscreen](../reference/feature-fullscreen.md), [pip](../reference/feature-pip.md), [remotePlayback](../reference/feature-remote-playback.md), [controls](../reference/feature-controls.md), [textTrack](../reference/feature-text-tracks.md), [error](../reference/feature-error.md), [live](../reference/feature-live.md), [metadata](../reference/feature-metadata.md)
2753
2713
 
2754
2714
  Skins
2755
2715
 
@@ -0,0 +1,133 @@
1
+ # Add a poster placeholder
2
+
3
+ Show a lightweight placeholder while the poster image loads
4
+
5
+ To show something while your video’s poster loads, customize the image drawn by the skin or the [Poster](../reference/poster.md) component.
6
+
7
+ The examples keep the placeholder aligned with the poster position. Change `contain` to `cover` if your poster fills its container.
8
+
9
+ [
10
+
11
+ Set your own poster
12
+
13
+ Configure your own poster URL
14
+
15
+ ](../reference/poster.md)
16
+
17
+ ## Use a framework image component
18
+
19
+ Many frameworks have image components that render both an image and a placeholder while it loads. Pass one to `renderPoster`; it receives `src` from Video.js when the media provides a poster.
20
+
21
+ tsx
22
+
23
+ ```
24
+ import NextImage from 'next/image';
25
+ import type { ComponentProps } from 'react';
26
+ import { MuxVideo } from '@videojs/react/media/mux-video';
27
+ import { VideoPlayer, VideoSkin } from '@videojs/react/video';
28
+
29
+ export function MyPlayer() {
30
+ return (
31
+ <VideoPlayer>
32
+ <VideoSkin
33
+ renderPoster={({ src, ...props }: ComponentProps<'img'>) =>
34
+ src ? (
35
+ <NextImage
36
+ {...props}
37
+ src={src}
38
+ alt=""
39
+ fill
40
+ placeholder="blur"
41
+ blurDataURL="data:image/webp;base64,…"
42
+ />
43
+ ) : null
44
+ }
45
+ >
46
+ <MuxVideo
47
+ source={{ playbackId: 'BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM' }}
48
+ playsInline
49
+ />
50
+ </VideoSkin>
51
+ </VideoPlayer>
52
+ );
53
+ }
54
+ ```
55
+
56
+ Next.js generates blur data for static imports. Because this poster URL arrives at runtime, pass `blurDataURL` yourself.
57
+
58
+ ## Use a plain image
59
+
60
+ A background image on the poster works well for a lightweight placeholder:
61
+
62
+ tsx
63
+
64
+ ```
65
+ import { MuxVideo } from '@videojs/react/media/mux-video';
66
+ import { VideoPlayer, VideoSkin } from '@videojs/react/video';
67
+
68
+ export function MyPlayer() {
69
+ return (
70
+ <VideoPlayer>
71
+ <VideoSkin
72
+ renderPoster={(props) => (
73
+ <img
74
+ {...props}
75
+ style={{
76
+ ...props.style,
77
+ background:
78
+ "url('data:image/webp;base64,…') var(--media-object-position, center) / contain no-repeat",
79
+ }}
80
+ />
81
+ )}
82
+ >
83
+ <MuxVideo
84
+ source={{ playbackId: 'BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM' }}
85
+ playsInline
86
+ />
87
+ </VideoSkin>
88
+ </VideoPlayer>
89
+ );
90
+ }
91
+ ```
92
+
93
+ ## Use the Poster component
94
+
95
+ If you built your own UI, use the same `render` prop directly on `Poster`:
96
+
97
+ tsx
98
+
99
+ ```
100
+ import { Container, Poster } from '@videojs/react';
101
+ import { MuxVideo } from '@videojs/react/media/mux-video';
102
+ import { VideoPlayer } from '@videojs/react/video';
103
+
104
+ export function MyPlayer() {
105
+ return (
106
+ <VideoPlayer>
107
+ <Container style={{ position: 'relative', aspectRatio: '16 / 9' }}>
108
+ <MuxVideo
109
+ source={{ playbackId: 'BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM' }}
110
+ playsInline
111
+ />
112
+ <Poster
113
+ render={(props) => (
114
+ <img
115
+ {...props}
116
+ style={{
117
+ ...props.style,
118
+ position: 'absolute',
119
+ inset: 0,
120
+ width: '100%',
121
+ height: '100%',
122
+ background:
123
+ "url('data:image/webp;base64,…') var(--media-object-position, center) / contain no-repeat",
124
+ objectFit: 'contain',
125
+ }}
126
+ />
127
+ )}
128
+ />
129
+ </Container>
130
+ </VideoPlayer>
131
+ );
132
+ }
133
+ ```