mirror of
https://github.com/linsa-io/linsa.git
synced 2026-04-27 02:38:45 +02:00
Implement soft fullscreen mode with timeout handling in VideoPlayer component; update fullscreen state management and styling accordingly.
This commit is contained in:
@@ -19,9 +19,18 @@ export function VideoPlayer({
|
|||||||
const [isMuted, setIsMuted] = useState(muted)
|
const [isMuted, setIsMuted] = useState(muted)
|
||||||
const [volume, setVolume] = useState(1)
|
const [volume, setVolume] = useState(1)
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false)
|
const [isFullscreen, setIsFullscreen] = useState(false)
|
||||||
|
const [isSoftFullscreen, setIsSoftFullscreen] = useState(false)
|
||||||
const [showControls, setShowControls] = useState(true)
|
const [showControls, setShowControls] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const hideControlsTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
const hideControlsTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
|
const softFullscreenTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
|
|
||||||
|
const clearSoftFullscreenTimeout = () => {
|
||||||
|
if (softFullscreenTimeoutRef.current) {
|
||||||
|
clearTimeout(softFullscreenTimeoutRef.current)
|
||||||
|
softFullscreenTimeoutRef.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const video = videoRef.current
|
const video = videoRef.current
|
||||||
@@ -80,6 +89,21 @@ export function VideoPlayer({
|
|||||||
}
|
}
|
||||||
}, [src, autoPlay])
|
}, [src, autoPlay])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
clearSoftFullscreenTimeout()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isSoftFullscreen || typeof document === "undefined") return
|
||||||
|
const previousOverflow = document.body.style.overflow
|
||||||
|
document.body.style.overflow = "hidden"
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = previousOverflow
|
||||||
|
}
|
||||||
|
}, [isSoftFullscreen])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const video = videoRef.current
|
const video = videoRef.current
|
||||||
if (!video) return
|
if (!video) return
|
||||||
@@ -94,11 +118,25 @@ export function VideoPlayer({
|
|||||||
const updateFullscreenState = () => {
|
const updateFullscreenState = () => {
|
||||||
const isDocFullscreen = !!doc.fullscreenElement || !!doc.webkitFullscreenElement
|
const isDocFullscreen = !!doc.fullscreenElement || !!doc.webkitFullscreenElement
|
||||||
const isVideoFullscreen = !!videoEl.webkitDisplayingFullscreen
|
const isVideoFullscreen = !!videoEl.webkitDisplayingFullscreen
|
||||||
setIsFullscreen(isDocFullscreen || isVideoFullscreen)
|
const isNowFullscreen = isDocFullscreen || isVideoFullscreen
|
||||||
|
setIsFullscreen(isNowFullscreen)
|
||||||
|
if (isNowFullscreen) {
|
||||||
|
clearSoftFullscreenTimeout()
|
||||||
|
setIsSoftFullscreen(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onWebkitBegin = () => setIsFullscreen(true)
|
const onWebkitBegin = () => {
|
||||||
const onWebkitEnd = () => setIsFullscreen(false)
|
clearSoftFullscreenTimeout()
|
||||||
|
video.controls = true
|
||||||
|
setIsFullscreen(true)
|
||||||
|
setIsSoftFullscreen(false)
|
||||||
|
}
|
||||||
|
const onWebkitEnd = () => {
|
||||||
|
video.controls = false
|
||||||
|
setIsFullscreen(false)
|
||||||
|
setIsSoftFullscreen(false)
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener("fullscreenchange", updateFullscreenState)
|
document.addEventListener("fullscreenchange", updateFullscreenState)
|
||||||
document.addEventListener("webkitfullscreenchange", updateFullscreenState)
|
document.addEventListener("webkitfullscreenchange", updateFullscreenState)
|
||||||
@@ -153,6 +191,7 @@ export function VideoPlayer({
|
|||||||
const video = videoRef.current
|
const video = videoRef.current
|
||||||
const container = containerRef.current
|
const container = containerRef.current
|
||||||
if (!video || !container) return
|
if (!video || !container) return
|
||||||
|
clearSoftFullscreenTimeout()
|
||||||
|
|
||||||
const doc = document as Document & {
|
const doc = document as Document & {
|
||||||
webkitFullscreenElement?: Element | null
|
webkitFullscreenElement?: Element | null
|
||||||
@@ -193,11 +232,33 @@ export function VideoPlayer({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isSoftFullscreen) {
|
||||||
|
setIsSoftFullscreen(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const scheduleSoftFullscreenFallback = () => {
|
||||||
|
softFullscreenTimeoutRef.current = setTimeout(() => {
|
||||||
|
const isDocFullscreenNow = !!doc.fullscreenElement || !!doc.webkitFullscreenElement
|
||||||
|
const isVideoFullscreenNow = !!videoEl.webkitDisplayingFullscreen
|
||||||
|
if (!isDocFullscreenNow && !isVideoFullscreenNow) {
|
||||||
|
video.controls = false
|
||||||
|
setIsSoftFullscreen(true)
|
||||||
|
}
|
||||||
|
}, 400)
|
||||||
|
}
|
||||||
|
|
||||||
if (isAppleMobile && videoEl.webkitEnterFullscreen) {
|
if (isAppleMobile && videoEl.webkitEnterFullscreen) {
|
||||||
try {
|
try {
|
||||||
|
video.controls = true
|
||||||
|
if (video.paused) {
|
||||||
|
video.play().then(() => setIsPlaying(true)).catch(() => {})
|
||||||
|
}
|
||||||
videoEl.webkitEnterFullscreen()
|
videoEl.webkitEnterFullscreen()
|
||||||
|
scheduleSoftFullscreenFallback()
|
||||||
return
|
return
|
||||||
} catch {
|
} catch {
|
||||||
|
video.controls = false
|
||||||
// Fall back to other fullscreen methods.
|
// Fall back to other fullscreen methods.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,6 +281,8 @@ export function VideoPlayer({
|
|||||||
setIsFullscreen(true)
|
setIsFullscreen(true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
setIsSoftFullscreen(true)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Fall through to video fullscreen methods.
|
// Fall through to video fullscreen methods.
|
||||||
@@ -235,9 +298,12 @@ export function VideoPlayer({
|
|||||||
} else if (videoEl.webkitEnterFullscreen) {
|
} else if (videoEl.webkitEnterFullscreen) {
|
||||||
videoEl.webkitEnterFullscreen()
|
videoEl.webkitEnterFullscreen()
|
||||||
setIsFullscreen(true)
|
setIsFullscreen(true)
|
||||||
|
scheduleSoftFullscreenFallback()
|
||||||
|
} else {
|
||||||
|
setIsSoftFullscreen(true)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore fullscreen errors to avoid breaking playback.
|
setIsSoftFullscreen(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,6 +317,8 @@ export function VideoPlayer({
|
|||||||
}, 3000)
|
}, 3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isFullscreenActive = isFullscreen || isSoftFullscreen
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center bg-neutral-900 text-neutral-400">
|
<div className="flex h-full w-full items-center justify-center bg-neutral-900 text-neutral-400">
|
||||||
@@ -262,7 +330,9 @@ export function VideoPlayer({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className="group relative h-full w-full bg-black"
|
className={`group bg-black ${
|
||||||
|
isSoftFullscreen ? "fixed inset-0 z-50 h-screen w-screen" : "relative h-full w-full"
|
||||||
|
}`}
|
||||||
onMouseMove={handleMouseMove}
|
onMouseMove={handleMouseMove}
|
||||||
onMouseLeave={() => isPlaying && setShowControls(false)}
|
onMouseLeave={() => isPlaying && setShowControls(false)}
|
||||||
>
|
>
|
||||||
@@ -344,7 +414,7 @@ export function VideoPlayer({
|
|||||||
onClick={handleFullscreen}
|
onClick={handleFullscreen}
|
||||||
className="text-white transition-transform hover:scale-110"
|
className="text-white transition-transform hover:scale-110"
|
||||||
>
|
>
|
||||||
{isFullscreen ? (
|
{isFullscreenActive ? (
|
||||||
<svg className="h-6 w-6" fill="currentColor" viewBox="0 0 24 24">
|
<svg className="h-6 w-6" fill="currentColor" viewBox="0 0 24 24">
|
||||||
<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" />
|
<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -120,11 +120,7 @@ function StreamPage() {
|
|||||||
const stream = data?.stream ?? null
|
const stream = data?.stream ?? null
|
||||||
const playback = stream?.playback ?? null
|
const playback = stream?.playback ?? null
|
||||||
const fallbackPlayback = stream?.hls_url
|
const fallbackPlayback = stream?.hls_url
|
||||||
? resolveStreamPlayback({
|
? { type: "hls", url: stream.hls_url }
|
||||||
hlsUrl: stream.hls_url,
|
|
||||||
webrtcUrl: null,
|
|
||||||
preferWebRtc: false,
|
|
||||||
})
|
|
||||||
: null
|
: null
|
||||||
const activePlayback =
|
const activePlayback =
|
||||||
playback?.type === "webrtc" && webRtcFailed
|
playback?.type === "webrtc" && webRtcFailed
|
||||||
|
|||||||
Reference in New Issue
Block a user