-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathuseVideoCapture.ts
110 lines (97 loc) · 2.99 KB
/
useVideoCapture.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import {useCallback, useEffect, useState} from 'react'
import useAsyncEffect from 'use-async-effect'
interface UseVideoCaptureProps {
videoElement?: HTMLVideoElement | null
canvasElement?: HTMLCanvasElement | null
onFrame: (frameData: string) => void
deviceId?: string
mode?: 'webcam' | 'screen'
fps?: number
setActive: (active: boolean) => void
}
const useVideoCapture = ({
videoElement,
canvasElement,
onFrame,
deviceId,
mode,
fps = 1,
setActive,
}: UseVideoCaptureProps) => {
const [stream, setStream] = useState<MediaStream | null>(null)
const [isCapturing, setIsCapturing] = useState(false)
const cleanup = useCallback(() => {
if (stream) {
stream.getTracks().forEach(track => track.stop())
}
if (videoElement) {
videoElement.srcObject = null
}
setStream(null)
setIsCapturing(false)
setActive(false)
}, [stream, videoElement, setActive])
useAsyncEffect(async () => {
let intervalId: NodeJS.Timeout
try {
if (!videoElement || !canvasElement || !mode) {
throw new Error('Source disappeared')
}
if (stream) return
let newStream: MediaStream | null = null
if (mode === 'webcam' && deviceId) {
newStream = await navigator.mediaDevices.getUserMedia({
video: deviceId ? {deviceId: {exact: deviceId}} : true,
})
}
if (mode === 'screen') {
newStream = await navigator.mediaDevices.getDisplayMedia({video: true})
}
if (!newStream) {
throw new Error('Could not retrieve the stream')
}
if (mode === 'screen') {
newStream.getVideoTracks()[0].onended = () => {
cleanup()
}
}
videoElement.srcObject = newStream
videoElement.playsInline = true
videoElement.autoplay = true
await new Promise<void>(resolve => {
videoElement.onloadedmetadata = () => {
canvasElement.width = videoElement.videoWidth
canvasElement.height = videoElement.videoHeight
resolve()
}
})
await videoElement.play()
const ctx = canvasElement.getContext('2d')
if (!ctx) {
throw new Error('No canvas context found')
}
setIsCapturing(true)
setStream(newStream)
intervalId = setInterval(() => {
if (videoElement.readyState === videoElement.HAVE_ENOUGH_DATA) {
ctx.drawImage(videoElement, 0, 0)
const frameData = canvasElement.toDataURL('image/jpeg').split(',')[1]
onFrame(frameData)
}
}, 1000 / fps)
} catch (err) {
console.log('Stream acquisition failed:', err)
clearInterval(intervalId)
cleanup()
}
return () => {
clearInterval(intervalId)
cleanup()
}
}, [videoElement, canvasElement, mode, stream, cleanup, setActive, deviceId])
useEffect(() => {
if (!isCapturing || !videoElement || !canvasElement) return
}, [isCapturing, videoElement, canvasElement, fps, onFrame])
return {isCapturing}
}
export default useVideoCapture