@nikelroot/video-player 0.2.2 → 0.2.4

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
@@ -37,7 +37,7 @@ export function Example() {
37
37
  }
38
38
  ```
39
39
 
40
- When `active` is `true`, pressing `F` toggles fullscreen. When `active` is `false`, the player releases its media source and waits until it becomes active again. In lists with several players, pass `active={index === activeIndex}` so inactive videos do not keep HLS/media pipelines open.
40
+ When `active` is `true`, pressing `F` toggles fullscreen. When `active` is `false`, the player releases native media loading so inactive MP4 players do not keep network requests open.
41
41
 
42
42
  For public CDN-hosted videos, prefer non-credentialed requests unless the CDN is explicitly configured for cookies:
43
43
 
package/dist/index.cjs CHANGED
@@ -725,6 +725,7 @@ var LIVE_STALL_WINDOW_MS = 2e4;
725
725
  var LIVE_STALLS_BEFORE_STABILITY = 3;
726
726
  var hlsCtorPromise = null;
727
727
  var activeExclusiveVideo = null;
728
+ var exclusiveMediaReleasers = /* @__PURE__ */ new WeakMap();
728
729
  var loadHlsCtor = () => {
729
730
  if (!hlsCtorPromise) {
730
731
  hlsCtorPromise = import("hls.js").then((mod) => mod.default);
@@ -738,6 +739,9 @@ var isInterruptedPlayRequestError = (error) => {
738
739
  var canAttemptPlayback = (video) => {
739
740
  return !!video && video.readyState >= HTMLMediaElement.HAVE_METADATA;
740
741
  };
742
+ var isVideoSourceAttached = (video) => {
743
+ return !!video.currentSrc || !!video.getAttribute("src");
744
+ };
741
745
  function hasNativeHlsSupport() {
742
746
  if (typeof navigator === "undefined" || typeof document === "undefined") return false;
743
747
  const isSafari = /^((?!chrome|android|firefox|opera|edg).)*safari/i.test(navigator.userAgent);
@@ -854,6 +858,7 @@ var VideoPlayerBase = (props, ref) => {
854
858
  const liveStallEvents = (0, import_react3.useRef)([]);
855
859
  const controlTime = (0, import_react3.useRef)(null);
856
860
  const sourceLoadId = (0, import_react3.useRef)(0);
861
+ const sourceLoadMode = (0, import_react3.useRef)("native");
857
862
  const [playingState, setPlayingState] = (0, import_react3.useState)(!!defaultPlaying);
858
863
  const [mutedState, setMutedState] = (0, import_react3.useState)(!!defaultMuted);
859
864
  const [durationState, setDurationState] = (0, import_react3.useState)(0);
@@ -869,7 +874,6 @@ var VideoPlayerBase = (props, ref) => {
869
874
  const duration = durationProp != null ? durationProp : durationState;
870
875
  const currentTime = currentTimeState;
871
876
  const fullscreenAllowed = active != null ? active : true;
872
- const shouldLoadMedia = active !== false;
873
877
  const onTimeChangeRef = (0, import_react3.useRef)(onTimeChange);
874
878
  const onDurationChangeRef = (0, import_react3.useRef)(onDurationChange);
875
879
  onTimeChangeRef.current = onTimeChange;
@@ -910,6 +914,20 @@ var VideoPlayerBase = (props, ref) => {
910
914
  video.removeAttribute("src");
911
915
  video.load();
912
916
  }, [clearTimers]);
917
+ const releaseNativeMedia = (0, import_react3.useCallback)(() => {
918
+ const video = videoRef.current;
919
+ if (!video) return;
920
+ video.pause();
921
+ if (sourceLoadMode.current !== "native" || !isVideoSourceAttached(video)) return;
922
+ video.removeAttribute("src");
923
+ video.load();
924
+ }, []);
925
+ const ensureNativeMediaAttached = (0, import_react3.useCallback)(() => {
926
+ const video = videoRef.current;
927
+ if (!video || sourceLoadMode.current !== "native" || isVideoSourceAttached(video)) return;
928
+ video.src = videoSrc;
929
+ if (initialTime > 0) video.currentTime = initialTime;
930
+ }, [initialTime, videoSrc]);
913
931
  const setNextPlaying = (0, import_react3.useCallback)(
914
932
  (value) => {
915
933
  if (playingProp === void 0) setPlayingState(value);
@@ -975,6 +993,7 @@ var VideoPlayerBase = (props, ref) => {
975
993
  if (prev) {
976
994
  prev.removeEventListener("timeupdate", onTime);
977
995
  prev.removeEventListener("durationchange", onDur);
996
+ exclusiveMediaReleasers.delete(prev);
978
997
  }
979
998
  videoRef.current = el;
980
999
  assignRef(externalVideoRef, el);
@@ -984,8 +1003,9 @@ var VideoPlayerBase = (props, ref) => {
984
1003
  el.muted = muted;
985
1004
  el.addEventListener("timeupdate", onTime);
986
1005
  el.addEventListener("durationchange", onDur);
1006
+ exclusiveMediaReleasers.set(el, releaseNativeMedia);
987
1007
  },
988
- [externalVideoRef, muted, notifyActiveChange, onDur, onTime, onVideoRefChange]
1008
+ [externalVideoRef, muted, notifyActiveChange, onDur, onTime, onVideoRefChange, releaseNativeMedia]
989
1009
  );
990
1010
  const pauseOtherVideos = (0, import_react3.useCallback)(
991
1011
  (el) => {
@@ -994,8 +1014,10 @@ var VideoPlayerBase = (props, ref) => {
994
1014
  exclusivePlayback.pauseOthers(el);
995
1015
  return;
996
1016
  }
997
- if (activeExclusiveVideo && activeExclusiveVideo !== el && !activeExclusiveVideo.paused) {
998
- activeExclusiveVideo.pause();
1017
+ if (activeExclusiveVideo && activeExclusiveVideo !== el) {
1018
+ const releasePrevious = exclusiveMediaReleasers.get(activeExclusiveVideo);
1019
+ if (releasePrevious) releasePrevious();
1020
+ else activeExclusiveVideo.pause();
999
1021
  }
1000
1022
  activeExclusiveVideo = el;
1001
1023
  },
@@ -1005,6 +1027,7 @@ var VideoPlayerBase = (props, ref) => {
1005
1027
  const el = videoRef.current;
1006
1028
  if (!el) return;
1007
1029
  pauseOtherVideos(el);
1030
+ ensureNativeMediaAttached();
1008
1031
  try {
1009
1032
  await el.play();
1010
1033
  clearErrorState();
@@ -1026,7 +1049,16 @@ var VideoPlayerBase = (props, ref) => {
1026
1049
  });
1027
1050
  }
1028
1051
  }
1029
- }, [clearErrorState, messages.playFailed, notifyActiveChange, pauseOtherVideos, reportError, scrollTo, setNextPlaying]);
1052
+ }, [
1053
+ clearErrorState,
1054
+ ensureNativeMediaAttached,
1055
+ messages.playFailed,
1056
+ notifyActiveChange,
1057
+ pauseOtherVideos,
1058
+ reportError,
1059
+ scrollTo,
1060
+ setNextPlaying
1061
+ ]);
1030
1062
  const pauseAction = (0, import_react3.useCallback)(() => {
1031
1063
  const el = videoRef.current;
1032
1064
  if (!el) return;
@@ -1104,6 +1136,7 @@ var VideoPlayerBase = (props, ref) => {
1104
1136
  async (startAt = initialTime, loadId = sourceLoadId.current) => {
1105
1137
  const startVideoRef = videoRef.current;
1106
1138
  if (!startVideoRef) return;
1139
+ sourceLoadMode.current = "hls";
1107
1140
  let HlsCtor;
1108
1141
  try {
1109
1142
  HlsCtor = await loadHlsCtor();
@@ -1246,6 +1279,7 @@ var VideoPlayerBase = (props, ref) => {
1246
1279
  if (loadId !== sourceLoadId.current) return;
1247
1280
  const video = videoRef.current;
1248
1281
  if (!video) return;
1282
+ sourceLoadMode.current = "native";
1249
1283
  video.src = videoSrc;
1250
1284
  video.currentTime = initialTime;
1251
1285
  if (autoPlay) void playAction();
@@ -1253,6 +1287,7 @@ var VideoPlayerBase = (props, ref) => {
1253
1287
  const loadVideoHls = (0, import_react3.useCallback)(async (loadId = sourceLoadId.current) => {
1254
1288
  const video = videoRef.current;
1255
1289
  const canNativeHls = !!(video == null ? void 0 : video.canPlayType) && video.canPlayType("application/vnd.apple.mpegurl") !== "";
1290
+ sourceLoadMode.current = "hls";
1256
1291
  let HlsCtor;
1257
1292
  try {
1258
1293
  HlsCtor = await loadHlsCtor();
@@ -1388,6 +1423,10 @@ var VideoPlayerBase = (props, ref) => {
1388
1423
  if (playingProp) void playAction();
1389
1424
  else pauseAction();
1390
1425
  }, [pauseAction, playAction, playingProp]);
1426
+ (0, import_react3.useEffect)(() => {
1427
+ if (active !== false) return;
1428
+ releaseNativeMedia();
1429
+ }, [active, releaseNativeMedia]);
1391
1430
  (0, import_react3.useEffect)(() => {
1392
1431
  if (!videoRef.current) return;
1393
1432
  const loadId = sourceLoadId.current + 1;
@@ -1399,32 +1438,26 @@ var VideoPlayerBase = (props, ref) => {
1399
1438
  liveStabilityMode.current = false;
1400
1439
  liveStallEvents.current = [];
1401
1440
  resetMediaForSourceChange();
1402
- if (!shouldLoadMedia) {
1403
- setTimeD.current.cancel();
1404
- setCurrentTimeUi.current.cancel();
1405
- setDurationState(0);
1406
- setCurrentTimeState(0);
1407
- setPlayingState(false);
1408
- return () => {
1409
- sourceLoadId.current += 1;
1410
- resetMediaForSourceChange();
1411
- };
1441
+ if (active === false) return;
1442
+ const nextSourceLoadMode = sourceType === "hls" || sourceType !== "native" && type !== "video" && isLikelyHlsSource(videoSrc) ? "hls" : "native";
1443
+ sourceLoadMode.current = nextSourceLoadMode;
1444
+ if (nextSourceLoadMode === "hls") {
1445
+ if (sourceType === "hls") void loadVideo(initialTime, loadId);
1446
+ else void loadVideoHls(loadId);
1447
+ } else {
1448
+ loadVideoNative(loadId);
1412
1449
  }
1413
- if (sourceType === "native" || type === "video") loadVideoNative(loadId);
1414
- else if (sourceType === "hls") void loadVideo(initialTime, loadId);
1415
- else if (isLikelyHlsSource(videoSrc)) void loadVideoHls(loadId);
1416
- else loadVideoNative(loadId);
1417
1450
  return () => {
1418
1451
  sourceLoadId.current += 1;
1419
1452
  resetMediaForSourceChange();
1420
1453
  };
1421
1454
  }, [
1422
1455
  clearErrorState,
1456
+ active,
1423
1457
  loadVideo,
1424
1458
  loadVideoHls,
1425
1459
  loadVideoNative,
1426
1460
  initialTime,
1427
- shouldLoadMedia,
1428
1461
  resetMediaForSourceChange,
1429
1462
  reloadKey,
1430
1463
  reloadToken,
@@ -1521,7 +1554,7 @@ var VideoPlayerBase = (props, ref) => {
1521
1554
  crossOrigin,
1522
1555
  playsInline: true,
1523
1556
  muted,
1524
- preload: shouldLoadMedia ? preload : "none",
1557
+ preload,
1525
1558
  loop,
1526
1559
  onPlay: () => setNextPlaying(true),
1527
1560
  onPause: () => setNextPlaying(false),
package/dist/index.js CHANGED
@@ -696,6 +696,7 @@ var LIVE_STALL_WINDOW_MS = 2e4;
696
696
  var LIVE_STALLS_BEFORE_STABILITY = 3;
697
697
  var hlsCtorPromise = null;
698
698
  var activeExclusiveVideo = null;
699
+ var exclusiveMediaReleasers = /* @__PURE__ */ new WeakMap();
699
700
  var loadHlsCtor = () => {
700
701
  if (!hlsCtorPromise) {
701
702
  hlsCtorPromise = import("hls.js").then((mod) => mod.default);
@@ -709,6 +710,9 @@ var isInterruptedPlayRequestError = (error) => {
709
710
  var canAttemptPlayback = (video) => {
710
711
  return !!video && video.readyState >= HTMLMediaElement.HAVE_METADATA;
711
712
  };
713
+ var isVideoSourceAttached = (video) => {
714
+ return !!video.currentSrc || !!video.getAttribute("src");
715
+ };
712
716
  function hasNativeHlsSupport() {
713
717
  if (typeof navigator === "undefined" || typeof document === "undefined") return false;
714
718
  const isSafari = /^((?!chrome|android|firefox|opera|edg).)*safari/i.test(navigator.userAgent);
@@ -825,6 +829,7 @@ var VideoPlayerBase = (props, ref) => {
825
829
  const liveStallEvents = useRef2([]);
826
830
  const controlTime = useRef2(null);
827
831
  const sourceLoadId = useRef2(0);
832
+ const sourceLoadMode = useRef2("native");
828
833
  const [playingState, setPlayingState] = useState(!!defaultPlaying);
829
834
  const [mutedState, setMutedState] = useState(!!defaultMuted);
830
835
  const [durationState, setDurationState] = useState(0);
@@ -840,7 +845,6 @@ var VideoPlayerBase = (props, ref) => {
840
845
  const duration = durationProp != null ? durationProp : durationState;
841
846
  const currentTime = currentTimeState;
842
847
  const fullscreenAllowed = active != null ? active : true;
843
- const shouldLoadMedia = active !== false;
844
848
  const onTimeChangeRef = useRef2(onTimeChange);
845
849
  const onDurationChangeRef = useRef2(onDurationChange);
846
850
  onTimeChangeRef.current = onTimeChange;
@@ -881,6 +885,20 @@ var VideoPlayerBase = (props, ref) => {
881
885
  video.removeAttribute("src");
882
886
  video.load();
883
887
  }, [clearTimers]);
888
+ const releaseNativeMedia = useCallback(() => {
889
+ const video = videoRef.current;
890
+ if (!video) return;
891
+ video.pause();
892
+ if (sourceLoadMode.current !== "native" || !isVideoSourceAttached(video)) return;
893
+ video.removeAttribute("src");
894
+ video.load();
895
+ }, []);
896
+ const ensureNativeMediaAttached = useCallback(() => {
897
+ const video = videoRef.current;
898
+ if (!video || sourceLoadMode.current !== "native" || isVideoSourceAttached(video)) return;
899
+ video.src = videoSrc;
900
+ if (initialTime > 0) video.currentTime = initialTime;
901
+ }, [initialTime, videoSrc]);
884
902
  const setNextPlaying = useCallback(
885
903
  (value) => {
886
904
  if (playingProp === void 0) setPlayingState(value);
@@ -946,6 +964,7 @@ var VideoPlayerBase = (props, ref) => {
946
964
  if (prev) {
947
965
  prev.removeEventListener("timeupdate", onTime);
948
966
  prev.removeEventListener("durationchange", onDur);
967
+ exclusiveMediaReleasers.delete(prev);
949
968
  }
950
969
  videoRef.current = el;
951
970
  assignRef(externalVideoRef, el);
@@ -955,8 +974,9 @@ var VideoPlayerBase = (props, ref) => {
955
974
  el.muted = muted;
956
975
  el.addEventListener("timeupdate", onTime);
957
976
  el.addEventListener("durationchange", onDur);
977
+ exclusiveMediaReleasers.set(el, releaseNativeMedia);
958
978
  },
959
- [externalVideoRef, muted, notifyActiveChange, onDur, onTime, onVideoRefChange]
979
+ [externalVideoRef, muted, notifyActiveChange, onDur, onTime, onVideoRefChange, releaseNativeMedia]
960
980
  );
961
981
  const pauseOtherVideos = useCallback(
962
982
  (el) => {
@@ -965,8 +985,10 @@ var VideoPlayerBase = (props, ref) => {
965
985
  exclusivePlayback.pauseOthers(el);
966
986
  return;
967
987
  }
968
- if (activeExclusiveVideo && activeExclusiveVideo !== el && !activeExclusiveVideo.paused) {
969
- activeExclusiveVideo.pause();
988
+ if (activeExclusiveVideo && activeExclusiveVideo !== el) {
989
+ const releasePrevious = exclusiveMediaReleasers.get(activeExclusiveVideo);
990
+ if (releasePrevious) releasePrevious();
991
+ else activeExclusiveVideo.pause();
970
992
  }
971
993
  activeExclusiveVideo = el;
972
994
  },
@@ -976,6 +998,7 @@ var VideoPlayerBase = (props, ref) => {
976
998
  const el = videoRef.current;
977
999
  if (!el) return;
978
1000
  pauseOtherVideos(el);
1001
+ ensureNativeMediaAttached();
979
1002
  try {
980
1003
  await el.play();
981
1004
  clearErrorState();
@@ -997,7 +1020,16 @@ var VideoPlayerBase = (props, ref) => {
997
1020
  });
998
1021
  }
999
1022
  }
1000
- }, [clearErrorState, messages.playFailed, notifyActiveChange, pauseOtherVideos, reportError, scrollTo, setNextPlaying]);
1023
+ }, [
1024
+ clearErrorState,
1025
+ ensureNativeMediaAttached,
1026
+ messages.playFailed,
1027
+ notifyActiveChange,
1028
+ pauseOtherVideos,
1029
+ reportError,
1030
+ scrollTo,
1031
+ setNextPlaying
1032
+ ]);
1001
1033
  const pauseAction = useCallback(() => {
1002
1034
  const el = videoRef.current;
1003
1035
  if (!el) return;
@@ -1075,6 +1107,7 @@ var VideoPlayerBase = (props, ref) => {
1075
1107
  async (startAt = initialTime, loadId = sourceLoadId.current) => {
1076
1108
  const startVideoRef = videoRef.current;
1077
1109
  if (!startVideoRef) return;
1110
+ sourceLoadMode.current = "hls";
1078
1111
  let HlsCtor;
1079
1112
  try {
1080
1113
  HlsCtor = await loadHlsCtor();
@@ -1217,6 +1250,7 @@ var VideoPlayerBase = (props, ref) => {
1217
1250
  if (loadId !== sourceLoadId.current) return;
1218
1251
  const video = videoRef.current;
1219
1252
  if (!video) return;
1253
+ sourceLoadMode.current = "native";
1220
1254
  video.src = videoSrc;
1221
1255
  video.currentTime = initialTime;
1222
1256
  if (autoPlay) void playAction();
@@ -1224,6 +1258,7 @@ var VideoPlayerBase = (props, ref) => {
1224
1258
  const loadVideoHls = useCallback(async (loadId = sourceLoadId.current) => {
1225
1259
  const video = videoRef.current;
1226
1260
  const canNativeHls = !!(video == null ? void 0 : video.canPlayType) && video.canPlayType("application/vnd.apple.mpegurl") !== "";
1261
+ sourceLoadMode.current = "hls";
1227
1262
  let HlsCtor;
1228
1263
  try {
1229
1264
  HlsCtor = await loadHlsCtor();
@@ -1359,6 +1394,10 @@ var VideoPlayerBase = (props, ref) => {
1359
1394
  if (playingProp) void playAction();
1360
1395
  else pauseAction();
1361
1396
  }, [pauseAction, playAction, playingProp]);
1397
+ useEffect(() => {
1398
+ if (active !== false) return;
1399
+ releaseNativeMedia();
1400
+ }, [active, releaseNativeMedia]);
1362
1401
  useEffect(() => {
1363
1402
  if (!videoRef.current) return;
1364
1403
  const loadId = sourceLoadId.current + 1;
@@ -1370,32 +1409,26 @@ var VideoPlayerBase = (props, ref) => {
1370
1409
  liveStabilityMode.current = false;
1371
1410
  liveStallEvents.current = [];
1372
1411
  resetMediaForSourceChange();
1373
- if (!shouldLoadMedia) {
1374
- setTimeD.current.cancel();
1375
- setCurrentTimeUi.current.cancel();
1376
- setDurationState(0);
1377
- setCurrentTimeState(0);
1378
- setPlayingState(false);
1379
- return () => {
1380
- sourceLoadId.current += 1;
1381
- resetMediaForSourceChange();
1382
- };
1412
+ if (active === false) return;
1413
+ const nextSourceLoadMode = sourceType === "hls" || sourceType !== "native" && type !== "video" && isLikelyHlsSource(videoSrc) ? "hls" : "native";
1414
+ sourceLoadMode.current = nextSourceLoadMode;
1415
+ if (nextSourceLoadMode === "hls") {
1416
+ if (sourceType === "hls") void loadVideo(initialTime, loadId);
1417
+ else void loadVideoHls(loadId);
1418
+ } else {
1419
+ loadVideoNative(loadId);
1383
1420
  }
1384
- if (sourceType === "native" || type === "video") loadVideoNative(loadId);
1385
- else if (sourceType === "hls") void loadVideo(initialTime, loadId);
1386
- else if (isLikelyHlsSource(videoSrc)) void loadVideoHls(loadId);
1387
- else loadVideoNative(loadId);
1388
1421
  return () => {
1389
1422
  sourceLoadId.current += 1;
1390
1423
  resetMediaForSourceChange();
1391
1424
  };
1392
1425
  }, [
1393
1426
  clearErrorState,
1427
+ active,
1394
1428
  loadVideo,
1395
1429
  loadVideoHls,
1396
1430
  loadVideoNative,
1397
1431
  initialTime,
1398
- shouldLoadMedia,
1399
1432
  resetMediaForSourceChange,
1400
1433
  reloadKey,
1401
1434
  reloadToken,
@@ -1492,7 +1525,7 @@ var VideoPlayerBase = (props, ref) => {
1492
1525
  crossOrigin,
1493
1526
  playsInline: true,
1494
1527
  muted,
1495
- preload: shouldLoadMedia ? preload : "none",
1528
+ preload,
1496
1529
  loop,
1497
1530
  onPlay: () => setNextPlaying(true),
1498
1531
  onPause: () => setNextPlaying(false),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nikelroot/video-player",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Standalone React video player with HLS support.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",