Enhance video playback controls for better user experience
Implement play/pause, mute, and fullscreen toggles in the video modal, along with temporary control visibility. Includes improved error handling for fragment loading stats. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 11420304-80a9-4ef2-adff-cbdaa418ffa8 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/11420304-80a9-4ef2-adff-cbdaa418ffa8/Qpg7dKb
This commit is contained in:
parent
1bd4584dcc
commit
4ee20431aa
@ -59,6 +59,10 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
|
|||||||
const [showShareMenu, setShowShareMenu] = useState(false);
|
const [showShareMenu, setShowShareMenu] = useState(false);
|
||||||
const [showEditModal, setShowEditModal] = useState(false);
|
const [showEditModal, setShowEditModal] = useState(false);
|
||||||
const [videoThumbnail, setVideoThumbnail] = useState<string | null>(null);
|
const [videoThumbnail, setVideoThumbnail] = useState<string | null>(null);
|
||||||
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
|
const [isMuted, setIsMuted] = useState(false);
|
||||||
|
const [showControls, setShowControls] = useState(true);
|
||||||
|
const [controlsTimeout, setControlsTimeout] = useState<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleEscape = (e: KeyboardEvent) => {
|
const handleEscape = (e: KeyboardEvent) => {
|
||||||
@ -163,12 +167,18 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
|
|||||||
console.log('Razlog preklopa: adaptivni algoritem na podlagi omrežne hitrosti');
|
console.log('Razlog preklopa: adaptivni algoritem na podlagi omrežne hitrosti');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fragment loading stats
|
// Fragment loading stats - fixed error handling
|
||||||
hls.on(Hls.Events.FRAG_LOADED, (event, data) => {
|
hls.on(Hls.Events.FRAG_LOADED, (event, data) => {
|
||||||
const stats = data.stats;
|
try {
|
||||||
const loadTime = stats.loading.end - stats.loading.start;
|
if (data.frag && data.frag.stats) {
|
||||||
const speed = (stats.total * 8) / loadTime; // bits per ms = kbps
|
const stats = data.frag.stats;
|
||||||
console.log(`Fragment naložen v ${loadTime}ms, hitrost: ${Math.round(speed)} kbps`);
|
const loadTime = stats.loading.end - stats.loading.start;
|
||||||
|
const speed = (stats.total * 8) / loadTime; // bits per ms = kbps
|
||||||
|
console.log(`Fragment naložen v ${loadTime}ms, hitrost: ${Math.round(speed)} kbps`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore stats errors, they don't affect playback
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Buffer monitoring for dynamic adjustment
|
// Buffer monitoring for dynamic adjustment
|
||||||
@ -222,7 +232,7 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
|
|||||||
console.log('Using native video support for MP4');
|
console.log('Using native video support for MP4');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capture thumbnail when video loads
|
// Video event listeners
|
||||||
videoElement.addEventListener('loadeddata', () => {
|
videoElement.addEventListener('loadeddata', () => {
|
||||||
console.log('Video data loaded, capturing thumbnail');
|
console.log('Video data loaded, capturing thumbnail');
|
||||||
setTimeout(() => captureVideoThumbnail(), 1000);
|
setTimeout(() => captureVideoThumbnail(), 1000);
|
||||||
@ -232,6 +242,10 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
|
|||||||
console.log('Video can play, capturing thumbnail');
|
console.log('Video can play, capturing thumbnail');
|
||||||
captureVideoThumbnail();
|
captureVideoThumbnail();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
videoElement.addEventListener('play', () => setIsPlaying(true));
|
||||||
|
videoElement.addEventListener('pause', () => setIsPlaying(false));
|
||||||
|
videoElement.addEventListener('volumechange', () => setIsMuted(videoElement.muted));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup when modal closes
|
// Cleanup when modal closes
|
||||||
@ -280,6 +294,52 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const togglePlay = () => {
|
||||||
|
if (videoRef.current) {
|
||||||
|
if (isPlaying) {
|
||||||
|
videoRef.current.pause();
|
||||||
|
} else {
|
||||||
|
videoRef.current.play();
|
||||||
|
handleVideoPlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleMute = () => {
|
||||||
|
if (videoRef.current) {
|
||||||
|
videoRef.current.muted = !videoRef.current.muted;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleFullscreen = () => {
|
||||||
|
if (videoRef.current) {
|
||||||
|
if (document.fullscreenElement) {
|
||||||
|
document.exitFullscreen();
|
||||||
|
} else {
|
||||||
|
videoRef.current.requestFullscreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const showControlsTemporarily = () => {
|
||||||
|
setShowControls(true);
|
||||||
|
if (controlsTimeout) {
|
||||||
|
clearTimeout(controlsTimeout);
|
||||||
|
}
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
setShowControls(false);
|
||||||
|
}, 3000);
|
||||||
|
setControlsTimeout(timeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (controlsTimeout) {
|
||||||
|
clearTimeout(controlsTimeout);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [controlsTimeout]);
|
||||||
|
|
||||||
const getShareUrl = () => {
|
const getShareUrl = () => {
|
||||||
if (!video?.id) return window.location.origin;
|
if (!video?.id) return window.location.origin;
|
||||||
return `${window.location.origin}?video=${video.id}`;
|
return `${window.location.origin}?video=${video.id}`;
|
||||||
@ -339,15 +399,21 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="relative bg-black rounded-lg overflow-hidden">
|
<div className="relative bg-black rounded-lg overflow-hidden">
|
||||||
<video
|
<div
|
||||||
ref={videoRef}
|
className="relative flex items-center justify-center bg-black"
|
||||||
className="w-full h-auto max-h-[80vh]"
|
onMouseMove={showControlsTemporarily}
|
||||||
controls
|
onMouseEnter={() => setShowControls(true)}
|
||||||
preload="metadata"
|
onMouseLeave={() => setShowControls(false)}
|
||||||
onPlay={handleVideoPlay}
|
|
||||||
data-testid="video-player"
|
|
||||||
crossOrigin="anonymous"
|
|
||||||
>
|
>
|
||||||
|
<video
|
||||||
|
ref={videoRef}
|
||||||
|
className="w-full h-auto max-h-[80vh] cursor-pointer"
|
||||||
|
preload="metadata"
|
||||||
|
onPlay={handleVideoPlay}
|
||||||
|
data-testid="video-player"
|
||||||
|
crossOrigin="anonymous"
|
||||||
|
onClick={togglePlay}
|
||||||
|
>
|
||||||
Your browser does not support the video tag.
|
Your browser does not support the video tag.
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user