/
use-offthread-video-texture.ts
125 lines (108 loc) 路 3.25 KB
/
use-offthread-video-texture.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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import {useCallback, useLayoutEffect, useMemo, useState} from 'react';
import {
Internals,
cancelRender,
continueRender,
delayRender,
getRemotionEnvironment,
useCurrentFrame,
useVideoConfig,
} from 'remotion';
import {NoReactInternals} from 'remotion/no-react';
export type UseOffthreadVideoTextureOptions = {
src: string;
playbackRate?: number;
transparent?: boolean;
toneMapped?: boolean;
};
export const useInnerVideoTexture = ({
playbackRate,
src,
transparent,
toneMapped,
}: {
playbackRate: number;
src: string;
transparent: boolean;
toneMapped: boolean;
}) => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const mediaStartsAt = Internals.useMediaStartsAt();
const currentTime = useMemo(() => {
return (
NoReactInternals.getExpectedMediaFrameUncorrected({
frame,
playbackRate,
startFrom: -mediaStartsAt,
}) / fps
);
}, [frame, playbackRate, mediaStartsAt, fps]);
const offthreadVideoFrameSrc = useMemo(() => {
return NoReactInternals.getOffthreadVideoSource({
currentTime,
src,
transparent,
toneMapped,
});
}, [toneMapped, currentTime, src, transparent]);
const [textLoaderPromise] = useState(
() => import('three/src/loaders/TextureLoader.js'),
);
const [imageTexture, setImageTexture] = useState<THREE.Texture | null>(null);
const fetchTexture = useCallback(() => {
const imageTextureHandle = delayRender('fetch offthread video frame');
let textureLoaded: THREE.Texture | null = null;
let cleanedUp = false;
textLoaderPromise.then((loader) => {
new loader.TextureLoader()
.loadAsync(offthreadVideoFrameSrc)
.then((texture) => {
textureLoaded = texture;
if (cleanedUp) {
return;
}
setImageTexture(texture);
continueRender(imageTextureHandle);
})
.catch((err) => {
cancelRender(err);
});
});
return () => {
cleanedUp = true;
textureLoaded?.dispose();
continueRender(imageTextureHandle);
};
}, [offthreadVideoFrameSrc, textLoaderPromise]);
useLayoutEffect(() => {
const cleanup = fetchTexture();
return () => {
cleanup();
};
}, [offthreadVideoFrameSrc, fetchTexture]);
return imageTexture;
};
/**
* @description Allows you to use a video in React Three Fiber that is synchronized with Remotion's `useCurrentFrame()` using the `<OffthreadVideo>`.
* @see [Documentation](https://remotion.dev/docs/use-offthread-video-texture)
* @param {UseOffthreadVideoTextureOptions} options Configuration options including the video source (`src`), playback rate (`playbackRate`), transparency (`transparent`), and tone mapping (`toneMapped`).
* @returns {THREE.Texture | null} A THREE.Texture if available, otherwise null. To be used as a texture in 3D objects in React Three Fiber.
*/
export function useOffthreadVideoTexture({
src,
playbackRate = 1,
transparent = false,
toneMapped = true,
}: UseOffthreadVideoTextureOptions) {
if (!src) {
throw new Error('src must be provided to useOffthreadVideoTexture');
}
const {isRendering} = getRemotionEnvironment();
if (!isRendering) {
throw new Error(
'useOffthreadVideoTexture() can only be used during rendering. Use getRemotionEnvironment().isRendering to render it conditionally.',
);
}
return useInnerVideoTexture({playbackRate, src, transparent, toneMapped});
}