@nikelroot/video-player 0.2.3 → 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.
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);
@@ -909,6 +914,20 @@ var VideoPlayerBase = (props, ref) => {
909
914
  video.removeAttribute("src");
910
915
  video.load();
911
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]);
912
931
  const setNextPlaying = (0, import_react3.useCallback)(
913
932
  (value) => {
914
933
  if (playingProp === void 0) setPlayingState(value);
@@ -974,6 +993,7 @@ var VideoPlayerBase = (props, ref) => {
974
993
  if (prev) {
975
994
  prev.removeEventListener("timeupdate", onTime);
976
995
  prev.removeEventListener("durationchange", onDur);
996
+ exclusiveMediaReleasers.delete(prev);
977
997
  }
978
998
  videoRef.current = el;
979
999
  assignRef(externalVideoRef, el);
@@ -983,8 +1003,9 @@ var VideoPlayerBase = (props, ref) => {
983
1003
  el.muted = muted;
984
1004
  el.addEventListener("timeupdate", onTime);
985
1005
  el.addEventListener("durationchange", onDur);
1006
+ exclusiveMediaReleasers.set(el, releaseNativeMedia);
986
1007
  },
987
- [externalVideoRef, muted, notifyActiveChange, onDur, onTime, onVideoRefChange]
1008
+ [externalVideoRef, muted, notifyActiveChange, onDur, onTime, onVideoRefChange, releaseNativeMedia]
988
1009
  );
989
1010
  const pauseOtherVideos = (0, import_react3.useCallback)(
990
1011
  (el) => {
@@ -993,8 +1014,10 @@ var VideoPlayerBase = (props, ref) => {
993
1014
  exclusivePlayback.pauseOthers(el);
994
1015
  return;
995
1016
  }
996
- if (activeExclusiveVideo && activeExclusiveVideo !== el && !activeExclusiveVideo.paused) {
997
- activeExclusiveVideo.pause();
1017
+ if (activeExclusiveVideo && activeExclusiveVideo !== el) {
1018
+ const releasePrevious = exclusiveMediaReleasers.get(activeExclusiveVideo);
1019
+ if (releasePrevious) releasePrevious();
1020
+ else activeExclusiveVideo.pause();
998
1021
  }
999
1022
  activeExclusiveVideo = el;
1000
1023
  },
@@ -1004,6 +1027,7 @@ var VideoPlayerBase = (props, ref) => {
1004
1027
  const el = videoRef.current;
1005
1028
  if (!el) return;
1006
1029
  pauseOtherVideos(el);
1030
+ ensureNativeMediaAttached();
1007
1031
  try {
1008
1032
  await el.play();
1009
1033
  clearErrorState();
@@ -1025,7 +1049,16 @@ var VideoPlayerBase = (props, ref) => {
1025
1049
  });
1026
1050
  }
1027
1051
  }
1028
- }, [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
+ ]);
1029
1062
  const pauseAction = (0, import_react3.useCallback)(() => {
1030
1063
  const el = videoRef.current;
1031
1064
  if (!el) return;
@@ -1103,6 +1136,7 @@ var VideoPlayerBase = (props, ref) => {
1103
1136
  async (startAt = initialTime, loadId = sourceLoadId.current) => {
1104
1137
  const startVideoRef = videoRef.current;
1105
1138
  if (!startVideoRef) return;
1139
+ sourceLoadMode.current = "hls";
1106
1140
  let HlsCtor;
1107
1141
  try {
1108
1142
  HlsCtor = await loadHlsCtor();
@@ -1245,6 +1279,7 @@ var VideoPlayerBase = (props, ref) => {
1245
1279
  if (loadId !== sourceLoadId.current) return;
1246
1280
  const video = videoRef.current;
1247
1281
  if (!video) return;
1282
+ sourceLoadMode.current = "native";
1248
1283
  video.src = videoSrc;
1249
1284
  video.currentTime = initialTime;
1250
1285
  if (autoPlay) void playAction();
@@ -1252,6 +1287,7 @@ var VideoPlayerBase = (props, ref) => {
1252
1287
  const loadVideoHls = (0, import_react3.useCallback)(async (loadId = sourceLoadId.current) => {
1253
1288
  const video = videoRef.current;
1254
1289
  const canNativeHls = !!(video == null ? void 0 : video.canPlayType) && video.canPlayType("application/vnd.apple.mpegurl") !== "";
1290
+ sourceLoadMode.current = "hls";
1255
1291
  let HlsCtor;
1256
1292
  try {
1257
1293
  HlsCtor = await loadHlsCtor();
@@ -1387,6 +1423,10 @@ var VideoPlayerBase = (props, ref) => {
1387
1423
  if (playingProp) void playAction();
1388
1424
  else pauseAction();
1389
1425
  }, [pauseAction, playAction, playingProp]);
1426
+ (0, import_react3.useEffect)(() => {
1427
+ if (active !== false) return;
1428
+ releaseNativeMedia();
1429
+ }, [active, releaseNativeMedia]);
1390
1430
  (0, import_react3.useEffect)(() => {
1391
1431
  if (!videoRef.current) return;
1392
1432
  const loadId = sourceLoadId.current + 1;
@@ -1398,16 +1438,22 @@ var VideoPlayerBase = (props, ref) => {
1398
1438
  liveStabilityMode.current = false;
1399
1439
  liveStallEvents.current = [];
1400
1440
  resetMediaForSourceChange();
1401
- if (sourceType === "native" || type === "video") loadVideoNative(loadId);
1402
- else if (sourceType === "hls") void loadVideo(initialTime, loadId);
1403
- else if (isLikelyHlsSource(videoSrc)) void loadVideoHls(loadId);
1404
- else loadVideoNative(loadId);
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);
1449
+ }
1405
1450
  return () => {
1406
1451
  sourceLoadId.current += 1;
1407
1452
  resetMediaForSourceChange();
1408
1453
  };
1409
1454
  }, [
1410
1455
  clearErrorState,
1456
+ active,
1411
1457
  loadVideo,
1412
1458
  loadVideoHls,
1413
1459
  loadVideoNative,
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);
@@ -880,6 +885,20 @@ var VideoPlayerBase = (props, ref) => {
880
885
  video.removeAttribute("src");
881
886
  video.load();
882
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]);
883
902
  const setNextPlaying = useCallback(
884
903
  (value) => {
885
904
  if (playingProp === void 0) setPlayingState(value);
@@ -945,6 +964,7 @@ var VideoPlayerBase = (props, ref) => {
945
964
  if (prev) {
946
965
  prev.removeEventListener("timeupdate", onTime);
947
966
  prev.removeEventListener("durationchange", onDur);
967
+ exclusiveMediaReleasers.delete(prev);
948
968
  }
949
969
  videoRef.current = el;
950
970
  assignRef(externalVideoRef, el);
@@ -954,8 +974,9 @@ var VideoPlayerBase = (props, ref) => {
954
974
  el.muted = muted;
955
975
  el.addEventListener("timeupdate", onTime);
956
976
  el.addEventListener("durationchange", onDur);
977
+ exclusiveMediaReleasers.set(el, releaseNativeMedia);
957
978
  },
958
- [externalVideoRef, muted, notifyActiveChange, onDur, onTime, onVideoRefChange]
979
+ [externalVideoRef, muted, notifyActiveChange, onDur, onTime, onVideoRefChange, releaseNativeMedia]
959
980
  );
960
981
  const pauseOtherVideos = useCallback(
961
982
  (el) => {
@@ -964,8 +985,10 @@ var VideoPlayerBase = (props, ref) => {
964
985
  exclusivePlayback.pauseOthers(el);
965
986
  return;
966
987
  }
967
- if (activeExclusiveVideo && activeExclusiveVideo !== el && !activeExclusiveVideo.paused) {
968
- activeExclusiveVideo.pause();
988
+ if (activeExclusiveVideo && activeExclusiveVideo !== el) {
989
+ const releasePrevious = exclusiveMediaReleasers.get(activeExclusiveVideo);
990
+ if (releasePrevious) releasePrevious();
991
+ else activeExclusiveVideo.pause();
969
992
  }
970
993
  activeExclusiveVideo = el;
971
994
  },
@@ -975,6 +998,7 @@ var VideoPlayerBase = (props, ref) => {
975
998
  const el = videoRef.current;
976
999
  if (!el) return;
977
1000
  pauseOtherVideos(el);
1001
+ ensureNativeMediaAttached();
978
1002
  try {
979
1003
  await el.play();
980
1004
  clearErrorState();
@@ -996,7 +1020,16 @@ var VideoPlayerBase = (props, ref) => {
996
1020
  });
997
1021
  }
998
1022
  }
999
- }, [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
+ ]);
1000
1033
  const pauseAction = useCallback(() => {
1001
1034
  const el = videoRef.current;
1002
1035
  if (!el) return;
@@ -1074,6 +1107,7 @@ var VideoPlayerBase = (props, ref) => {
1074
1107
  async (startAt = initialTime, loadId = sourceLoadId.current) => {
1075
1108
  const startVideoRef = videoRef.current;
1076
1109
  if (!startVideoRef) return;
1110
+ sourceLoadMode.current = "hls";
1077
1111
  let HlsCtor;
1078
1112
  try {
1079
1113
  HlsCtor = await loadHlsCtor();
@@ -1216,6 +1250,7 @@ var VideoPlayerBase = (props, ref) => {
1216
1250
  if (loadId !== sourceLoadId.current) return;
1217
1251
  const video = videoRef.current;
1218
1252
  if (!video) return;
1253
+ sourceLoadMode.current = "native";
1219
1254
  video.src = videoSrc;
1220
1255
  video.currentTime = initialTime;
1221
1256
  if (autoPlay) void playAction();
@@ -1223,6 +1258,7 @@ var VideoPlayerBase = (props, ref) => {
1223
1258
  const loadVideoHls = useCallback(async (loadId = sourceLoadId.current) => {
1224
1259
  const video = videoRef.current;
1225
1260
  const canNativeHls = !!(video == null ? void 0 : video.canPlayType) && video.canPlayType("application/vnd.apple.mpegurl") !== "";
1261
+ sourceLoadMode.current = "hls";
1226
1262
  let HlsCtor;
1227
1263
  try {
1228
1264
  HlsCtor = await loadHlsCtor();
@@ -1358,6 +1394,10 @@ var VideoPlayerBase = (props, ref) => {
1358
1394
  if (playingProp) void playAction();
1359
1395
  else pauseAction();
1360
1396
  }, [pauseAction, playAction, playingProp]);
1397
+ useEffect(() => {
1398
+ if (active !== false) return;
1399
+ releaseNativeMedia();
1400
+ }, [active, releaseNativeMedia]);
1361
1401
  useEffect(() => {
1362
1402
  if (!videoRef.current) return;
1363
1403
  const loadId = sourceLoadId.current + 1;
@@ -1369,16 +1409,22 @@ var VideoPlayerBase = (props, ref) => {
1369
1409
  liveStabilityMode.current = false;
1370
1410
  liveStallEvents.current = [];
1371
1411
  resetMediaForSourceChange();
1372
- if (sourceType === "native" || type === "video") loadVideoNative(loadId);
1373
- else if (sourceType === "hls") void loadVideo(initialTime, loadId);
1374
- else if (isLikelyHlsSource(videoSrc)) void loadVideoHls(loadId);
1375
- else loadVideoNative(loadId);
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);
1420
+ }
1376
1421
  return () => {
1377
1422
  sourceLoadId.current += 1;
1378
1423
  resetMediaForSourceChange();
1379
1424
  };
1380
1425
  }, [
1381
1426
  clearErrorState,
1427
+ active,
1382
1428
  loadVideo,
1383
1429
  loadVideoHls,
1384
1430
  loadVideoNative,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nikelroot/video-player",
3
- "version": "0.2.3",
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",