"use client" import { cx } from "class-variance-authority" import { useCallback, useEffect, useRef, useState } from "react" import { languages } from "@scandic-hotels/common/constants/language" import { useIntl } from "react-intl" import Image from "../Image" import { VideoPlayerButton } from "./Button" import { VideoPlayerProps } from "./types" import { useVideoDimensions } from "./useVideoDimensions" import { getVideoPropsByVariant } from "./utils" import { variants } from "./variants" import styles from "./videoPlayer.module.css" export function VideoPlayer({ sources, captions, focalPoint = { x: 50, y: 50 }, className, variant = "inline", poster, autoPlay, hasOverlay, }: VideoPlayerProps) { const intl = useIntl() const videoRef = useRef(null) const shouldAutoPlay = (variant === "hero" && (autoPlay ?? true)) || !!autoPlay const [hasManuallyPlayed, setHasManuallyPlayed] = useState(false) const [hasToggledMute, setHasToggledMute] = useState(false) const [isPlaying, setIsPlaying] = useState(shouldAutoPlay) const [isMuted, setIsMuted] = useState(true) const [userPaused, setUserPaused] = useState(false) const [showPoster, setShowPoster] = useState(!shouldAutoPlay) const { containerRef, handleMetadataLoaded, containerWidth, hasError, handleError, } = useVideoDimensions() const defaultProps = getVideoPropsByVariant( variant, hasManuallyPlayed, shouldAutoPlay ) const classNames = variants({ className, variant, }) const showPlayButton = !hasError && (variant === "hero" || (variant === "inline" && !hasManuallyPlayed)) const showMuteButton = !hasError && variant === "inline" && hasManuallyPlayed && !hasToggledMute const handleIntersection = useCallback( (entries: IntersectionObserverEntry[]) => { entries.forEach((entry) => { if (entry.intersectionRatio >= 0.1 && !userPaused) { videoRef.current?.play() } else if (entry.intersectionRatio < 0.1) { videoRef.current?.pause() } }) }, [userPaused] ) function togglePlay() { const videoElement = videoRef.current if (videoElement) { if (variant === "hero") { if (videoElement.paused) { setUserPaused(false) videoElement.play() } else { setUserPaused(true) videoElement.pause() } } else { setHasManuallyPlayed(true) videoElement.play() } } } function handleMuteToggle() { const videoElement = videoRef.current if (videoElement) { const currentlyMuted = videoElement.muted videoElement.muted = !currentlyMuted setIsMuted(!currentlyMuted) setHasToggledMute(true) } } function handleVolumeChangeEvent(event: React.UIEvent) { if (event.currentTarget.muted && !isMuted) { setIsMuted(true) } else if (!event.currentTarget.muted && isMuted) { setIsMuted(false) } } function handlePlay() { setShowPoster(false) setIsPlaying(true) } useEffect(() => { const videoElement = videoRef.current if (!videoElement || variant !== "hero") { return } const observer = new IntersectionObserver(handleIntersection, { // Play video when at least 10% of it is visible threshold: [0, 0.1, 1], }) observer.observe(videoElement) return () => { observer.disconnect() } }, [variant, handleIntersection]) if (!sources.length) { return null } // Sort sources to prioritize WebM format for better compression const sortedSources = [...sources].sort((a, b) => { const aIsWebM = a.type.includes("webm") const bIsWebM = b.type.includes("webm") return aIsWebM === bIsWebM ? 0 : aIsWebM ? -1 : 1 }) return (
{(showPoster || hasError) && poster ? ( ) : null} {showPlayButton ? ( ) : null} {showMuteButton ? ( ) : null}
) }