softbuilders-react-video-player 1.1.55 → 1.1.57

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.
@@ -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 };