@nikelroot/video-player 0.2.9 → 0.3.0

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/dist/index.js CHANGED
@@ -2,16 +2,104 @@
2
2
  import {
3
3
  forwardRef,
4
4
  memo,
5
- useCallback,
6
- useEffect,
5
+ useCallback as useCallback2,
6
+ useEffect as useEffect2,
7
7
  useImperativeHandle,
8
8
  useMemo as useMemo2,
9
- useRef as useRef2,
10
- useState
9
+ useRef as useRef3,
10
+ useState as useState2
11
11
  } from "react";
12
12
 
13
+ // src/useAudioFallback.ts
14
+ import { useCallback, useEffect, useRef, useState } from "react";
15
+ function useAudioFallback(options) {
16
+ const latest = useRef(options);
17
+ latest.current = options;
18
+ const [videoOnly, setVideoOnly] = useState(false);
19
+ const mode = useRef(false);
20
+ const cooldown = useRef(0);
21
+ const audioErrors = useRef(0);
22
+ const audioErrorTimer = useRef(null);
23
+ const snapshot = useRef(null);
24
+ const switchSource = useCallback((next, failure = false) => {
25
+ const { video, fallback, onChange } = latest.current;
26
+ const element = video.current;
27
+ if (!fallback || !element || mode.current === next) return false;
28
+ snapshot.current = {
29
+ time: Number.isFinite(element.currentTime) ? element.currentTime : 0,
30
+ playing: latest.current.playingIntent.current,
31
+ rate: element.playbackRate,
32
+ volume: element.volume,
33
+ muted: element.muted
34
+ };
35
+ mode.current = next;
36
+ audioErrors.current = 0;
37
+ if (audioErrorTimer.current) clearTimeout(audioErrorTimer.current);
38
+ audioErrorTimer.current = null;
39
+ if (failure) cooldown.current = Date.now() + 15e3;
40
+ setVideoOnly(next);
41
+ onChange == null ? void 0 : onChange(next);
42
+ return true;
43
+ }, []);
44
+ const onAudioError = useCallback((data) => {
45
+ var _a, _b, _c;
46
+ if (!latest.current.fallback || mode.current) return false;
47
+ const isAudio = ((_a = data == null ? void 0 : data.frag) == null ? void 0 : _a.type) === "audio" || (data == null ? void 0 : data.parent) === "audio" || ((_b = data == null ? void 0 : data.context) == null ? void 0 : _b.type) === "audioTrack" || /^audioTrack/.test((data == null ? void 0 : data.details) || "");
48
+ if (!isAudio) return false;
49
+ if ((data == null ? void 0 : data.details) === "fragGap" && ((_c = latest.current.ranges) == null ? void 0 : _c.some((range) => {
50
+ var _a2, _b2;
51
+ const time = (_b2 = (_a2 = latest.current.video.current) == null ? void 0 : _a2.currentTime) != null ? _b2 : 0;
52
+ return time >= range.start - 0.05 && time < range.end - 0.05;
53
+ }))) return true;
54
+ audioErrors.current++;
55
+ if ((data == null ? void 0 : data.fatal) || audioErrors.current >= 2) return switchSource(true, true);
56
+ if (!audioErrorTimer.current) {
57
+ audioErrorTimer.current = setTimeout(() => {
58
+ audioErrorTimer.current = null;
59
+ switchSource(true, true);
60
+ }, 8e3);
61
+ }
62
+ return true;
63
+ }, [switchSource]);
64
+ const onAudioProgress = useCallback(() => {
65
+ audioErrors.current = 0;
66
+ if (audioErrorTimer.current) clearTimeout(audioErrorTimer.current);
67
+ audioErrorTimer.current = null;
68
+ }, []);
69
+ useEffect(() => {
70
+ mode.current = false;
71
+ setVideoOnly(false);
72
+ snapshot.current = null;
73
+ cooldown.current = 0;
74
+ onAudioProgress();
75
+ return onAudioProgress;
76
+ }, [options.primary, options.fallback, onAudioProgress]);
77
+ useEffect(() => {
78
+ const check = () => {
79
+ const { ranges, video, fallback } = latest.current;
80
+ if (!fallback || !ranges || snapshot.current) return;
81
+ const element = video.current;
82
+ if (!element) return;
83
+ const time = element.currentTime;
84
+ const available = ranges.some((range) => time >= range.start - 0.05 && time < range.end - 0.05);
85
+ if (!mode.current && !available) switchSource(true);
86
+ else if (mode.current && available && Date.now() >= cooldown.current && ranges.some((range) => time >= range.start - 0.05 && time + 4 <= range.end)) switchSource(false);
87
+ };
88
+ const timer = setInterval(check, 500);
89
+ return () => clearInterval(timer);
90
+ }, [switchSource]);
91
+ return {
92
+ videoOnly,
93
+ source: videoOnly && options.fallback ? options.fallback : options.primary,
94
+ snapshot,
95
+ switchSource,
96
+ onAudioError,
97
+ onAudioProgress
98
+ };
99
+ }
100
+
13
101
  // src/controls.tsx
14
- import { useRef } from "react";
102
+ import { useRef as useRef2 } from "react";
15
103
 
16
104
  // src/styles.ts
17
105
  import { css, styled } from "styled-components";
@@ -151,6 +239,18 @@ var VideoPlayerStyles = styled.div`
151
239
  align-items: center;
152
240
  }
153
241
 
242
+ .video-audio-status {
243
+ position: absolute;
244
+ top: 0.75rem;
245
+ left: 0.75rem;
246
+ padding: 0.35rem 0.6rem;
247
+ color: inherit;
248
+ background: rgb(24 27 31 / 85%);
249
+ border-radius: 0.25rem;
250
+ font-size: 0.75rem;
251
+ pointer-events: none;
252
+ }
253
+
154
254
  .video-error-overlay {
155
255
  position: absolute;
156
256
  inset: 0;
@@ -401,7 +501,7 @@ var createThrottledNumberFn = (fn, waitMs) => {
401
501
  // src/controls.tsx
402
502
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
403
503
  var SeekSlider = ({ playAction, duration, currentTime, seekSetTime }) => {
404
- const ref = useRef(null);
504
+ const ref = useRef2(null);
405
505
  const clickHandler = (e) => {
406
506
  e.preventDefault();
407
507
  e.stopPropagation();
@@ -815,56 +915,67 @@ var VideoPlayerBase = (props, ref) => {
815
915
  messages: messagesProp,
816
916
  accentColor,
817
917
  videoSrc: rawVideoSrc = "",
918
+ videoOnlySrc,
919
+ audioRanges,
920
+ onAudioFallbackChange,
818
921
  ...videoProps
819
922
  } = props;
820
- const videoSrc = rawVideoSrc.replaceAll(" ", "%20").replaceAll("#", "%23");
923
+ const primarySrc = rawVideoSrc.replaceAll(" ", "%20").replaceAll("#", "%23");
924
+ const propsRef = useRef3(props);
925
+ propsRef.current = props;
821
926
  const messages = useMemo2(() => ({ ...defaultMessages, ...messagesProp }), [messagesProp]);
822
927
  const [, isMobile, mobileType] = useWindowWidth();
823
- const videoRef = useRef2(null);
824
- const hlsRef = useRef2(null);
825
- const containerRef = useRef2(null);
826
- const netRetryCount = useRef2(0);
827
- const mediaRecoverCount = useRef2(0);
828
- const fatalReloadCount = useRef2(0);
829
- const reloadTimer = useRef2(null);
830
- const stalledNudgeTimer = useRef2(null);
831
- const liveStabilityMode = useRef2(false);
832
- const liveStallEvents = useRef2([]);
833
- const controlTime = useRef2(null);
834
- const sourceLoadId = useRef2(0);
835
- const sourceLoadMode = useRef2("native");
836
- const pendingPlay = useRef2(false);
837
- const playRequestInFlight = useRef2(false);
838
- const [playingState, setPlayingState] = useState(!!defaultPlaying);
839
- const [mutedState, setMutedState] = useState(!!defaultMuted);
840
- const [durationState, setDurationState] = useState(0);
841
- const [currentTimeState, setCurrentTimeState] = useState(0);
842
- const [isFullscreen, setIsFullscreen] = useState(false);
843
- const [showControl, setControlVisible] = useState(true);
844
- const [errorState, setErrorState] = useState(null);
845
- const [reloadToken, setReloadToken] = useState(0);
846
- const showControlRef = useRef2(showControl);
928
+ const videoRef = useRef3(null);
929
+ const playbackIntent = useRef3(autoPlay || defaultPlaying || Boolean(playingProp));
930
+ const audioFallback = useAudioFallback({ primary: primarySrc, fallback: videoOnlySrc, ranges: audioRanges, video: videoRef, playingIntent: playbackIntent, onChange: onAudioFallbackChange });
931
+ const videoSrc = audioFallback.source;
932
+ const { snapshot: resumeSnapshot, onAudioError, onAudioProgress, switchSource } = audioFallback;
933
+ const hlsRef = useRef3(null);
934
+ const containerRef = useRef3(null);
935
+ const lastProgressTime = useRef3(0);
936
+ const netRetryCount = useRef3(0);
937
+ const mediaRecoverCount = useRef3(0);
938
+ const fatalReloadCount = useRef3(0);
939
+ const reloadTimer = useRef3(null);
940
+ const stalledNudgeTimer = useRef3(null);
941
+ const liveStabilityMode = useRef3(false);
942
+ const liveStallEvents = useRef3([]);
943
+ const controlTime = useRef3(null);
944
+ const sourceLoadId = useRef3(0);
945
+ const sourceLoadMode = useRef3("native");
946
+ const pendingPlay = useRef3(false);
947
+ const playRequestInFlight = useRef3(false);
948
+ const playRequestId = useRef3(0);
949
+ const [playingState, setPlayingState] = useState2(!!defaultPlaying);
950
+ const [mutedState, setMutedState] = useState2(!!defaultMuted);
951
+ const [durationState, setDurationState] = useState2(0);
952
+ const [currentTimeState, setCurrentTimeState] = useState2(0);
953
+ const [isFullscreen, setIsFullscreen] = useState2(false);
954
+ const [showControl, setControlVisible] = useState2(true);
955
+ const [errorState, setErrorState] = useState2(null);
956
+ const [reloadToken, setReloadToken] = useState2(0);
957
+ const showControlRef = useRef3(showControl);
847
958
  const playing = playingProp != null ? playingProp : playingState;
848
959
  const muted = mutedProp != null ? mutedProp : mutedState;
849
960
  const isDurationControlled = durationProp !== void 0;
850
961
  const duration = durationProp != null ? durationProp : durationState;
851
962
  const currentTime = currentTimeState;
852
963
  const fullscreenAllowed = active != null ? active : true;
853
- const onTimeChangeRef = useRef2(onTimeChange);
854
- const onDurationChangeRef = useRef2(onDurationChange);
964
+ const onTimeChangeRef = useRef3(onTimeChange);
965
+ const onDurationChangeRef = useRef3(onDurationChange);
855
966
  onTimeChangeRef.current = onTimeChange;
856
967
  onDurationChangeRef.current = onDurationChange;
857
968
  showControlRef.current = showControl;
858
969
  const { currentTime: _ignoredCurrentTime, ...domVideoProps } = videoProps;
859
- const setTimeD = useRef2(
970
+ const setTimeD = useRef3(
860
971
  createThrottledNumberFn((value) => {
861
972
  const v = videoRef.current;
862
973
  if (!v) return;
863
974
  v.currentTime = value;
864
975
  }, 30)
865
976
  );
866
- const setCurrentTimeUi = useRef2(createThrottledNumberFn((value) => setCurrentTimeState(value), 250));
867
- const clearTimers = useCallback(() => {
977
+ const setCurrentTimeUi = useRef3(createThrottledNumberFn((value) => setCurrentTimeState(value), 250));
978
+ const clearTimers = useCallback2(() => {
868
979
  if (reloadTimer.current) {
869
980
  clearTimeout(reloadTimer.current);
870
981
  reloadTimer.current = null;
@@ -878,9 +989,11 @@ var VideoPlayerBase = (props, ref) => {
878
989
  controlTime.current = null;
879
990
  }
880
991
  }, []);
881
- const resetMediaForSourceChange = useCallback(() => {
992
+ const resetMediaForSourceChange = useCallback2(() => {
882
993
  clearTimers();
883
994
  pendingPlay.current = false;
995
+ playRequestId.current++;
996
+ playRequestInFlight.current = false;
884
997
  if (hlsRef.current) {
885
998
  hlsRef.current.destroy();
886
999
  hlsRef.current = null;
@@ -891,7 +1004,7 @@ var VideoPlayerBase = (props, ref) => {
891
1004
  video.removeAttribute("src");
892
1005
  video.load();
893
1006
  }, [clearTimers]);
894
- const releaseNativeMedia = useCallback(() => {
1007
+ const releaseNativeMedia = useCallback2(() => {
895
1008
  const video = videoRef.current;
896
1009
  if (!video) return;
897
1010
  pendingPlay.current = false;
@@ -900,7 +1013,7 @@ var VideoPlayerBase = (props, ref) => {
900
1013
  video.removeAttribute("src");
901
1014
  video.load();
902
1015
  }, []);
903
- const ensureNativeMediaAttached = useCallback(() => {
1016
+ const ensureNativeMediaAttached = useCallback2(() => {
904
1017
  const video = videoRef.current;
905
1018
  if (!video || sourceLoadMode.current !== "native" || isVideoSourceAttached(video)) return false;
906
1019
  video.src = videoSrc;
@@ -908,27 +1021,30 @@ var VideoPlayerBase = (props, ref) => {
908
1021
  video.load();
909
1022
  return true;
910
1023
  }, [initialTime, videoSrc]);
911
- const setNextPlaying = useCallback(
1024
+ const setNextPlaying = useCallback2(
912
1025
  (value) => {
913
- if (playingProp === void 0) setPlayingState(value);
914
- onPlayingChange == null ? void 0 : onPlayingChange(value);
1026
+ var _a, _b;
1027
+ if (propsRef.current.playing === void 0) setPlayingState(value);
1028
+ (_b = (_a = propsRef.current).onPlayingChange) == null ? void 0 : _b.call(_a, value);
915
1029
  },
916
- [onPlayingChange, playingProp]
1030
+ []
917
1031
  );
918
- const setNextMuted = useCallback(
1032
+ const setNextMuted = useCallback2(
919
1033
  (value) => {
920
- if (mutedProp === void 0) setMutedState(value);
1034
+ var _a, _b;
1035
+ if (propsRef.current.muted === void 0) setMutedState(value);
921
1036
  const el = videoRef.current;
922
1037
  if (el) el.muted = value;
923
- onMutedChange == null ? void 0 : onMutedChange(value);
1038
+ (_b = (_a = propsRef.current).onMutedChange) == null ? void 0 : _b.call(_a, value);
924
1039
  },
925
- [mutedProp, onMutedChange]
1040
+ []
926
1041
  );
927
- const clearErrorState = useCallback(() => {
1042
+ const clearErrorState = useCallback2(() => {
928
1043
  setErrorState((prev) => prev ? null : prev);
929
1044
  }, []);
930
- const reportError = useCallback(
1045
+ const reportError = useCallback2(
931
1046
  (error, data, message = messages.defaultError) => {
1047
+ var _a, _b;
932
1048
  const details = formatPlaybackErrorDetails(error, data);
933
1049
  const code = getPlaybackErrorCodeText(error);
934
1050
  setNextPlaying(false);
@@ -940,25 +1056,32 @@ var VideoPlayerBase = (props, ref) => {
940
1056
  error,
941
1057
  data
942
1058
  });
943
- onPlaybackError == null ? void 0 : onPlaybackError({ error, data, message, details });
1059
+ (_b = (_a = propsRef.current).onPlaybackError) == null ? void 0 : _b.call(_a, { error, data, message, details });
944
1060
  },
945
- [messages.defaultError, onPlaybackError, setNextPlaying]
1061
+ [messages.defaultError, setNextPlaying]
946
1062
  );
947
- const notifyActiveChange = useCallback(
1063
+ const notifyActiveChange = useCallback2(
948
1064
  (video, reason) => {
949
- onActiveChange == null ? void 0 : onActiveChange({ video, reason });
1065
+ var _a, _b;
1066
+ (_b = (_a = propsRef.current).onActiveChange) == null ? void 0 : _b.call(_a, { video, reason });
950
1067
  },
951
- [onActiveChange]
1068
+ []
952
1069
  );
953
- const onTime = useCallback(() => {
1070
+ const onTime = useCallback2(() => {
954
1071
  var _a;
955
1072
  const el = videoRef.current;
956
1073
  if (!el) return;
957
1074
  const nextTime = Number.isFinite(el.currentTime) ? el.currentTime : 0;
1075
+ if (!el.paused && !el.seeking && nextTime > lastProgressTime.current + 0.5) {
1076
+ netRetryCount.current = 0;
1077
+ fatalReloadCount.current = 0;
1078
+ mediaRecoverCount.current = 0;
1079
+ lastProgressTime.current = nextTime;
1080
+ }
958
1081
  setCurrentTimeUi.current(nextTime);
959
1082
  (_a = onTimeChangeRef.current) == null ? void 0 : _a.call(onTimeChangeRef, nextTime, el);
960
1083
  }, []);
961
- const onDur = useCallback(() => {
1084
+ const onDur = useCallback2(() => {
962
1085
  var _a;
963
1086
  const el = videoRef.current;
964
1087
  if (!el) return;
@@ -966,8 +1089,9 @@ var VideoPlayerBase = (props, ref) => {
966
1089
  if (!isDurationControlled) setDurationState(nextDuration);
967
1090
  (_a = onDurationChangeRef.current) == null ? void 0 : _a.call(onDurationChangeRef, nextDuration, el);
968
1091
  }, [isDurationControlled]);
969
- const setVideoEl = useCallback(
1092
+ const setVideoEl = useCallback2(
970
1093
  (el) => {
1094
+ var _a, _b, _c;
971
1095
  const prev = videoRef.current;
972
1096
  if (prev === el) return;
973
1097
  if (prev) {
@@ -976,18 +1100,18 @@ var VideoPlayerBase = (props, ref) => {
976
1100
  exclusiveMediaReleasers.delete(prev);
977
1101
  }
978
1102
  videoRef.current = el;
979
- assignRef(externalVideoRef, el);
980
- onVideoRefChange == null ? void 0 : onVideoRefChange(el);
1103
+ assignRef(propsRef.current.videoRef, el);
1104
+ (_b = (_a = propsRef.current).onVideoRefChange) == null ? void 0 : _b.call(_a, el);
981
1105
  notifyActiveChange(el, "ref");
982
1106
  if (!el) return;
983
- el.muted = muted;
1107
+ el.muted = (_c = propsRef.current.muted) != null ? _c : mutedState;
984
1108
  el.addEventListener("timeupdate", onTime);
985
1109
  el.addEventListener("durationchange", onDur);
986
1110
  exclusiveMediaReleasers.set(el, releaseNativeMedia);
987
1111
  },
988
- [externalVideoRef, muted, notifyActiveChange, onDur, onTime, onVideoRefChange, releaseNativeMedia]
1112
+ [notifyActiveChange, onDur, onTime, releaseNativeMedia]
989
1113
  );
990
- const pauseOtherVideos = useCallback(
1114
+ const pauseOtherVideos = useCallback2(
991
1115
  (el) => {
992
1116
  if (!exclusivePlayback) return;
993
1117
  if (typeof exclusivePlayback === "object") {
@@ -1003,20 +1127,25 @@ var VideoPlayerBase = (props, ref) => {
1003
1127
  },
1004
1128
  [exclusivePlayback]
1005
1129
  );
1006
- const playAction = useCallback(async () => {
1130
+ const playAction = useCallback2(async () => {
1007
1131
  const el = videoRef.current;
1008
1132
  if (!el) return;
1133
+ const requestId = ++playRequestId.current;
1009
1134
  pendingPlay.current = true;
1135
+ playbackIntent.current = true;
1136
+ if (resumeSnapshot.current) resumeSnapshot.current.playing = true;
1010
1137
  pauseOtherVideos(el);
1011
1138
  ensureNativeMediaAttached();
1012
1139
  try {
1013
1140
  playRequestInFlight.current = true;
1014
1141
  await el.play();
1142
+ if (requestId !== playRequestId.current || videoRef.current !== el) return;
1015
1143
  pendingPlay.current = false;
1016
1144
  clearErrorState();
1017
1145
  setNextPlaying(true);
1018
1146
  notifyActiveChange(el, "play");
1019
1147
  } catch (e) {
1148
+ if (requestId !== playRequestId.current || videoRef.current !== el) return;
1020
1149
  if (isInterruptedPlayRequestError(e)) {
1021
1150
  pendingPlay.current = true;
1022
1151
  return;
@@ -1025,7 +1154,7 @@ var VideoPlayerBase = (props, ref) => {
1025
1154
  console.debug(e);
1026
1155
  reportError(e, { type: "play" }, messages.playFailed);
1027
1156
  } finally {
1028
- playRequestInFlight.current = false;
1157
+ if (requestId === playRequestId.current) playRequestInFlight.current = false;
1029
1158
  }
1030
1159
  if (scrollTo === true) {
1031
1160
  const rect = el.getBoundingClientRect();
@@ -1048,15 +1177,17 @@ var VideoPlayerBase = (props, ref) => {
1048
1177
  scrollTo,
1049
1178
  setNextPlaying
1050
1179
  ]);
1051
- const pauseAction = useCallback(() => {
1180
+ const pauseAction = useCallback2(() => {
1052
1181
  const el = videoRef.current;
1053
1182
  if (!el) return;
1054
1183
  pendingPlay.current = false;
1184
+ if (resumeSnapshot.current) resumeSnapshot.current.playing = false;
1185
+ playbackIntent.current = false;
1055
1186
  el.pause();
1056
1187
  setNextPlaying(false);
1057
1188
  notifyActiveChange(el, "pause");
1058
1189
  }, [notifyActiveChange, setNextPlaying]);
1059
- const seekSetTime = useCallback(
1190
+ const seekSetTime = useCallback2(
1060
1191
  (value) => {
1061
1192
  const el = videoRef.current;
1062
1193
  if (!el) return;
@@ -1068,7 +1199,7 @@ var VideoPlayerBase = (props, ref) => {
1068
1199
  },
1069
1200
  [duration, notifyActiveChange]
1070
1201
  );
1071
- const seekPrevAction = useCallback(
1202
+ const seekPrevAction = useCallback2(
1072
1203
  (sec = 15) => {
1073
1204
  const el = videoRef.current;
1074
1205
  if (!el) return;
@@ -1076,7 +1207,7 @@ var VideoPlayerBase = (props, ref) => {
1076
1207
  },
1077
1208
  [seekSetTime]
1078
1209
  );
1079
- const seekNextAction = useCallback(
1210
+ const seekNextAction = useCallback2(
1080
1211
  (sec = 15) => {
1081
1212
  const el = videoRef.current;
1082
1213
  if (!el) return;
@@ -1084,7 +1215,7 @@ var VideoPlayerBase = (props, ref) => {
1084
1215
  },
1085
1216
  [seekSetTime]
1086
1217
  );
1087
- const toggleAction = useCallback(
1218
+ const toggleAction = useCallback2(
1088
1219
  (force) => {
1089
1220
  const el = videoRef.current;
1090
1221
  if (!el) return;
@@ -1096,12 +1227,12 @@ var VideoPlayerBase = (props, ref) => {
1096
1227
  },
1097
1228
  [pauseAction, playAction]
1098
1229
  );
1099
- const nudgeOnStall = useCallback(() => {
1230
+ const nudgeOnStall = useCallback2(() => {
1100
1231
  const v = videoRef.current;
1101
1232
  if (!v) return;
1102
1233
  v.currentTime = Math.max(0, v.currentTime + 0.05);
1103
1234
  }, []);
1104
- const shouldSwitchLiveToStability = useCallback(() => {
1235
+ const shouldSwitchLiveToStability = useCallback2(() => {
1105
1236
  if (!live || liveStabilityMode.current) return false;
1106
1237
  const now = Date.now();
1107
1238
  liveStallEvents.current = [...liveStallEvents.current.filter((ts) => now - ts <= LIVE_STALL_WINDOW_MS), now];
@@ -1110,7 +1241,7 @@ var VideoPlayerBase = (props, ref) => {
1110
1241
  liveStallEvents.current = [];
1111
1242
  return true;
1112
1243
  }, [live]);
1113
- const resolvedHlsConfig = useCallback(() => {
1244
+ const resolvedHlsConfig = useCallback2(() => {
1114
1245
  const modeConfig = live ? liveHlsConfig : vodHlsConfig;
1115
1246
  const base = live ? confLiveStability : confVod;
1116
1247
  return withCredentialsConfig(
@@ -1122,8 +1253,8 @@ var VideoPlayerBase = (props, ref) => {
1122
1253
  hlsCredentials
1123
1254
  );
1124
1255
  }, [hlsConfig, hlsCredentials, live, liveHlsConfig, vodHlsConfig]);
1125
- const loadVideo = useCallback(
1126
- async (startAt = initialTime, loadId = sourceLoadId.current) => {
1256
+ const loadVideo = useCallback2(
1257
+ async (startAt = ((_b) => (_b = ((_a) => (_a = resumeSnapshot.current) == null ? void 0 : _a.time)()) != null ? _b : initialTime)(), loadId = sourceLoadId.current) => {
1127
1258
  const startVideoRef = videoRef.current;
1128
1259
  if (!startVideoRef) return;
1129
1260
  sourceLoadMode.current = "hls";
@@ -1131,6 +1262,7 @@ var VideoPlayerBase = (props, ref) => {
1131
1262
  try {
1132
1263
  HlsCtor = await loadHlsCtor();
1133
1264
  } catch (error) {
1265
+ if (loadId !== sourceLoadId.current || videoRef.current !== startVideoRef) return;
1134
1266
  reportError(error, { type: "hls-loader", src: videoSrc }, messages.streamLoadFailed);
1135
1267
  return;
1136
1268
  }
@@ -1142,10 +1274,11 @@ var VideoPlayerBase = (props, ref) => {
1142
1274
  hlsRef.current = h;
1143
1275
  const isStaleLoad = () => loadId !== sourceLoadId.current || hlsRef.current !== h || videoRef.current !== video;
1144
1276
  h.on(HlsCtor.Events.ERROR, (_evt, data) => {
1145
- var _a, _b;
1277
+ var _a2, _b2;
1146
1278
  if (isStaleLoad()) return;
1147
1279
  const { type: errorType, details, fatal } = data;
1148
- if (details === HlsCtor.ErrorDetails.BUFFER_APPEND_ERROR || details === "bufferAppendingError") return;
1280
+ if (onAudioError(data)) return;
1281
+ if (!fatal && (details === HlsCtor.ErrorDetails.BUFFER_APPEND_ERROR || details === "bufferAppendingError")) return;
1149
1282
  if (!fatal) {
1150
1283
  switch (details) {
1151
1284
  case HlsCtor.ErrorDetails.BUFFER_STALLED_ERROR:
@@ -1157,7 +1290,7 @@ var VideoPlayerBase = (props, ref) => {
1157
1290
  }, 200);
1158
1291
  }
1159
1292
  if (shouldSwitchLiveToStability()) {
1160
- const resumeTime2 = ((_a = videoRef.current) == null ? void 0 : _a.currentTime) || 0;
1293
+ const resumeTime2 = ((_a2 = videoRef.current) == null ? void 0 : _a2.currentTime) || 0;
1161
1294
  clearTimers();
1162
1295
  reloadTimer.current = setTimeout(() => {
1163
1296
  if (hlsRef.current) {
@@ -1172,7 +1305,7 @@ var VideoPlayerBase = (props, ref) => {
1172
1305
  return;
1173
1306
  }
1174
1307
  }
1175
- const resumeTime = ((_b = videoRef.current) == null ? void 0 : _b.currentTime) || 0;
1308
+ const resumeTime = ((_b2 = videoRef.current) == null ? void 0 : _b2.currentTime) || 0;
1176
1309
  if (errorType === HlsCtor.ErrorTypes.NETWORK_ERROR) {
1177
1310
  if (netRetryCount.current < MAX_NET_RETRIES) {
1178
1311
  netRetryCount.current += 1;
@@ -1236,21 +1369,25 @@ var VideoPlayerBase = (props, ref) => {
1236
1369
  });
1237
1370
  h.on(HlsCtor.Events.MANIFEST_PARSED, () => {
1238
1371
  if (isStaleLoad()) return;
1239
- fatalReloadCount.current = 0;
1240
- netRetryCount.current = 0;
1241
- mediaRecoverCount.current = 0;
1242
- if (!live && startAt > 0) {
1372
+ if (startAt > 0) {
1243
1373
  h.startLoad(startAt);
1244
1374
  video.currentTime = startAt;
1245
1375
  } else {
1246
1376
  h.startLoad();
1247
1377
  }
1248
- if (autoPlay) void playAction();
1378
+ if (resumeSnapshot.current ? resumeSnapshot.current.playing : autoPlay) void playAction();
1379
+ });
1380
+ h.on(HlsCtor.Events.FRAG_BUFFERED, (_evt, data) => {
1381
+ if (isStaleLoad()) return;
1382
+ if (data.frag.type === "audio") onAudioProgress();
1249
1383
  });
1250
1384
  h.attachMedia(video);
1251
1385
  },
1252
1386
  [
1253
1387
  autoPlay,
1388
+ onAudioError,
1389
+ onAudioProgress,
1390
+ resumeSnapshot,
1254
1391
  clearErrorState,
1255
1392
  clearTimers,
1256
1393
  initialTime,
@@ -1265,21 +1402,23 @@ var VideoPlayerBase = (props, ref) => {
1265
1402
  videoSrc
1266
1403
  ]
1267
1404
  );
1268
- const loadVideoNative = useCallback(
1405
+ const loadVideoNative = useCallback2(
1269
1406
  (loadId = sourceLoadId.current, options) => {
1407
+ var _a, _b;
1270
1408
  if (loadId !== sourceLoadId.current) return;
1271
1409
  const video = videoRef.current;
1272
1410
  if (!video) return;
1273
1411
  sourceLoadMode.current = "native";
1274
1412
  video.src = videoSrc;
1275
1413
  if (!(options == null ? void 0 : options.metadataOnly)) {
1276
- video.currentTime = initialTime;
1277
- if (autoPlay) void playAction();
1414
+ video.currentTime = (_b = (_a = resumeSnapshot.current) == null ? void 0 : _a.time) != null ? _b : initialTime;
1415
+ if (resumeSnapshot.current ? resumeSnapshot.current.playing : autoPlay) void playAction();
1278
1416
  }
1279
1417
  },
1280
1418
  [autoPlay, initialTime, playAction, videoSrc]
1281
1419
  );
1282
- const loadVideoHls = useCallback(async (loadId = sourceLoadId.current) => {
1420
+ const loadVideoHls = useCallback2(async (loadId = sourceLoadId.current) => {
1421
+ var _a, _b;
1283
1422
  const video = videoRef.current;
1284
1423
  const canNativeHls = !!(video == null ? void 0 : video.canPlayType) && video.canPlayType("application/vnd.apple.mpegurl") !== "";
1285
1424
  sourceLoadMode.current = "hls";
@@ -1287,13 +1426,14 @@ var VideoPlayerBase = (props, ref) => {
1287
1426
  try {
1288
1427
  HlsCtor = await loadHlsCtor();
1289
1428
  } catch (error) {
1429
+ if (loadId !== sourceLoadId.current || videoRef.current !== video) return;
1290
1430
  reportError(error, { type: "hls-loader", src: videoSrc }, messages.streamLoadFailed);
1291
1431
  return;
1292
1432
  }
1293
1433
  if (loadId !== sourceLoadId.current) return;
1294
1434
  if (!videoRef.current || videoRef.current !== video) return;
1295
1435
  if (HlsCtor.isSupported()) {
1296
- void loadVideo(initialTime, loadId);
1436
+ void loadVideo((_b = (_a = resumeSnapshot.current) == null ? void 0 : _a.time) != null ? _b : initialTime, loadId);
1297
1437
  return;
1298
1438
  }
1299
1439
  if (canNativeHls) {
@@ -1302,7 +1442,8 @@ var VideoPlayerBase = (props, ref) => {
1302
1442
  }
1303
1443
  reportError(null, { type: "hls", src: videoSrc, reason: "HLS is not supported in this browser" }, messages.unsupported);
1304
1444
  }, [initialTime, loadVideo, loadVideoNative, messages.streamLoadFailed, messages.unsupported, reportError, videoSrc]);
1305
- const destroy = useCallback(() => {
1445
+ const destroy = useCallback2(() => {
1446
+ var _a, _b;
1306
1447
  clearTimers();
1307
1448
  if (hlsRef.current) {
1308
1449
  hlsRef.current.destroy();
@@ -1318,27 +1459,25 @@ var VideoPlayerBase = (props, ref) => {
1318
1459
  if (activeExclusiveVideo === v) activeExclusiveVideo = null;
1319
1460
  }
1320
1461
  videoRef.current = null;
1321
- onVideoRefChange == null ? void 0 : onVideoRefChange(null);
1462
+ (_b = (_a = propsRef.current).onVideoRefChange) == null ? void 0 : _b.call(_a, null);
1322
1463
  notifyActiveChange(null, "destroy");
1323
1464
  setNextPlaying(false);
1324
- if (durationProp === void 0) setDurationState(0);
1465
+ if (propsRef.current.duration === void 0) setDurationState(0);
1325
1466
  setCurrentTimeUi.current.cancel();
1326
1467
  setCurrentTimeState(0);
1327
1468
  }, [
1328
1469
  clearTimers,
1329
- durationProp,
1330
1470
  notifyActiveChange,
1331
1471
  onDur,
1332
1472
  onTime,
1333
- onVideoRefChange,
1334
1473
  setNextPlaying
1335
1474
  ]);
1336
- const videoClickHandler = useCallback(() => {
1475
+ const videoClickHandler = useCallback2(() => {
1337
1476
  if (!handleClick) return;
1338
1477
  if (playOnClick) toggleAction();
1339
1478
  onVideoClick == null ? void 0 : onVideoClick();
1340
1479
  }, [handleClick, onVideoClick, playOnClick, toggleAction]);
1341
- const fullScreenAction = useCallback(() => {
1480
+ const fullScreenAction = useCallback2(() => {
1342
1481
  if (typeof document === "undefined") return;
1343
1482
  const doc = document;
1344
1483
  const videoEl = videoRef.current;
@@ -1363,33 +1502,51 @@ var VideoPlayerBase = (props, ref) => {
1363
1502
  else if (videoEl.webkitEnterFullscreen) void videoEl.webkitEnterFullscreen();
1364
1503
  }
1365
1504
  }, [fullscreenAllowed]);
1366
- const videoDbClickHandler = useCallback(() => {
1505
+ const videoDbClickHandler = useCallback2(() => {
1367
1506
  if (!handleClick) return;
1368
1507
  fullScreenAction();
1369
1508
  onVideoDoubleClick == null ? void 0 : onVideoDoubleClick();
1370
1509
  }, [fullScreenAction, handleClick, onVideoDoubleClick]);
1371
- const showClickHandler = useCallback(() => {
1510
+ const showClickHandler = useCallback2(() => {
1372
1511
  setControlVisible((st) => !st);
1373
1512
  }, []);
1374
- const retryLoadHandler = useCallback(() => {
1513
+ const retryLoadHandler = useCallback2(() => {
1375
1514
  clearErrorState();
1376
1515
  setReloadToken((value) => value + 1);
1377
1516
  }, [clearErrorState]);
1378
- const videoErrorHandler = useCallback(() => {
1517
+ const videoErrorHandler = useCallback2(() => {
1379
1518
  const video = videoRef.current;
1380
1519
  const error = video == null ? void 0 : video.error;
1381
1520
  const src = (video == null ? void 0 : video.currentSrc) || videoSrc;
1382
1521
  if (hlsRef.current && src.startsWith("blob:")) return;
1522
+ if (switchSource(true, true)) return;
1383
1523
  pendingPlay.current = false;
1384
1524
  reportError(error, { type: "native", src }, getNativeVideoErrorMessage(error, messages));
1385
- }, [messages, reportError, videoSrc]);
1386
- const videoLoadedHandler = useCallback(() => {
1525
+ }, [messages, reportError, switchSource, videoSrc]);
1526
+ const videoLoadedHandler = useCallback2(() => {
1387
1527
  clearErrorState();
1528
+ const saved = resumeSnapshot.current;
1529
+ const element = videoRef.current;
1530
+ if (saved && element && element.readyState >= HTMLMediaElement.HAVE_METADATA) {
1531
+ let time = saved.time;
1532
+ if (element.seekable.length) {
1533
+ const start = element.seekable.start(0);
1534
+ const end = element.seekable.end(element.seekable.length - 1);
1535
+ time = Math.max(start, Math.min(time, Math.max(start, end - 0.05)));
1536
+ }
1537
+ element.currentTime = time;
1538
+ element.playbackRate = saved.rate;
1539
+ element.volume = saved.volume;
1540
+ element.muted = saved.muted;
1541
+ resumeSnapshot.current = null;
1542
+ if (saved.playing) void playAction();
1543
+ else pauseAction();
1544
+ }
1388
1545
  if (playRequestInFlight.current) return;
1389
1546
  if (!pendingPlay.current || active === false || !canAttemptPlayback(videoRef.current)) return;
1390
1547
  void playAction();
1391
- }, [active, clearErrorState, playAction]);
1392
- const mouseMoveHandler = useCallback(() => {
1548
+ }, [active, clearErrorState, playAction, pauseAction, resumeSnapshot]);
1549
+ const mouseMoveHandler = useCallback2(() => {
1393
1550
  if (controlTime.current) clearTimeout(controlTime.current);
1394
1551
  if (!showControlRef.current) {
1395
1552
  showControlRef.current = true;
@@ -1411,22 +1568,26 @@ var VideoPlayerBase = (props, ref) => {
1411
1568
  }),
1412
1569
  [fullScreenAction, pauseAction, playAction, seekSetTime]
1413
1570
  );
1414
- useEffect(() => {
1571
+ useEffect2(() => {
1415
1572
  const el = videoRef.current;
1416
1573
  if (!el) return;
1417
1574
  el.muted = muted;
1418
1575
  }, [muted]);
1419
- useEffect(() => {
1576
+ useEffect2(() => {
1420
1577
  if (playingProp === void 0) return;
1421
1578
  if (playingProp) void playAction();
1422
1579
  else pauseAction();
1423
1580
  }, [pauseAction, playAction, playingProp]);
1424
- useEffect(() => {
1581
+ useEffect2(() => {
1425
1582
  if (active !== false) return;
1426
1583
  releaseNativeMedia();
1427
1584
  }, [active, releaseNativeMedia]);
1428
- useEffect(() => {
1585
+ const sourceLoaders = useRef3({ loadVideo, loadVideoHls, loadVideoNative });
1586
+ sourceLoaders.current = { loadVideo, loadVideoHls, loadVideoNative };
1587
+ useEffect2(() => {
1588
+ var _a, _b, _c, _d;
1429
1589
  if (!videoRef.current) return;
1590
+ const { loadVideo: loadVideo2, loadVideoHls: loadVideoHls2, loadVideoNative: loadVideoNative2 } = sourceLoaders.current;
1430
1591
  const loadId = sourceLoadId.current + 1;
1431
1592
  sourceLoadId.current = loadId;
1432
1593
  clearErrorState();
@@ -1435,20 +1596,21 @@ var VideoPlayerBase = (props, ref) => {
1435
1596
  mediaRecoverCount.current = 0;
1436
1597
  liveStabilityMode.current = false;
1437
1598
  liveStallEvents.current = [];
1599
+ lastProgressTime.current = (_b = (_a = resumeSnapshot.current) == null ? void 0 : _a.time) != null ? _b : initialTime;
1438
1600
  resetMediaForSourceChange();
1439
1601
  const nextSourceLoadMode = getSourceLoadMode(sourceType, type, videoSrc);
1440
1602
  sourceLoadMode.current = nextSourceLoadMode;
1441
1603
  if (active === false) {
1442
1604
  if (nextSourceLoadMode === "native" && preload === "metadata") {
1443
- loadVideoNative(loadId, { metadataOnly: true });
1605
+ loadVideoNative2(loadId, { metadataOnly: true });
1444
1606
  }
1445
1607
  return;
1446
1608
  }
1447
1609
  if (nextSourceLoadMode === "hls") {
1448
- if (sourceType === "hls") void loadVideo(initialTime, loadId);
1449
- else void loadVideoHls(loadId);
1610
+ if (sourceType === "hls") void loadVideo2((_d = (_c = resumeSnapshot.current) == null ? void 0 : _c.time) != null ? _d : initialTime, loadId);
1611
+ else void loadVideoHls2(loadId);
1450
1612
  } else {
1451
- loadVideoNative(loadId);
1613
+ loadVideoNative2(loadId);
1452
1614
  }
1453
1615
  return () => {
1454
1616
  sourceLoadId.current += 1;
@@ -1457,9 +1619,6 @@ var VideoPlayerBase = (props, ref) => {
1457
1619
  }, [
1458
1620
  clearErrorState,
1459
1621
  active,
1460
- loadVideo,
1461
- loadVideoHls,
1462
- loadVideoNative,
1463
1622
  initialTime,
1464
1623
  resetMediaForSourceChange,
1465
1624
  reloadKey,
@@ -1468,12 +1627,13 @@ var VideoPlayerBase = (props, ref) => {
1468
1627
  type,
1469
1628
  videoSrc
1470
1629
  ]);
1471
- useEffect(() => {
1630
+ useEffect2(() => {
1472
1631
  if (!autoPlay) return;
1632
+ if (resumeSnapshot.current) return;
1473
1633
  if (!canAttemptPlayback(videoRef.current)) return;
1474
1634
  void playAction();
1475
1635
  }, [autoPlay, playAction]);
1476
- useEffect(() => {
1636
+ useEffect2(() => {
1477
1637
  if (typeof document === "undefined") return;
1478
1638
  const handleKeyDown = (event) => {
1479
1639
  if (event.code !== "KeyF") return;
@@ -1488,7 +1648,7 @@ var VideoPlayerBase = (props, ref) => {
1488
1648
  document.removeEventListener("keydown", handleKeyDown);
1489
1649
  };
1490
1650
  }, [active, fullScreenAction]);
1491
- useEffect(() => {
1651
+ useEffect2(() => {
1492
1652
  if (typeof document === "undefined") return;
1493
1653
  const doc = document;
1494
1654
  const handleFsChange = () => {
@@ -1506,15 +1666,15 @@ var VideoPlayerBase = (props, ref) => {
1506
1666
  document.removeEventListener("MSFullscreenChange", handleFsChange);
1507
1667
  };
1508
1668
  }, []);
1509
- useEffect(() => {
1669
+ useEffect2(() => {
1510
1670
  return () => {
1511
1671
  setTimeD.current.cancel();
1512
1672
  setCurrentTimeUi.current.cancel();
1513
1673
  destroy();
1514
1674
  };
1515
1675
  }, [destroy]);
1516
- const lastActiveState = useRef2(active);
1517
- useEffect(() => {
1676
+ const lastActiveState = useRef3(active);
1677
+ useEffect2(() => {
1518
1678
  const prevActive = lastActiveState.current;
1519
1679
  lastActiveState.current = active;
1520
1680
  if (active === true && prevActive !== true) {
@@ -1559,8 +1719,13 @@ var VideoPlayerBase = (props, ref) => {
1559
1719
  muted,
1560
1720
  preload,
1561
1721
  loop,
1562
- onPlay: () => setNextPlaying(true),
1563
- onPause: () => setNextPlaying(false),
1722
+ onPlay: () => {
1723
+ playbackIntent.current = true;
1724
+ setNextPlaying(true);
1725
+ },
1726
+ onPause: () => {
1727
+ if (!resumeSnapshot.current) setNextPlaying(false);
1728
+ },
1564
1729
  onEnded: () => setNextPlaying(false),
1565
1730
  onLoadedMetadata: videoLoadedHandler,
1566
1731
  onLoadedData: videoLoadedHandler,
@@ -1571,6 +1736,7 @@ var VideoPlayerBase = (props, ref) => {
1571
1736
  poster
1572
1737
  }
1573
1738
  ),
1739
+ audioFallback.videoOnly && /* @__PURE__ */ jsx2("div", { role: "status", className: "video-audio-status", children: "\u0417\u0432\u0443\u043A \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D" }),
1574
1740
  errorState && /* @__PURE__ */ jsx2("div", { className: "video-error-overlay", children: /* @__PURE__ */ jsxs2("div", { className: "video-error-content", children: [
1575
1741
  /* @__PURE__ */ jsx2("div", { className: "video-error-title", children: errorState.message }),
1576
1742
  errorState.details.length > 0 && /* @__PURE__ */ jsx2("div", { className: "video-error-details", children: errorState.details.map((detail) => /* @__PURE__ */ jsx2("div", { className: "video-error-detail", children: detail }, detail)) }),