softbuilders-react-video-player 1.1.54 → 1.1.56

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