@nikelroot/video-player 0.2.5 → 0.2.7

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,30 @@ 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 native playback loading so inactive MP4 players do not keep playback requests open; with `preload="metadata"`, inactive native videos still load metadata.
40
+ When `active` is `true`, pressing `F` toggles fullscreen.
41
+
42
+ ## Multiple MP4 players
43
+
44
+ Use `active` when rendering several video players on the same page and only one of them should be ready for playback.
45
+
46
+ ```tsx
47
+ <VideoPlayer
48
+ videoSrc="https://cdn.example.com/video-a.mp4"
49
+ sourceType="native"
50
+ active={isSelected}
51
+ preload="metadata"
52
+ />
53
+ ```
54
+
55
+ This solves a switching problem where previous MP4 players can keep large browser media requests open after another player starts. In Chrome DevTools this often appears as new video requests staying in a long `Pending` state, even when CDN, disk, and network throughput are healthy.
56
+
57
+ For native MP4 sources:
58
+
59
+ - `active={true}` allows playback and fullscreen.
60
+ - `active={false}` releases playback loading so inactive players do not keep competing media requests open.
61
+ - `preload="metadata"` still loads metadata for inactive native videos, so duration and basic media information can be available without starting playback loading.
62
+
63
+ By default `exclusivePlayback` is enabled. When a player starts playback, the previous active native MP4 player is paused and its media source is released, freeing the browser media request before the next video loads.
41
64
 
42
65
  For public CDN-hosted videos, prefer non-credentialed requests unless the CDN is explicitly configured for cookies:
43
66
 
@@ -56,4 +79,3 @@ Build before publishing:
56
79
  ```bash
57
80
  npm run build
58
81
  ```
59
- # video-player
package/dist/index.cjs CHANGED
@@ -862,6 +862,7 @@ var VideoPlayerBase = (props, ref) => {
862
862
  const controlTime = (0, import_react3.useRef)(null);
863
863
  const sourceLoadId = (0, import_react3.useRef)(0);
864
864
  const sourceLoadMode = (0, import_react3.useRef)("native");
865
+ const pendingPlay = (0, import_react3.useRef)(false);
865
866
  const [playingState, setPlayingState] = (0, import_react3.useState)(!!defaultPlaying);
866
867
  const [mutedState, setMutedState] = (0, import_react3.useState)(!!defaultMuted);
867
868
  const [durationState, setDurationState] = (0, import_react3.useState)(0);
@@ -907,6 +908,7 @@ var VideoPlayerBase = (props, ref) => {
907
908
  }, []);
908
909
  const resetMediaForSourceChange = (0, import_react3.useCallback)(() => {
909
910
  clearTimers();
911
+ pendingPlay.current = false;
910
912
  if (hlsRef.current) {
911
913
  hlsRef.current.destroy();
912
914
  hlsRef.current = null;
@@ -920,6 +922,7 @@ var VideoPlayerBase = (props, ref) => {
920
922
  const releaseNativeMedia = (0, import_react3.useCallback)(() => {
921
923
  const video = videoRef.current;
922
924
  if (!video) return;
925
+ pendingPlay.current = false;
923
926
  video.pause();
924
927
  if (sourceLoadMode.current !== "native" || !isVideoSourceAttached(video)) return;
925
928
  video.removeAttribute("src");
@@ -1029,15 +1032,22 @@ var VideoPlayerBase = (props, ref) => {
1029
1032
  const playAction = (0, import_react3.useCallback)(async () => {
1030
1033
  const el = videoRef.current;
1031
1034
  if (!el) return;
1035
+ pendingPlay.current = true;
1032
1036
  pauseOtherVideos(el);
1033
1037
  ensureNativeMediaAttached();
1038
+ if (sourceLoadMode.current === "native" && !canAttemptPlayback(el)) return;
1034
1039
  try {
1035
1040
  await el.play();
1041
+ pendingPlay.current = false;
1036
1042
  clearErrorState();
1037
1043
  setNextPlaying(true);
1038
1044
  notifyActiveChange(el, "play");
1039
1045
  } catch (e) {
1040
- if (isInterruptedPlayRequestError(e)) return;
1046
+ if (isInterruptedPlayRequestError(e)) {
1047
+ pendingPlay.current = true;
1048
+ return;
1049
+ }
1050
+ pendingPlay.current = false;
1041
1051
  console.debug(e);
1042
1052
  reportError(e, { type: "play" }, messages.playFailed);
1043
1053
  }
@@ -1065,6 +1075,7 @@ var VideoPlayerBase = (props, ref) => {
1065
1075
  const pauseAction = (0, import_react3.useCallback)(() => {
1066
1076
  const el = videoRef.current;
1067
1077
  if (!el) return;
1078
+ pendingPlay.current = false;
1068
1079
  el.pause();
1069
1080
  setNextPlaying(false);
1070
1081
  notifyActiveChange(el, "pause");
@@ -1393,11 +1404,14 @@ var VideoPlayerBase = (props, ref) => {
1393
1404
  const error = video == null ? void 0 : video.error;
1394
1405
  const src = (video == null ? void 0 : video.currentSrc) || videoSrc;
1395
1406
  if (hlsRef.current && src.startsWith("blob:")) return;
1407
+ pendingPlay.current = false;
1396
1408
  reportError(error, { type: "native", src }, getNativeVideoErrorMessage(error, messages));
1397
1409
  }, [messages, reportError, videoSrc]);
1398
1410
  const videoLoadedHandler = (0, import_react3.useCallback)(() => {
1399
1411
  clearErrorState();
1400
- }, [clearErrorState]);
1412
+ if (!pendingPlay.current || active === false || !canAttemptPlayback(videoRef.current)) return;
1413
+ void playAction();
1414
+ }, [active, clearErrorState, playAction]);
1401
1415
  const mouseMoveHandler = (0, import_react3.useCallback)(() => {
1402
1416
  if (controlTime.current) clearTimeout(controlTime.current);
1403
1417
  if (!showControlRef.current) {
package/dist/index.js CHANGED
@@ -833,6 +833,7 @@ var VideoPlayerBase = (props, ref) => {
833
833
  const controlTime = useRef2(null);
834
834
  const sourceLoadId = useRef2(0);
835
835
  const sourceLoadMode = useRef2("native");
836
+ const pendingPlay = useRef2(false);
836
837
  const [playingState, setPlayingState] = useState(!!defaultPlaying);
837
838
  const [mutedState, setMutedState] = useState(!!defaultMuted);
838
839
  const [durationState, setDurationState] = useState(0);
@@ -878,6 +879,7 @@ var VideoPlayerBase = (props, ref) => {
878
879
  }, []);
879
880
  const resetMediaForSourceChange = useCallback(() => {
880
881
  clearTimers();
882
+ pendingPlay.current = false;
881
883
  if (hlsRef.current) {
882
884
  hlsRef.current.destroy();
883
885
  hlsRef.current = null;
@@ -891,6 +893,7 @@ var VideoPlayerBase = (props, ref) => {
891
893
  const releaseNativeMedia = useCallback(() => {
892
894
  const video = videoRef.current;
893
895
  if (!video) return;
896
+ pendingPlay.current = false;
894
897
  video.pause();
895
898
  if (sourceLoadMode.current !== "native" || !isVideoSourceAttached(video)) return;
896
899
  video.removeAttribute("src");
@@ -1000,15 +1003,22 @@ var VideoPlayerBase = (props, ref) => {
1000
1003
  const playAction = useCallback(async () => {
1001
1004
  const el = videoRef.current;
1002
1005
  if (!el) return;
1006
+ pendingPlay.current = true;
1003
1007
  pauseOtherVideos(el);
1004
1008
  ensureNativeMediaAttached();
1009
+ if (sourceLoadMode.current === "native" && !canAttemptPlayback(el)) return;
1005
1010
  try {
1006
1011
  await el.play();
1012
+ pendingPlay.current = false;
1007
1013
  clearErrorState();
1008
1014
  setNextPlaying(true);
1009
1015
  notifyActiveChange(el, "play");
1010
1016
  } catch (e) {
1011
- if (isInterruptedPlayRequestError(e)) return;
1017
+ if (isInterruptedPlayRequestError(e)) {
1018
+ pendingPlay.current = true;
1019
+ return;
1020
+ }
1021
+ pendingPlay.current = false;
1012
1022
  console.debug(e);
1013
1023
  reportError(e, { type: "play" }, messages.playFailed);
1014
1024
  }
@@ -1036,6 +1046,7 @@ var VideoPlayerBase = (props, ref) => {
1036
1046
  const pauseAction = useCallback(() => {
1037
1047
  const el = videoRef.current;
1038
1048
  if (!el) return;
1049
+ pendingPlay.current = false;
1039
1050
  el.pause();
1040
1051
  setNextPlaying(false);
1041
1052
  notifyActiveChange(el, "pause");
@@ -1364,11 +1375,14 @@ var VideoPlayerBase = (props, ref) => {
1364
1375
  const error = video == null ? void 0 : video.error;
1365
1376
  const src = (video == null ? void 0 : video.currentSrc) || videoSrc;
1366
1377
  if (hlsRef.current && src.startsWith("blob:")) return;
1378
+ pendingPlay.current = false;
1367
1379
  reportError(error, { type: "native", src }, getNativeVideoErrorMessage(error, messages));
1368
1380
  }, [messages, reportError, videoSrc]);
1369
1381
  const videoLoadedHandler = useCallback(() => {
1370
1382
  clearErrorState();
1371
- }, [clearErrorState]);
1383
+ if (!pendingPlay.current || active === false || !canAttemptPlayback(videoRef.current)) return;
1384
+ void playAction();
1385
+ }, [active, clearErrorState, playAction]);
1372
1386
  const mouseMoveHandler = useCallback(() => {
1373
1387
  if (controlTime.current) clearTimeout(controlTime.current);
1374
1388
  if (!showControlRef.current) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nikelroot/video-player",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Standalone React video player with HLS support.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",