diff --git a/package-lock.json b/package-lock.json
index f6143ec..56f6ef5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,6 +23,7 @@
"react-idle-timer": "^5.7.2",
"react-intersection-observer": "^9.16.0",
"short-unique-id": "^5.2.0",
+ "srt-webvtt": "^2.0.0",
"ts-key-enum": "^3.0.13",
"video.js": "^8.23.3",
"zustand": "^4.3.2"
@@ -3504,6 +3505,12 @@
"node": ">=0.10.0"
}
},
+ "node_modules/srt-webvtt": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/srt-webvtt/-/srt-webvtt-2.0.0.tgz",
+ "integrity": "sha512-G2Z7/Jf2NRKrmLYNSIhSYZZYE6OFlKXFp9Au2/zJBKgrioUzmrAys1x7GT01dwl6d2sEnqr5uahEIOd0JW/Rbw==",
+ "license": "MIT"
+ },
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
diff --git a/package.json b/package.json
index 7649c95..57dc322 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"react-idle-timer": "^5.7.2",
"react-intersection-observer": "^9.16.0",
"short-unique-id": "^5.2.0",
+ "srt-webvtt": "^2.0.0",
"ts-key-enum": "^3.0.13",
"video.js": "^8.23.3",
"zustand": "^4.3.2"
diff --git a/src/components/VideoPlayer/LoadingVideo.tsx b/src/components/VideoPlayer/LoadingVideo.tsx
index e5eb5a4..4d83a5b 100644
--- a/src/components/VideoPlayer/LoadingVideo.tsx
+++ b/src/components/VideoPlayer/LoadingVideo.tsx
@@ -42,7 +42,10 @@ export const LoadingVideo = ({
height: "100%",
}}
>
-
+ {status !== "NOT_PUBLISHED" && (
+
+
+ )}
{status && (
- {status === "NOT_PUBLISHED" && (
+
+ {status === "NOT_PUBLISHED" ? (
<>Video file was not published. Please inform the publisher!>
- )}
- {status === "REFETCHING" ? (
+ ) : status === "REFETCHING" ? (
<>
<>
{getDownloadProgress(
diff --git a/src/components/VideoPlayer/SubtitleManager.tsx b/src/components/VideoPlayer/SubtitleManager.tsx
new file mode 100644
index 0000000..08efddc
--- /dev/null
+++ b/src/components/VideoPlayer/SubtitleManager.tsx
@@ -0,0 +1,159 @@
+import { useCallback, useEffect, useState } from "react"
+import { QortalGetMetadata } from "../../types/interfaces/resources"
+import { Box, ButtonBase, Dialog, DialogContent, DialogTitle, IconButton, Typography } from "@mui/material"
+import CloseIcon from "@mui/icons-material/Close";
+import { useListStore } from "../../state/lists";
+import { useResources } from "../../hooks/useResources";
+import { useGlobal } from "../../context/GlobalProvider";
+interface SubtitleManagerProps {
+ qortalMetadata: QortalGetMetadata
+ close: ()=> void
+ open: boolean
+}
+export const SubtitleManager = ({qortalMetadata, open, close}: SubtitleManagerProps) => {
+ const [mode, setMode] = useState(1)
+ const {lists} = useGlobal()
+ const {fetchResources} = useResources()
+ // const [subtitles, setSubtitles] = useState([])
+ const subtitles = useListStore(
+ (state) => state.lists[`${qortalMetadata?.service}- ${qortalMetadata?.name}-${qortalMetadata?.identifier}`]?.items || []
+);
+ const getPublishedSubtitles = useCallback(async ()=> {
+ try {
+ await fetchResources(qortalMetadata, `${qortalMetadata?.service}- ${qortalMetadata?.name}-${qortalMetadata?.identifier}`, "BASE64");
+
+ } catch (error) {
+ console.error(error)
+ }
+ }, [])
+
+ useEffect(()=> {
+ if(!qortalMetadata?.identifier || !qortalMetadata?.name || !qortalMetadata?.service) return
+
+ // getPublishedSubtitles()
+ }, [qortalMetadata?.identifier, qortalMetadata?.service, qortalMetadata?.name, getPublishedSubtitles])
+
+ const handleClose = () => {
+ close()
+ setMode(1);
+ // setTitle("");
+ // setDescription("");
+ // setHasMetadata(false);
+ };
+
+ const onSelect = ()=> {
+
+ }
+ return (
+
+ )
+}
+
+interface PublisherSubtitlesProps {
+ publisherName: string
+ subtitles: any[]
+ setMode: (val: number)=> void
+ onSelect: (subtitle: any)=> void
+}
+
+const PublisherSubtitles = ({
+ publisherName,
+ subtitles,
+ setMode,
+ onSelect,
+}: PublisherSubtitlesProps) => {
+
+ return (
+ <>
+
+
+ setMode(2)}
+ >
+
+ Create new index
+
+
+
+
+
+
+ >
+ );
+};
\ No newline at end of file
diff --git a/src/components/VideoPlayer/VideoControls.tsx b/src/components/VideoPlayer/VideoControls.tsx
index f3c00c8..a909e60 100644
--- a/src/components/VideoPlayer/VideoControls.tsx
+++ b/src/components/VideoPlayer/VideoControls.tsx
@@ -50,7 +50,7 @@ export const ReloadButton = ({reloadVideo, isScreenSmall}: any) => {
);
};
-export const ProgressSlider = ({progress, duration, playerRef, extractFrames}: any) => {
+export const ProgressSlider = ({progress, duration, playerRef}: any) => {
const sliderRef = useRef(null);
const [hoverX, setHoverX] = useState(null);
@@ -58,7 +58,8 @@ export const ProgressSlider = ({progress, duration, playerRef, extractFrames}:
const [showDuration, setShowDuration] = useState(0)
const onProgressChange = (_: any, value: number | number[]) => {
if (!playerRef.current) return;
- playerRef.current.currentTime(value as number);
+
+ playerRef.current?.currentTime(value as number);
};
const THUMBNAIL_DEBOUNCE = 500;
@@ -68,31 +69,7 @@ export const ProgressSlider = ({progress, duration, playerRef, extractFrames}:
const debounceTimeoutRef = useRef(null);
const previousBlobUrlRef = useRef(null);
- const debouncedExtract = useCallback(
- (time: number, clientX: number) => {
- const last = lastRequestedTimeRef.current;
- console.log('hello101')
- console.log('last', last)
- if (last !== null && Math.abs(time - last) < THUMBNAIL_MIN_DIFF) return;
- lastRequestedTimeRef.current = time;
- console.log('hello102')
-
- extractFrames(time).then((blobUrl: string | null) => {
- console.log('blobUrl', blobUrl)
- if (!blobUrl) return;
-
- // Clean up previous blob URL
- if (previousBlobUrlRef.current) {
- URL.revokeObjectURL(previousBlobUrlRef.current);
- }
-
- previousBlobUrlRef.current = blobUrl;
- setThumbnailUrl(blobUrl);
-
- });
- },
- [extractFrames]
- );
+
const handleMouseMove = (e: React.MouseEvent) => {
const slider = sliderRef.current;
@@ -140,6 +117,8 @@ console.log('thumbnailUrl', thumbnailUrl, hoverX)
}
+ console.log('duration', duration)
+
return (
{
+export const VideoTime = ({progress, isScreenSmall, duration}: any) => {
+
return (
{
- {videoRef.current?.duration ? formatTime(progress) : ""}
- {" / "}
- {videoRef.current?.duration
- ? formatTime(videoRef.current?.duration)
- : ""}
+ {typeof duration === 'number' ? formatTime(progress) : ''}
+ {' / '}
+ {typeof duration === 'number' ? formatTime(duration) : ''}
);
diff --git a/src/components/VideoPlayer/VideoControlsBar.tsx b/src/components/VideoPlayer/VideoControlsBar.tsx
index 39a2598..ebe588e 100644
--- a/src/components/VideoPlayer/VideoControlsBar.tsx
+++ b/src/components/VideoPlayer/VideoControlsBar.tsx
@@ -1,4 +1,4 @@
-import { Box } from "@mui/material";
+import { Box, IconButton } from "@mui/material";
import { ControlsContainer } from "./VideoPlayer-styles";
// import { MobileControlsBar } from "./MobileControlsBar";
import {
@@ -18,7 +18,6 @@ interface VideoControlsBarProps {
canPlay: boolean
isScreenSmall: boolean
controlsHeight?: string
- videoRef:Ref;
progress: number;
duration: number
isPlaying: boolean;
@@ -32,9 +31,13 @@ interface VideoControlsBarProps {
showControlsFullScreen: boolean;
isFullScreen: boolean;
playerRef: any
+ increaseSpeed: ()=> void
+ decreaseSpeed: ()=> void
+ playbackRate: number
+ openSubtitleManager: ()=> void
}
-export const VideoControlsBar = ({showControls, isFullScreen, showControlsFullScreen, reloadVideo, onVolumeChange, volume, isPlaying, canPlay, isScreenSmall, controlsHeight, videoRef, playerRef, duration, progress, togglePlay, toggleFullscreen, extractFrames}: VideoControlsBarProps) => {
+export const VideoControlsBar = ({showControls, playbackRate, increaseSpeed,decreaseSpeed, isFullScreen, showControlsFullScreen, reloadVideo, onVolumeChange, volume, isPlaying, canPlay, isScreenSmall, controlsHeight, playerRef, duration, progress, togglePlay, toggleFullscreen, extractFrames, openSubtitleManager}: VideoControlsBarProps) => {
const showMobileControls = isScreenSmall && canPlay;
@@ -75,7 +78,7 @@ export const VideoControlsBar = ({showControls, isFullScreen, showControlsFullSc
width: '100%'
}}>
-
+
-
+
-
+
+
+ sub
+
diff --git a/src/components/VideoPlayer/VideoPlayer.tsx b/src/components/VideoPlayer/VideoPlayer.tsx
index f2d3612..e09192d 100644
--- a/src/components/VideoPlayer/VideoPlayer.tsx
+++ b/src/components/VideoPlayer/VideoPlayer.tsx
@@ -10,6 +10,7 @@ import videojs from 'video.js';
import 'video.js/dist/video-js.css';
import Player from "video.js/dist/types/player";
+import { SubtitleManager } from "./SubtitleManager";
type StretchVideoType = "contain" | "fill" | "cover" | "none" | "scale-down";
@@ -25,8 +26,8 @@ type StretchVideoType = "contain" | "fill" | "cover" | "none" | "scale-down";
}
const videoStyles = {
- videoContainer: { aspectRatio: "16 / 9" },
- video: { aspectRatio: "16 / 9" },
+ videoContainer: { },
+ video: { },
};
async function loadMediaInfo(wasmPath = '/MediaInfoModule.wasm') {
@@ -125,12 +126,14 @@ export const VideoPlayer = ({
const containerRef = useRef | null>(null);
const [videoObjectFit] = useState("contain");
const [isPlaying, setIsPlaying] = useState(false);
- const { volume, setVolume } = useVideoStore((state) => ({
+ const { volume, setVolume, setPlaybackRate, playbackRate } = useVideoStore((state) => ({
volume: state.playbackSettings.volume,
setVolume: state.setVolume,
+ setPlaybackRate: state.setPlaybackRate,
+ playbackRate: state.playbackSettings.playbackRate
}));
const playerRef = useRef(null);
-
+ const [isPlayerInitialized, setIsPlayerInitialized] = useState(false)
const [videoCodec, setVideoCodec] = useState(null)
const [isMuted, setIsMuted] = useState(false);
const { setProgress } = useProgressStore();
@@ -138,6 +141,7 @@ export const VideoPlayer = ({
const [duration, setDuration] = useState(0)
const [isLoading, setIsLoading] = useState(true);
const [showControls, setShowControls] = useState(false)
+ const [isOpenSubtitleManage, setIsOpenSubtitleManage] = useState(false)
const {
reloadVideo,
togglePlay,
@@ -158,12 +162,14 @@ export const VideoPlayer = ({
setProgressAbsolute,
setAlwaysShowControls,
status, percentLoaded,
- showControlsFullScreen
+ showControlsFullScreen,
+
} = useVideoPlayerController({
autoPlay,
- videoRef,
+ playerRef,
qortalVideoResource,
- retryAttempts
+ retryAttempts,
+ isPlayerInitialized
});
const hotkeyHandlers = useMemo(
@@ -199,8 +205,12 @@ export const VideoPlayer = ({
-
-
+const closeSubtitleManager = useCallback(()=> {
+ setIsOpenSubtitleManage(false)
+}, [])
+ const openSubtitleManager = useCallback(()=> {
+ setIsOpenSubtitleManage(true)
+}, [])
const videoLocation = useMemo(() => {
if (!qortalVideoResource) return null;
@@ -208,23 +218,27 @@ export const VideoPlayer = ({
}, [qortalVideoResource]);
useVideoPlayerHotKeys(hotkeyHandlers);
- const updateProgress = () => {
- const ref = videoRef as React.RefObject;
- if (!ref.current || !videoLocation) return;
- if (typeof ref.current.currentTime === "number") {
- setProgress(videoLocation, ref.current.currentTime);
- setLocalProgress(ref.current.currentTime)
- }
- };
- useEffect(() => {
- const ref = videoRef as React.RefObject;
- if (!ref.current) return;
- if (ref.current) {
- ref.current.volume = volume;
- }
- // Only run on mount
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ const updateProgress = useCallback(() => {
+ console.log('currentTime2')
+ const player = playerRef?.current;
+ if (!player || typeof player?.currentTime !== 'function') return;
+
+ const currentTime = player.currentTime();
+ console.log('currentTime3', currentTime)
+ if (typeof currentTime === 'number' && videoLocation && currentTime > 0.1) {
+ setProgress(videoLocation, currentTime);
+ setLocalProgress(currentTime);
+ }
+}, [videoLocation]);
+ // useEffect(() => {
+ // const ref = videoRef as React.RefObject;
+ // if (!ref.current) return;
+ // if (ref.current) {
+ // ref.current.volume = volume;
+ // }
+ // // Only run on mount
+ // // eslint-disable-next-line react-hooks/exhaustive-deps
+ // }, []);
const onPlay = useCallback(() => {
setIsPlaying(true);
@@ -235,9 +249,14 @@ export const VideoPlayer = ({
}, [setIsPlaying]);
const onVolumeChangeHandler = useCallback(
(e: React.SyntheticEvent) => {
- const video = e.currentTarget;
+ try {
+ const video = e.currentTarget;
+ console.log('onVolumeChangeHandler')
setVolume(video.volume);
setIsMuted(video.muted);
+ } catch (error) {
+ console.error('onVolumeChangeHandler', onVolumeChangeHandler)
+ }
},
[setIsMuted, setVolume]
);
@@ -246,10 +265,11 @@ export const VideoPlayer = ({
const videoStylesContainer = useMemo(() => {
return {
- cursor: !showControls && isFullscreen ? "none" : "auto",
+ cursor: showControls ? 'auto' : 'none',
+ aspectRatio: '16 / 9',
...videoStyles?.videoContainer,
};
- }, [showControls, isFullscreen]);
+ }, [showControls]);
console.log('isFullscreen', isFullscreen, showControlsFullScreen)
@@ -276,24 +296,25 @@ export const VideoPlayer = ({
setIsLoading(false);
}, [setIsLoading])
- useEffect(() => {
- const ref = videoRef as React.RefObject;
- if (!ref.current) return;
- const video = ref.current;
- if (!video) return;
+ useEffect(() => {
+ if(!isPlayerInitialized) return
+ const player = playerRef.current;
+ if (!player || typeof player.on !== 'function') return;
- const handleLoadedMetadata = () => {
- if(video?.duration){
- setDuration(video.duration)
- }
- };
+ const handleLoadedMetadata = () => {
+ const duration = player.duration?.();
+ if (typeof duration === 'number' && !isNaN(duration)) {
+ setDuration(duration);
+ }
+ };
- video.addEventListener('loadedmetadata', handleLoadedMetadata);
+ player.on('loadedmetadata', handleLoadedMetadata);
+
+ return () => {
+ player.off('loadedmetadata', handleLoadedMetadata);
+ };
+}, [isPlayerInitialized]);
- return () => {
- video.removeEventListener('loadedmetadata', handleLoadedMetadata);
- };
- }, []);
const enterFullscreen = () => {
const ref = containerRef?.current as any;
@@ -318,41 +339,41 @@ export const VideoPlayer = ({
const canvasRef = useRef(null)
const videoRefForCanvas = useRef(null)
-const extractFrames = useCallback(async (time: number): Promise => {
- const video = videoRefForCanvas?.current;
- const canvas: any = canvasRef.current;
+const extractFrames = useCallback( (time: number): void => {
+ // const video = videoRefForCanvas?.current;
+ // const canvas: any = canvasRef.current;
- if (!video || !canvas) return null;
+ // if (!video || !canvas) return null;
- // Avoid unnecessary resize if already correct
- if (canvas.width !== video.videoWidth || canvas.height !== video.videoHeight) {
- canvas.width = video.videoWidth;
- canvas.height = video.videoHeight;
- }
+ // // Avoid unnecessary resize if already correct
+ // if (canvas.width !== video.videoWidth || canvas.height !== video.videoHeight) {
+ // canvas.width = video.videoWidth;
+ // canvas.height = video.videoHeight;
+ // }
- const context = canvas.getContext("2d");
- if (!context) return null;
+ // const context = canvas.getContext("2d");
+ // if (!context) return null;
- // If video is already near the correct time, don't seek again
- const threshold = 0.01; // 10ms threshold
- if (Math.abs(video.currentTime - time) > threshold) {
- await new Promise((resolve) => {
- const onSeeked = () => resolve();
- video.addEventListener("seeked", onSeeked, { once: true });
- video.currentTime = time;
- });
- }
+ // // If video is already near the correct time, don't seek again
+ // const threshold = 0.01; // 10ms threshold
+ // if (Math.abs(video.currentTime - time) > threshold) {
+ // await new Promise((resolve) => {
+ // const onSeeked = () => resolve();
+ // video.addEventListener("seeked", onSeeked, { once: true });
+ // video.currentTime = time;
+ // });
+ // }
- context.drawImage(video, 0, 0, canvas.width, canvas.height);
+ // context.drawImage(video, 0, 0, canvas.width, canvas.height);
- // Use a faster method for image export (optional tradeoff)
- const blob = await new Promise((resolve) => {
- canvas.toBlob((blob: any) => resolve(blob), "image/webp", 0.7);
- });
+ // // Use a faster method for image export (optional tradeoff)
+ // const blob = await new Promise((resolve) => {
+ // canvas.toBlob((blob: any) => resolve(blob), "image/webp", 0.7);
+ // });
- if (!blob) return null;
+ // if (!blob) return null;
- return URL.createObjectURL(blob);
+ // return URL.createObjectURL(blob);
}, []);
@@ -385,25 +406,20 @@ useEffect(() => {
if (hideTimeout.current) clearTimeout(hideTimeout.current);
}, [setShowControls]);
- const onLoadedMetadata= (e: any)=> {
- console.log('eeeeeeeeeee', e)
- const ref = videoRef as any;
- if (!ref.current) return;
- console.log('datataa', ref.current.audioTracks , // List of available audio tracks
-ref.current.textTracks , // Subtitles/closed captions
-ref.current.videoTracks )
- }
const videoLocactionStringified = useMemo(()=> {
return JSON.stringify(qortalVideoResource)
}, [qortalVideoResource])
useEffect(() => {
- if (!resourceUrl || !isReady || !videoLocactionStringified) return;
+ if (!resourceUrl || !isReady || !videoLocactionStringified || !startPlay) return;
+ console.log("EFFECT TRIGGERED", { isReady, resourceUrl, startPlay, poster, videoLocactionStringified });
+
const resource = JSON.parse(videoLocactionStringified)
let canceled = false;
- const setupPlayer = async () => {
+ try {
+ const setupPlayer = async () => {
const type = await getVideoMimeTypeFromUrl(resource);
if (canceled) return;
@@ -413,6 +429,7 @@ useEffect(() => {
responsive: true,
fluid: true,
poster: startPlay ? "" : poster,
+ aspectRatio: '16:9' ,
sources: [
{
src: resourceUrl,
@@ -426,25 +443,75 @@ useEffect(() => {
if (!playerRef.current && ref.current) {
playerRef.current = videojs(ref.current, options, () => {
+ setIsPlayerInitialized(true)
playerRef.current?.poster('');
+ playerRef.current?.playbackRate(playbackRate)
+ playerRef.current?.volume(volume);
+
+ playerRef.current?.addRemoteTextTrack({
+ kind: 'subtitles',
+ src: 'http://127.0.0.1:22393/arbitrary/DOCUMENT/a-test/test-identifier',
+ srclang: 'en',
+ label: 'English',
+ default: true
+ }, true);
playerRef.current?.play();
+
});
+ playerRef.current?.on('error', () => {
+ const error = playerRef.current?.error();
+ console.error('Video.js playback error:', error);
+ // Optional: display user-friendly message
+ });
}
};
setupPlayer();
- return () => {
- canceled = true;
- if (playerRef.current) {
- playerRef.current.dispose();
- playerRef.current = null;
+ } catch (error) {
+ console.error('useEffect start player', error)
+ }
+ return () => {
+ console.log('canceled')
+ canceled = true;
+ const player = playerRef.current;
+
+ if (player && typeof player.dispose === 'function') {
+ try {
+ player.dispose();
+ } catch (err) {
+ console.error('Error disposing Video.js player:', err);
}
- };
+ playerRef.current = null;
+ }
+};
}, [isReady, resourceUrl, startPlay, poster, videoLocactionStringified]);
-
+ useEffect(() => {
+ if(!isPlayerInitialized) return
+ const player = playerRef?.current;
+ console.log('player rate', player)
+ if (!player) return;
+
+ const handleRateChange = () => {
+ const newRate = player?.playbackRate();
+ console.log('Playback rate changed:', newRate);
+ if(newRate){
+ setPlaybackRate(newRate); // or any other state/action
+ }
+ };
+
+ player.on('ratechange', handleRateChange);
+
+ return () => {
+ player.off('ratechange', handleRateChange);
+ };
+}, [isPlayerInitialized]);
+
return (
+ <>
+ {/* */}
+
{
tabIndex={0}
className="video-js"
- // src={isReady && startPlay ? resourceUrl || undefined : undefined}
+ src={isReady && startPlay ? resourceUrl || undefined : undefined}
poster={startPlay ? "" : poster}
onTimeUpdate={updateProgress}
autoPlay={autoPlay}
@@ -471,17 +538,18 @@ useEffect(() => {
onPause={onPause}
onVolumeChange={onVolumeChangeHandler}
controls={false}
- onLoadedMetadata={onLoadedMetadata}
/>
-
-
+ {/* */}
+
{isReady && (
-
+
)}
-
+
+ >
);
};
diff --git a/src/components/VideoPlayer/useVideoPlayerController.tsx b/src/components/VideoPlayer/useVideoPlayerController.tsx
index 19d6bc5..dc6d627 100644
--- a/src/components/VideoPlayer/useVideoPlayerController.tsx
+++ b/src/components/VideoPlayer/useVideoPlayerController.tsx
@@ -19,14 +19,15 @@ const maxSpeed = 4.0;
const speedChange = 0.25;
interface UseVideoControls {
- videoRef: Ref;
+ playerRef: any;
autoPlay?: boolean;
qortalVideoResource: QortalGetMetadata;
retryAttempts?: number;
+ isPlayerInitialized: boolean
}
export const useVideoPlayerController = (props: UseVideoControls) => {
- const { autoPlay, videoRef, qortalVideoResource, retryAttempts } = props;
+ const { autoPlay, playerRef, qortalVideoResource, retryAttempts, isPlayerInitialized } = props;
const [isFullscreen, setIsFullscreen] = useState(false);
const [showControlsFullScreen, setShowControlsFullScreen] = useState(false)
@@ -60,48 +61,67 @@ export const useVideoPlayerController = (props: UseVideoControls) => {
}, [qortalVideoResource]);
useEffect(() => {
- if (videoLocation) {
- const ref = videoRef as React.RefObject;
+ if (videoLocation && isPlayerInitialized) {
+ console.log('hellohhhh5')
+ try {
+ const ref = playerRef as any;
if (!ref.current) return;
const savedProgress = getProgress(videoLocation);
+ console.log('savedProgress', savedProgress)
if (typeof savedProgress === "number") {
- ref.current.currentTime = savedProgress;
+ playerRef.current?.currentTime(savedProgress);
+
+ }
+ } catch (error) {
+ console.error('line 74', error)
}
}
- }, [videoLocation, getProgress]);
+ }, [videoLocation, getProgress, isPlayerInitialized]);
const [playbackRate, _setLocalPlaybackRate] = useState(
playbackSettings.playbackRate
);
const updatePlaybackRate = useCallback(
- (newSpeed: number) => {
- const ref = videoRef as React.RefObject;
- if (!ref.current) return;
+ (newSpeed: number) => {
+ try {
+ const player = playerRef.current;
+ if (!player) return;
+
+ if (newSpeed > maxSpeed || newSpeed < minSpeed) newSpeed = minSpeed;
+
+ const clampedSpeed = Math.min(Math.max(newSpeed, minSpeed), maxSpeed);
+ player.playbackRate(clampedSpeed); // ✅ Video.js API
+
+ // _setLocalPlaybackRate(clampedSpeed);
+ // setPlaybackRate(clampedSpeed);
+ } catch (error) {
+ console.error('updatePlaybackRate', error)
+ }
+ },
+ [setPlaybackRate, _setLocalPlaybackRate, minSpeed, maxSpeed]
+);
- if (newSpeed > maxSpeed || newSpeed < minSpeed) newSpeed = minSpeed;
- ref.current.playbackRate = newSpeed;
- _setLocalPlaybackRate(newSpeed);
- setPlaybackRate(newSpeed);
- },
- [setPlaybackRate, _setLocalPlaybackRate]
- );
const increaseSpeed = useCallback(
(wrapOverflow = true) => {
- const changedSpeed = playbackRate + speedChange;
+ try {
+ const changedSpeed = playbackSettings.playbackRate + speedChange;
const newSpeed = wrapOverflow
? changedSpeed
: Math.min(changedSpeed, maxSpeed);
updatePlaybackRate(newSpeed);
+ } catch (error) {
+ console.error('increaseSpeed', increaseSpeed)
+ }
},
- [updatePlaybackRate, playbackRate]
+ [updatePlaybackRate, playbackSettings.playbackRate]
);
const decreaseSpeed = useCallback(() => {
- updatePlaybackRate(playbackRate - speedChange);
- }, [updatePlaybackRate, playbackRate]);
+ updatePlaybackRate(playbackSettings.playbackRate - speedChange);
+ }, [updatePlaybackRate, playbackSettings.playbackRate]);
const toggleAlwaysShowControls = useCallback(() => {
setAlwaysShowControls((prev) => !prev);
@@ -118,88 +138,139 @@ export const useVideoPlayerController = (props: UseVideoControls) => {
const onVolumeChange = useCallback(
(_: any, value: number | number[]) => {
- const newVolume = value as number;
- const ref = videoRef as React.RefObject;
+ try {
+ const newVolume = value as number;
+ const ref = playerRef as any;
if (!ref.current) return;
- if (ref.current) ref.current.volume = newVolume;
+ if (ref.current) {
+ playerRef.current?.volume(newVolume);
+
+ }
+ } catch (error) {
+ console.error('onVolumeChange', error)
+ }
},
[]
);
const toggleMute = useCallback(() => {
- const ref = videoRef as React.RefObject;
- if (!ref.current) return;
+ try {
+ const ref = playerRef as any;
+ if (!ref.current?.muted) return;
- ref.current.muted = !ref.current.muted;
+ ref.current?.muted(!ref.current?.muted)
+ } catch (error) {
+ console.error('toggleMute', toggleMute)
+ }
}, []);
const changeVolume = useCallback(
- (delta: number) => {
- const ref = videoRef as React.RefObject;
- if (!ref.current) return;
-
- // Get current volume directly from video element
- const currentVolume = ref.current.volume;
- let newVolume = Math.max(0, Math.min(currentVolume + delta, 1));
- newVolume = +newVolume.toFixed(2);
-
- ref.current.volume = newVolume;
- ref.current.muted = false;
-
- },
- []
- );
+ (delta: number) => {
+ try {
+ const player = playerRef.current;
+ if (!player || typeof player.volume !== 'function') return;
+
+ const currentVolume = player.volume(); // Get current volume (0–1)
+ let newVolume = Math.max(0, Math.min(currentVolume + delta, 1));
+ newVolume = +newVolume.toFixed(2); // Round to 2 decimal places
+
+ player.volume(newVolume); // Set new volume
+ player.muted(false); // Ensure it's unmuted
+ } catch (error) {
+ console.error('changeVolume', error)
+ }
+ },
+ []
+);
+
- const setProgressRelative = useCallback((seconds: number) => {
- const ref = videoRef as React.RefObject;
- const current = ref.current.currentTime;
- const duration = ref.current.duration || 100;
- const newTime = Math.max(0, Math.min(current + seconds, duration));
- ref.current.currentTime = newTime;
- }, []);
+const setProgressRelative = useCallback((seconds: number) => {
+ try {
+ const player = playerRef.current;
+ if (!player || typeof player.currentTime !== 'function' || typeof player.duration !== 'function') return;
- const setProgressAbsolute = useCallback((percent: number) => {
- const ref = videoRef as React.RefObject;
+ const current = player.currentTime();
+ const duration = player.duration() || 100;
+ const newTime = Math.max(0, Math.min(current + seconds, duration));
+
+ player.currentTime(newTime);
+ } catch (error) {
+ console.error('setProgressRelative', error)
+ }
+}, []);
+
+
+ const setProgressAbsolute = useCallback((percent: number) => {
+try {
+ const player = playerRef.current;
+ if (!player || typeof player.duration !== 'function' || typeof player.currentTime !== 'function') return;
+
+ const duration = player.duration();
+ const clampedPercent = Math.min(100, Math.max(0, percent));
+ const finalTime = (duration * clampedPercent) / 100;
+
+ player.currentTime(finalTime);
+} catch (error) {
+ console.error('setProgressAbsolute', error)
+}
+}, []);
- if (!ref.current) return;
- const finalTime =
- (ref.current.duration * Math.min(100, Math.max(0, percent))) / 100;
- ref.current.currentTime = finalTime;
- }, []);
const toggleObjectFit = useCallback(() => {
setVideoObjectFit(videoObjectFit === "contain" ? "fill" : "contain");
}, [setVideoObjectFit]);
- const togglePlay = useCallback(async () => {
- const ref = videoRef as React.RefObject;
- if (!ref.current) return;
- if (!startedFetchRef.current) {
- setStartedFetch(true);
- startedFetchRef.current = true;
- setStartPlay(true);
- return;
- }
- if (isReady && ref.current) {
- if (ref.current.paused) {
- ref.current.play();
- } else {
- ref.current.pause();
- }
-
- }
- }, [ setStartedFetch, isReady]);
+const togglePlay = useCallback(async () => {
+
+
+ try {
+ if (!startedFetchRef.current) {
+ setStartedFetch(true);
+ startedFetchRef.current = true;
+ setStartPlay(true);
+ return;
+ }
+ const player = playerRef.current;
+ if (!player) return;
+ if (isReady) {
+ if (player.paused()) {
+ try {
+ await player.play();
+ } catch (err) {
+ console.warn('Play failed:', err);
+ }
+ } else {
+ player.pause();
+ }
+ }
+ } catch (error) {
+ console.error('togglePlay', error)
+ }
+}, [setStartedFetch, isReady]);
+
+
+ const reloadVideo = useCallback(async () => {
+ try {
+ const player = playerRef.current;
+ if (!player || !isReady || !resourceUrl) return;
+
+ const currentTime = player.currentTime();
+
+ player.src({ src: resourceUrl, type: 'video/mp4' }); // Adjust type if needed
+ player.load();
+
+ player.ready(() => {
+ player.currentTime(currentTime);
+ player.play().catch((err: any) => {
+ console.warn('Playback failed after reload:', err);
+ });
+ });
+ } catch (error) {
+ console.error(error)
+ }
+}, [isReady, resourceUrl]);
- const reloadVideo = useCallback(async () => {
- const ref = videoRef as React.RefObject;
- if (!ref?.current || !isReady || !resourceUrl) return;
- const currentTime = ref.current.currentTime;
- ref.current.src = resourceUrl;
- ref.current.load();
- ref.current.currentTime = currentTime;
- ref.current.play();
- }, [isReady, resourceUrl]);
useEffect(() => {
if (autoPlay) togglePlay();