softbuilders-react-video-player 1.1.54 → 1.1.56

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useRef, useState } from "react";
1
+ import React, { forwardRef, useEffect, useRef, useState } from "react";
2
2
  import ReactDOM from "react-dom/client";
3
3
  import videojs from "video.js";
4
4
  import Player from "video.js/dist/types/player";
@@ -111,70 +111,161 @@ export type Props<T = any> = {
111
111
  onPlay?: (time: number) => void;
112
112
  onPause?: (time: number) => void;
113
113
  disableNote?: boolean;
114
+ childRef?: React.Ref<HTMLDivElement>;
114
115
  };
115
- const VideoPlayerComponent = <T,>({
116
- id,
117
- options,
118
- notes,
119
- chapters,
120
- startTime = 0,
121
- handleSaveNoteAction,
122
- poster,
123
- onPlay,
124
- onPause,
125
- disableNote,
126
- }: Props<T>) => {
127
- const videoRef = useRef<any>(undefined);
128
- const playerRef = useRef<Player | undefined>(undefined);
129
- const [isReady, setIsReady] = useState(false);
130
- const [isPaused, setIsPaused] = useState(!options.autoplay);
131
- const [duration, setDuration] = useState(1);
132
- const [opacity, setOpacity] = useState("0");
133
- const [isControlBarPresent, setIsControlBarPresent] = useState(true);
134
- const [bgColor, setBgColor] = useState("transparent");
135
- const [isQualityMenuOpen, setIsQualityMenuOpen] = useState(false);
136
- const [isSubtitleMenuOpen, setIsSubtitleMenuOpen] = useState(false);
137
- const [isHovered, setIsHovered] = useState(false);
138
- const onReady = (player: Player) => {
139
- if (playerRef) {
140
- playerRef.current = player;
141
- setIsReady(true);
142
- player?.currentTime(startTime);
143
- player.on("waiting", () => {});
144
- player.on("dispose", () => {
145
- videojs.log("player will dispose");
146
- setIsReady(false);
147
- });
148
- player.on("loadedmetadata", () => {
149
- const d = player.duration() || 0;
150
- setDuration(d);
151
- });
152
- }
153
- };
154
- useEffect(() => {
155
- if (!playerRef.current) {
156
- const videoElement = document.createElement("video-js");
116
+ const VideoPlayerComponent = forwardRef<HTMLDivElement, Props<any>>(
117
+ ({
118
+ id,
119
+ options,
120
+ notes,
121
+ chapters,
122
+ startTime = 0,
123
+ handleSaveNoteAction,
124
+ poster,
125
+ onPlay,
126
+ onPause,
127
+ disableNote,
128
+ childRef,
129
+ }: Props) => {
130
+ const videoRef = useRef<any>(undefined);
131
+ const playerRef = useRef<Player | undefined>(undefined);
132
+ const [isReady, setIsReady] = useState(false);
133
+ const [isPaused, setIsPaused] = useState(!options.autoplay);
134
+ const [duration, setDuration] = useState(1);
135
+ const [opacity, setOpacity] = useState("0");
136
+ const [isControlBarPresent, setIsControlBarPresent] = useState(true);
137
+ const [bgColor, setBgColor] = useState("transparent");
138
+ const [isQualityMenuOpen, setIsQualityMenuOpen] = useState(false);
139
+ const [isSubtitleMenuOpen, setIsSubtitleMenuOpen] = useState(false);
140
+ const [isHovered, setIsHovered] = useState(false);
141
+ const onReady = (player: Player) => {
142
+ if (playerRef) {
143
+ playerRef.current = player;
144
+ setIsReady(true);
145
+ player?.currentTime(startTime);
146
+ player.on("waiting", () => {});
147
+ player.on("dispose", () => {
148
+ videojs.log("player will dispose");
149
+ setIsReady(false);
150
+ });
151
+ player.on("loadedmetadata", () => {
152
+ const d = player.duration() || 0;
153
+ setDuration(d);
154
+ });
155
+ }
156
+ };
157
+ useEffect(() => {
158
+ if (!playerRef.current) {
159
+ const videoElement = document.createElement("video-js");
160
+
161
+ videoElement.setAttribute("playsinline", "true");
157
162
 
158
- videoElement.setAttribute("playsinline", "true");
163
+ videoElement.classList.add("vjs-big-play-centered");
164
+ // Set the poster attribute here
165
+ if (poster) {
166
+ videoElement.setAttribute("poster", poster);
167
+ }
168
+ videoRef.current.appendChild(videoElement);
169
+ videoElement.style.width = "100%";
170
+ videoElement.style.height = "100%";
171
+ videoElement.style.objectFit = "cover";
172
+ playerRef.current = videojs(videoElement, options, () => {
173
+ onReady(playerRef.current as Player);
174
+ });
175
+ }
176
+ return () => {
177
+ if (playerRef.current) {
178
+ playerRef.current.dispose();
179
+ playerRef.current = undefined;
180
+ setTimeout(() => {
181
+ if (bigPlayButtonRoot[id]) {
182
+ bigPlayButtonRoot[id].unmount();
183
+ bigPlayButtonRoot[id] = undefined;
184
+ }
185
+ if (controlBarRoot[id]) {
186
+ controlBarRoot[id].unmount();
187
+ controlBarRoot[id] = undefined;
188
+ }
189
+ }, 0);
190
+ }
191
+ };
192
+ }, [options, poster]); // Added poster to dependency array
193
+ useEffect(() => {
194
+ if (playerRef.current && isReady) {
195
+ const currentTime = playerRef.current.currentTime() || 0;
196
+ if (isPaused) {
197
+ if (onPause) onPause(currentTime);
198
+ } else {
199
+ if (onPlay) onPlay(currentTime);
200
+ }
201
+ }
202
+ }, [isPaused, isReady]);
159
203
 
160
- videoElement.classList.add("vjs-big-play-centered");
161
- // Set the poster attribute here
162
- if (poster) {
163
- videoElement.setAttribute("poster", poster);
204
+ useEffect(() => {
205
+ if (isReady) {
206
+ const controlBarTimeout = setTimeout(() => {
207
+ renderControlBar(
208
+ id,
209
+ playerRef.current,
210
+ isPaused,
211
+ setIsPaused,
212
+ duration,
213
+ notes,
214
+ chapters,
215
+ 5,
216
+ handleSaveNoteAction,
217
+ opacity,
218
+ (e: any) => {
219
+ handlePlayerClick(e, true);
220
+ },
221
+ bgColor,
222
+ setIsQualityMenuOpen,
223
+ setIsSubtitleMenuOpen,
224
+ disableNote
225
+ );
226
+ }, 500);
227
+ return () => clearTimeout(controlBarTimeout);
164
228
  }
165
- videoRef.current.appendChild(videoElement);
166
- videoElement.style.width = "100%";
167
- videoElement.style.height = "100%";
168
- videoElement.style.objectFit = "cover";
169
- playerRef.current = videojs(videoElement, options, () => {
170
- onReady(playerRef.current as Player);
171
- });
172
- }
173
- return () => {
229
+ }, [
230
+ id,
231
+ playerRef,
232
+ isPaused,
233
+ setIsPaused,
234
+ notes,
235
+ chapters,
236
+ isReady,
237
+ handleSaveNoteAction,
238
+ duration,
239
+ opacity,
240
+ ]);
241
+ useEffect(() => {
242
+ if (isReady) {
243
+ const playButtonTimeout = setTimeout(() => {
244
+ renderBigPlayButton(
245
+ id,
246
+ playerRef.current,
247
+ isPaused,
248
+ setIsPaused,
249
+ opacity
250
+ );
251
+ }, 500);
252
+ return () => clearTimeout(playButtonTimeout);
253
+ }
254
+ }, [id, isPaused, isReady, opacity]);
255
+ useEffect(() => {
174
256
  if (playerRef.current) {
175
- playerRef.current.dispose();
176
- playerRef.current = undefined;
177
- setTimeout(() => {
257
+ const intervalId = setInterval(() => {
258
+ if (playerRef.current) setIsPaused(playerRef.current.paused());
259
+ }, 500);
260
+ return () => clearInterval(intervalId);
261
+ }
262
+ }, []);
263
+ useEffect(() => {
264
+ return () => {
265
+ if (playerRef.current) {
266
+ playerRef.current.dispose();
267
+ playerRef.current = undefined;
268
+ // Cleanup play button and control bar renders
178
269
  if (bigPlayButtonRoot[id]) {
179
270
  bigPlayButtonRoot[id].unmount();
180
271
  bigPlayButtonRoot[id] = undefined;
@@ -183,234 +274,149 @@ const VideoPlayerComponent = <T,>({
183
274
  controlBarRoot[id].unmount();
184
275
  controlBarRoot[id] = undefined;
185
276
  }
186
- }, 0);
187
- }
188
- };
189
- }, [options, poster]); // Added poster to dependency array
190
- useEffect(() => {
191
- if (playerRef.current && isReady) {
192
- const currentTime = playerRef.current.currentTime() || 0;
193
- if (isPaused) {
194
- if (onPause) onPause(currentTime);
195
- } else {
196
- if (onPlay) onPlay(currentTime);
197
- }
198
- }
199
- }, [isPaused, isReady]);
200
-
201
- useEffect(() => {
202
- if (isReady) {
203
- const controlBarTimeout = setTimeout(() => {
204
- renderControlBar(
205
- id,
206
- playerRef.current,
207
- isPaused,
208
- setIsPaused,
209
- duration,
210
- notes,
211
- chapters,
212
- 5,
213
- handleSaveNoteAction,
214
- opacity,
215
- (e: any) => {
216
- handlePlayerClick(e, true);
217
- },
218
- bgColor,
219
- setIsQualityMenuOpen,
220
- setIsSubtitleMenuOpen,
221
- disableNote
222
- );
223
- }, 500);
224
- return () => clearTimeout(controlBarTimeout);
225
- }
226
- }, [
227
- id,
228
- playerRef,
229
- isPaused,
230
- setIsPaused,
231
- notes,
232
- chapters,
233
- isReady,
234
- handleSaveNoteAction,
235
- duration,
236
- opacity,
237
- ]);
238
- useEffect(() => {
239
- if (isReady) {
240
- const playButtonTimeout = setTimeout(() => {
241
- renderBigPlayButton(
242
- id,
243
- playerRef.current,
244
- isPaused,
245
- setIsPaused,
246
- opacity
247
- );
248
- }, 500);
249
- return () => clearTimeout(playButtonTimeout);
250
- }
251
- }, [id, isPaused, isReady, opacity]);
252
- useEffect(() => {
253
- if (playerRef.current) {
254
- const intervalId = setInterval(() => {
255
- if (playerRef.current) setIsPaused(playerRef.current.paused());
256
- }, 500);
257
- return () => clearInterval(intervalId);
258
- }
259
- }, []);
260
- useEffect(() => {
261
- return () => {
262
- if (playerRef.current) {
263
- playerRef.current.dispose();
264
- playerRef.current = undefined;
265
- // Cleanup play button and control bar renders
266
- if (bigPlayButtonRoot[id]) {
267
- bigPlayButtonRoot[id].unmount();
268
- bigPlayButtonRoot[id] = undefined;
269
277
  }
270
- if (controlBarRoot[id]) {
271
- controlBarRoot[id].unmount();
272
- controlBarRoot[id] = undefined;
278
+ };
279
+ }, []);
280
+ const timeoutRef = useRef<any>(null);
281
+ useEffect(() => {
282
+ if (isQualityMenuOpen || isSubtitleMenuOpen) {
283
+ if (timeoutRef.current) {
284
+ clearTimeout(timeoutRef.current);
285
+ }
286
+ setOpacity("100");
287
+ } else {
288
+ if (timeoutRef.current) {
289
+ clearTimeout(timeoutRef.current);
273
290
  }
291
+ setOpacity("0");
292
+
293
+ timeoutRef.current = setTimeout(() => {
294
+ setIsControlBarPresent(false);
295
+ }, 3000);
274
296
  }
275
- };
276
- }, []);
277
- const timeoutRef = useRef<any>(null);
278
- useEffect(() => {
279
- if (isQualityMenuOpen || isSubtitleMenuOpen) {
297
+ }, [isQualityMenuOpen, isSubtitleMenuOpen]);
298
+
299
+ const handlePlayerClick = async (e: any, isTimerOnly = false) => {
300
+ e.preventDefault();
280
301
  if (timeoutRef.current) {
281
302
  clearTimeout(timeoutRef.current);
282
303
  }
283
304
  setOpacity("100");
284
- } else {
285
- if (timeoutRef.current) {
286
- clearTimeout(timeoutRef.current);
287
- }
288
- setOpacity("0");
289
-
305
+ setIsControlBarPresent(true);
306
+ // setBgColor("rgba(200, 200, 200, 0.5)");
307
+ // not using now
290
308
  timeoutRef.current = setTimeout(() => {
309
+ setOpacity("0");
310
+ setBgColor("transparent");
291
311
  setIsControlBarPresent(false);
292
312
  }, 3000);
293
- }
294
- }, [isQualityMenuOpen, isSubtitleMenuOpen]);
295
-
296
- const handlePlayerClick = async (e: any, isTimerOnly = false) => {
297
- e.preventDefault();
298
- if (timeoutRef.current) {
299
- clearTimeout(timeoutRef.current);
300
- }
301
- setOpacity("100");
302
- setIsControlBarPresent(true);
303
- // setBgColor("rgba(200, 200, 200, 0.5)");
304
- // not using now
305
- timeoutRef.current = setTimeout(() => {
306
- setOpacity("0");
307
- setBgColor("transparent");
308
- setIsControlBarPresent(false);
309
- }, 3000);
310
- if (isTimerOnly) {
311
- return;
312
- }
313
- if (!isControlBarPresent) {
314
- return;
315
- }
316
- if (playerRef.current) {
317
- if (playerRef.current.paused()) {
318
- try {
319
- await playerRef.current.play();
320
- setIsPaused(false);
321
- } catch (error) {
322
- console.error("Failed to play video:", error);
313
+ if (isTimerOnly) {
314
+ return;
315
+ }
316
+ if (!isControlBarPresent) {
317
+ return;
318
+ }
319
+ if (playerRef.current) {
320
+ if (playerRef.current.paused()) {
321
+ try {
322
+ await playerRef.current.play();
323
+ setIsPaused(false);
324
+ } catch (error) {
325
+ console.error("Failed to play video:", error);
326
+ }
327
+ } else {
328
+ playerRef.current.pause();
329
+ setIsPaused(true);
330
+ if (onPause) onPause(playerRef.current.currentTime() || 0);
323
331
  }
324
- } else {
325
- playerRef.current.pause();
326
- setIsPaused(true);
327
- if (onPause) onPause(playerRef.current.currentTime() || 0);
328
332
  }
329
- }
330
- };
331
- const videoRefs = useRef<HTMLDivElement>(null); // Create a reference for the element
333
+ };
334
+ const videoRefs = useRef<HTMLDivElement>(null); // Create a reference for the element
332
335
 
333
- useEffect(() => {
334
- const observer = new IntersectionObserver(
335
- (entries) => {
336
- entries.forEach((entry) => {
337
- if (entry.isIntersecting === false) {
338
- if (playerRef?.current?.paused() === false) {
339
- try {
340
- playerRef?.current?.pause();
341
- setIsPaused(true);
342
- } catch (error) {
343
- console.error("Failed to play video:", error);
336
+ useEffect(() => {
337
+ const observer = new IntersectionObserver(
338
+ (entries) => {
339
+ entries.forEach((entry) => {
340
+ if (entry.isIntersecting === false) {
341
+ if (playerRef?.current?.paused() === false) {
342
+ try {
343
+ playerRef?.current?.pause();
344
+ setIsPaused(true);
345
+ } catch (error) {
346
+ console.error("Failed to play video:", error);
347
+ }
344
348
  }
345
349
  }
346
- }
347
- });
348
- },
349
- {
350
- threshold: 0.1, // The amount of the component that must be visible (0.1 means 10% visible)
351
- }
352
- );
353
-
354
- if (videoRefs.current) {
355
- observer.observe(videoRefs.current); // Start observing the component
356
- }
350
+ });
351
+ },
352
+ {
353
+ threshold: 0.1, // The amount of the component that must be visible (0.1 means 10% visible)
354
+ }
355
+ );
357
356
 
358
- return () => {
359
357
  if (videoRefs.current) {
360
- observer.unobserve(videoRef.current); // Clean up the observer when the component unmounts
358
+ observer.observe(videoRefs.current); // Start observing the component
361
359
  }
362
- };
363
- }, []);
364
360
 
365
- const [timeSeeker, setTimeSeeker] = useState<string>("0");
366
- useEffect(() => {
367
- const updateTimeSeeker = () => {
368
- if (playerRef?.current) {
369
- const currentTime = playerRef.current?.currentTime?.();
370
- const duration = playerRef.current?.duration?.();
361
+ return () => {
362
+ if (videoRefs.current) {
363
+ observer.unobserve(videoRef.current); // Clean up the observer when the component unmounts
364
+ }
365
+ };
366
+ }, []);
371
367
 
372
- if (duration && currentTime !== undefined) {
373
- const value = `${(currentTime / duration) * 100}%`;
374
- setTimeSeeker(value);
375
- } else {
376
- setTimeSeeker("0");
368
+ const [timeSeeker, setTimeSeeker] = useState<string>("0");
369
+ useEffect(() => {
370
+ const updateTimeSeeker = () => {
371
+ if (playerRef?.current) {
372
+ const currentTime = playerRef.current?.currentTime?.();
373
+ const duration = playerRef.current?.duration?.();
374
+
375
+ if (duration && currentTime !== undefined) {
376
+ const value = `${(currentTime / duration) * 100}%`;
377
+ setTimeSeeker(value);
378
+ } else {
379
+ setTimeSeeker("0");
380
+ }
377
381
  }
378
- }
379
- };
380
- updateTimeSeeker();
381
- }, [playerRef?.current?.currentTime()]);
382
+ };
383
+ updateTimeSeeker();
384
+ }, [playerRef?.current?.currentTime()]);
382
385
 
383
- return (
384
- <div
385
- ref={videoRefs}
386
- id={`video-container-${id}`}
387
- onMouseMove={() => {
388
- handlePlayerClick(event, true);
389
- }}
390
- className="sb-relative sb-overflow-hidden sb-w-full sb-h-full sb-bottom-2 "
391
- onMouseEnter={() => setIsHovered(true)}
392
- onMouseLeave={() => setIsHovered(false)}
393
- >
386
+ return (
394
387
  <div
395
- className={`sb-h-[3px] sb-transition-opacity sb-duration-500 sb-delay-400 sb-z-10 ease-in-out sb-border-spacing-x-2 sb-absolute sb-bg-[red] sb-bottom-0 ${
396
- opacity == "100" ? "sb-opacity-0" : "sb-opacity-100"
397
- }`}
398
- style={{
399
- width: timeSeeker,
388
+ ref={videoRefs}
389
+ id={`video-container-${id}`}
390
+ onMouseMove={() => {
391
+ handlePlayerClick(event, true);
400
392
  }}
401
- ></div>
402
- <div
403
- className="hover:sb-cursor-pointer sb-w-full sb-h-full"
404
- // Attach click event
405
- data-vjs-player
393
+ className="sb-relative sb-rounded-md sb-overflow-hidden sb-w-full sb-h-full sb-bottom-2 "
394
+ onMouseEnter={() => setIsHovered(true)}
395
+ onMouseLeave={() => setIsHovered(false)}
406
396
  >
407
397
  <div
398
+ ref={childRef}
408
399
  onClick={handlePlayerClick}
409
- ref={videoRef}
410
- className="sb-h-full sb-w-full sb-relative"
400
+ className={`sb-h-[3px] sb-transition-opacity sb-duration-500 sb-delay-400 sb-z-10 ease-in-out sb-border-spacing-x-2 sb-absolute sb-bg-[red] sb-bottom-0 ${
401
+ opacity == "100" ? "sb-opacity-0" : "sb-opacity-100"
402
+ }`}
403
+ style={{
404
+ width: timeSeeker,
405
+ }}
411
406
  ></div>
407
+ <div
408
+ className="hover:sb-cursor-pointer sb-w-full sb-h-full"
409
+ // Attach click event
410
+ data-vjs-player
411
+ >
412
+ <div
413
+ onClick={handlePlayerClick}
414
+ ref={videoRef}
415
+ className="sb-h-full sb-w-full sb-relative"
416
+ ></div>
417
+ </div>
412
418
  </div>
413
- </div>
414
- );
415
- };
419
+ );
420
+ }
421
+ );
416
422
  export default VideoPlayerComponent;
package/dist/index.d.mts CHANGED
@@ -1,5 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import React from 'react';
1
+ import React$1 from 'react';
3
2
 
4
3
  type SoftBuildersVideoPlayerSource = {
5
4
  src: string;
@@ -25,6 +24,7 @@ type SoftBuildersVideoPlayerOptions = {
25
24
  tracks?: SoftBuildersVideoPlayerTrack[];
26
25
  width?: number;
27
26
  height?: number;
27
+ childRef?: React.Ref<HTMLDivElement>;
28
28
  };
29
29
  interface SoftBuildersVideoPlayerNote {
30
30
  time: number;
@@ -46,7 +46,8 @@ type Props<T = any> = {
46
46
  onPause?: (time: number) => void;
47
47
  isFocused?: boolean;
48
48
  disableNote?: boolean;
49
+ childRef?: React$1.Ref<HTMLDivElement>;
49
50
  };
50
- declare const SoftBuildersVideoPlayer: React.MemoExoticComponent<(<T>({ options, notes, chapters, startTime, handleSaveNoteAction, onPlay, onPause, isFocused, disableNote, }: Props<T>) => react_jsx_runtime.JSX.Element)>;
51
+ declare const SoftBuildersVideoPlayer: React$1.MemoExoticComponent<React$1.ForwardRefExoticComponent<Props<any> & React$1.RefAttributes<HTMLDivElement>>>;
51
52
 
52
53
  export { type SoftBuildersVideoPlayerChapter, type SoftBuildersVideoPlayerNote, type SoftBuildersVideoPlayerOptions, type SoftBuildersVideoPlayerSource, type SoftBuildersVideoPlayerTrack, SoftBuildersVideoPlayer as default };