1
+ /* eslint react-hooks/exhaustive-deps: 1 */
1
2
import * as React from 'react'
2
3
import * as THREE from 'three'
3
- import { useEffect , useRef } from 'react'
4
+ import { forwardRef , useEffect , useImperativeHandle , useRef } from 'react'
4
5
import { useThree } from '@react-three/fiber'
5
6
import { suspend } from 'suspend-react'
6
7
import { type default as Hls , Events } from 'hls.js'
@@ -31,11 +32,22 @@ export function useVideoTexture(
31
32
muted = true ,
32
33
loop = true ,
33
34
playsInline = true ,
35
+ onVideoFrame,
34
36
...videoProps
35
37
} : {
38
+ /** Event name that will unsuspend the video */
36
39
unsuspend ?: keyof HTMLVideoElementEventMap
40
+ /** Auto start the video once unsuspended */
37
41
start ?: boolean
42
+ /** HLS config */
38
43
hls ?: Parameters < typeof getHls > [ 0 ]
44
+ /**
45
+ * request Video Frame Callback (rVFC)
46
+ *
47
+ * @see https://web.dev/requestvideoframecallback-rvfc/
48
+ * @see https://www.remotion.dev/docs/video-manipulation
49
+ * */
50
+ onVideoFrame ?: VideoFrameRequestCallback
39
51
} & Partial < Omit < HTMLVideoElement , 'children' | 'src' | 'srcObject' > > = { }
40
52
) {
41
53
const gl = useThree ( ( state ) => state . gl )
@@ -81,6 +93,9 @@ export function useVideoTexture(
81
93
[ srcOrSrcObject ]
82
94
)
83
95
96
+ const video = texture . source . data as HTMLVideoElement
97
+ useVideoFrame ( video , onVideoFrame )
98
+
84
99
useEffect ( ( ) => {
85
100
start && texture . image . play ( )
86
101
@@ -96,22 +111,45 @@ export function useVideoTexture(
96
111
}
97
112
98
113
//
114
+ // VideoTexture
115
+ //
116
+
117
+ type UseVideoTextureParams = Parameters < typeof useVideoTexture >
118
+ type VideoTexture = ReturnType < typeof useVideoTexture >
119
+
120
+ export type VideoTextureProps = {
121
+ children ?: ( texture : VideoTexture ) => React . ReactNode
122
+ src : UseVideoTextureParams [ 0 ]
123
+ } & UseVideoTextureParams [ 1 ]
124
+
125
+ export const VideoTexture = /* @__PURE__ */ forwardRef < VideoTexture , VideoTextureProps > (
126
+ ( { children, src, ...config } , fref ) => {
127
+ const texture = useVideoTexture ( src , config )
99
128
100
- type UseVideoTexture = Parameters < typeof useVideoTexture >
129
+ useEffect ( ( ) => {
130
+ return ( ) => void texture . dispose ( )
131
+ } , [ texture ] )
101
132
102
- export const VideoTexture = ( {
103
- children,
104
- src,
105
- ...config
106
- } : {
107
- children ?: ( texture : ReturnType < typeof useVideoTexture > ) => React . ReactNode
108
- src : UseVideoTexture [ 0 ]
109
- } & UseVideoTexture [ 1 ] ) => {
110
- const ret = useVideoTexture ( src , config )
133
+ useImperativeHandle ( fref , ( ) => texture , [ texture ] ) // expose texture through ref
111
134
135
+ return < > { children ?.( texture ) } </ >
136
+ }
137
+ )
138
+
139
+ // rVFC hook
140
+
141
+ const useVideoFrame = ( video : HTMLVideoElement , f ?: VideoFrameRequestCallback ) => {
112
142
useEffect ( ( ) => {
113
- return ( ) => void ret . dispose ( )
114
- } , [ ret ] )
143
+ if ( ! f ) return
144
+ if ( ! video . requestVideoFrameCallback ) return
145
+
146
+ let handle : ReturnType < ( typeof video ) [ 'requestVideoFrameCallback' ] >
147
+ const callback : VideoFrameRequestCallback = ( ...args ) => {
148
+ f ( ...args )
149
+ handle = video . requestVideoFrameCallback ( callback )
150
+ }
151
+ video . requestVideoFrameCallback ( callback )
115
152
116
- return < > { children ?.( ret ) } </ >
153
+ return ( ) => video . cancelVideoFrameCallback ( handle )
154
+ } , [ video , f ] )
117
155
}
0 commit comments