@livepeer-frameworks/player-react 0.1.0 → 0.1.2

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 (187) hide show
  1. package/README.md +7 -9
  2. package/dist/cjs/_virtual/_rollupPluginBabelHelpers.js +359 -0
  3. package/dist/cjs/_virtual/_rollupPluginBabelHelpers.js.map +1 -0
  4. package/dist/cjs/assets/logomark.svg.js +8 -0
  5. package/dist/cjs/assets/logomark.svg.js.map +1 -0
  6. package/dist/cjs/components/DevModePanel.js +826 -0
  7. package/dist/cjs/components/DevModePanel.js.map +1 -0
  8. package/dist/cjs/components/DvdLogo.js +200 -0
  9. package/dist/cjs/components/DvdLogo.js.map +1 -0
  10. package/dist/cjs/components/Icons.js +439 -0
  11. package/dist/cjs/components/Icons.js.map +1 -0
  12. package/dist/cjs/components/IdleScreen.js +587 -0
  13. package/dist/cjs/components/IdleScreen.js.map +1 -0
  14. package/dist/cjs/components/LoadingScreen.js +523 -0
  15. package/dist/cjs/components/LoadingScreen.js.map +1 -0
  16. package/dist/cjs/components/Player.js +420 -0
  17. package/dist/cjs/components/Player.js.map +1 -0
  18. package/dist/cjs/components/PlayerControls.js +798 -0
  19. package/dist/cjs/components/PlayerControls.js.map +1 -0
  20. package/dist/cjs/components/PlayerErrorBoundary.js +80 -0
  21. package/dist/cjs/components/PlayerErrorBoundary.js.map +1 -0
  22. package/dist/cjs/components/SeekBar.js +253 -0
  23. package/dist/cjs/components/SeekBar.js.map +1 -0
  24. package/dist/cjs/components/SkipIndicator.js +92 -0
  25. package/dist/cjs/components/SkipIndicator.js.map +1 -0
  26. package/dist/cjs/components/SpeedIndicator.js +43 -0
  27. package/dist/cjs/components/SpeedIndicator.js.map +1 -0
  28. package/dist/cjs/components/StatsPanel.js +202 -0
  29. package/dist/cjs/components/StatsPanel.js.map +1 -0
  30. package/dist/cjs/components/StreamStateOverlay.js +229 -0
  31. package/dist/cjs/components/StreamStateOverlay.js.map +1 -0
  32. package/dist/cjs/components/ThumbnailOverlay.js +86 -0
  33. package/dist/cjs/components/ThumbnailOverlay.js.map +1 -0
  34. package/dist/cjs/components/TitleOverlay.js +32 -0
  35. package/dist/cjs/components/TitleOverlay.js.map +1 -0
  36. package/dist/cjs/context/PlayerContext.js +46 -0
  37. package/dist/cjs/context/PlayerContext.js.map +1 -0
  38. package/dist/cjs/hooks/useMetaTrack.js +165 -0
  39. package/dist/cjs/hooks/useMetaTrack.js.map +1 -0
  40. package/dist/cjs/hooks/usePlaybackQuality.js +131 -0
  41. package/dist/cjs/hooks/usePlaybackQuality.js.map +1 -0
  42. package/dist/cjs/hooks/usePlayerController.js +518 -0
  43. package/dist/cjs/hooks/usePlayerController.js.map +1 -0
  44. package/dist/cjs/hooks/usePlayerSelection.js +90 -0
  45. package/dist/cjs/hooks/usePlayerSelection.js.map +1 -0
  46. package/dist/cjs/hooks/useStreamState.js +360 -0
  47. package/dist/cjs/hooks/useStreamState.js.map +1 -0
  48. package/dist/cjs/hooks/useTelemetry.js +120 -0
  49. package/dist/cjs/hooks/useTelemetry.js.map +1 -0
  50. package/dist/cjs/hooks/useViewerEndpoints.js +222 -0
  51. package/dist/cjs/hooks/useViewerEndpoints.js.map +1 -0
  52. package/dist/cjs/index.js +97 -1
  53. package/dist/cjs/index.js.map +1 -1
  54. package/dist/cjs/ui/badge.js +34 -0
  55. package/dist/cjs/ui/badge.js.map +1 -0
  56. package/dist/cjs/ui/button.js +74 -0
  57. package/dist/cjs/ui/button.js.map +1 -0
  58. package/dist/cjs/ui/context-menu.js +163 -0
  59. package/dist/cjs/ui/context-menu.js.map +1 -0
  60. package/dist/cjs/ui/slider.js +60 -0
  61. package/dist/cjs/ui/slider.js.map +1 -0
  62. package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +329 -0
  63. package/dist/esm/_virtual/_rollupPluginBabelHelpers.js.map +1 -0
  64. package/dist/esm/assets/logomark.svg.js +4 -0
  65. package/dist/esm/assets/logomark.svg.js.map +1 -0
  66. package/dist/esm/components/DevModePanel.js +822 -0
  67. package/dist/esm/components/DevModePanel.js.map +1 -0
  68. package/dist/esm/components/DvdLogo.js +196 -0
  69. package/dist/esm/components/DvdLogo.js.map +1 -0
  70. package/dist/esm/components/Icons.js +421 -0
  71. package/dist/esm/components/Icons.js.map +1 -0
  72. package/dist/esm/components/IdleScreen.js +582 -0
  73. package/dist/esm/components/IdleScreen.js.map +1 -0
  74. package/dist/esm/components/LoadingScreen.js +519 -0
  75. package/dist/esm/components/LoadingScreen.js.map +1 -0
  76. package/dist/esm/components/Player.js +416 -0
  77. package/dist/esm/components/Player.js.map +1 -0
  78. package/dist/esm/components/PlayerControls.js +794 -0
  79. package/dist/esm/components/PlayerControls.js.map +1 -0
  80. package/dist/esm/components/PlayerErrorBoundary.js +76 -0
  81. package/dist/esm/components/PlayerErrorBoundary.js.map +1 -0
  82. package/dist/esm/components/SeekBar.js +249 -0
  83. package/dist/esm/components/SeekBar.js.map +1 -0
  84. package/dist/esm/components/SkipIndicator.js +88 -0
  85. package/dist/esm/components/SkipIndicator.js.map +1 -0
  86. package/dist/esm/components/SpeedIndicator.js +39 -0
  87. package/dist/esm/components/SpeedIndicator.js.map +1 -0
  88. package/dist/esm/components/StatsPanel.js +198 -0
  89. package/dist/esm/components/StatsPanel.js.map +1 -0
  90. package/dist/esm/components/StreamStateOverlay.js +224 -0
  91. package/dist/esm/components/StreamStateOverlay.js.map +1 -0
  92. package/dist/esm/components/ThumbnailOverlay.js +82 -0
  93. package/dist/esm/components/ThumbnailOverlay.js.map +1 -0
  94. package/dist/esm/components/TitleOverlay.js +28 -0
  95. package/dist/esm/components/TitleOverlay.js.map +1 -0
  96. package/dist/esm/context/PlayerContext.js +41 -0
  97. package/dist/esm/context/PlayerContext.js.map +1 -0
  98. package/dist/esm/hooks/useMetaTrack.js +163 -0
  99. package/dist/esm/hooks/useMetaTrack.js.map +1 -0
  100. package/dist/esm/hooks/usePlaybackQuality.js +129 -0
  101. package/dist/esm/hooks/usePlaybackQuality.js.map +1 -0
  102. package/dist/esm/hooks/usePlayerController.js +516 -0
  103. package/dist/esm/hooks/usePlayerController.js.map +1 -0
  104. package/dist/esm/hooks/usePlayerSelection.js +88 -0
  105. package/dist/esm/hooks/usePlayerSelection.js.map +1 -0
  106. package/dist/esm/hooks/useStreamState.js +358 -0
  107. package/dist/esm/hooks/useStreamState.js.map +1 -0
  108. package/dist/esm/hooks/useTelemetry.js +118 -0
  109. package/dist/esm/hooks/useTelemetry.js.map +1 -0
  110. package/dist/esm/hooks/useViewerEndpoints.js +220 -0
  111. package/dist/esm/hooks/useViewerEndpoints.js.map +1 -0
  112. package/dist/esm/index.js +23 -1
  113. package/dist/esm/index.js.map +1 -1
  114. package/dist/esm/ui/badge.js +31 -0
  115. package/dist/esm/ui/badge.js.map +1 -0
  116. package/dist/esm/ui/button.js +52 -0
  117. package/dist/esm/ui/button.js.map +1 -0
  118. package/dist/esm/ui/context-menu.js +132 -0
  119. package/dist/esm/ui/context-menu.js.map +1 -0
  120. package/dist/esm/ui/slider.js +38 -0
  121. package/dist/esm/ui/slider.js.map +1 -0
  122. package/dist/types/components/DvdLogo.d.ts +1 -1
  123. package/dist/types/components/Icons.d.ts +1 -1
  124. package/dist/types/components/Player.d.ts +1 -1
  125. package/dist/types/components/PlayerErrorBoundary.d.ts +2 -1
  126. package/dist/types/components/StreamStateOverlay.d.ts +2 -2
  127. package/dist/types/components/SubtitleRenderer.d.ts +2 -2
  128. package/dist/types/context/PlayerContext.d.ts +2 -2
  129. package/dist/types/context/index.d.ts +2 -2
  130. package/dist/types/hooks/useMetaTrack.d.ts +3 -3
  131. package/dist/types/hooks/usePlaybackQuality.d.ts +2 -2
  132. package/dist/types/hooks/usePlayerController.d.ts +26 -3
  133. package/dist/types/hooks/usePlayerSelection.d.ts +1 -1
  134. package/dist/types/hooks/useStreamState.d.ts +1 -1
  135. package/dist/types/hooks/useTelemetry.d.ts +1 -1
  136. package/dist/types/hooks/useViewerEndpoints.d.ts +3 -3
  137. package/dist/types/index.d.ts +28 -28
  138. package/dist/types/types.d.ts +3 -3
  139. package/dist/types/ui/select.d.ts +1 -1
  140. package/package.json +22 -14
  141. package/src/components/DevModePanel.tsx +244 -143
  142. package/src/components/DvdLogo.tsx +1 -1
  143. package/src/components/Icons.tsx +105 -25
  144. package/src/components/IdleScreen.tsx +262 -128
  145. package/src/components/LoadingScreen.tsx +169 -151
  146. package/src/components/LogoOverlay.tsx +3 -6
  147. package/src/components/Player.tsx +126 -59
  148. package/src/components/PlayerControls.tsx +384 -272
  149. package/src/components/PlayerErrorBoundary.tsx +7 -13
  150. package/src/components/SeekBar.tsx +96 -88
  151. package/src/components/SkipIndicator.tsx +2 -12
  152. package/src/components/SpeedIndicator.tsx +2 -11
  153. package/src/components/StatsPanel.tsx +31 -22
  154. package/src/components/StreamStateOverlay.tsx +105 -49
  155. package/src/components/SubtitleRenderer.tsx +29 -29
  156. package/src/components/ThumbnailOverlay.tsx +5 -6
  157. package/src/components/TitleOverlay.tsx +2 -8
  158. package/src/context/PlayerContext.tsx +4 -8
  159. package/src/context/index.ts +3 -3
  160. package/src/hooks/useMetaTrack.ts +27 -27
  161. package/src/hooks/usePlaybackQuality.ts +3 -3
  162. package/src/hooks/usePlayerController.ts +246 -138
  163. package/src/hooks/usePlayerSelection.ts +6 -6
  164. package/src/hooks/useStreamState.ts +51 -56
  165. package/src/hooks/useTelemetry.ts +18 -3
  166. package/src/hooks/useViewerEndpoints.ts +34 -23
  167. package/src/index.tsx +36 -28
  168. package/src/types.ts +8 -8
  169. package/src/ui/badge.tsx +6 -5
  170. package/src/ui/button.tsx +9 -8
  171. package/src/ui/context-menu.tsx +42 -61
  172. package/src/ui/select.tsx +13 -7
  173. package/src/ui/slider.tsx +18 -29
  174. package/dist/types/components/players/DashJsPlayer.d.ts +0 -18
  175. package/dist/types/components/players/HlsJsPlayer.d.ts +0 -18
  176. package/dist/types/components/players/MewsWsPlayer/index.d.ts +0 -18
  177. package/dist/types/components/players/MistPlayer.d.ts +0 -20
  178. package/dist/types/components/players/MistWebRTCPlayer/index.d.ts +0 -20
  179. package/dist/types/components/players/NativePlayer.d.ts +0 -19
  180. package/dist/types/components/players/VideoJsPlayer.d.ts +0 -18
  181. package/src/components/players/DashJsPlayer.tsx +0 -56
  182. package/src/components/players/HlsJsPlayer.tsx +0 -56
  183. package/src/components/players/MewsWsPlayer/index.tsx +0 -56
  184. package/src/components/players/MistPlayer.tsx +0 -60
  185. package/src/components/players/MistWebRTCPlayer/index.tsx +0 -59
  186. package/src/components/players/NativePlayer.tsx +0 -58
  187. package/src/components/players/VideoJsPlayer.tsx +0 -56
@@ -0,0 +1,798 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _rollupPluginBabelHelpers = require('../_virtual/_rollupPluginBabelHelpers.js');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+ var React = require('react');
8
+ var PlayerContext = require('../context/PlayerContext.js');
9
+ var playerCore = require('@livepeer-frameworks/player-core');
10
+ var slider = require('../ui/slider.js');
11
+ var SeekBar = require('./SeekBar.js');
12
+ var Icons = require('./Icons.js');
13
+
14
+ var PlayerControls = function PlayerControls(_ref) {
15
+ var _ctx$state, _mistStreamInfo$meta, _player$getTextTracks, _player$getTextTracks2, _mistStreamInfo$meta$, _mistStreamInfo$meta2, _mistStreamInfo$meta3, _player$getSeekableSt, _player$getLiveEdge;
16
+ var currentTime = _ref.currentTime,
17
+ duration = _ref.duration,
18
+ _ref$isVisible = _ref.isVisible,
19
+ isVisible = _ref$isVisible === void 0 ? true : _ref$isVisible;
20
+ _ref.className;
21
+ var _onSeek = _ref.onSeek,
22
+ mistStreamInfo = _ref.mistStreamInfo,
23
+ _ref$disabled = _ref.disabled,
24
+ disabled = _ref$disabled === void 0 ? false : _ref$disabled,
25
+ _ref$playbackMode = _ref.playbackMode,
26
+ playbackMode = _ref$playbackMode === void 0 ? "auto" : _ref$playbackMode,
27
+ onModeChange = _ref.onModeChange,
28
+ sourceType = _ref.sourceType,
29
+ isContentLive = _ref.isContentLive,
30
+ propVideoElement = _ref.videoElement,
31
+ _ref$qualities = _ref.qualities,
32
+ propQualities = _ref$qualities === void 0 ? [] : _ref$qualities,
33
+ onSelectQuality = _ref.onSelectQuality,
34
+ propIsMuted = _ref.isMuted,
35
+ propVolume = _ref.volume,
36
+ onVolumeChange = _ref.onVolumeChange,
37
+ onToggleMute = _ref.onToggleMute,
38
+ propIsPlaying = _ref.isPlaying,
39
+ onTogglePlay = _ref.onTogglePlay,
40
+ onToggleFullscreen = _ref.onToggleFullscreen,
41
+ propIsFullscreen = _ref.isFullscreen;
42
+ _ref.isLoopEnabled;
43
+ _ref.onToggleLoop;
44
+ var onJumpToLive = _ref.onJumpToLive;
45
+ // Context fallback - prefer props passed from parent over context
46
+ // Context provides UsePlayerControllerReturn which has state.videoElement and controller
47
+ var ctx = PlayerContext.usePlayerContextOptional();
48
+ var contextVideo = ctx === null || ctx === void 0 || (_ctx$state = ctx.state) === null || _ctx$state === void 0 ? void 0 : _ctx$state.videoElement;
49
+ var player = ctx === null || ctx === void 0 ? void 0 : ctx.controller;
50
+ // Robust video element detection - prefer prop, then context, then DOM query
51
+ var _useState = React.useState(null),
52
+ _useState2 = _rollupPluginBabelHelpers.slicedToArray(_useState, 2),
53
+ video = _useState2[0],
54
+ setVideo = _useState2[1];
55
+ var videoCheckIntervalRef = React.useRef(null);
56
+ var findVideoElement = React.useCallback(function () {
57
+ var _player$getVideoEleme, _ref2, _document$querySelect;
58
+ if (propVideoElement) return propVideoElement;
59
+ if (contextVideo) return contextVideo;
60
+ if (player !== null && player !== void 0 && (_player$getVideoEleme = player.getVideoElement) !== null && _player$getVideoEleme !== void 0 && _player$getVideoEleme.call(player)) return player.getVideoElement();
61
+ var domVideo = (_ref2 = (_document$querySelect = document.querySelector(".fw-player-video")) !== null && _document$querySelect !== void 0 ? _document$querySelect : document.querySelector('[data-player-container="true"] video')) !== null && _ref2 !== void 0 ? _ref2 : document.querySelector(".fw-player-container video");
62
+ return domVideo;
63
+ }, [propVideoElement, contextVideo, player]);
64
+ React.useEffect(function () {
65
+ var updateVideo = function updateVideo() {
66
+ var v = findVideoElement();
67
+ if (v && v !== video) {
68
+ setVideo(v);
69
+ }
70
+ };
71
+ updateVideo();
72
+ if (!video) {
73
+ videoCheckIntervalRef.current = setInterval(function () {
74
+ var v = findVideoElement();
75
+ if (v) {
76
+ setVideo(v);
77
+ if (videoCheckIntervalRef.current) {
78
+ clearInterval(videoCheckIntervalRef.current);
79
+ videoCheckIntervalRef.current = null;
80
+ }
81
+ }
82
+ }, 100);
83
+ setTimeout(function () {
84
+ if (videoCheckIntervalRef.current) {
85
+ clearInterval(videoCheckIntervalRef.current);
86
+ videoCheckIntervalRef.current = null;
87
+ }
88
+ }, 5000);
89
+ }
90
+ return function () {
91
+ if (videoCheckIntervalRef.current) {
92
+ clearInterval(videoCheckIntervalRef.current);
93
+ videoCheckIntervalRef.current = null;
94
+ }
95
+ };
96
+ }, [contextVideo, player, findVideoElement, video]);
97
+ var mistTracks = mistStreamInfo === null || mistStreamInfo === void 0 || (_mistStreamInfo$meta = mistStreamInfo.meta) === null || _mistStreamInfo$meta === void 0 ? void 0 : _mistStreamInfo$meta.tracks;
98
+ // Quality selection priority:
99
+ // 1. Player-provided qualities (HLS.js/DASH.js levels with correct numeric indices)
100
+ // 2. Mist track metadata (for players that don't provide quality API)
101
+ // This fixes a critical bug where Mist track IDs (e.g., "a1", "v0") were passed to
102
+ // HLS/DASH players which expect numeric indices (e.g., "0", "1", "2")
103
+ // Quality levels - prefer props, then player API, then Mist tracks
104
+ var qualities = React.useMemo(function () {
105
+ var _player$getQualities;
106
+ // Priority 1: Props from parent (usePlayerController hook)
107
+ if (propQualities && propQualities.length > 0) {
108
+ return propQualities;
109
+ }
110
+ // Priority 2: Player's quality API
111
+ var playerQualities = player === null || player === void 0 || (_player$getQualities = player.getQualities) === null || _player$getQualities === void 0 ? void 0 : _player$getQualities.call(player);
112
+ if (playerQualities && playerQualities.length > 0) {
113
+ return playerQualities;
114
+ }
115
+ // Fallback to Mist track metadata for players without quality API
116
+ if (mistTracks) {
117
+ return Object.entries(mistTracks).filter(function (_ref3) {
118
+ var _ref4 = _rollupPluginBabelHelpers.slicedToArray(_ref3, 2),
119
+ t = _ref4[1];
120
+ return t.type === "video";
121
+ }).map(function (_ref5) {
122
+ var _ref6 = _rollupPluginBabelHelpers.slicedToArray(_ref5, 2),
123
+ id = _ref6[0],
124
+ t = _ref6[1];
125
+ return {
126
+ id: id,
127
+ label: t.height ? "".concat(t.height, "p") : t.codec,
128
+ width: t.width,
129
+ height: t.height,
130
+ bitrate: t.bps
131
+ };
132
+ }).sort(function (a, b) {
133
+ return (b.height || 0) - (a.height || 0);
134
+ });
135
+ }
136
+ return [];
137
+ }, [propQualities, player, mistTracks]);
138
+ var textTracks = (_player$getTextTracks = player === null || player === void 0 || (_player$getTextTracks2 = player.getTextTracks) === null || _player$getTextTracks2 === void 0 ? void 0 : _player$getTextTracks2.call(player)) !== null && _player$getTextTracks !== void 0 ? _player$getTextTracks : [];
139
+ // Internal state - used as fallback when props not provided
140
+ var _useState3 = React.useState(false),
141
+ _useState4 = _rollupPluginBabelHelpers.slicedToArray(_useState3, 2),
142
+ internalIsPlaying = _useState4[0],
143
+ setInternalIsPlaying = _useState4[1];
144
+ var _useState5 = React.useState(false),
145
+ _useState6 = _rollupPluginBabelHelpers.slicedToArray(_useState5, 2),
146
+ internalIsMuted = _useState6[0],
147
+ setInternalIsMuted = _useState6[1];
148
+ var _useState7 = React.useState(false),
149
+ _useState8 = _rollupPluginBabelHelpers.slicedToArray(_useState7, 2),
150
+ internalIsFullscreen = _useState8[0],
151
+ setInternalIsFullscreen = _useState8[1];
152
+ var _useState9 = React.useState(true),
153
+ _useState0 = _rollupPluginBabelHelpers.slicedToArray(_useState9, 2),
154
+ hasAudio = _useState0[0],
155
+ setHasAudio = _useState0[1];
156
+ var _useState1 = React.useState(undefined),
157
+ _useState10 = _rollupPluginBabelHelpers.slicedToArray(_useState1, 2),
158
+ buffered = _useState10[0],
159
+ setBuffered = _useState10[1];
160
+ var _useState11 = React.useState(function () {
161
+ if (!video) return 100;
162
+ return Math.round(video.volume * 100);
163
+ }),
164
+ _useState12 = _rollupPluginBabelHelpers.slicedToArray(_useState11, 2),
165
+ internalVolume = _useState12[0],
166
+ setInternalVolume = _useState12[1];
167
+ var _useState13 = React.useState(function () {
168
+ var _video$playbackRate;
169
+ return (_video$playbackRate = video === null || video === void 0 ? void 0 : video.playbackRate) !== null && _video$playbackRate !== void 0 ? _video$playbackRate : 1;
170
+ }),
171
+ _useState14 = _rollupPluginBabelHelpers.slicedToArray(_useState13, 2),
172
+ playbackRate = _useState14[0],
173
+ setPlaybackRate = _useState14[1];
174
+ // Derived state - prefer props over internal state
175
+ var isPlaying = propIsPlaying !== null && propIsPlaying !== void 0 ? propIsPlaying : internalIsPlaying;
176
+ var isMuted = propIsMuted !== null && propIsMuted !== void 0 ? propIsMuted : internalIsMuted;
177
+ var isFullscreen = propIsFullscreen !== null && propIsFullscreen !== void 0 ? propIsFullscreen : internalIsFullscreen;
178
+ var actualVolume = propVolume !== undefined ? Math.round(propVolume * 100) : internalVolume;
179
+ // Show 0 when muted, actual volume otherwise
180
+ var volumeValue = isMuted ? 0 : actualVolume;
181
+ var _useState15 = React.useState("auto"),
182
+ _useState16 = _rollupPluginBabelHelpers.slicedToArray(_useState15, 2),
183
+ qualityValue = _useState16[0],
184
+ setQualityValue = _useState16[1];
185
+ var _useState17 = React.useState("none"),
186
+ _useState18 = _rollupPluginBabelHelpers.slicedToArray(_useState17, 2),
187
+ captionValue = _useState18[0],
188
+ setCaptionValue = _useState18[1];
189
+ var _useState19 = React.useState(false),
190
+ _useState20 = _rollupPluginBabelHelpers.slicedToArray(_useState19, 2),
191
+ isSettingsOpen = _useState20[0],
192
+ setIsSettingsOpen = _useState20[1];
193
+ // Hysteresis state for Live badge - prevents flip-flopping
194
+ var _useState21 = React.useState(true),
195
+ _useState22 = _rollupPluginBabelHelpers.slicedToArray(_useState21, 2),
196
+ isNearLiveState = _useState22[0],
197
+ setIsNearLiveState = _useState22[1];
198
+ // Close settings menu when clicking outside
199
+ React.useEffect(function () {
200
+ if (!isSettingsOpen) return;
201
+ var handleWindowClick = function handleWindowClick(event) {
202
+ var target = event.target;
203
+ if (target && !target.closest(".fw-settings-menu")) {
204
+ setIsSettingsOpen(false);
205
+ }
206
+ };
207
+ // Use setTimeout to avoid immediate close from the same click that opened it
208
+ var timeoutId = setTimeout(function () {
209
+ window.addEventListener("click", handleWindowClick);
210
+ }, 0);
211
+ return function () {
212
+ clearTimeout(timeoutId);
213
+ window.removeEventListener("click", handleWindowClick);
214
+ };
215
+ }, [isSettingsOpen]);
216
+ // Core utility-based calculations
217
+ var deriveBufferWindowMs = React.useCallback(function (tracks) {
218
+ if (!tracks) return undefined;
219
+ var list = Object.values(tracks);
220
+ if (list.length === 0) return undefined;
221
+ var firstmsValues = list.map(function (t) {
222
+ return t.firstms;
223
+ }).filter(function (v) {
224
+ return v !== undefined;
225
+ });
226
+ var lastmsValues = list.map(function (t) {
227
+ return t.lastms;
228
+ }).filter(function (v) {
229
+ return v !== undefined;
230
+ });
231
+ if (firstmsValues.length === 0 || lastmsValues.length === 0) return undefined;
232
+ var firstms = Math.max.apply(Math, _rollupPluginBabelHelpers.toConsumableArray(firstmsValues));
233
+ var lastms = Math.min.apply(Math, _rollupPluginBabelHelpers.toConsumableArray(lastmsValues));
234
+ var window = lastms - firstms;
235
+ if (!Number.isFinite(window) || window <= 0) return undefined;
236
+ return window;
237
+ }, []);
238
+ var bufferWindowMs = (_mistStreamInfo$meta$ = mistStreamInfo === null || mistStreamInfo === void 0 || (_mistStreamInfo$meta2 = mistStreamInfo.meta) === null || _mistStreamInfo$meta2 === void 0 ? void 0 : _mistStreamInfo$meta2.buffer_window) !== null && _mistStreamInfo$meta$ !== void 0 ? _mistStreamInfo$meta$ : deriveBufferWindowMs(mistStreamInfo === null || mistStreamInfo === void 0 || (_mistStreamInfo$meta3 = mistStreamInfo.meta) === null || _mistStreamInfo$meta3 === void 0 ? void 0 : _mistStreamInfo$meta3.tracks);
239
+ var isLive = React.useMemo(function () {
240
+ return playerCore.isLiveContent(isContentLive, mistStreamInfo, duration);
241
+ }, [isContentLive, mistStreamInfo, duration]);
242
+ var isWebRTC = React.useMemo(function () {
243
+ return playerCore.isMediaStreamSource(video);
244
+ }, [video]);
245
+ var supportsPlaybackRate = React.useMemo(function () {
246
+ return playerCore.supportsPlaybackRate(video);
247
+ }, [video]);
248
+ // Seekable range using core calculation (allow controller override)
249
+ var allowMediaStreamDvr = playerCore.isMediaStreamSource(video) && bufferWindowMs !== undefined && bufferWindowMs > 0 && sourceType !== "whep" && sourceType !== "webrtc";
250
+ var _useMemo = React.useMemo(function () {
251
+ return playerCore.calculateSeekableRange({
252
+ isLive: isLive,
253
+ video: video,
254
+ mistStreamInfo: mistStreamInfo,
255
+ currentTime: currentTime,
256
+ duration: duration,
257
+ allowMediaStreamDvr: allowMediaStreamDvr
258
+ });
259
+ }, [isLive, video, mistStreamInfo, currentTime, duration, allowMediaStreamDvr]),
260
+ calcSeekableStart = _useMemo.seekableStart,
261
+ calcLiveEdge = _useMemo.liveEdge;
262
+ var controllerSeekableStart = player === null || player === void 0 || (_player$getSeekableSt = player.getSeekableStart) === null || _player$getSeekableSt === void 0 ? void 0 : _player$getSeekableSt.call(player);
263
+ var controllerLiveEdge = player === null || player === void 0 || (_player$getLiveEdge = player.getLiveEdge) === null || _player$getLiveEdge === void 0 ? void 0 : _player$getLiveEdge.call(player);
264
+ var useControllerRange = Number.isFinite(controllerSeekableStart) && Number.isFinite(controllerLiveEdge) && controllerLiveEdge >= controllerSeekableStart && (controllerLiveEdge > 0 || controllerSeekableStart > 0);
265
+ var seekableStart = useControllerRange ? controllerSeekableStart : calcSeekableStart;
266
+ var liveEdge = useControllerRange ? controllerLiveEdge : calcLiveEdge;
267
+ var hasDvrWindow = isLive && Number.isFinite(liveEdge) && Number.isFinite(seekableStart) && liveEdge > seekableStart;
268
+ var commitOnRelease = isLive;
269
+ // Live thresholds with buffer window scaling
270
+ var liveThresholds = React.useMemo(function () {
271
+ return playerCore.calculateLiveThresholds(sourceType, isWebRTC, bufferWindowMs);
272
+ }, [sourceType, isWebRTC, bufferWindowMs]);
273
+ // Can seek - prefer PlayerController's computed value (includes player-specific canSeek)
274
+ // Fall back to utility function when controller not available
275
+ var baseCanSeek = React.useMemo(function () {
276
+ // PlayerController already computes canSeek with player-specific logic
277
+ if (player && typeof player.canSeekStream === "function") {
278
+ return player.canSeekStream();
279
+ }
280
+ // Fallback when no controller
281
+ return playerCore.canSeekStream({
282
+ video: video,
283
+ isLive: isLive,
284
+ duration: duration,
285
+ bufferWindowMs: bufferWindowMs
286
+ });
287
+ }, [video, isLive, duration, bufferWindowMs, player]);
288
+ var canSeek = baseCanSeek && (!isLive || hasDvrWindow);
289
+ // Hysteresis for live badge - using core calculation
290
+ React.useEffect(function () {
291
+ if (!isLive) {
292
+ setIsNearLiveState(true);
293
+ return;
294
+ }
295
+ var newState = playerCore.calculateIsNearLive(currentTime, liveEdge, liveThresholds, isNearLiveState);
296
+ if (newState !== isNearLiveState) {
297
+ setIsNearLiveState(newState);
298
+ }
299
+ }, [isLive, liveEdge, currentTime, liveThresholds, isNearLiveState]);
300
+ // Track if we've already seeked to live on initial playback
301
+ var hasSeekToLiveRef = React.useRef(false);
302
+ // Sync internal state from video element (only when props not provided)
303
+ React.useEffect(function () {
304
+ if (!video) return;
305
+ var updatePlayingState = function updatePlayingState() {
306
+ return setInternalIsPlaying(!video.paused);
307
+ };
308
+ var updateMutedState = function updateMutedState() {
309
+ var muted = video.muted || video.volume === 0;
310
+ setInternalIsMuted(muted);
311
+ setInternalVolume(Math.round(video.volume * 100));
312
+ };
313
+ var updateFullscreenState = function updateFullscreenState() {
314
+ if (typeof document !== "undefined") setInternalIsFullscreen(!!document.fullscreenElement);
315
+ };
316
+ var updatePlaybackRate = function updatePlaybackRate() {
317
+ return setPlaybackRate(video.playbackRate);
318
+ };
319
+ updatePlayingState();
320
+ updateMutedState();
321
+ updateFullscreenState();
322
+ updatePlaybackRate();
323
+ video.addEventListener("play", updatePlayingState);
324
+ video.addEventListener("pause", updatePlayingState);
325
+ video.addEventListener("playing", updatePlayingState);
326
+ video.addEventListener("volumechange", updateMutedState);
327
+ video.addEventListener("ratechange", updatePlaybackRate);
328
+ if (typeof document !== "undefined") document.addEventListener("fullscreenchange", updateFullscreenState);
329
+ return function () {
330
+ video.removeEventListener("play", updatePlayingState);
331
+ video.removeEventListener("pause", updatePlayingState);
332
+ video.removeEventListener("playing", updatePlayingState);
333
+ video.removeEventListener("volumechange", updateMutedState);
334
+ video.removeEventListener("ratechange", updatePlaybackRate);
335
+ if (typeof document !== "undefined") document.removeEventListener("fullscreenchange", updateFullscreenState);
336
+ };
337
+ }, [video, isLive]);
338
+ // Reset the seek-to-live flag when video element changes (new stream)
339
+ React.useEffect(function () {
340
+ hasSeekToLiveRef.current = false;
341
+ }, [video]);
342
+ React.useEffect(function () {
343
+ var activeTrack = textTracks.find(function (track) {
344
+ return track.active;
345
+ });
346
+ setCaptionValue(activeTrack ? activeTrack.id : "none");
347
+ }, [textTracks]);
348
+ // Track buffered ranges for SeekBar
349
+ React.useEffect(function () {
350
+ if (!video) return;
351
+ var updateBuffered = function updateBuffered() {
352
+ var _player$getBufferedRa, _player$getBufferedRa2;
353
+ var next = (_player$getBufferedRa = player === null || player === void 0 || (_player$getBufferedRa2 = player.getBufferedRanges) === null || _player$getBufferedRa2 === void 0 ? void 0 : _player$getBufferedRa2.call(player)) !== null && _player$getBufferedRa !== void 0 ? _player$getBufferedRa : video.buffered;
354
+ setBuffered(next);
355
+ };
356
+ updateBuffered();
357
+ video.addEventListener("progress", updateBuffered);
358
+ video.addEventListener("loadeddata", updateBuffered);
359
+ return function () {
360
+ video.removeEventListener("progress", updateBuffered);
361
+ video.removeEventListener("loadeddata", updateBuffered);
362
+ };
363
+ }, [video]);
364
+ React.useEffect(function () {
365
+ if (!video) {
366
+ setHasAudio(true);
367
+ return;
368
+ }
369
+ var checkAudio = function checkAudio() {
370
+ if (video.srcObject instanceof MediaStream) {
371
+ var audioTracks = video.srcObject.getAudioTracks();
372
+ setHasAudio(audioTracks.length > 0);
373
+ return;
374
+ }
375
+ var videoAny = video;
376
+ if (videoAny.audioTracks && videoAny.audioTracks.length !== undefined) {
377
+ setHasAudio(videoAny.audioTracks.length > 0);
378
+ return;
379
+ }
380
+ setHasAudio(true);
381
+ };
382
+ checkAudio();
383
+ video.addEventListener("loadedmetadata", checkAudio);
384
+ return function () {
385
+ return video.removeEventListener("loadedmetadata", checkAudio);
386
+ };
387
+ }, [video]);
388
+ var handlePlayPause = function handlePlayPause() {
389
+ var _ref7, _player$isPaused, _player$isPaused2;
390
+ if (disabled) return;
391
+ // Prefer prop callback from usePlayerController
392
+ if (onTogglePlay) {
393
+ onTogglePlay();
394
+ return;
395
+ }
396
+ // Fallback: direct video/player manipulation
397
+ if (!video && !player) return;
398
+ var isPaused = (_ref7 = (_player$isPaused = player === null || player === void 0 || (_player$isPaused2 = player.isPaused) === null || _player$isPaused2 === void 0 ? void 0 : _player$isPaused2.call(player)) !== null && _player$isPaused !== void 0 ? _player$isPaused : video === null || video === void 0 ? void 0 : video.paused) !== null && _ref7 !== void 0 ? _ref7 : true;
399
+ if (isPaused) {
400
+ if (player !== null && player !== void 0 && player.play) player.play()["catch"](function () {});else if (video) video.play()["catch"](function () {});
401
+ } else {
402
+ if (player !== null && player !== void 0 && player.pause) player.pause();else if (video) video.pause();
403
+ }
404
+ };
405
+ var handleSkipBack = function handleSkipBack() {
406
+ var newTime = Math.max(0, currentTime - 10);
407
+ if (_onSeek) {
408
+ _onSeek(newTime);
409
+ return;
410
+ }
411
+ var v = findVideoElement();
412
+ if (v) v.currentTime = newTime;
413
+ };
414
+ var handleSkipForward = function handleSkipForward() {
415
+ var maxTime = Number.isFinite(duration) ? duration : currentTime + 10;
416
+ var newTime = Math.min(maxTime, currentTime + 10);
417
+ if (_onSeek) {
418
+ _onSeek(newTime);
419
+ return;
420
+ }
421
+ var v = findVideoElement();
422
+ if (v) v.currentTime = newTime;
423
+ };
424
+ var handleMute = function handleMute() {
425
+ var _player$isMuted, _player$isMuted2, _player$setMuted;
426
+ if (disabled) return;
427
+ // Prefer prop callback from usePlayerController
428
+ if (onToggleMute) {
429
+ onToggleMute();
430
+ return;
431
+ }
432
+ // Fallback: direct video/player manipulation
433
+ var v = video !== null && video !== void 0 ? video : document.querySelector(".fw-player-video");
434
+ if (!v) return;
435
+ var nextMuted = !((_player$isMuted = player === null || player === void 0 || (_player$isMuted2 = player.isMuted) === null || _player$isMuted2 === void 0 ? void 0 : _player$isMuted2.call(player)) !== null && _player$isMuted !== void 0 ? _player$isMuted : v.muted);
436
+ player === null || player === void 0 || (_player$setMuted = player.setMuted) === null || _player$setMuted === void 0 || _player$setMuted.call(player, nextMuted);
437
+ v.muted = nextMuted;
438
+ setInternalIsMuted(nextMuted);
439
+ if (nextMuted) setInternalVolume(0);else setInternalVolume(Math.round(v.volume * 100));
440
+ };
441
+ var handleVolumeChange = React.useCallback(function (value) {
442
+ var _value$;
443
+ if (disabled) return;
444
+ var next = Math.max(0, Math.min(100, (_value$ = value[0]) !== null && _value$ !== void 0 ? _value$ : 0));
445
+ // Prefer prop callback from usePlayerController
446
+ if (onVolumeChange) {
447
+ onVolumeChange(next / 100);
448
+ return;
449
+ }
450
+ // Fallback: direct video manipulation
451
+ var v = video !== null && video !== void 0 ? video : document.querySelector(".fw-player-video");
452
+ if (!v) return;
453
+ v.volume = next / 100;
454
+ v.muted = next === 0;
455
+ setInternalVolume(next);
456
+ setInternalIsMuted(next === 0);
457
+ }, [disabled, onVolumeChange, video]);
458
+ var handleFullscreen = function handleFullscreen() {
459
+ if (disabled) return;
460
+ // Prefer prop callback from usePlayerController
461
+ if (onToggleFullscreen) {
462
+ onToggleFullscreen();
463
+ return;
464
+ }
465
+ // Fallback: direct DOM manipulation
466
+ if (typeof document === "undefined") return;
467
+ var container = document.querySelector('[data-player-container="true"]');
468
+ if (!container) return;
469
+ if (document.fullscreenElement) document.exitFullscreen()["catch"](function () {});else container.requestFullscreen()["catch"](function () {});
470
+ };
471
+ var handleGoLive = function handleGoLive() {
472
+ var _player$jumpToLive;
473
+ if (disabled) return;
474
+ if (onJumpToLive) {
475
+ onJumpToLive();
476
+ return;
477
+ }
478
+ player === null || player === void 0 || (_player$jumpToLive = player.jumpToLive) === null || _player$jumpToLive === void 0 || _player$jumpToLive.call(player);
479
+ };
480
+ var handleSpeedChange = function handleSpeedChange(value) {
481
+ if (disabled) return;
482
+ var rate = Number(value);
483
+ setPlaybackRate(rate);
484
+ // Use player API if available, fall back to direct video element
485
+ if (player !== null && player !== void 0 && player.setPlaybackRate) {
486
+ player.setPlaybackRate(rate);
487
+ } else {
488
+ var v = findVideoElement();
489
+ if (v) v.playbackRate = rate;
490
+ }
491
+ };
492
+ var handleQualityChange = function handleQualityChange(value) {
493
+ var _player$selectQuality;
494
+ if (disabled) return;
495
+ setQualityValue(value);
496
+ // Prefer prop callback from usePlayerController
497
+ if (onSelectQuality) {
498
+ onSelectQuality(value);
499
+ return;
500
+ }
501
+ // Fallback: direct player manipulation
502
+ player === null || player === void 0 || (_player$selectQuality = player.selectQuality) === null || _player$selectQuality === void 0 || _player$selectQuality.call(player, value);
503
+ };
504
+ var handleCaptionChange = function handleCaptionChange(value) {
505
+ var _player$selectTextTra, _player$selectTextTra2;
506
+ if (disabled) return;
507
+ setCaptionValue(value);
508
+ if (value === "none") player === null || player === void 0 || (_player$selectTextTra = player.selectTextTrack) === null || _player$selectTextTra === void 0 || _player$selectTextTra.call(player, null);else player === null || player === void 0 || (_player$selectTextTra2 = player.selectTextTrack) === null || _player$selectTextTra2 === void 0 || _player$selectTextTra2.call(player, value);
509
+ };
510
+ // Time display - using core formatTimeDisplay
511
+ var timeDisplay = React.useMemo(function () {
512
+ return playerCore.formatTimeDisplay({
513
+ isLive: isLive,
514
+ currentTime: currentTime,
515
+ duration: duration,
516
+ liveEdge: liveEdge,
517
+ seekableStart: seekableStart,
518
+ unixoffset: mistStreamInfo === null || mistStreamInfo === void 0 ? void 0 : mistStreamInfo.unixoffset
519
+ });
520
+ }, [isLive, currentTime, duration, liveEdge, seekableStart, mistStreamInfo === null || mistStreamInfo === void 0 ? void 0 : mistStreamInfo.unixoffset]);
521
+ var _useState23 = React.useState(false),
522
+ _useState24 = _rollupPluginBabelHelpers.slicedToArray(_useState23, 2),
523
+ isVolumeHovered = _useState24[0],
524
+ setIsVolumeHovered = _useState24[1];
525
+ var _useState25 = React.useState(false),
526
+ _useState26 = _rollupPluginBabelHelpers.slicedToArray(_useState25, 2),
527
+ isVolumeFocused = _useState26[0],
528
+ setIsVolumeFocused = _useState26[1];
529
+ var isVolumeExpanded = isVolumeHovered || isVolumeFocused;
530
+ var volumeGroupRef = React.useRef(null);
531
+ // Non-passive wheel listener for volume control
532
+ React.useEffect(function () {
533
+ var el = volumeGroupRef.current;
534
+ if (!el) return;
535
+ var handler = function handler(e) {
536
+ if (disabled || !hasAudio) return;
537
+ e.preventDefault();
538
+ var delta = e.deltaY < 0 ? 5 : -5;
539
+ handleVolumeChange([actualVolume + delta]);
540
+ };
541
+ el.addEventListener("wheel", handler, {
542
+ passive: false
543
+ });
544
+ return function () {
545
+ return el.removeEventListener("wheel", handler);
546
+ };
547
+ }, [disabled, hasAudio, actualVolume, handleVolumeChange]);
548
+ return jsxRuntime.jsx("div", {
549
+ className: playerCore.cn("fw-player-surface fw-controls-wrapper", isVisible ? "fw-controls-wrapper--visible" : "fw-controls-wrapper--hidden"),
550
+ children: jsxRuntime.jsxs("div", {
551
+ className: "fw-control-bar pointer-events-auto",
552
+ onClick: function onClick(e) {
553
+ return e.stopPropagation();
554
+ },
555
+ children: [canSeek && jsxRuntime.jsx("div", {
556
+ className: "fw-seek-wrapper",
557
+ children: jsxRuntime.jsx(SeekBar.default, {
558
+ currentTime: currentTime,
559
+ duration: duration,
560
+ buffered: buffered,
561
+ disabled: disabled,
562
+ isLive: isLive,
563
+ seekableStart: seekableStart,
564
+ liveEdge: liveEdge,
565
+ commitOnRelease: commitOnRelease,
566
+ onSeek: function onSeek(time) {
567
+ if (_onSeek) {
568
+ _onSeek(time);
569
+ } else if (video) {
570
+ video.currentTime = time;
571
+ }
572
+ }
573
+ })
574
+ }), jsxRuntime.jsxs("div", {
575
+ className: "fw-controls-row",
576
+ children: [jsxRuntime.jsxs("div", {
577
+ className: "fw-controls-left",
578
+ children: [jsxRuntime.jsxs("div", {
579
+ className: "fw-control-group",
580
+ children: [jsxRuntime.jsx("button", {
581
+ type: "button",
582
+ className: "fw-btn-flush",
583
+ "aria-label": isPlaying ? "Pause" : "Play",
584
+ onClick: handlePlayPause,
585
+ children: jsxRuntime.jsx(Icons.PlayPauseIcon, {
586
+ isPlaying: isPlaying,
587
+ size: 18
588
+ })
589
+ }), canSeek && jsxRuntime.jsxs(jsxRuntime.Fragment, {
590
+ children: [jsxRuntime.jsx("button", {
591
+ type: "button",
592
+ className: "fw-btn-flush hidden sm:flex",
593
+ "aria-label": "Skip back 10 seconds",
594
+ onClick: handleSkipBack,
595
+ children: jsxRuntime.jsx(Icons.SkipBackIcon, {
596
+ size: 16
597
+ })
598
+ }), jsxRuntime.jsx("button", {
599
+ type: "button",
600
+ className: "fw-btn-flush hidden sm:flex",
601
+ "aria-label": "Skip forward 10 seconds",
602
+ onClick: handleSkipForward,
603
+ children: jsxRuntime.jsx(Icons.SkipForwardIcon, {
604
+ size: 16
605
+ })
606
+ })]
607
+ })]
608
+ }), jsxRuntime.jsxs("div", {
609
+ ref: volumeGroupRef,
610
+ className: playerCore.cn("fw-volume-group", isVolumeExpanded && "fw-volume-group--expanded", !hasAudio && "fw-volume-group--disabled"),
611
+ onMouseEnter: function onMouseEnter() {
612
+ return hasAudio && setIsVolumeHovered(true);
613
+ },
614
+ onMouseLeave: function onMouseLeave() {
615
+ setIsVolumeHovered(false);
616
+ setIsVolumeFocused(false);
617
+ },
618
+ onFocusCapture: function onFocusCapture() {
619
+ return hasAudio && setIsVolumeFocused(true);
620
+ },
621
+ onBlurCapture: function onBlurCapture(e) {
622
+ if (!e.currentTarget.contains(e.relatedTarget)) setIsVolumeFocused(false);
623
+ },
624
+ onClick: function onClick(e) {
625
+ // Click on the pill (not slider) toggles mute
626
+ if (hasAudio && e.target === e.currentTarget) {
627
+ handleMute();
628
+ }
629
+ },
630
+ children: [jsxRuntime.jsx("button", {
631
+ type: "button",
632
+ className: "fw-volume-btn",
633
+ "aria-label": !hasAudio ? "No audio" : isMuted ? "Unmute" : "Mute",
634
+ onClick: hasAudio ? handleMute : undefined,
635
+ disabled: !hasAudio,
636
+ children: jsxRuntime.jsx(Icons.VolumeIcon, {
637
+ isMuted: isMuted || !hasAudio,
638
+ size: 16
639
+ })
640
+ }), jsxRuntime.jsx("div", {
641
+ className: playerCore.cn("fw-volume-slider-wrapper", isVolumeExpanded ? "fw-volume-slider-wrapper--expanded" : "fw-volume-slider-wrapper--collapsed"),
642
+ children: jsxRuntime.jsx(slider.Slider, {
643
+ orientation: "horizontal",
644
+ "aria-label": "Volume",
645
+ max: 100,
646
+ step: 1,
647
+ value: [volumeValue],
648
+ onValueChange: handleVolumeChange,
649
+ className: "w-full",
650
+ disabled: !hasAudio
651
+ })
652
+ })]
653
+ }), jsxRuntime.jsx("div", {
654
+ className: "fw-control-group",
655
+ children: jsxRuntime.jsx("span", {
656
+ className: "fw-time-display",
657
+ children: timeDisplay
658
+ })
659
+ }), isLive && jsxRuntime.jsx("div", {
660
+ className: "fw-control-group",
661
+ children: jsxRuntime.jsxs("button", {
662
+ type: "button",
663
+ onClick: handleGoLive,
664
+ disabled: !hasDvrWindow || isNearLiveState,
665
+ className: playerCore.cn("fw-live-badge", !hasDvrWindow || isNearLiveState ? "fw-live-badge--active" : "fw-live-badge--behind"),
666
+ title: !hasDvrWindow ? "Live only" : isNearLiveState ? "At live edge" : "Jump to live",
667
+ children: ["LIVE", !isNearLiveState && hasDvrWindow && jsxRuntime.jsx(Icons.SeekToLiveIcon, {
668
+ size: 10
669
+ })]
670
+ })
671
+ })]
672
+ }), jsxRuntime.jsxs("div", {
673
+ className: "fw-controls-right",
674
+ children: [jsxRuntime.jsxs("div", {
675
+ className: "fw-control-group relative",
676
+ children: [jsxRuntime.jsx("button", {
677
+ type: "button",
678
+ className: playerCore.cn("fw-btn-flush group", isSettingsOpen && "fw-btn-flush--active"),
679
+ "aria-label": "Settings",
680
+ title: "Settings",
681
+ onClick: function onClick() {
682
+ return setIsSettingsOpen(!isSettingsOpen);
683
+ },
684
+ children: jsxRuntime.jsx(Icons.SettingsIcon, {
685
+ size: 16,
686
+ className: "transition-transform group-hover:rotate-90"
687
+ })
688
+ }), isSettingsOpen && jsxRuntime.jsxs("div", {
689
+ className: "fw-player-surface fw-settings-menu",
690
+ children: [onModeChange && isContentLive !== false && jsxRuntime.jsxs("div", {
691
+ className: "fw-settings-section",
692
+ children: [jsxRuntime.jsx("div", {
693
+ className: "fw-settings-label",
694
+ children: "Mode"
695
+ }), jsxRuntime.jsx("div", {
696
+ className: "fw-settings-options",
697
+ children: ["auto", "low-latency", "quality"].map(function (mode) {
698
+ return jsxRuntime.jsx("button", {
699
+ className: playerCore.cn("fw-settings-btn", playbackMode === mode && "fw-settings-btn--active"),
700
+ onClick: function onClick() {
701
+ onModeChange(mode);
702
+ setIsSettingsOpen(false);
703
+ },
704
+ children: mode === "low-latency" ? "Fast" : mode === "quality" ? "Stable" : "Auto"
705
+ }, mode);
706
+ })
707
+ })]
708
+ }), supportsPlaybackRate && jsxRuntime.jsxs("div", {
709
+ className: "fw-settings-section",
710
+ children: [jsxRuntime.jsx("div", {
711
+ className: "fw-settings-label",
712
+ children: "Speed"
713
+ }), jsxRuntime.jsx("div", {
714
+ className: "fw-settings-options fw-settings-options--wrap",
715
+ children: playerCore.SPEED_PRESETS.map(function (rate) {
716
+ return jsxRuntime.jsxs("button", {
717
+ className: playerCore.cn("fw-settings-btn", playbackRate === rate && "fw-settings-btn--active"),
718
+ onClick: function onClick() {
719
+ handleSpeedChange(String(rate));
720
+ setIsSettingsOpen(false);
721
+ },
722
+ children: [rate, "x"]
723
+ }, rate);
724
+ })
725
+ })]
726
+ }), qualities.length > 0 && jsxRuntime.jsxs("div", {
727
+ className: "fw-settings-section",
728
+ children: [jsxRuntime.jsx("div", {
729
+ className: "fw-settings-label",
730
+ children: "Quality"
731
+ }), jsxRuntime.jsxs("div", {
732
+ className: "fw-settings-list",
733
+ children: [jsxRuntime.jsx("button", {
734
+ className: playerCore.cn("fw-settings-list-item", qualityValue === "auto" && "fw-settings-list-item--active"),
735
+ onClick: function onClick() {
736
+ handleQualityChange("auto");
737
+ setIsSettingsOpen(false);
738
+ },
739
+ children: "Auto"
740
+ }), qualities.map(function (q) {
741
+ return jsxRuntime.jsx("button", {
742
+ className: playerCore.cn("fw-settings-list-item", qualityValue === q.id && "fw-settings-list-item--active"),
743
+ onClick: function onClick() {
744
+ handleQualityChange(q.id);
745
+ setIsSettingsOpen(false);
746
+ },
747
+ children: q.label
748
+ }, q.id);
749
+ })]
750
+ })]
751
+ }), textTracks.length > 0 && jsxRuntime.jsxs("div", {
752
+ className: "fw-settings-section",
753
+ children: [jsxRuntime.jsx("div", {
754
+ className: "fw-settings-label",
755
+ children: "Captions"
756
+ }), jsxRuntime.jsxs("div", {
757
+ className: "fw-settings-list",
758
+ children: [jsxRuntime.jsx("button", {
759
+ className: playerCore.cn("fw-settings-list-item", captionValue === "none" && "fw-settings-list-item--active"),
760
+ onClick: function onClick() {
761
+ handleCaptionChange("none");
762
+ setIsSettingsOpen(false);
763
+ },
764
+ children: "Off"
765
+ }), textTracks.map(function (t) {
766
+ return jsxRuntime.jsx("button", {
767
+ className: playerCore.cn("fw-settings-list-item", captionValue === t.id && "fw-settings-list-item--active"),
768
+ onClick: function onClick() {
769
+ handleCaptionChange(t.id);
770
+ setIsSettingsOpen(false);
771
+ },
772
+ children: t.label || t.id
773
+ }, t.id);
774
+ })]
775
+ })]
776
+ })]
777
+ })]
778
+ }), jsxRuntime.jsx("div", {
779
+ className: "fw-control-group",
780
+ children: jsxRuntime.jsx("button", {
781
+ type: "button",
782
+ className: "fw-btn-flush",
783
+ "aria-label": "Toggle fullscreen",
784
+ onClick: handleFullscreen,
785
+ children: jsxRuntime.jsx(Icons.FullscreenToggleIcon, {
786
+ isFullscreen: isFullscreen,
787
+ size: 16
788
+ })
789
+ })
790
+ })]
791
+ })]
792
+ })]
793
+ })
794
+ });
795
+ };
796
+
797
+ exports.default = PlayerControls;
798
+ //# sourceMappingURL=PlayerControls.js.map