Skip to content

Commit

Permalink
feat: use video
Browse files Browse the repository at this point in the history
  • Loading branch information
wkylin committed Dec 27, 2024
1 parent d7e4cf6 commit c101256
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 10 deletions.
183 changes: 183 additions & 0 deletions src/components/hooks/useVideo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { useCallback, useEffect, useState, type RefObject } from 'react'

Check failure on line 1 in src/components/hooks/useVideo/index.tsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package

const useVideo = (ref: RefObject<HTMLVideoElement | null>) => {
const video = ref.current

const [videoState, setVideoState] = useState({
isPaused: video ? video?.paused : true,
isMuted: video ? video?.muted : false,
currentVolume: video ? video?.volume : 100,
currentTime: video ? video?.currentTime : 0,
})

const play = useCallback(() => {
video?.play()

Check notice on line 14 in src/components/hooks/useVideo/index.tsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Result of method call returning a promise is ignored

Promise returned from play is ignored
setVideoState((prev) => {
return {
...prev,
isPaused: false,
isMuted: video ? video.muted : prev.isMuted,
}
})
}, [video])

const pause = useCallback(() => {
video?.pause()
setVideoState((prev) => {
return {
...prev,
isPaused: true,
}
})
}, [video])

const handlePlayPauseControl = (e: Event) => {
setVideoState((prev) => {
return {
...prev,
isPaused: (e.target as HTMLVideoElement).paused,
}
})
}

const togglePause = useCallback(() => (video?.paused ? play() : pause()), [video, play, pause])

const handleVolume = useCallback(
(delta: number) => {
const deltaDecimal = delta / 100

if (video) {
let newVolume = video?.volume + deltaDecimal

if (newVolume >= 1) {
newVolume = 1
} else if (newVolume <= 0) {
newVolume = 0
}

video.volume = newVolume
setVideoState((prev) => {
return {
...prev,
currentVolume: newVolume * 100,
}
})
}
},
[video]
)

const handleVolumeControl = useCallback(
(e: Event) => {
if (e.target && video) {
const newVolume = (e.target as HTMLVideoElement).volume * 100

if (newVolume === videoState.currentVolume) {
handleMute(video.muted)
return
}

setVideoState((prev) => ({
...prev,
currentVolume: (e.target as HTMLVideoElement).volume * 100,
}))
}
},
[video, videoState]
)

const handleMute = useCallback(
(mute: boolean) => {
if (video) {
video.muted = mute
setVideoState((prev) => {
return {
...prev,
isMuted: mute,
}
})
}
},
[video]
)

const handleTime = useCallback(
(delta = 5) => {
if (video) {
let newTime = video.currentTime + delta

if (newTime >= video.duration) {
newTime = video.duration
} else if (newTime <= 0) {
newTime = 0
}

video.currentTime = newTime
setVideoState((prev) => {
return {
...prev,
currentTime: newTime,
}
})
}
},
[video]
)

const handleTimeControl = useCallback((e: Event) => {
setVideoState((prev) => {
return {
...prev,
currentTime: (e.target as HTMLVideoElement).currentTime,
}
})
}, [])

const toggleFullscreen = useCallback(() => {
if (document.fullscreenElement) {
document.exitFullscreen()

Check notice on line 138 in src/components/hooks/useVideo/index.tsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Result of method call returning a promise is ignored

Promise returned from exitFullscreen is ignored
} else {
video?.requestFullscreen().catch((err) => {
console.log(err)
})
}
}, [video])

useEffect(() => {
return () => {
pause()
}
}, [pause])

useEffect(() => {
if (!video) return
video.addEventListener('volumechange', handleVolumeControl)
video.addEventListener('play', handlePlayPauseControl)
video.addEventListener('pause', handlePlayPauseControl)
video.addEventListener('timeupdate', handleTimeControl)

return () => {
video.removeEventListener('volumechange', handleVolumeControl)
video.removeEventListener('play', handlePlayPauseControl)
video.removeEventListener('pause', handlePlayPauseControl)
video.removeEventListener('timeupdate', handleTimeControl)
}
}, [video])

return {
...videoState,
play,
pause,
togglePause,
increaseVolume: (increase = 5) => handleVolume(increase),
decreaseVolume: (decrease = 5) => handleVolume(decrease * -1),
mute: () => handleMute(true),
unmute: () => handleMute(false),
toggleMute: () => handleMute(!video?.muted),
forward: (increase = 5) => handleTime(increase),
back: (decrease = 5) => handleTime(decrease * -1),
toggleFullscreen,
}
}

export default useVideo
8 changes: 4 additions & 4 deletions src/components/stateless/ContentPlaceholder/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export const Word = ({ width }) => <div className={styles.word} style={{ width }

const Paragraph = ({ words }) => (
<div className={styles.paragraph}>
{words.map((width) => (
<Word width={width} />
{words.map((width, index) => (
<Word key={index} width={width} />
))}
</div>
)
Expand All @@ -35,8 +35,8 @@ const ContentPlaceholder = () => (
<Word width={245} />
<Word width={120} />
</section>
{paragraphs.map((words) => (
<Paragraph words={words} />
{paragraphs.map((words, index) => (
<Paragraph key={index} words={words} />
))}
</motion.div>
)
Expand Down
3 changes: 1 addition & 2 deletions src/components/stateless/TypedText/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ const TypedText = ({ children, delay = 110 }) => {

useEffect(() => {
if (revealedLetters === children.length) clearInterval(interval)
return () => clearInterval(interval)
}, [children, interval, revealedLetters])

useEffect(() => () => clearInterval(interval), [interval])

return (
<>
{children.substring(0, revealedLetters)}
Expand Down
4 changes: 1 addition & 3 deletions src/pages/motion/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,7 @@ const ParallaxVert = () => {
const scrYProCardX = useTransform(scrYProCard, [0, 1], ['1%', '-50%'])

const scaleSec = useTransform(scrYPro, (value) => value * 3)
useMotionValueEvent(scrYPro, 'change', (current) => {
console.log('scrYPro', current)
})
useMotionValueEvent(scrYPro, 'change', (current) => {})

Check warning on line 126 in src/pages/motion/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused parameter current

const x = useTransform(scrollY, (value) => {
return (value * 1) / 5
Expand Down
86 changes: 85 additions & 1 deletion src/pages/video/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useRef } from 'react'
import FixTabPanel from '@stateless/FixTabPanel'

import VideoJS from '@stateless/Video'
import useVideo from '@hooks/useVideo'

const MyVideo = () => {
const playerRef = useRef(null)
Expand Down Expand Up @@ -29,15 +30,98 @@ const MyVideo = () => {
player.on('dispose', () => {})
}

const useVideoRef = useRef(null)

Check warning on line 33 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant useVideoRef
const {
isPaused,

Check warning on line 35 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant isPaused
isMuted,

Check warning on line 36 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant isMuted
currentVolume,

Check warning on line 37 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant currentVolume
currentTime,

Check warning on line 38 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant currentTime
play,

Check warning on line 39 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant play
pause,

Check warning on line 40 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant pause
togglePause,

Check warning on line 41 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant togglePause
increaseVolume,

Check warning on line 42 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant increaseVolume
decreaseVolume,

Check warning on line 43 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant decreaseVolume
mute,

Check warning on line 44 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant mute
unmute,

Check warning on line 45 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant unmute
toggleMute,

Check warning on line 46 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant toggleMute
forward,

Check warning on line 47 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant forward
back,

Check warning on line 48 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant back
toggleFullscreen,

Check warning on line 49 in src/pages/video/index.jsx

View workflow job for this annotation

GitHub Actions / Qodana for JS

Unused local symbol

Unused constant toggleFullscreen
} = useVideo(videoRef)

return (
<FixTabPanel>
<video ref={videoRef} controls muted controlsList="nodownload" style={{ width: 900 }}>
<track kind="captions" />
<source src="https://media.w3.org/2010/05/sintel/trailer.mp4" type="video/mp4" />
</video>
<div style={{ width: 900 }}>
<div style={{ marginTop: 30, width: 900 }}>
<VideoJS options={videoJsOptions} onReady={handlePlayerReady} />
</div>

{/* <section style={{marginTop: 30}}>
<div>
<video ref={useVideoRef} className="w-full mb-4 h-72">
<source src="https://cuicui.day/video/google-deepmind-demo-video.mp4" type="video/mp4" />
<track
kind="captions"
srcLang="en"
src="/video/google-deepmind-demo-video.vtt"
/>
Your browser does not support the
</video>
<div className="flex flex-col gap-1">
<div className="flex gap-1 *:w-full">
<button type="button" onClick={togglePause}>
{isPaused ? "Play" : "Pause"}
</button>
<button type="button" onClick={toggleMute}>
{isMuted ? "Unmute" : "Mute"}
</button>
</div>
<p>
<input
type="range"
disabled={true}
min={0}
max={100}
value={currentVolume}
// onChange={(e) => increaseVolume(Number(e.target.value))}
/>
</p>
<div className="flex *:w-full gap-1">
<button type="button" onClick={() => increaseVolume(5)}>
Increase Volume
</button>
<button type="button" onClick={() => decreaseVolume(5)}>
Decrease Volume
</button>
</div>
<input
type="range"
min={0}
max={videoRef.current?.duration ?? 0}
value={currentTime}
onChange={(e) => {
if (videoRef.current) {
videoRef.current.currentTime = Number(e.target.value);
}
}}
/>
<div className="flex gap-1 *:w-full">
<button type="button" onClick={() => forward(10)}>
Forward 10s
</button>
<button type="button" onClick={() => back(10)}>
Back 10s
</button>
</div>
<button type="button" onClick={toggleFullscreen}>
Fullscreen
</button>
</div>
</div>
</section> */}
</FixTabPanel>
)
}
Expand Down

0 comments on commit c101256

Please sign in to comment.