@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
@@ -383,7 +383,6 @@ export interface VideoPlayerProps {
383
383
  style?: CSSProperties;
384
384
  className?: string;
385
385
  renderPoster?: Poster.Props['render'];
386
- placeholder?: string | undefined;
387
386
  }
388
387
 
389
388
  /**
@@ -395,18 +394,10 @@ export interface VideoPlayerProps {
395
394
  * />
396
395
  * ```
397
396
  */
398
- export function VideoPlayer({ src, poster, className, renderPoster, placeholder, style, ...rest }: VideoPlayerProps): ReactNode {
399
- const containerStyle = placeholder
400
- ? ({ '--media-poster-placeholder': `url(${placeholder})`, ...style } as CSSProperties)
401
- : style;
402
-
397
+ export function VideoPlayer({ src, poster, className, renderPoster, style, ...rest }: VideoPlayerProps): ReactNode {
403
398
  return (
404
399
  <Player poster={poster}>
405
- <Container
406
- className={`media-default-skin media-default-skin--video ${className ?? ''}`}
407
- style={containerStyle}
408
- {...rest}
409
- >
400
+ <Container className={`media-default-skin media-default-skin--video ${className ?? ''}`} style={style} {...rest}>
410
401
  <Video src={src} playsInline />
411
402
 
412
403
  <Poster render={renderPoster} />
@@ -1095,7 +1086,7 @@ export const { Player } = createPlayer({ features: videoFeatures });
1095
1086
  @media (prefers-reduced-motion: reduce) {
1096
1087
  .media-default-skin .media-button {
1097
1088
  scale: 1;
1098
- transition-property: background-color, color, outline-offset;
1089
+ transition-property: background-color, color;
1099
1090
  will-change: auto;
1100
1091
  }
1101
1092
  }
@@ -1140,8 +1131,7 @@ export const { Player } = createPlayer({ features: videoFeatures });
1140
1131
  transition: opacity 0.25s;
1141
1132
  }
1142
1133
  .media-default-skin media-poster:not([data-visible]),
1143
- .media-default-skin > img:not([data-visible]),
1144
- .media-default-skin > img[data-visible]:not([data-loaded]) {
1134
+ .media-default-skin > img:not([data-visible]) {
1145
1135
  opacity: 0;
1146
1136
  }
1147
1137
  .media-default-skin media-poster ::slotted(img),
@@ -1160,30 +1150,6 @@ export const { Player } = createPlayer({ features: videoFeatures });
1160
1150
  border-radius: inherit;
1161
1151
  }
1162
1152
 
1163
- /* Blurred placeholder: HTML path targets media-poster::before, React path targets the skin container::before */
1164
- .media-default-skin media-poster::before,
1165
- .media-default-skin::before {
1166
- position: absolute;
1167
- inset: 0;
1168
- pointer-events: none;
1169
- content: "";
1170
- background-image: var(--media-poster-placeholder, none);
1171
- background-repeat: no-repeat;
1172
- background-position: var(--media-object-position, center);
1173
- background-size: var(--media-object-fit, contain);
1174
- filter: blur(var(--media-poster-placeholder-blur, 20px));
1175
- }
1176
-
1177
- /* React path: hidden by default, transitions in while poster loads */
1178
- .media-default-skin::before {
1179
- opacity: 0;
1180
- transition: opacity 0.25s;
1181
- }
1182
- /* Show placeholder while the poster is visible but not yet loaded */
1183
- .media-default-skin:has(img[data-visible]:not([data-loaded]))::before {
1184
- opacity: 1;
1185
- }
1186
-
1187
1153
  .media-default-skin:fullscreen media-poster ::slotted(img),
1188
1154
  .media-default-skin:fullscreen media-poster img,
1189
1155
  .media-default-skin:fullscreen > img {
@@ -1788,7 +1754,6 @@ export const { Player } = createPlayer({ features: videoFeatures });
1788
1754
  transition-timing-function: ease-out;
1789
1755
  transition-duration: var(--menu-transition-duration);
1790
1756
  transition-property: translate, filter;
1791
- will-change: translate, filter;
1792
1757
 
1793
1758
  &:where([data-starting-style], [data-ending-style]) {
1794
1759
  overflow: hidden;
@@ -1972,7 +1937,6 @@ export const { Player } = createPlayer({ features: videoFeatures });
1972
1937
  transition:
1973
1938
  translate var(--menu-transition-duration) ease-out,
1974
1939
  filter var(--menu-transition-duration) ease-out;
1975
- will-change: translate, filter;
1976
1940
  }
1977
1941
 
1978
1942
  &[data-submenu-expanded="true"] > :not([data-submenu]) {
@@ -2526,12 +2490,10 @@ export const { Player } = createPlayer({ features: videoFeatures });
2526
2490
 
2527
2491
  @media (pointer: fine) {
2528
2492
  transition-property: filter, opacity, scale, translate;
2529
- will-change: filter, opacity, scale, translate;
2530
2493
  }
2531
2494
 
2532
2495
  @media (pointer: coarse) {
2533
2496
  transition-property: opacity, scale, translate;
2534
- will-change: opacity, scale, translate;
2535
2497
  }
2536
2498
  }
2537
2499
 
@@ -2616,12 +2578,10 @@ export const { Player } = createPlayer({ features: videoFeatures });
2616
2578
 
2617
2579
  @media (pointer: fine) {
2618
2580
  transition-property: filter, opacity, scale, translate;
2619
- will-change: filter, opacity, scale, translate;
2620
2581
  }
2621
2582
 
2622
2583
  @media (pointer: coarse) {
2623
2584
  transition-property: opacity, scale, translate;
2624
- will-change: opacity, scale, translate;
2625
2585
  }
2626
2586
 
2627
2587
  @container media-root (width > 42rem) {
@@ -3302,7 +3262,7 @@ export const { Player } = createPlayer({ features: audioFeatures });
3302
3262
  @media (prefers-reduced-motion: reduce) {
3303
3263
  .media-default-skin .media-button {
3304
3264
  scale: 1;
3305
- transition-property: background-color, color, outline-offset;
3265
+ transition-property: background-color, color;
3306
3266
  will-change: auto;
3307
3267
  }
3308
3268
  }
@@ -3874,7 +3834,6 @@ export const { Player } = createPlayer({ features: audioFeatures });
3874
3834
  transition-timing-function: ease-out;
3875
3835
  transition-duration: var(--menu-transition-duration);
3876
3836
  transition-property: translate, filter;
3877
- will-change: translate, filter;
3878
3837
 
3879
3838
  &:where([data-starting-style], [data-ending-style]) {
3880
3839
  overflow: hidden;
@@ -4058,7 +4017,6 @@ export const { Player } = createPlayer({ features: audioFeatures });
4058
4017
  transition:
4059
4018
  translate var(--menu-transition-duration) ease-out,
4060
4019
  filter var(--menu-transition-duration) ease-out;
4061
- will-change: translate, filter;
4062
4020
  }
4063
4021
 
4064
4022
  &[data-submenu-expanded="true"] > :not([data-submenu]) {
@@ -4457,7 +4415,6 @@ export interface VideoPlayerProps {
4457
4415
  style?: CSSProperties;
4458
4416
  className?: string;
4459
4417
  renderPoster?: Poster.Props['render'];
4460
- placeholder?: string | undefined;
4461
4418
  }
4462
4419
 
4463
4420
  /**
@@ -4469,18 +4426,10 @@ export interface VideoPlayerProps {
4469
4426
  * />
4470
4427
  * ```
4471
4428
  */
4472
- export function VideoPlayer({ src, poster, className, renderPoster, placeholder, style, ...rest }: VideoPlayerProps): ReactNode {
4473
- const containerStyle = placeholder
4474
- ? ({ '--media-poster-placeholder': `url(${placeholder})`, ...style } as CSSProperties)
4475
- : style;
4476
-
4429
+ export function VideoPlayer({ src, poster, className, renderPoster, style, ...rest }: VideoPlayerProps): ReactNode {
4477
4430
  return (
4478
4431
  <Player poster={poster}>
4479
- <Container
4480
- className={`media-minimal-skin media-minimal-skin--video ${className ?? ''}`}
4481
- style={containerStyle}
4482
- {...rest}
4483
- >
4432
+ <Container className={`media-minimal-skin media-minimal-skin--video ${className ?? ''}`} style={style} {...rest}>
4484
4433
  <Video src={src} playsInline />
4485
4434
 
4486
4435
  <Poster render={renderPoster} />
@@ -5237,7 +5186,7 @@ export const { Player } = createPlayer({ features: videoFeatures });
5237
5186
  @media (prefers-reduced-motion: reduce) {
5238
5187
  .media-minimal-skin .media-button {
5239
5188
  scale: 1;
5240
- transition-property: background-color, outline-offset;
5189
+ transition-property: background-color;
5241
5190
  will-change: auto;
5242
5191
  }
5243
5192
  }
@@ -5282,8 +5231,7 @@ export const { Player } = createPlayer({ features: videoFeatures });
5282
5231
  transition: opacity 0.25s;
5283
5232
  }
5284
5233
  .media-minimal-skin media-poster:not([data-visible]),
5285
- .media-minimal-skin > img:not([data-visible]),
5286
- .media-minimal-skin > img[data-visible]:not([data-loaded]) {
5234
+ .media-minimal-skin > img:not([data-visible]) {
5287
5235
  opacity: 0;
5288
5236
  }
5289
5237
  .media-minimal-skin media-poster ::slotted(img),
@@ -5302,30 +5250,6 @@ export const { Player } = createPlayer({ features: videoFeatures });
5302
5250
  border-radius: inherit;
5303
5251
  }
5304
5252
 
5305
- /* Blurred placeholder: HTML path targets media-poster::before, React path targets the skin container::before */
5306
- .media-minimal-skin media-poster::before,
5307
- .media-minimal-skin::before {
5308
- position: absolute;
5309
- inset: 0;
5310
- pointer-events: none;
5311
- content: "";
5312
- background-image: var(--media-poster-placeholder, none);
5313
- background-repeat: no-repeat;
5314
- background-position: var(--media-object-position, center);
5315
- background-size: var(--media-object-fit, contain);
5316
- filter: blur(var(--media-poster-placeholder-blur, 20px));
5317
- }
5318
-
5319
- /* React path: hidden by default, transitions in while poster loads */
5320
- .media-minimal-skin::before {
5321
- opacity: 0;
5322
- transition: opacity 0.25s;
5323
- }
5324
- /* Show placeholder while the poster is visible but not yet loaded */
5325
- .media-minimal-skin:has(img[data-visible]:not([data-loaded]))::before {
5326
- opacity: 1;
5327
- }
5328
-
5329
5253
  .media-minimal-skin:fullscreen media-poster ::slotted(img),
5330
5254
  .media-minimal-skin:fullscreen media-poster img,
5331
5255
  .media-minimal-skin:fullscreen > img {
@@ -5932,7 +5856,6 @@ export const { Player } = createPlayer({ features: videoFeatures });
5932
5856
  transition-timing-function: ease-out;
5933
5857
  transition-duration: var(--menu-transition-duration);
5934
5858
  transition-property: translate, filter;
5935
- will-change: translate, filter;
5936
5859
 
5937
5860
  &:where([data-starting-style], [data-ending-style]) {
5938
5861
  overflow: hidden;
@@ -6111,7 +6034,6 @@ export const { Player } = createPlayer({ features: videoFeatures });
6111
6034
  transition:
6112
6035
  translate var(--menu-transition-duration) ease-out,
6113
6036
  filter var(--menu-transition-duration) ease-out;
6114
- will-change: translate, filter;
6115
6037
  }
6116
6038
 
6117
6039
  &[data-submenu-expanded="true"] > :not([data-submenu]) {
@@ -6667,12 +6589,10 @@ export const { Player } = createPlayer({ features: videoFeatures });
6667
6589
 
6668
6590
  @media (pointer: fine) {
6669
6591
  transition-property: translate, filter, opacity;
6670
- will-change: translate, filter, opacity;
6671
6592
  }
6672
6593
 
6673
6594
  @media (pointer: coarse) {
6674
6595
  transition-property: translate, opacity;
6675
- will-change: translate, opacity;
6676
6596
  }
6677
6597
 
6678
6598
  &:not([data-visible]) {
@@ -7460,7 +7380,7 @@ export const { Player } = createPlayer({ features: audioFeatures });
7460
7380
  @media (prefers-reduced-motion: reduce) {
7461
7381
  .media-minimal-skin .media-button {
7462
7382
  scale: 1;
7463
- transition-property: background-color, outline-offset;
7383
+ transition-property: background-color;
7464
7384
  will-change: auto;
7465
7385
  }
7466
7386
  }
@@ -8031,7 +7951,6 @@ export const { Player } = createPlayer({ features: audioFeatures });
8031
7951
  transition-timing-function: ease-out;
8032
7952
  transition-duration: var(--menu-transition-duration);
8033
7953
  transition-property: translate, filter;
8034
- will-change: translate, filter;
8035
7954
 
8036
7955
  &:where([data-starting-style], [data-ending-style]) {
8037
7956
  overflow: hidden;
@@ -8210,7 +8129,6 @@ export const { Player } = createPlayer({ features: audioFeatures });
8210
8129
  transition:
8211
8130
  translate var(--menu-transition-duration) ease-out,
8212
8131
  filter var(--menu-transition-duration) ease-out;
8213
- will-change: translate, filter;
8214
8132
  }
8215
8133
 
8216
8134
  &[data-submenu-expanded="true"] > :not([data-submenu]) {
@@ -243,17 +243,23 @@ The poster is a skin concern. Build an `image.mux.com` URL and hand it over:
243
243
  jsx
244
244
 
245
245
  ```
246
- <VideoSkin
247
- poster="https://image.mux.com/EcHgOK9coz5K…/thumbnail.webp?time=2"
248
- placeholder="data:image/webp;base64,…"
249
- >
250
- <MuxVideo source={{ playbackId: 'EcHgOK9coz5K…' }} />
251
- </VideoSkin>
246
+ <VideoPlayer poster="https://image.mux.com/EcHgOK9coz5K…/thumbnail.webp?time=2">
247
+ <VideoSkin
248
+ renderPoster={
249
+ <img
250
+ alt=""
251
+ style={{ background: "url('data:image/webp;base64,…') center / contain no-repeat" }}
252
+ />
253
+ }
254
+ >
255
+ <MuxVideo source={{ playbackId: 'EcHgOK9coz5K…' }} />
256
+ </VideoSkin>
257
+ </VideoPlayer>
252
258
  ```
253
259
 
254
- `placeholder` is the blurred stand-in shown while the real poster loads, Mux Player’s `placeholder`.
260
+ `renderPoster` customizes the poster image, so its background can show Mux Player’s `placeholder` while the real poster loads.
255
261
 
256
- Mux serves the poster from its `thumbnail` endpoint, so every Mux Player poster attribute becomes a query parameter you append: `thumbnail-time` is `?time=`, and the size, crop, rotation, and format options work the same way. See the [Poster](../reference/poster.md) reference for the component itself.
262
+ Mux serves the poster from its `thumbnail` endpoint, so every Mux Player poster attribute becomes a query parameter you append: `thumbnail-time` is `?time=`, and the size, crop, rotation, and format options work the same way. See [Add a poster placeholder](./add-a-poster-placeholder.md) for more examples or the [Poster](../reference/poster.md) reference for the component itself.
257
263
 
258
264
  ## Customize your player
259
265
 
package/docs/llms.txt CHANGED
@@ -40,6 +40,7 @@ Understanding-oriented pages that explain how and why things work. Read these to
40
40
  Task-oriented guides with step-by-step instructions to achieve a specific outcome by applying one or more concepts. Each guide may assume you already understand the relevant concepts.
41
41
 
42
42
  - [Customize skins](./how-to/customize-skins.md): Learn how to customize Video.js v10 skins by copying and modifying them
43
+ - [Add a poster placeholder](./how-to/add-a-poster-placeholder.md): Show a lightweight placeholder while the poster image loads
43
44
  - [Build your own UI component](./how-to/build-your-own-component.md): Create custom player controls that read state, dispatch actions, and stay accessible.
44
45
  ### Internationalization
45
46
 
@@ -84,6 +85,7 @@ API Reference for UI components for building media player interfaces.
84
85
  - [Thumbnail](./reference/thumbnail.md): Time-based thumbnail preview component for timeline scrubbing and hover previews
85
86
  - [Time](./reference/time.md): Time display components for showing current time, duration, and remaining time in a video player
86
87
  - [TimeSlider](./reference/time-slider.md): A slider component for seeking through media playback time
88
+ - [Title](./reference/title.md): Displays the resolved content title for the current media
87
89
  - [Tooltip](./reference/tooltip.md): A tooltip component for displaying contextual labels on hover and focus
88
90
  - [VolumeSlider](./reference/volume-slider.md): A slider component for controlling media playback volume
89
91
 
@@ -110,6 +112,7 @@ API reference for the feature modules passed to createPlayer, which provide play
110
112
  - [Error](./reference/feature-error.md): Media error state and actions for the player store
111
113
  - [Fullscreen](./reference/feature-fullscreen.md): Fullscreen state and actions for the player store
112
114
  - [Live](./reference/feature-live.md): Live edge state for the player store
115
+ - [Metadata](./reference/feature-metadata.md): Resolved title and poster values for the player store
113
116
  - [Orientation lock](./reference/feature-orientation-lock.md): Screen orientation locking while fullscreen is active
114
117
  - [Picture-in-picture](./reference/feature-pip.md): Picture-in-picture state and actions for the player store
115
118
  - [Playback](./reference/feature-playback.md): Play/pause state and actions for the player store
@@ -97,15 +97,16 @@ function FullscreenButton() {
97
97
 
98
98
  Add [`features.orientationLock`](./feature-orientation-lock.md) to lock screen orientation while fullscreen is active. Unsupported browsers and rejected lock requests are ignored, so iOS Safari continues to use its normal fullscreen behavior.
99
99
 
100
- By default, the feature locks to `landscape`. Pass a Screen Orientation API type to customize it:
100
+ The feature locks to `landscape` unless the provider sets another Screen Orientation API type.
101
101
 
102
- ts
102
+ tsx
103
103
 
104
104
  ```
105
- import { createPlayer, features } from '@videojs/react';
106
- import { videoFeatures } from '@videojs/react/video';
105
+ <Player orientationLockType="portrait">
106
+ <Container>
107
+ <video src="/video.mp4" />
108
+ </Container>
109
+ </Player>
110
+ ```
107
111
 
108
- const { Player } = createPlayer({
109
- features: [...videoFeatures, features.orientationLock({ type: 'portrait' })],
110
- });
111
- ```
112
+ See [Orientation lock](./feature-orientation-lock.md) for the player setup.
@@ -0,0 +1,129 @@
1
+ # Metadata
2
+
3
+ Resolved title and poster values for the player store
4
+
5
+ Resolves what is playing into two values your UI can render: `title` and `poster`.
6
+
7
+ `title` and `poster` each resolve independently through the same two tiers: the value you set, then the value the media reports. The first tier that holds a value wins; when neither does, the resolved value is an empty string. Because the tiers stay separate, clearing the value you set reveals the media’s, and a source that reports a poster but no title contributes to one while leaving the other empty.
8
+
9
+ An empty string counts as a value and stops the chain: set `title` to `''` and the resolved title is `''`. Pass `null` to clear your value.
10
+
11
+ The metadata feature is included by the `videoFeatures`, `audioFeatures`, `liveVideoFeatures`, and `liveAudioFeatures` [presets](../concepts/presets.md); apps that build a custom preset can compose it in directly.
12
+
13
+ ## API Reference
14
+
15
+ ### Configuration
16
+
17
+ Props the Player component accepts. They exist only while this feature is selected.
18
+
19
+ Prop
20
+
21
+ Type
22
+
23
+ Default
24
+
25
+ Details
26
+
27
+ `title`
28
+
29
+ `undefined | null | string`
30
+
31
+ `—`
32
+
33
+
34
+
35
+ Description
36
+
37
+ The title to display. Takes precedence over the title the media carries.
38
+
39
+ `poster`
40
+
41
+ `undefined | null | string`
42
+
43
+ `—`
44
+
45
+
46
+
47
+ Description
48
+
49
+ The poster to display. Takes precedence over the poster the media carries.
50
+
51
+ ### State
52
+
53
+ Property
54
+
55
+ Type
56
+
57
+ Details
58
+
59
+ `title`
60
+
61
+ `string`
62
+
63
+
64
+
65
+ Description
66
+
67
+ The resolved content title. Set it through the player, not through the store.
68
+
69
+ `poster`
70
+
71
+ `string`
72
+
73
+
74
+
75
+ Description
76
+
77
+ The resolved poster URL, independent of the media element's own `poster`. Set it through the player, not through the store.
78
+
79
+ ### Selector
80
+
81
+ Pass `selectMetadata` to [`usePlayer`](./use-player.md) to subscribe to metadata state. Returns `undefined` if the metadata feature is not configured.
82
+
83
+ ContentTitle.tsx
84
+
85
+ ```
86
+ import { selectMetadata, usePlayer } from '@videojs/react';
87
+
88
+ function ContentTitle() {
89
+ const metadata = usePlayer(selectMetadata);
90
+ if (!metadata?.title) return null;
91
+
92
+ return <h2 className="content-title">{metadata.title}</h2>;
93
+ }
94
+ ```
95
+
96
+ ### Player inputs
97
+
98
+ The store publishes the resolved values and takes no writes. A player input is the only way to set one.
99
+
100
+ Every input is a prop on [`Player`](./player-provider.md). A player built without the metadata feature has none of them, and none is forwarded.
101
+
102
+ App.tsx
103
+
104
+ ```
105
+ import { Container, createPlayer } from '@videojs/react';
106
+ import { videoFeatures } from '@videojs/react/video';
107
+
108
+ const { Player } = createPlayer({ features: videoFeatures });
109
+
110
+ function App({ episode }: { episode: { title: string | null; art: string | null } }) {
111
+ return (
112
+ <Player title={episode.title} poster={episode.art}>
113
+ <Container>
114
+ <video src="episode.mp4" />
115
+ </Container>
116
+ </Player>
117
+ );
118
+ }
119
+ ```
120
+
121
+ Passing `null` clears your value, so `title={null}` falls through to the title the media reports.
122
+
123
+ ### Media-reported values
124
+
125
+ The media tier comes from media that reports content data and announces changes to it, and it covers `title` and `poster`. A media reports only the keys it can vouch for, so either may be absent while the other arrives. A key that never arrives means that tier never contributes, and the resolved value is whatever you set. Detaching the media clears its values; the values you set survive and apply to the next source.
126
+
127
+ `poster` is the feature’s own resolved value, not the media element’s `poster` attribute. Setting one does not set the other.
128
+
129
+ A low-resolution stand-in to show while the poster loads is not part of this feature. The [poster](./poster.md) renders an `<img>` you control, so give it a `background-image` and that shows until the poster itself paints over it.
@@ -6,14 +6,74 @@ Locks screen orientation while fullscreen is active. Add it explicitly to a feat
6
6
 
7
7
  ## API Reference
8
8
 
9
+ ### Configuration
10
+
11
+ Props the Player component accepts. They exist only while this feature is selected.
12
+
13
+ Prop
14
+
15
+ Type
16
+
17
+ Default
18
+
19
+ Details
20
+
21
+ `orientationLockType`
22
+
23
+ `undefined | null | 'any' | 'landscape' | 'landscape-primary' | 'landscape-secondary' | 'natural' | 'portrait' | 'portrait-primary' | 'portrait-secondary'`
24
+
25
+ `'landscape'`
26
+
27
+
28
+
29
+ Description
30
+
31
+ Screen orientation type to lock while fullscreen is active.
32
+
33
+ ### State
34
+
35
+ Property
36
+
37
+ Type
38
+
39
+ Details
40
+
41
+ `orientationLockType`
42
+
43
+ `'any' | 'landscape' | 'landscape-primary' | 'landscape-secondary' | 'natural' | 'portrait' | 'portrait-primary' | 'portrait-secondary'`
44
+
45
+
46
+
47
+ Description
48
+
49
+ Screen orientation type locked while fullscreen is active.
50
+
51
+ ### Actions
52
+
53
+ Action
54
+
55
+ Type
56
+
57
+ Details
58
+
59
+ `setOrientationLockType`
60
+
61
+ `(value: undefined | null | 'any' | 'landscape' | 'landscape-primary' | 'landscape-secondary' | 'natural' | 'portrait' | 'portrait-primary' | 'portrait-secondary') => void`
62
+
63
+
64
+
65
+ Description
66
+
67
+ Sets the locked orientation type. Absent input — nullish, or the empty string a valueless HTML attribute produces — restores the default.
68
+
9
69
  ### Usage
10
70
 
11
- By default, the feature locks to `landscape`. Pass a Screen Orientation API type to customize it.
71
+ Selecting the feature adds it to the player.
12
72
 
13
73
  player.tsx
14
74
 
15
75
  ```
16
- import { createPlayer, features } from '@videojs/react';
76
+ import { Container, createPlayer, features } from '@videojs/react';
17
77
  import { videoFeatures } from '@videojs/react/video';
18
78
 
19
79
  export const { Player } = createPlayer({
@@ -21,15 +81,41 @@ export const { Player } = createPlayer({
21
81
  });
22
82
  ```
23
83
 
24
- portrait-player.tsx
84
+ ### Orientation type
85
+
86
+ The feature locks to `landscape` unless the provider sets another Screen Orientation API type. The value can change while the player is running; if the screen is already locked, it re-locks to the new type.
87
+
88
+ tsx
25
89
 
26
90
  ```
27
- import { createPlayer, features } from '@videojs/react';
28
- import { videoFeatures } from '@videojs/react/video';
91
+ <Player orientationLockType="portrait">
92
+ <Container>
93
+ <video src="/video.mp4" />
94
+ </Container>
95
+ </Player>
96
+ ```
29
97
 
30
- export const { Player } = createPlayer({
31
- features: [...videoFeatures, features.orientationLock({ type: 'portrait' })],
32
- });
98
+ Clearing the value restores `landscape`.
99
+
100
+ ### Selector
101
+
102
+ Pass `selectOrientationLock` to [`usePlayer`](./use-player.md) to subscribe to the lock state. Returns `undefined` if the orientation lock feature is not configured.
103
+
104
+ OrientationToggle.tsx
105
+
106
+ ```
107
+ import { selectOrientationLock, usePlayer } from '@videojs/react';
108
+
109
+ function OrientationToggle() {
110
+ const lock = usePlayer(selectOrientationLock);
111
+ if (!lock) return null;
112
+
113
+ return (
114
+ <button onClick={() => lock.setOrientationLockType('portrait')}>
115
+ Lock portrait
116
+ </button>
117
+ );
118
+ }
33
119
  ```
34
120
 
35
121
  Unsupported browsers and rejected lock requests are ignored.
@@ -46,6 +46,16 @@ Description
46
46
 
47
47
  The `<track>` element's `src` for resolving relative cue text URLs.
48
48
 
49
+ `thumbnailTrackCrossOrigin`
50
+
51
+ `null | 'anonymous' | 'use-credentials'`
52
+
53
+
54
+
55
+ Description
56
+
57
+ The media element's CORS mode, mapped through the CORS-settings-attribute rules, or `null` when it is not in CORS mode. Thumbnail UI fetches the sprite sheets the cues point at with this mode, since a cross-origin `<track>` only loads at all when the media element is CORS-enabled.
58
+
49
59
  `textTrackList`
50
60
 
51
61
  `MediaTextTrack<TextTrackKind>[]`
@@ -442,7 +442,6 @@ export default function BasicUsage() {
442
442
  transition-timing-function: ease-in-out;
443
443
  transition-duration: var(--menu-transition-duration);
444
444
  transition-property: translate, filter;
445
- will-change: translate;
446
445
  }
447
446
 
448
447
  .menu[data-submenu-expanded="true"] > :not([data-submenu]) {
@@ -227,6 +227,10 @@ Description
227
227
 
228
228
  CORS setting forwarded to the inner `<img>`.
229
229
 
230
+ Left unset, this follows the media element: a cross-origin thumbnail `<track>` only loads when the media is CORS-enabled, so the sprite sheets its cues point at are fetched with that same mode. Pass `null` to opt out and fetch them without CORS. Thumbnails supplied directly never inherit, since they need not be related to the media element at all.
231
+
232
+ `''` is a value like any other, read as Anonymous by the CORS-settings attribute rules — it does not opt out.
233
+
230
234
  Type
231
235
 
232
236
  `'anonymous' | 'use-credentials' | '' | null`