softbuilders-react-video-player 1.1.55 → 1.1.57

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