@rxdrag/website-lib-core 0.0.108 → 0.0.110

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rxdrag/website-lib-core",
3
- "version": "0.0.108",
3
+ "version": "0.0.110",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.ts"
@@ -31,6 +31,7 @@ export const ReactVideoPlayer = forwardRef<
31
31
  endTitle?: string;
32
32
  media: Media;
33
33
  posterUrl?: string;
34
+ eagerLoad?: boolean;
34
35
  classNames?: VideoPlayerClassNames;
35
36
  callToAction?: string;
36
37
  onToggleSelect?: (id: ID) => void;
@@ -42,6 +43,7 @@ export const ReactVideoPlayer = forwardRef<
42
43
  media,
43
44
  onToggleSelect,
44
45
  posterUrl,
46
+ eagerLoad,
45
47
  classNames,
46
48
  callToAction,
47
49
  designMode,
@@ -51,6 +53,57 @@ export const ReactVideoPlayer = forwardRef<
51
53
  const hlsRef = useRef<Hls | null>(null);
52
54
  const [isPlaying, setIsPlaying] = useState(false);
53
55
  const [isEnded, setIsEnded] = useState(false);
56
+ const [isSourceReady, setIsSourceReady] = useState(false);
57
+ const sourceReadyRef = useRef(false);
58
+ const [isLoading, setIsLoading] = useState(false);
59
+ const isEndedRef = useRef(false);
60
+
61
+ const ensureSourceReady = useCallback(() => {
62
+ const video = videoRef.current;
63
+ const url = media.file?.original;
64
+ if (!video || !url) return false;
65
+ if (sourceReadyRef.current) return true;
66
+
67
+ if (media.storageType === "cloudflare_stream") {
68
+ if (!Hls.isSupported()) {
69
+ if (video.canPlayType("application/vnd.apple.mpegurl")) {
70
+ video.src = url;
71
+ sourceReadyRef.current = true;
72
+ setIsSourceReady(true);
73
+ return true;
74
+ }
75
+ return false;
76
+ }
77
+
78
+ const hls = new Hls({ enableWorker: true });
79
+ hls.on(Hls.Events.ERROR, (_event, data) => {
80
+ if (data.fatal) {
81
+ switch (data.type) {
82
+ case Hls.ErrorTypes.NETWORK_ERROR:
83
+ hls.startLoad();
84
+ break;
85
+ case Hls.ErrorTypes.MEDIA_ERROR:
86
+ hls.recoverMediaError();
87
+ break;
88
+ default:
89
+ hls.destroy();
90
+ break;
91
+ }
92
+ }
93
+ });
94
+ hls.loadSource(url);
95
+ hls.attachMedia(video);
96
+ hlsRef.current = hls;
97
+ sourceReadyRef.current = true;
98
+ setIsSourceReady(true);
99
+ return true;
100
+ }
101
+
102
+ video.src = url;
103
+ sourceReadyRef.current = true;
104
+ setIsSourceReady(true);
105
+ return true;
106
+ }, [media.file?.original, media.storageType]);
54
107
 
55
108
  const handleContainerClick = useCallback(
56
109
  (e: React.MouseEvent) => {
@@ -73,80 +126,83 @@ export const ReactVideoPlayer = forwardRef<
73
126
  e.stopPropagation();
74
127
  const v = videoRef.current;
75
128
  if (!v) return;
76
- if (isPlaying) v.pause();
77
- else v.play();
129
+ if (isPlaying) {
130
+ v.pause();
131
+ return;
132
+ }
133
+
134
+ const ok = ensureSourceReady();
135
+ if (!ok) return;
136
+
137
+ setIsLoading(true);
138
+
139
+ v.play().catch((error) => {
140
+ console.log("ReactVideoPlayer play() failed:", error);
141
+ setIsLoading(false);
142
+ });
78
143
  },
79
- [isPlaying, designMode]
144
+ [isPlaying, designMode, ensureSourceReady]
80
145
  );
81
146
 
82
147
  useEffect(() => {
83
148
  const video = videoRef.current;
84
- const url = media.file?.original;
85
- if (!video || !url) return;
149
+ if (!video) return;
150
+
151
+ video.pause();
152
+ if (hlsRef.current) {
153
+ hlsRef.current.destroy();
154
+ hlsRef.current = null;
155
+ }
156
+ video.removeAttribute("src");
157
+ video.load();
158
+ setIsSourceReady(false);
159
+ sourceReadyRef.current = false;
86
160
 
87
161
  const onPlay = () => {
88
162
  setIsPlaying(true);
89
163
  setIsEnded(false);
164
+ isEndedRef.current = false;
165
+ setIsLoading(false);
90
166
  };
91
167
  const onPause = () => setIsPlaying(false);
92
168
  const onEnded = () => {
93
169
  setIsPlaying(false);
94
170
  setIsEnded(true);
171
+ isEndedRef.current = true;
172
+ setIsLoading(false);
173
+ };
174
+ const onWaiting = () => {
175
+ if (!isEndedRef.current) setIsLoading(true);
176
+ };
177
+ const onCanPlay = () => {
178
+ setIsLoading(false);
95
179
  };
96
180
 
97
181
  video.addEventListener("play", onPlay);
98
182
  video.addEventListener("pause", onPause);
99
183
  video.addEventListener("ended", onEnded);
184
+ video.addEventListener("waiting", onWaiting);
185
+ video.addEventListener("canplay", onCanPlay);
186
+ video.addEventListener("playing", onCanPlay);
100
187
 
101
- if (media.storageType === "cloudflare_stream") {
102
- if (!Hls.isSupported()) {
103
- if (video.canPlayType("application/vnd.apple.mpegurl")) {
104
- video.src = url;
105
- }
106
- return () => {
107
- video.removeEventListener("play", onPlay);
108
- video.removeEventListener("pause", onPause);
109
- video.removeEventListener("ended", onEnded);
110
- };
111
- }
112
-
113
- const hls = new Hls({ enableWorker: true });
114
- hls.on(Hls.Events.ERROR, (_event, data) => {
115
- if (data.fatal) {
116
- switch (data.type) {
117
- case Hls.ErrorTypes.NETWORK_ERROR:
118
- hls.startLoad();
119
- break;
120
- case Hls.ErrorTypes.MEDIA_ERROR:
121
- hls.recoverMediaError();
122
- break;
123
- default:
124
- hls.destroy();
125
- break;
126
- }
127
- }
128
- });
188
+ if (eagerLoad) {
189
+ ensureSourceReady();
190
+ }
129
191
 
130
- hls.loadSource(url);
131
- hls.attachMedia(video);
132
- hlsRef.current = hls;
192
+ return () => {
193
+ video.removeEventListener("play", onPlay);
194
+ video.removeEventListener("pause", onPause);
195
+ video.removeEventListener("ended", onEnded);
196
+ video.removeEventListener("waiting", onWaiting);
197
+ video.removeEventListener("canplay", onCanPlay);
198
+ video.removeEventListener("playing", onCanPlay);
133
199
 
134
- return () => {
135
- video.removeEventListener("play", onPlay);
136
- video.removeEventListener("pause", onPause);
137
- video.removeEventListener("ended", onEnded);
138
- hls.destroy();
200
+ if (hlsRef.current) {
201
+ hlsRef.current.destroy();
139
202
  hlsRef.current = null;
140
- };
141
- } else {
142
- video.src = url;
143
- return () => {
144
- video.removeEventListener("play", onPlay);
145
- video.removeEventListener("pause", onPause);
146
- video.removeEventListener("ended", onEnded);
147
- };
148
- }
149
- }, [media.file?.original, media.storageType]);
203
+ }
204
+ };
205
+ }, [eagerLoad, ensureSourceReady, media.file?.original, media.storageType]);
150
206
 
151
207
  return (
152
208
  <div
@@ -161,7 +217,7 @@ export const ReactVideoPlayer = forwardRef<
161
217
  <video
162
218
  ref={videoRef}
163
219
  onClick={(e) => !designMode && e.stopPropagation()}
164
- preload="metadata"
220
+ preload={eagerLoad ? "metadata" : "none"}
165
221
  poster={posterUrl ?? media.file?.thumbnail}
166
222
  className={clsx(
167
223
  "w-full h-full rounded-lg object-cover",
@@ -169,13 +225,13 @@ export const ReactVideoPlayer = forwardRef<
169
225
  )}
170
226
  controls={!designMode}
171
227
  >
172
- {!media.storageType && media.file?.original ? (
228
+ {!media.storageType && media.file?.original && (eagerLoad || isSourceReady) ? (
173
229
  <source src={media.file.original} />
174
230
  ) : null}
175
231
  Your browser does not support video playback.
176
232
  </video>
177
233
 
178
- {!isPlaying && !isEnded && (
234
+ {!isPlaying && !isEnded && !isLoading && (
179
235
  <button
180
236
  type="button"
181
237
  onClick={handlePlayClick}
@@ -216,6 +272,21 @@ export const ReactVideoPlayer = forwardRef<
216
272
  </button>
217
273
  )}
218
274
 
275
+ {!isPlaying && !isEnded && isLoading && (
276
+ <div
277
+ className={clsx(
278
+ "absolute inset-0 flex items-center justify-center w-full h-full rounded-lg z-10 bg-black/20",
279
+ classNames?.playButton
280
+ )}
281
+ onClick={(e) => e.stopPropagation()}
282
+ >
283
+ <div className="flex items-center gap-3 rounded-full bg-black/50 px-4 py-2 text-white">
284
+ <div className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
285
+ <span className="text-sm">Loading...</span>
286
+ </div>
287
+ </div>
288
+ )}
289
+
219
290
  <div
220
291
  className={clsx(
221
292
  "absolute inset-0 bg-black/50 flex-col gap-6 items-center justify-center text-center text-white hidden z-10 rounded-2xl",
@@ -248,9 +319,15 @@ export const ReactVideoPlayer = forwardRef<
248
319
  e.stopPropagation();
249
320
  const v = videoRef.current;
250
321
  if (!v) return;
322
+ const ok = ensureSourceReady();
323
+ if (!ok) return;
324
+ setIsLoading(true);
251
325
  v.currentTime = 0;
252
326
  setIsEnded(false);
253
- v.play();
327
+ v.play().catch((error) => {
328
+ console.log("ReactVideoPlayer play() failed:", error);
329
+ setIsLoading(false);
330
+ });
254
331
  }}
255
332
  >
256
333
  Replay