@nikelroot/video-player 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,6 +28,7 @@ export function Example() {
28
28
  videoRef={videoRef}
29
29
  videoSrc="https://example.com/stream.m3u8"
30
30
  controlsVariant="full"
31
+ active={true}
31
32
  live={false}
32
33
  autoPlay={false}
33
34
  onTimeChange={(time) => console.log(time)}
package/dist/index.cjs CHANGED
@@ -37,6 +37,9 @@ __export(index_exports, {
37
37
  module.exports = __toCommonJS(index_exports);
38
38
 
39
39
  // src/VideoPlayer.tsx
40
+ var import_react3 = require("react");
41
+
42
+ // src/controls.tsx
40
43
  var import_react2 = require("react");
41
44
 
42
45
  // src/styles.ts
@@ -87,6 +90,15 @@ var VideoPlayerControlStyles = import_styled_components.styled.div`
87
90
  outline: none;
88
91
  }
89
92
 
93
+ .video-player-control-button:disabled {
94
+ opacity: 0.42;
95
+ cursor: not-allowed;
96
+ }
97
+
98
+ .video-player-control-button:disabled:hover {
99
+ background: rgba(255, 255, 255, 0.12);
100
+ }
101
+
90
102
  .video-player-icon {
91
103
  width: 17px;
92
104
  height: 17px;
@@ -277,6 +289,13 @@ var VideoPlayerStyles = import_styled_components.styled.div`
277
289
  }
278
290
  }
279
291
 
292
+ &:hover {
293
+ .video-right-bar {
294
+ background: rgba(0, 0, 0, 0.1);
295
+ opacity: 1;
296
+ }
297
+ }
298
+
280
299
  ${(props) => (props.$showControl || props.$alwaysShowSegments) && import_styled_components.css`
281
300
  .video-right-bar {
282
301
  background: rgba(0, 0, 0, 0.1);
@@ -411,6 +430,7 @@ var createThrottledNumberFn = (fn, waitMs) => {
411
430
  // src/controls.tsx
412
431
  var import_jsx_runtime = require("react/jsx-runtime");
413
432
  var SeekSlider = ({ playAction, duration, currentTime, seekSetTime }) => {
433
+ const ref = (0, import_react2.useRef)(null);
414
434
  const clickHandler = (e) => {
415
435
  e.preventDefault();
416
436
  e.stopPropagation();
@@ -419,10 +439,14 @@ var SeekSlider = ({ playAction, duration, currentTime, seekSetTime }) => {
419
439
  const safeCurrentTime = Math.max(0, Math.min(Number.isFinite(currentTime) ? currentTime : 0, safeDuration || Infinity));
420
440
  const completeChange = () => {
421
441
  void playAction();
442
+ if (ref.current) {
443
+ ref.current.blur();
444
+ }
422
445
  };
423
446
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SeekSliderStyled, { onClick: clickHandler, className: "seek-slider", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
424
447
  "input",
425
448
  {
449
+ ref,
426
450
  "aria-label": "Seek",
427
451
  className: "seek-range",
428
452
  disabled: safeDuration <= 0,
@@ -482,11 +506,23 @@ var Icon = ({ name }) => {
482
506
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { "aria-hidden": "true", className: "video-player-icon", focusable: "false", viewBox: "0 0 24 24", children: paths[name] });
483
507
  };
484
508
  var ControlButton = ({
509
+ disabled,
485
510
  icon,
486
511
  label,
487
512
  onClick
488
513
  }) => {
489
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { "aria-label": label, className: "video-player-control-button", onClick: () => void onClick(), title: label, type: "button", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { name: icon }) });
514
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
515
+ "button",
516
+ {
517
+ "aria-label": label,
518
+ className: "video-player-control-button",
519
+ disabled,
520
+ onClick: () => void onClick(),
521
+ title: label,
522
+ type: "button",
523
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { name: icon })
524
+ }
525
+ );
490
526
  };
491
527
  var stopControlClick = (e) => {
492
528
  e.preventDefault();
@@ -496,6 +532,7 @@ var VideoPlayerControls = (props) => {
496
532
  const {
497
533
  variant,
498
534
  fullScreenAction,
535
+ fullscreenAllowed,
499
536
  showControl,
500
537
  playAction,
501
538
  pauseAction,
@@ -511,19 +548,6 @@ var VideoPlayerControls = (props) => {
511
548
  } = props;
512
549
  const [, isMobile] = useWindowWidth();
513
550
  if (variant === "none") return null;
514
- if (variant === "tiny") {
515
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VideoPlayerControlStyles, { $tiny: true, $isMobile: isMobile, onClick: stopControlClick, $showControl: showControl, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "video-player-control-row", children: [
516
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "full" }),
517
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SeekSlider, { playAction, duration, currentTime, seekSetTime })
518
- ] }) });
519
- }
520
- if (variant === "fullscreen-only") {
521
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VideoPlayerControlStyles, { $isMobile: isMobile, onClick: stopControlClick, $showControl: showControl, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "video-player-control-row", children: [
522
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "full" }),
523
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, { icon: muted ? "mute" : "sound", label: muted ? "Unmute" : "Mute", onClick: () => onMutedChange(!muted) }),
524
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, { icon: "fullscreen", label: "Fullscreen", onClick: fullScreenAction })
525
- ] }) });
526
- }
527
551
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VideoPlayerControlStyles, { $isMobile: isMobile, onClick: stopControlClick, $showControl: showControl, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "video-player-control-row", children: [
528
552
  playing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, { icon: "pause", label: "Pause", onClick: pauseAction }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, { icon: "play", label: "Play", onClick: playAction }),
529
553
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, { icon: "back", label: "Back 15 seconds", onClick: () => seekPrevAction() }),
@@ -533,7 +557,7 @@ var VideoPlayerControls = (props) => {
533
557
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SeekSlider, { playAction, duration, currentTime, seekSetTime }),
534
558
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimeBlock, { duration, currentTime }),
535
559
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, { icon: muted ? "mute" : "sound", label: muted ? "Unmute" : "Mute", onClick: () => onMutedChange(!muted) }),
536
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, { icon: "fullscreen", label: "Fullscreen", onClick: fullScreenAction })
560
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ControlButton, { disabled: !fullscreenAllowed, icon: "fullscreen", label: "Fullscreen", onClick: fullScreenAction })
537
561
  ] }) });
538
562
  };
539
563
 
@@ -751,6 +775,7 @@ var assignRef = (ref, value) => {
751
775
  var VideoPlayerBase = (props, ref) => {
752
776
  const {
753
777
  controlsVariant = "none",
778
+ active,
754
779
  autoPlay = false,
755
780
  preload = "metadata",
756
781
  loop = false,
@@ -799,39 +824,40 @@ var VideoPlayerBase = (props, ref) => {
799
824
  ...videoProps
800
825
  } = props;
801
826
  const videoSrc = rawVideoSrc.replaceAll(" ", "%20").replaceAll("#", "%23");
802
- const messages = (0, import_react2.useMemo)(() => ({ ...defaultMessages, ...messagesProp }), [messagesProp]);
827
+ const messages = (0, import_react3.useMemo)(() => ({ ...defaultMessages, ...messagesProp }), [messagesProp]);
803
828
  const [, isMobile, mobileType] = useWindowWidth();
804
- const videoRef = (0, import_react2.useRef)(null);
805
- const hlsRef = (0, import_react2.useRef)(null);
806
- const containerRef = (0, import_react2.useRef)(null);
807
- const netRetryCount = (0, import_react2.useRef)(0);
808
- const mediaRecoverCount = (0, import_react2.useRef)(0);
809
- const fatalReloadCount = (0, import_react2.useRef)(0);
810
- const reloadTimer = (0, import_react2.useRef)(null);
811
- const stalledNudgeTimer = (0, import_react2.useRef)(null);
812
- const liveStabilityMode = (0, import_react2.useRef)(false);
813
- const liveStallEvents = (0, import_react2.useRef)([]);
814
- const controlTime = (0, import_react2.useRef)(null);
815
- const [playingState, setPlayingState] = (0, import_react2.useState)(!!defaultPlaying);
816
- const [mutedState, setMutedState] = (0, import_react2.useState)(!!defaultMuted);
817
- const [durationState, setDurationState] = (0, import_react2.useState)(0);
818
- const [currentTimeState, setCurrentTimeState] = (0, import_react2.useState)(0);
819
- const [isFullscreen, setIsFullscreen] = (0, import_react2.useState)(false);
820
- const [showControl, setControlVisible] = (0, import_react2.useState)(true);
821
- const [errorState, setErrorState] = (0, import_react2.useState)(null);
822
- const [reloadToken, setReloadToken] = (0, import_react2.useState)(0);
829
+ const videoRef = (0, import_react3.useRef)(null);
830
+ const hlsRef = (0, import_react3.useRef)(null);
831
+ const containerRef = (0, import_react3.useRef)(null);
832
+ const netRetryCount = (0, import_react3.useRef)(0);
833
+ const mediaRecoverCount = (0, import_react3.useRef)(0);
834
+ const fatalReloadCount = (0, import_react3.useRef)(0);
835
+ const reloadTimer = (0, import_react3.useRef)(null);
836
+ const stalledNudgeTimer = (0, import_react3.useRef)(null);
837
+ const liveStabilityMode = (0, import_react3.useRef)(false);
838
+ const liveStallEvents = (0, import_react3.useRef)([]);
839
+ const controlTime = (0, import_react3.useRef)(null);
840
+ const [playingState, setPlayingState] = (0, import_react3.useState)(!!defaultPlaying);
841
+ const [mutedState, setMutedState] = (0, import_react3.useState)(!!defaultMuted);
842
+ const [durationState, setDurationState] = (0, import_react3.useState)(0);
843
+ const [currentTimeState, setCurrentTimeState] = (0, import_react3.useState)(0);
844
+ const [isFullscreen, setIsFullscreen] = (0, import_react3.useState)(false);
845
+ const [showControl, setControlVisible] = (0, import_react3.useState)(true);
846
+ const [errorState, setErrorState] = (0, import_react3.useState)(null);
847
+ const [reloadToken, setReloadToken] = (0, import_react3.useState)(0);
823
848
  const playing = playingProp != null ? playingProp : playingState;
824
849
  const muted = mutedProp != null ? mutedProp : mutedState;
825
850
  const duration = durationProp != null ? durationProp : durationState;
826
851
  const currentTime = currentTimeProp != null ? currentTimeProp : currentTimeState;
827
- const setTimeD = (0, import_react2.useRef)(
852
+ const fullscreenAllowed = active != null ? active : true;
853
+ const setTimeD = (0, import_react3.useRef)(
828
854
  createThrottledNumberFn((value) => {
829
855
  const v = videoRef.current;
830
856
  if (!v) return;
831
857
  v.currentTime = value;
832
858
  }, 30)
833
859
  );
834
- const clearTimers = (0, import_react2.useCallback)(() => {
860
+ const clearTimers = (0, import_react3.useCallback)(() => {
835
861
  if (reloadTimer.current) {
836
862
  clearTimeout(reloadTimer.current);
837
863
  reloadTimer.current = null;
@@ -845,14 +871,14 @@ var VideoPlayerBase = (props, ref) => {
845
871
  controlTime.current = null;
846
872
  }
847
873
  }, []);
848
- const setNextPlaying = (0, import_react2.useCallback)(
874
+ const setNextPlaying = (0, import_react3.useCallback)(
849
875
  (value) => {
850
876
  if (playingProp === void 0) setPlayingState(value);
851
877
  onPlayingChange == null ? void 0 : onPlayingChange(value);
852
878
  },
853
879
  [onPlayingChange, playingProp]
854
880
  );
855
- const setNextMuted = (0, import_react2.useCallback)(
881
+ const setNextMuted = (0, import_react3.useCallback)(
856
882
  (value) => {
857
883
  if (mutedProp === void 0) setMutedState(value);
858
884
  const el = videoRef.current;
@@ -861,10 +887,10 @@ var VideoPlayerBase = (props, ref) => {
861
887
  },
862
888
  [mutedProp, onMutedChange]
863
889
  );
864
- const clearErrorState = (0, import_react2.useCallback)(() => {
890
+ const clearErrorState = (0, import_react3.useCallback)(() => {
865
891
  setErrorState((prev) => prev ? null : prev);
866
892
  }, []);
867
- const reportError = (0, import_react2.useCallback)(
893
+ const reportError = (0, import_react3.useCallback)(
868
894
  (error, data, message = messages.defaultError) => {
869
895
  const details = formatPlaybackErrorDetails(error, data);
870
896
  const code = getPlaybackErrorCodeText(error);
@@ -881,27 +907,27 @@ var VideoPlayerBase = (props, ref) => {
881
907
  },
882
908
  [messages.defaultError, onPlaybackError, setNextPlaying]
883
909
  );
884
- const notifyActiveChange = (0, import_react2.useCallback)(
910
+ const notifyActiveChange = (0, import_react3.useCallback)(
885
911
  (video, reason) => {
886
912
  onActiveChange == null ? void 0 : onActiveChange({ video, reason });
887
913
  },
888
914
  [onActiveChange]
889
915
  );
890
- const onTime = (0, import_react2.useCallback)(() => {
916
+ const onTime = (0, import_react3.useCallback)(() => {
891
917
  const el = videoRef.current;
892
918
  if (!el) return;
893
919
  const nextTime = Number.isFinite(el.currentTime) ? el.currentTime : 0;
894
920
  if (currentTimeProp === void 0) setCurrentTimeState(nextTime);
895
921
  onTimeChange == null ? void 0 : onTimeChange(nextTime, el);
896
922
  }, [currentTimeProp, onTimeChange]);
897
- const onDur = (0, import_react2.useCallback)(() => {
923
+ const onDur = (0, import_react3.useCallback)(() => {
898
924
  const el = videoRef.current;
899
925
  if (!el) return;
900
926
  const nextDuration = Number.isFinite(el.duration) ? el.duration : 0;
901
927
  if (durationProp === void 0) setDurationState(nextDuration);
902
928
  onDurationChange == null ? void 0 : onDurationChange(nextDuration, el);
903
929
  }, [durationProp, onDurationChange]);
904
- const setVideoEl = (0, import_react2.useCallback)(
930
+ const setVideoEl = (0, import_react3.useCallback)(
905
931
  (el) => {
906
932
  const prev = videoRef.current;
907
933
  if (prev === el) return;
@@ -920,7 +946,7 @@ var VideoPlayerBase = (props, ref) => {
920
946
  },
921
947
  [externalVideoRef, muted, notifyActiveChange, onDur, onTime, onVideoRefChange]
922
948
  );
923
- const pauseOtherVideos = (0, import_react2.useCallback)(
949
+ const pauseOtherVideos = (0, import_react3.useCallback)(
924
950
  (el) => {
925
951
  if (!exclusivePlayback) return;
926
952
  if (typeof exclusivePlayback === "object") {
@@ -934,7 +960,7 @@ var VideoPlayerBase = (props, ref) => {
934
960
  },
935
961
  [exclusivePlayback]
936
962
  );
937
- const playAction = (0, import_react2.useCallback)(async () => {
963
+ const playAction = (0, import_react3.useCallback)(async () => {
938
964
  const el = videoRef.current;
939
965
  if (!el) return;
940
966
  pauseOtherVideos(el);
@@ -960,14 +986,14 @@ var VideoPlayerBase = (props, ref) => {
960
986
  }
961
987
  }
962
988
  }, [clearErrorState, messages.playFailed, notifyActiveChange, pauseOtherVideos, reportError, scrollTo, setNextPlaying]);
963
- const pauseAction = (0, import_react2.useCallback)(() => {
989
+ const pauseAction = (0, import_react3.useCallback)(() => {
964
990
  const el = videoRef.current;
965
991
  if (!el) return;
966
992
  el.pause();
967
993
  setNextPlaying(false);
968
994
  notifyActiveChange(el, "pause");
969
995
  }, [notifyActiveChange, setNextPlaying]);
970
- const seekSetTime = (0, import_react2.useCallback)(
996
+ const seekSetTime = (0, import_react3.useCallback)(
971
997
  (value) => {
972
998
  const el = videoRef.current;
973
999
  if (!el) return;
@@ -978,7 +1004,7 @@ var VideoPlayerBase = (props, ref) => {
978
1004
  },
979
1005
  [currentTimeProp, duration, notifyActiveChange]
980
1006
  );
981
- const seekPrevAction = (0, import_react2.useCallback)(
1007
+ const seekPrevAction = (0, import_react3.useCallback)(
982
1008
  (sec = 15) => {
983
1009
  const el = videoRef.current;
984
1010
  if (!el) return;
@@ -986,7 +1012,7 @@ var VideoPlayerBase = (props, ref) => {
986
1012
  },
987
1013
  [seekSetTime]
988
1014
  );
989
- const seekNextAction = (0, import_react2.useCallback)(
1015
+ const seekNextAction = (0, import_react3.useCallback)(
990
1016
  (sec = 15) => {
991
1017
  const el = videoRef.current;
992
1018
  if (!el) return;
@@ -994,7 +1020,7 @@ var VideoPlayerBase = (props, ref) => {
994
1020
  },
995
1021
  [seekSetTime]
996
1022
  );
997
- const toggleAction = (0, import_react2.useCallback)(
1023
+ const toggleAction = (0, import_react3.useCallback)(
998
1024
  (force) => {
999
1025
  const el = videoRef.current;
1000
1026
  if (!el) return;
@@ -1006,12 +1032,12 @@ var VideoPlayerBase = (props, ref) => {
1006
1032
  },
1007
1033
  [pauseAction, playAction]
1008
1034
  );
1009
- const nudgeOnStall = (0, import_react2.useCallback)(() => {
1035
+ const nudgeOnStall = (0, import_react3.useCallback)(() => {
1010
1036
  const v = videoRef.current;
1011
1037
  if (!v) return;
1012
1038
  v.currentTime = Math.max(0, v.currentTime + 0.05);
1013
1039
  }, []);
1014
- const shouldSwitchLiveToStability = (0, import_react2.useCallback)(() => {
1040
+ const shouldSwitchLiveToStability = (0, import_react3.useCallback)(() => {
1015
1041
  if (!live || liveStabilityMode.current) return false;
1016
1042
  const now = Date.now();
1017
1043
  liveStallEvents.current = [...liveStallEvents.current.filter((ts) => now - ts <= LIVE_STALL_WINDOW_MS), now];
@@ -1020,7 +1046,7 @@ var VideoPlayerBase = (props, ref) => {
1020
1046
  liveStallEvents.current = [];
1021
1047
  return true;
1022
1048
  }, [live]);
1023
- const resolvedHlsConfig = (0, import_react2.useCallback)(() => {
1049
+ const resolvedHlsConfig = (0, import_react3.useCallback)(() => {
1024
1050
  const modeConfig = live ? liveHlsConfig : vodHlsConfig;
1025
1051
  const base = live ? confLiveStability : confVod;
1026
1052
  return withCredentialsConfig(
@@ -1032,7 +1058,7 @@ var VideoPlayerBase = (props, ref) => {
1032
1058
  hlsCredentials
1033
1059
  );
1034
1060
  }, [hlsConfig, hlsCredentials, live, liveHlsConfig, vodHlsConfig]);
1035
- const loadVideo = (0, import_react2.useCallback)(
1061
+ const loadVideo = (0, import_react3.useCallback)(
1036
1062
  async (startAt = initialTime) => {
1037
1063
  const startVideoRef = videoRef.current;
1038
1064
  if (!startVideoRef) return;
@@ -1169,14 +1195,14 @@ var VideoPlayerBase = (props, ref) => {
1169
1195
  videoSrc
1170
1196
  ]
1171
1197
  );
1172
- const loadVideoNative = (0, import_react2.useCallback)(() => {
1198
+ const loadVideoNative = (0, import_react3.useCallback)(() => {
1173
1199
  const video = videoRef.current;
1174
1200
  if (!video) return;
1175
1201
  video.src = videoSrc;
1176
1202
  video.currentTime = initialTime;
1177
1203
  if (autoPlay) void playAction();
1178
1204
  }, [autoPlay, initialTime, playAction, videoSrc]);
1179
- const loadVideoHls = (0, import_react2.useCallback)(async () => {
1205
+ const loadVideoHls = (0, import_react3.useCallback)(async () => {
1180
1206
  const video = videoRef.current;
1181
1207
  const canNativeHls = !!(video == null ? void 0 : video.canPlayType) && video.canPlayType("application/vnd.apple.mpegurl") !== "";
1182
1208
  let HlsCtor;
@@ -1197,7 +1223,7 @@ var VideoPlayerBase = (props, ref) => {
1197
1223
  }
1198
1224
  reportError(null, { type: "hls", src: videoSrc, reason: "HLS is not supported in this browser" }, messages.unsupported);
1199
1225
  }, [initialTime, loadVideo, loadVideoNative, messages.streamLoadFailed, messages.unsupported, reportError, videoSrc]);
1200
- const destroy = (0, import_react2.useCallback)(() => {
1226
+ const destroy = (0, import_react3.useCallback)(() => {
1201
1227
  clearTimers();
1202
1228
  if (hlsRef.current) {
1203
1229
  hlsRef.current.destroy();
@@ -1228,12 +1254,12 @@ var VideoPlayerBase = (props, ref) => {
1228
1254
  onVideoRefChange,
1229
1255
  setNextPlaying
1230
1256
  ]);
1231
- const videoClickHandler = (0, import_react2.useCallback)(() => {
1257
+ const videoClickHandler = (0, import_react3.useCallback)(() => {
1232
1258
  if (!handleClick) return;
1233
1259
  if (playOnClick) toggleAction();
1234
1260
  onVideoClick == null ? void 0 : onVideoClick();
1235
1261
  }, [handleClick, onVideoClick, playOnClick, toggleAction]);
1236
- const fullScreenAction = (0, import_react2.useCallback)(() => {
1262
+ const fullScreenAction = (0, import_react3.useCallback)(() => {
1237
1263
  if (typeof document === "undefined") return;
1238
1264
  const doc = document;
1239
1265
  const videoEl = videoRef.current;
@@ -1245,6 +1271,7 @@ var VideoPlayerBase = (props, ref) => {
1245
1271
  else if (doc.msExitFullscreen) void doc.msExitFullscreen();
1246
1272
  return;
1247
1273
  }
1274
+ if (!fullscreenAllowed) return;
1248
1275
  if (containerEl) {
1249
1276
  if (containerEl.requestFullscreen) void containerEl.requestFullscreen();
1250
1277
  else if (containerEl.webkitRequestFullscreen) void containerEl.webkitRequestFullscreen();
@@ -1256,37 +1283,37 @@ var VideoPlayerBase = (props, ref) => {
1256
1283
  if (videoEl.requestFullscreen) void videoEl.requestFullscreen();
1257
1284
  else if (videoEl.webkitEnterFullscreen) void videoEl.webkitEnterFullscreen();
1258
1285
  }
1259
- }, []);
1260
- const videoDbClickHandler = (0, import_react2.useCallback)(() => {
1286
+ }, [fullscreenAllowed]);
1287
+ const videoDbClickHandler = (0, import_react3.useCallback)(() => {
1261
1288
  if (!handleClick) return;
1262
1289
  fullScreenAction();
1263
1290
  onVideoDoubleClick == null ? void 0 : onVideoDoubleClick();
1264
1291
  }, [fullScreenAction, handleClick, onVideoDoubleClick]);
1265
- const showClickHandler = (0, import_react2.useCallback)(() => {
1292
+ const showClickHandler = (0, import_react3.useCallback)(() => {
1266
1293
  setControlVisible((st) => !st);
1267
1294
  }, []);
1268
- const retryLoadHandler = (0, import_react2.useCallback)(() => {
1295
+ const retryLoadHandler = (0, import_react3.useCallback)(() => {
1269
1296
  clearErrorState();
1270
1297
  setReloadToken((value) => value + 1);
1271
1298
  }, [clearErrorState]);
1272
- const videoErrorHandler = (0, import_react2.useCallback)(() => {
1299
+ const videoErrorHandler = (0, import_react3.useCallback)(() => {
1273
1300
  const video = videoRef.current;
1274
1301
  const error = video == null ? void 0 : video.error;
1275
1302
  const src = (video == null ? void 0 : video.currentSrc) || videoSrc;
1276
1303
  if (hlsRef.current && src.startsWith("blob:")) return;
1277
1304
  reportError(error, { type: "native", src }, getNativeVideoErrorMessage(error, messages));
1278
1305
  }, [messages, reportError, videoSrc]);
1279
- const videoLoadedHandler = (0, import_react2.useCallback)(() => {
1306
+ const videoLoadedHandler = (0, import_react3.useCallback)(() => {
1280
1307
  clearErrorState();
1281
1308
  }, [clearErrorState]);
1282
- const mouseMoveHandler = (0, import_react2.useCallback)(() => {
1309
+ const mouseMoveHandler = (0, import_react3.useCallback)(() => {
1283
1310
  if (controlTime.current) clearTimeout(controlTime.current);
1284
1311
  setControlVisible(true);
1285
1312
  controlTime.current = setTimeout(() => {
1286
1313
  setControlVisible(false);
1287
1314
  }, 1e3 * 10);
1288
1315
  }, []);
1289
- (0, import_react2.useImperativeHandle)(
1316
+ (0, import_react3.useImperativeHandle)(
1290
1317
  ref,
1291
1318
  () => ({
1292
1319
  play: playAction,
@@ -1297,24 +1324,24 @@ var VideoPlayerBase = (props, ref) => {
1297
1324
  }),
1298
1325
  [fullScreenAction, pauseAction, playAction, seekSetTime]
1299
1326
  );
1300
- (0, import_react2.useEffect)(() => {
1327
+ (0, import_react3.useEffect)(() => {
1301
1328
  const el = videoRef.current;
1302
1329
  if (!el) return;
1303
1330
  el.muted = muted;
1304
1331
  }, [muted]);
1305
- (0, import_react2.useEffect)(() => {
1332
+ (0, import_react3.useEffect)(() => {
1306
1333
  if (playingProp === void 0) return;
1307
1334
  if (!canAttemptPlayback(videoRef.current)) return;
1308
1335
  if (playingProp) void playAction();
1309
1336
  else pauseAction();
1310
1337
  }, [pauseAction, playAction, playingProp]);
1311
- (0, import_react2.useEffect)(() => {
1338
+ (0, import_react3.useEffect)(() => {
1312
1339
  const el = videoRef.current;
1313
1340
  if (!el || currentTimeProp === void 0) return;
1314
1341
  if (Math.abs(el.currentTime - currentTimeProp) < 0.25) return;
1315
1342
  el.currentTime = clampTime(currentTimeProp, duration);
1316
1343
  }, [currentTimeProp, duration]);
1317
- (0, import_react2.useEffect)(() => {
1344
+ (0, import_react3.useEffect)(() => {
1318
1345
  if (!videoRef.current) return;
1319
1346
  clearErrorState();
1320
1347
  fatalReloadCount.current = 0;
@@ -1350,12 +1377,12 @@ var VideoPlayerBase = (props, ref) => {
1350
1377
  type,
1351
1378
  videoSrc
1352
1379
  ]);
1353
- (0, import_react2.useEffect)(() => {
1380
+ (0, import_react3.useEffect)(() => {
1354
1381
  if (!autoPlay) return;
1355
1382
  if (!canAttemptPlayback(videoRef.current)) return;
1356
1383
  void playAction();
1357
1384
  }, [autoPlay, playAction]);
1358
- (0, import_react2.useEffect)(() => {
1385
+ (0, import_react3.useEffect)(() => {
1359
1386
  if (typeof document === "undefined") return;
1360
1387
  const doc = document;
1361
1388
  const handleFsChange = () => {
@@ -1373,7 +1400,7 @@ var VideoPlayerBase = (props, ref) => {
1373
1400
  document.removeEventListener("MSFullscreenChange", handleFsChange);
1374
1401
  };
1375
1402
  }, []);
1376
- (0, import_react2.useEffect)(() => {
1403
+ (0, import_react3.useEffect)(() => {
1377
1404
  return () => {
1378
1405
  setTimeD.current.cancel();
1379
1406
  destroy();
@@ -1439,6 +1466,7 @@ var VideoPlayerBase = (props, ref) => {
1439
1466
  {
1440
1467
  variant: controlsVariant,
1441
1468
  fullScreenAction,
1469
+ fullscreenAllowed,
1442
1470
  showControl,
1443
1471
  playAction,
1444
1472
  pauseAction,
@@ -1457,7 +1485,7 @@ var VideoPlayerBase = (props, ref) => {
1457
1485
  }
1458
1486
  );
1459
1487
  };
1460
- var VideoPlayer = (0, import_react2.memo)((0, import_react2.forwardRef)(VideoPlayerBase));
1488
+ var VideoPlayer = (0, import_react3.memo)((0, import_react3.forwardRef)(VideoPlayerBase));
1461
1489
  var VideoPlayer_default = VideoPlayer;
1462
1490
  // Annotate the CommonJS export names for ESM import in node:
1463
1491
  0 && (module.exports = {
package/dist/index.d.cts CHANGED
@@ -37,6 +37,7 @@ interface VideoPlayerProps extends Omit<React.VideoHTMLAttributes<HTMLVideoEleme
37
37
  sourceType?: 'auto' | 'hls' | 'native';
38
38
  type?: string;
39
39
  controlsVariant: ControlsVariant;
40
+ active?: boolean;
40
41
  autoPlay?: boolean;
41
42
  preload?: '' | 'none' | 'auto' | 'metadata';
42
43
  loop?: boolean;
package/dist/index.d.ts CHANGED
@@ -37,6 +37,7 @@ interface VideoPlayerProps extends Omit<React.VideoHTMLAttributes<HTMLVideoEleme
37
37
  sourceType?: 'auto' | 'hls' | 'native';
38
38
  type?: string;
39
39
  controlsVariant: ControlsVariant;
40
+ active?: boolean;
40
41
  autoPlay?: boolean;
41
42
  preload?: '' | 'none' | 'auto' | 'metadata';
42
43
  loop?: boolean;
package/dist/index.js CHANGED
@@ -6,10 +6,13 @@ import {
6
6
  useEffect,
7
7
  useImperativeHandle,
8
8
  useMemo as useMemo2,
9
- useRef,
9
+ useRef as useRef2,
10
10
  useState
11
11
  } from "react";
12
12
 
13
+ // src/controls.tsx
14
+ import { useRef } from "react";
15
+
13
16
  // src/styles.ts
14
17
  import { css, styled } from "styled-components";
15
18
  var VideoPlayerControlStyles = styled.div`
@@ -58,6 +61,15 @@ var VideoPlayerControlStyles = styled.div`
58
61
  outline: none;
59
62
  }
60
63
 
64
+ .video-player-control-button:disabled {
65
+ opacity: 0.42;
66
+ cursor: not-allowed;
67
+ }
68
+
69
+ .video-player-control-button:disabled:hover {
70
+ background: rgba(255, 255, 255, 0.12);
71
+ }
72
+
61
73
  .video-player-icon {
62
74
  width: 17px;
63
75
  height: 17px;
@@ -248,6 +260,13 @@ var VideoPlayerStyles = styled.div`
248
260
  }
249
261
  }
250
262
 
263
+ &:hover {
264
+ .video-right-bar {
265
+ background: rgba(0, 0, 0, 0.1);
266
+ opacity: 1;
267
+ }
268
+ }
269
+
251
270
  ${(props) => (props.$showControl || props.$alwaysShowSegments) && css`
252
271
  .video-right-bar {
253
272
  background: rgba(0, 0, 0, 0.1);
@@ -382,6 +401,7 @@ var createThrottledNumberFn = (fn, waitMs) => {
382
401
  // src/controls.tsx
383
402
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
384
403
  var SeekSlider = ({ playAction, duration, currentTime, seekSetTime }) => {
404
+ const ref = useRef(null);
385
405
  const clickHandler = (e) => {
386
406
  e.preventDefault();
387
407
  e.stopPropagation();
@@ -390,10 +410,14 @@ var SeekSlider = ({ playAction, duration, currentTime, seekSetTime }) => {
390
410
  const safeCurrentTime = Math.max(0, Math.min(Number.isFinite(currentTime) ? currentTime : 0, safeDuration || Infinity));
391
411
  const completeChange = () => {
392
412
  void playAction();
413
+ if (ref.current) {
414
+ ref.current.blur();
415
+ }
393
416
  };
394
417
  return /* @__PURE__ */ jsx(SeekSliderStyled, { onClick: clickHandler, className: "seek-slider", children: /* @__PURE__ */ jsx(
395
418
  "input",
396
419
  {
420
+ ref,
397
421
  "aria-label": "Seek",
398
422
  className: "seek-range",
399
423
  disabled: safeDuration <= 0,
@@ -453,11 +477,23 @@ var Icon = ({ name }) => {
453
477
  return /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", className: "video-player-icon", focusable: "false", viewBox: "0 0 24 24", children: paths[name] });
454
478
  };
455
479
  var ControlButton = ({
480
+ disabled,
456
481
  icon,
457
482
  label,
458
483
  onClick
459
484
  }) => {
460
- return /* @__PURE__ */ jsx("button", { "aria-label": label, className: "video-player-control-button", onClick: () => void onClick(), title: label, type: "button", children: /* @__PURE__ */ jsx(Icon, { name: icon }) });
485
+ return /* @__PURE__ */ jsx(
486
+ "button",
487
+ {
488
+ "aria-label": label,
489
+ className: "video-player-control-button",
490
+ disabled,
491
+ onClick: () => void onClick(),
492
+ title: label,
493
+ type: "button",
494
+ children: /* @__PURE__ */ jsx(Icon, { name: icon })
495
+ }
496
+ );
461
497
  };
462
498
  var stopControlClick = (e) => {
463
499
  e.preventDefault();
@@ -467,6 +503,7 @@ var VideoPlayerControls = (props) => {
467
503
  const {
468
504
  variant,
469
505
  fullScreenAction,
506
+ fullscreenAllowed,
470
507
  showControl,
471
508
  playAction,
472
509
  pauseAction,
@@ -482,19 +519,6 @@ var VideoPlayerControls = (props) => {
482
519
  } = props;
483
520
  const [, isMobile] = useWindowWidth();
484
521
  if (variant === "none") return null;
485
- if (variant === "tiny") {
486
- return /* @__PURE__ */ jsx(VideoPlayerControlStyles, { $tiny: true, $isMobile: isMobile, onClick: stopControlClick, $showControl: showControl, children: /* @__PURE__ */ jsxs("div", { className: "video-player-control-row", children: [
487
- /* @__PURE__ */ jsx("div", { className: "full" }),
488
- /* @__PURE__ */ jsx(SeekSlider, { playAction, duration, currentTime, seekSetTime })
489
- ] }) });
490
- }
491
- if (variant === "fullscreen-only") {
492
- return /* @__PURE__ */ jsx(VideoPlayerControlStyles, { $isMobile: isMobile, onClick: stopControlClick, $showControl: showControl, children: /* @__PURE__ */ jsxs("div", { className: "video-player-control-row", children: [
493
- /* @__PURE__ */ jsx("div", { className: "full" }),
494
- /* @__PURE__ */ jsx(ControlButton, { icon: muted ? "mute" : "sound", label: muted ? "Unmute" : "Mute", onClick: () => onMutedChange(!muted) }),
495
- /* @__PURE__ */ jsx(ControlButton, { icon: "fullscreen", label: "Fullscreen", onClick: fullScreenAction })
496
- ] }) });
497
- }
498
522
  return /* @__PURE__ */ jsx(VideoPlayerControlStyles, { $isMobile: isMobile, onClick: stopControlClick, $showControl: showControl, children: /* @__PURE__ */ jsxs("div", { className: "video-player-control-row", children: [
499
523
  playing ? /* @__PURE__ */ jsx(ControlButton, { icon: "pause", label: "Pause", onClick: pauseAction }) : /* @__PURE__ */ jsx(ControlButton, { icon: "play", label: "Play", onClick: playAction }),
500
524
  /* @__PURE__ */ jsx(ControlButton, { icon: "back", label: "Back 15 seconds", onClick: () => seekPrevAction() }),
@@ -504,7 +528,7 @@ var VideoPlayerControls = (props) => {
504
528
  /* @__PURE__ */ jsx(SeekSlider, { playAction, duration, currentTime, seekSetTime }),
505
529
  /* @__PURE__ */ jsx(TimeBlock, { duration, currentTime }),
506
530
  /* @__PURE__ */ jsx(ControlButton, { icon: muted ? "mute" : "sound", label: muted ? "Unmute" : "Mute", onClick: () => onMutedChange(!muted) }),
507
- /* @__PURE__ */ jsx(ControlButton, { icon: "fullscreen", label: "Fullscreen", onClick: fullScreenAction })
531
+ /* @__PURE__ */ jsx(ControlButton, { disabled: !fullscreenAllowed, icon: "fullscreen", label: "Fullscreen", onClick: fullScreenAction })
508
532
  ] }) });
509
533
  };
510
534
 
@@ -722,6 +746,7 @@ var assignRef = (ref, value) => {
722
746
  var VideoPlayerBase = (props, ref) => {
723
747
  const {
724
748
  controlsVariant = "none",
749
+ active,
725
750
  autoPlay = false,
726
751
  preload = "metadata",
727
752
  loop = false,
@@ -772,17 +797,17 @@ var VideoPlayerBase = (props, ref) => {
772
797
  const videoSrc = rawVideoSrc.replaceAll(" ", "%20").replaceAll("#", "%23");
773
798
  const messages = useMemo2(() => ({ ...defaultMessages, ...messagesProp }), [messagesProp]);
774
799
  const [, isMobile, mobileType] = useWindowWidth();
775
- const videoRef = useRef(null);
776
- const hlsRef = useRef(null);
777
- const containerRef = useRef(null);
778
- const netRetryCount = useRef(0);
779
- const mediaRecoverCount = useRef(0);
780
- const fatalReloadCount = useRef(0);
781
- const reloadTimer = useRef(null);
782
- const stalledNudgeTimer = useRef(null);
783
- const liveStabilityMode = useRef(false);
784
- const liveStallEvents = useRef([]);
785
- const controlTime = useRef(null);
800
+ const videoRef = useRef2(null);
801
+ const hlsRef = useRef2(null);
802
+ const containerRef = useRef2(null);
803
+ const netRetryCount = useRef2(0);
804
+ const mediaRecoverCount = useRef2(0);
805
+ const fatalReloadCount = useRef2(0);
806
+ const reloadTimer = useRef2(null);
807
+ const stalledNudgeTimer = useRef2(null);
808
+ const liveStabilityMode = useRef2(false);
809
+ const liveStallEvents = useRef2([]);
810
+ const controlTime = useRef2(null);
786
811
  const [playingState, setPlayingState] = useState(!!defaultPlaying);
787
812
  const [mutedState, setMutedState] = useState(!!defaultMuted);
788
813
  const [durationState, setDurationState] = useState(0);
@@ -795,7 +820,8 @@ var VideoPlayerBase = (props, ref) => {
795
820
  const muted = mutedProp != null ? mutedProp : mutedState;
796
821
  const duration = durationProp != null ? durationProp : durationState;
797
822
  const currentTime = currentTimeProp != null ? currentTimeProp : currentTimeState;
798
- const setTimeD = useRef(
823
+ const fullscreenAllowed = active != null ? active : true;
824
+ const setTimeD = useRef2(
799
825
  createThrottledNumberFn((value) => {
800
826
  const v = videoRef.current;
801
827
  if (!v) return;
@@ -1216,6 +1242,7 @@ var VideoPlayerBase = (props, ref) => {
1216
1242
  else if (doc.msExitFullscreen) void doc.msExitFullscreen();
1217
1243
  return;
1218
1244
  }
1245
+ if (!fullscreenAllowed) return;
1219
1246
  if (containerEl) {
1220
1247
  if (containerEl.requestFullscreen) void containerEl.requestFullscreen();
1221
1248
  else if (containerEl.webkitRequestFullscreen) void containerEl.webkitRequestFullscreen();
@@ -1227,7 +1254,7 @@ var VideoPlayerBase = (props, ref) => {
1227
1254
  if (videoEl.requestFullscreen) void videoEl.requestFullscreen();
1228
1255
  else if (videoEl.webkitEnterFullscreen) void videoEl.webkitEnterFullscreen();
1229
1256
  }
1230
- }, []);
1257
+ }, [fullscreenAllowed]);
1231
1258
  const videoDbClickHandler = useCallback(() => {
1232
1259
  if (!handleClick) return;
1233
1260
  fullScreenAction();
@@ -1410,6 +1437,7 @@ var VideoPlayerBase = (props, ref) => {
1410
1437
  {
1411
1438
  variant: controlsVariant,
1412
1439
  fullScreenAction,
1440
+ fullscreenAllowed,
1413
1441
  showControl,
1414
1442
  playAction,
1415
1443
  pauseAction,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nikelroot/video-player",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Standalone React video player with HLS support.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",