Files
web/packages/design-system/lib/components/VideoPlayer/index.tsx
Erik Tiekstra 84593438e6 feat(BOOK-257): Added VideoPlayer component
Approved-by: Christel Westerberg
Approved-by: Bianca Widstam
2025-12-02 07:35:38 +00:00

180 lines
4.8 KiB
TypeScript

'use client'
import { cx, VariantProps } from 'class-variance-authority'
import { useRef, useState, VideoHTMLAttributes } from 'react'
import { Lang, languages } from '@scandic-hotels/common/constants/language'
import { FocalPoint } from '@scandic-hotels/common/utils/imageVault'
import { useIntl } from 'react-intl'
import { VideoPlayerButton } from './Button'
import { variants } from './variants'
import styles from './videoPlayer.module.css'
interface Caption {
src: string
srcLang: Lang
isDefault: boolean
}
interface VideoPlayerProps extends VariantProps<typeof variants> {
src: string
className?: string
captions?: Caption[]
focalPoint?: FocalPoint
autoPlay?: boolean
}
export function VideoPlayer({
src,
captions,
focalPoint = { x: 50, y: 50 },
className,
variant = 'inline',
autoPlay,
}: VideoPlayerProps) {
const intl = useIntl()
const videoRef = useRef<HTMLVideoElement>(null)
const [isActivated, setIsActivated] = useState(
(variant === 'hero' && (autoPlay ?? true)) || !!autoPlay
)
const [isPlaying, setIsPlaying] = useState(autoPlay ?? false)
const [isMuted, setIsMuted] = useState(true)
const defaultProps = getVideoPropsByVariant(variant, isActivated, autoPlay)
const classNames = variants({
className,
variant,
})
function togglePlay() {
const videoElement = videoRef.current
if (videoElement) {
if (variant === 'hero') {
if (videoElement.paused) {
videoElement.play()
} else {
videoElement.pause()
}
} else {
setIsActivated(true)
videoElement.play()
}
}
}
function handleMuteToggle() {
const videoElement = videoRef.current
if (videoElement) {
const currentlyMuted = videoElement.muted
videoElement.muted = !currentlyMuted
setIsMuted(!currentlyMuted)
}
}
function handleVolumeChangeEvent(event: React.UIEvent<HTMLVideoElement>) {
if (event.currentTarget.muted && !isMuted) {
setIsMuted(true)
} else if (!event.currentTarget.muted && isMuted) {
setIsMuted(false)
}
}
const showPlayButton =
variant === 'hero' || (variant === 'inline' && !isActivated)
const showMuteButton = variant === 'inline' && isActivated
return (
<div className={cx(classNames, { [styles.isActivated]: isActivated })}>
<video
ref={videoRef}
className={styles.video}
src={src}
style={
focalPoint
? { objectPosition: `${focalPoint.x}% ${focalPoint.y}%` }
: undefined
}
onPlay={() => setIsPlaying(true)}
onPause={() => setIsPlaying(false)}
onVolumeChange={handleVolumeChangeEvent}
{...defaultProps}
>
{captions?.length
? captions.map(({ src, srcLang, isDefault }) => (
<track
key={src}
src={src}
kind="captions"
srcLang={srcLang}
label={languages[srcLang] || srcLang}
default={isDefault}
/>
))
: null}
</video>
{showPlayButton ? (
<VideoPlayerButton
className={styles.playButton}
onPress={togglePlay}
iconName={isPlaying ? 'pause' : 'play_arrow'}
ariaLabel={
isPlaying
? intl.formatMessage({
id: 'videoPlayer.pause',
defaultMessage: 'Pause video',
})
: intl.formatMessage({
id: 'videoPlayer.play',
defaultMessage: 'Play video',
})
}
/>
) : null}
{showMuteButton ? (
<VideoPlayerButton
className={styles.muteButton}
onPress={handleMuteToggle}
iconName={isMuted ? 'volume_off' : 'volume_up'}
ariaLabel={
isMuted
? intl.formatMessage({
id: 'videoPlayer.mute',
defaultMessage: 'Mute video',
})
: intl.formatMessage({
id: 'videoPlayer.unmute',
defaultMessage: 'Unmute video',
})
}
/>
) : null}
</div>
)
}
function getVideoPropsByVariant(
variant: VideoPlayerProps['variant'],
isActive: boolean,
autoPlay?: boolean
): VideoHTMLAttributes<HTMLVideoElement> {
switch (variant) {
case 'hero':
return {
controls: false,
controlsList: 'nodownload nofullscreen noremoteplayback',
autoPlay: autoPlay ?? true,
muted: true,
loop: true,
}
case 'inline':
default:
return {
controls: isActive,
controlsList: 'nodownload noremoteplayback',
autoPlay: autoPlay ?? isActive,
muted: true,
loop: false,
}
}
}