@nikelroot/video-player 0.2.3 → 0.2.5

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 playback loading so inactive MP4 players do not keep playback requests open; with `preload="metadata"`, inactive native videos still load metadata.
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);
@@ -777,6 +781,9 @@ var isLikelyHlsSource = (src) => {
777
781
  return /\.m3u8($|[?#])/i.test(src);
778
782
  }
779
783
  };
784
+ var getSourceLoadMode = (sourceType, type, src) => {
785
+ return sourceType === "hls" || sourceType !== "native" && type !== "video" && isLikelyHlsSource(src) ? "hls" : "native";
786
+ };
780
787
  var assignRef = (ref, value) => {
781
788
  if (!ref) return;
782
789
  if (typeof ref === "function") {
@@ -854,6 +861,7 @@ var VideoPlayerBase = (props, ref) => {
854
861
  const liveStallEvents = (0, import_react3.useRef)([]);
855
862
  const controlTime = (0, import_react3.useRef)(null);
856
863
  const sourceLoadId = (0, import_react3.useRef)(0);
864
+ const sourceLoadMode = (0, import_react3.useRef)("native");
857
865
  const [playingState, setPlayingState] = (0, import_react3.useState)(!!defaultPlaying);
858
866
  const [mutedState, setMutedState] = (0, import_react3.useState)(!!defaultMuted);
859
867
  const [durationState, setDurationState] = (0, import_react3.useState)(0);
@@ -909,6 +917,20 @@ var VideoPlayerBase = (props, ref) => {
909
917
  video.removeAttribute("src");
910
918
  video.load();
911
919
  }, [clearTimers]);
920
+ const releaseNativeMedia = (0, import_react3.useCallback)(() => {
921
+ const video = videoRef.current;
922
+ if (!video) return;
923
+ video.pause();
924
+ if (sourceLoadMode.current !== "native" || !isVideoSourceAttached(video)) return;
925
+ video.removeAttribute("src");
926
+ video.load();
927
+ }, []);
928
+ const ensureNativeMediaAttached = (0, import_react3.useCallback)(() => {
929
+ const video = videoRef.current;
930
+ if (!video || sourceLoadMode.current !== "native" || isVideoSourceAttached(video)) return;
931
+ video.src = videoSrc;
932
+ if (initialTime > 0) video.currentTime = initialTime;
933
+ }, [initialTime, videoSrc]);
912
934
  const setNextPlaying = (0, import_react3.useCallback)(
913
935
  (value) => {
914
936
  if (playingProp === void 0) setPlayingState(value);
@@ -974,6 +996,7 @@ var VideoPlayerBase = (props, ref) => {
974
996
  if (prev) {
975
997
  prev.removeEventListener("timeupdate", onTime);
976
998
  prev.removeEventListener("durationchange", onDur);
999
+ exclusiveMediaReleasers.delete(prev);
977
1000
  }
978
1001
  videoRef.current = el;
979
1002
  assignRef(externalVideoRef, el);
@@ -983,8 +1006,9 @@ var VideoPlayerBase = (props, ref) => {
983
1006
  el.muted = muted;
984
1007
  el.addEventListener("timeupdate", onTime);
985
1008
  el.addEventListener("durationchange", onDur);
1009
+ exclusiveMediaReleasers.set(el, releaseNativeMedia);
986
1010
  },
987
- [externalVideoRef, muted, notifyActiveChange, onDur, onTime, onVideoRefChange]
1011
+ [externalVideoRef, muted, notifyActiveChange, onDur, onTime, onVideoRefChange, releaseNativeMedia]
988
1012
  );
989
1013
  const pauseOtherVideos = (0, import_react3.useCallback)(
990
1014
  (el) => {
@@ -993,8 +1017,10 @@ var VideoPlayerBase = (props, ref) => {
993
1017
  exclusivePlayback.pauseOthers(el);
994
1018
  return;
995
1019
  }
996
- if (activeExclusiveVideo && activeExclusiveVideo !== el && !activeExclusiveVideo.paused) {
997
- activeExclusiveVideo.pause();
1020
+ if (activeExclusiveVideo && activeExclusiveVideo !== el) {
1021
+ const releasePrevious = exclusiveMediaReleasers.get(activeExclusiveVideo);
1022
+ if (releasePrevious) releasePrevious();
1023
+ else activeExclusiveVideo.pause();
998
1024
  }
999
1025
  activeExclusiveVideo = el;
1000
1026
  },
@@ -1004,6 +1030,7 @@ var VideoPlayerBase = (props, ref) => {
1004
1030
  const el = videoRef.current;
1005
1031
  if (!el) return;
1006
1032
  pauseOtherVideos(el);
1033
+ ensureNativeMediaAttached();
1007
1034
  try {
1008
1035
  await el.play();
1009
1036
  clearErrorState();
@@ -1025,7 +1052,16 @@ var VideoPlayerBase = (props, ref) => {
1025
1052
  });
1026
1053
  }
1027
1054
  }
1028
- }, [clearErrorState, messages.playFailed, notifyActiveChange, pauseOtherVideos, reportError, scrollTo, setNextPlaying]);
1055
+ }, [
1056
+ clearErrorState,
1057
+ ensureNativeMediaAttached,
1058
+ messages.playFailed,
1059
+ notifyActiveChange,
1060
+ pauseOtherVideos,
1061
+ reportError,
1062
+ scrollTo,
1063
+ setNextPlaying
1064
+ ]);
1029
1065
  const pauseAction = (0, import_react3.useCallback)(() => {
1030
1066
  const el = videoRef.current;
1031
1067
  if (!el) return;
@@ -1103,6 +1139,7 @@ var VideoPlayerBase = (props, ref) => {
1103
1139
  async (startAt = initialTime, loadId = sourceLoadId.current) => {
1104
1140
  const startVideoRef = videoRef.current;
1105
1141
  if (!startVideoRef) return;
1142
+ sourceLoadMode.current = "hls";
1106
1143
  let HlsCtor;
1107
1144
  try {
1108
1145
  HlsCtor = await loadHlsCtor();
@@ -1241,17 +1278,24 @@ var VideoPlayerBase = (props, ref) => {
1241
1278
  videoSrc
1242
1279
  ]
1243
1280
  );
1244
- const loadVideoNative = (0, import_react3.useCallback)((loadId = sourceLoadId.current) => {
1245
- if (loadId !== sourceLoadId.current) return;
1246
- const video = videoRef.current;
1247
- if (!video) return;
1248
- video.src = videoSrc;
1249
- video.currentTime = initialTime;
1250
- if (autoPlay) void playAction();
1251
- }, [autoPlay, initialTime, playAction, videoSrc]);
1281
+ const loadVideoNative = (0, import_react3.useCallback)(
1282
+ (loadId = sourceLoadId.current, options) => {
1283
+ if (loadId !== sourceLoadId.current) return;
1284
+ const video = videoRef.current;
1285
+ if (!video) return;
1286
+ sourceLoadMode.current = "native";
1287
+ video.src = videoSrc;
1288
+ if (!(options == null ? void 0 : options.metadataOnly)) {
1289
+ video.currentTime = initialTime;
1290
+ if (autoPlay) void playAction();
1291
+ }
1292
+ },
1293
+ [autoPlay, initialTime, playAction, videoSrc]
1294
+ );
1252
1295
  const loadVideoHls = (0, import_react3.useCallback)(async (loadId = sourceLoadId.current) => {
1253
1296
  const video = videoRef.current;
1254
1297
  const canNativeHls = !!(video == null ? void 0 : video.canPlayType) && video.canPlayType("application/vnd.apple.mpegurl") !== "";
1298
+ sourceLoadMode.current = "hls";
1255
1299
  let HlsCtor;
1256
1300
  try {
1257
1301
  HlsCtor = await loadHlsCtor();
@@ -1387,6 +1431,10 @@ var VideoPlayerBase = (props, ref) => {
1387
1431
  if (playingProp) void playAction();
1388
1432
  else pauseAction();
1389
1433
  }, [pauseAction, playAction, playingProp]);
1434
+ (0, import_react3.useEffect)(() => {
1435
+ if (active !== false) return;
1436
+ releaseNativeMedia();
1437
+ }, [active, releaseNativeMedia]);
1390
1438
  (0, import_react3.useEffect)(() => {
1391
1439
  if (!videoRef.current) return;
1392
1440
  const loadId = sourceLoadId.current + 1;
@@ -1398,16 +1446,27 @@ var VideoPlayerBase = (props, ref) => {
1398
1446
  liveStabilityMode.current = false;
1399
1447
  liveStallEvents.current = [];
1400
1448
  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);
1449
+ const nextSourceLoadMode = getSourceLoadMode(sourceType, type, videoSrc);
1450
+ sourceLoadMode.current = nextSourceLoadMode;
1451
+ if (active === false) {
1452
+ if (nextSourceLoadMode === "native" && preload === "metadata") {
1453
+ loadVideoNative(loadId, { metadataOnly: true });
1454
+ }
1455
+ return;
1456
+ }
1457
+ if (nextSourceLoadMode === "hls") {
1458
+ if (sourceType === "hls") void loadVideo(initialTime, loadId);
1459
+ else void loadVideoHls(loadId);
1460
+ } else {
1461
+ loadVideoNative(loadId);
1462
+ }
1405
1463
  return () => {
1406
1464
  sourceLoadId.current += 1;
1407
1465
  resetMediaForSourceChange();
1408
1466
  };
1409
1467
  }, [
1410
1468
  clearErrorState,
1469
+ active,
1411
1470
  loadVideo,
1412
1471
  loadVideoHls,
1413
1472
  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);
@@ -748,6 +752,9 @@ var isLikelyHlsSource = (src) => {
748
752
  return /\.m3u8($|[?#])/i.test(src);
749
753
  }
750
754
  };
755
+ var getSourceLoadMode = (sourceType, type, src) => {
756
+ return sourceType === "hls" || sourceType !== "native" && type !== "video" && isLikelyHlsSource(src) ? "hls" : "native";
757
+ };
751
758
  var assignRef = (ref, value) => {
752
759
  if (!ref) return;
753
760
  if (typeof ref === "function") {
@@ -825,6 +832,7 @@ var VideoPlayerBase = (props, ref) => {
825
832
  const liveStallEvents = useRef2([]);
826
833
  const controlTime = useRef2(null);
827
834
  const sourceLoadId = useRef2(0);
835
+ const sourceLoadMode = useRef2("native");
828
836
  const [playingState, setPlayingState] = useState(!!defaultPlaying);
829
837
  const [mutedState, setMutedState] = useState(!!defaultMuted);
830
838
  const [durationState, setDurationState] = useState(0);
@@ -880,6 +888,20 @@ var VideoPlayerBase = (props, ref) => {
880
888
  video.removeAttribute("src");
881
889
  video.load();
882
890
  }, [clearTimers]);
891
+ const releaseNativeMedia = useCallback(() => {
892
+ const video = videoRef.current;
893
+ if (!video) return;
894
+ video.pause();
895
+ if (sourceLoadMode.current !== "native" || !isVideoSourceAttached(video)) return;
896
+ video.removeAttribute("src");
897
+ video.load();
898
+ }, []);
899
+ const ensureNativeMediaAttached = useCallback(() => {
900
+ const video = videoRef.current;
901
+ if (!video || sourceLoadMode.current !== "native" || isVideoSourceAttached(video)) return;
902
+ video.src = videoSrc;
903
+ if (initialTime > 0) video.currentTime = initialTime;
904
+ }, [initialTime, videoSrc]);
883
905
  const setNextPlaying = useCallback(
884
906
  (value) => {
885
907
  if (playingProp === void 0) setPlayingState(value);
@@ -945,6 +967,7 @@ var VideoPlayerBase = (props, ref) => {
945
967
  if (prev) {
946
968
  prev.removeEventListener("timeupdate", onTime);
947
969
  prev.removeEventListener("durationchange", onDur);
970
+ exclusiveMediaReleasers.delete(prev);
948
971
  }
949
972
  videoRef.current = el;
950
973
  assignRef(externalVideoRef, el);
@@ -954,8 +977,9 @@ var VideoPlayerBase = (props, ref) => {
954
977
  el.muted = muted;
955
978
  el.addEventListener("timeupdate", onTime);
956
979
  el.addEventListener("durationchange", onDur);
980
+ exclusiveMediaReleasers.set(el, releaseNativeMedia);
957
981
  },
958
- [externalVideoRef, muted, notifyActiveChange, onDur, onTime, onVideoRefChange]
982
+ [externalVideoRef, muted, notifyActiveChange, onDur, onTime, onVideoRefChange, releaseNativeMedia]
959
983
  );
960
984
  const pauseOtherVideos = useCallback(
961
985
  (el) => {
@@ -964,8 +988,10 @@ var VideoPlayerBase = (props, ref) => {
964
988
  exclusivePlayback.pauseOthers(el);
965
989
  return;
966
990
  }
967
- if (activeExclusiveVideo && activeExclusiveVideo !== el && !activeExclusiveVideo.paused) {
968
- activeExclusiveVideo.pause();
991
+ if (activeExclusiveVideo && activeExclusiveVideo !== el) {
992
+ const releasePrevious = exclusiveMediaReleasers.get(activeExclusiveVideo);
993
+ if (releasePrevious) releasePrevious();
994
+ else activeExclusiveVideo.pause();
969
995
  }
970
996
  activeExclusiveVideo = el;
971
997
  },
@@ -975,6 +1001,7 @@ var VideoPlayerBase = (props, ref) => {
975
1001
  const el = videoRef.current;
976
1002
  if (!el) return;
977
1003
  pauseOtherVideos(el);
1004
+ ensureNativeMediaAttached();
978
1005
  try {
979
1006
  await el.play();
980
1007
  clearErrorState();
@@ -996,7 +1023,16 @@ var VideoPlayerBase = (props, ref) => {
996
1023
  });
997
1024
  }
998
1025
  }
999
- }, [clearErrorState, messages.playFailed, notifyActiveChange, pauseOtherVideos, reportError, scrollTo, setNextPlaying]);
1026
+ }, [
1027
+ clearErrorState,
1028
+ ensureNativeMediaAttached,
1029
+ messages.playFailed,
1030
+ notifyActiveChange,
1031
+ pauseOtherVideos,
1032
+ reportError,
1033
+ scrollTo,
1034
+ setNextPlaying
1035
+ ]);
1000
1036
  const pauseAction = useCallback(() => {
1001
1037
  const el = videoRef.current;
1002
1038
  if (!el) return;
@@ -1074,6 +1110,7 @@ var VideoPlayerBase = (props, ref) => {
1074
1110
  async (startAt = initialTime, loadId = sourceLoadId.current) => {
1075
1111
  const startVideoRef = videoRef.current;
1076
1112
  if (!startVideoRef) return;
1113
+ sourceLoadMode.current = "hls";
1077
1114
  let HlsCtor;
1078
1115
  try {
1079
1116
  HlsCtor = await loadHlsCtor();
@@ -1212,17 +1249,24 @@ var VideoPlayerBase = (props, ref) => {
1212
1249
  videoSrc
1213
1250
  ]
1214
1251
  );
1215
- const loadVideoNative = useCallback((loadId = sourceLoadId.current) => {
1216
- if (loadId !== sourceLoadId.current) return;
1217
- const video = videoRef.current;
1218
- if (!video) return;
1219
- video.src = videoSrc;
1220
- video.currentTime = initialTime;
1221
- if (autoPlay) void playAction();
1222
- }, [autoPlay, initialTime, playAction, videoSrc]);
1252
+ const loadVideoNative = useCallback(
1253
+ (loadId = sourceLoadId.current, options) => {
1254
+ if (loadId !== sourceLoadId.current) return;
1255
+ const video = videoRef.current;
1256
+ if (!video) return;
1257
+ sourceLoadMode.current = "native";
1258
+ video.src = videoSrc;
1259
+ if (!(options == null ? void 0 : options.metadataOnly)) {
1260
+ video.currentTime = initialTime;
1261
+ if (autoPlay) void playAction();
1262
+ }
1263
+ },
1264
+ [autoPlay, initialTime, playAction, videoSrc]
1265
+ );
1223
1266
  const loadVideoHls = useCallback(async (loadId = sourceLoadId.current) => {
1224
1267
  const video = videoRef.current;
1225
1268
  const canNativeHls = !!(video == null ? void 0 : video.canPlayType) && video.canPlayType("application/vnd.apple.mpegurl") !== "";
1269
+ sourceLoadMode.current = "hls";
1226
1270
  let HlsCtor;
1227
1271
  try {
1228
1272
  HlsCtor = await loadHlsCtor();
@@ -1358,6 +1402,10 @@ var VideoPlayerBase = (props, ref) => {
1358
1402
  if (playingProp) void playAction();
1359
1403
  else pauseAction();
1360
1404
  }, [pauseAction, playAction, playingProp]);
1405
+ useEffect(() => {
1406
+ if (active !== false) return;
1407
+ releaseNativeMedia();
1408
+ }, [active, releaseNativeMedia]);
1361
1409
  useEffect(() => {
1362
1410
  if (!videoRef.current) return;
1363
1411
  const loadId = sourceLoadId.current + 1;
@@ -1369,16 +1417,27 @@ var VideoPlayerBase = (props, ref) => {
1369
1417
  liveStabilityMode.current = false;
1370
1418
  liveStallEvents.current = [];
1371
1419
  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);
1420
+ const nextSourceLoadMode = getSourceLoadMode(sourceType, type, videoSrc);
1421
+ sourceLoadMode.current = nextSourceLoadMode;
1422
+ if (active === false) {
1423
+ if (nextSourceLoadMode === "native" && preload === "metadata") {
1424
+ loadVideoNative(loadId, { metadataOnly: true });
1425
+ }
1426
+ return;
1427
+ }
1428
+ if (nextSourceLoadMode === "hls") {
1429
+ if (sourceType === "hls") void loadVideo(initialTime, loadId);
1430
+ else void loadVideoHls(loadId);
1431
+ } else {
1432
+ loadVideoNative(loadId);
1433
+ }
1376
1434
  return () => {
1377
1435
  sourceLoadId.current += 1;
1378
1436
  resetMediaForSourceChange();
1379
1437
  };
1380
1438
  }, [
1381
1439
  clearErrorState,
1440
+ active,
1382
1441
  loadVideo,
1383
1442
  loadVideoHls,
1384
1443
  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.5",
4
4
  "description": "Standalone React video player with HLS support.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",