1- import { useEffect , useState } from 'react' ;
1+ import { useEffect , useRef , useState } from 'react' ;
22import { Alert , Result , Spin } from 'antd' ;
33import clsx from 'clsx' ;
4- import { useAtomValue , useSetAtom } from 'jotai' ;
4+ import { useAtomValue , useSetAtom , useAtom } from 'jotai' ;
55import { useTranslation } from 'react-i18next' ;
66import { useMediaQuery } from 'react-responsive' ;
7-
87import { DeviceModal } from '@/components/device-modal' ;
98import { Keyboard } from '@/components/keyboard' ;
109import { Menu } from '@/components/menu' ;
1110import { Mouse } from '@/components/mouse' ;
1211import { VirtualKeyboard } from '@/components/virtual-keyboard' ;
13- import {
14- resolutionAtom ,
15- serialStateAtom ,
16- videoScaleAtom ,
17- videoStateAtom
18- } from '@/jotai/device.ts' ;
12+ import { resolutionAtom , serialStateAtom , videoRotateAtom , videoScaleAtom , videoStateAtom } from '@/jotai/device.ts' ;
1913import { isKeyboardEnableAtom } from '@/jotai/keyboard.ts' ;
2014import { mouseStyleAtom } from '@/jotai/mouse.ts' ;
2115import { camera } from '@/libs/camera' ;
@@ -28,15 +22,81 @@ const App = () => {
2822 const isBigScreen = useMediaQuery ( { minWidth : 850 } ) ;
2923
3024 const mouseStyle = useAtomValue ( mouseStyleAtom ) ;
31- const videoScale = useAtomValue ( videoScaleAtom ) ;
25+ const [ videoScale , setVideoScale ] = useAtom ( videoScaleAtom ) ;
26+ const [ videoRotate , setVideoRotate ] = useAtom ( videoRotateAtom ) ;
3227 const videoState = useAtomValue ( videoStateAtom ) ;
3328 const serialState = useAtomValue ( serialStateAtom ) ;
3429 const isKeyboardEnable = useAtomValue ( isKeyboardEnableAtom ) ;
3530 const setResolution = useSetAtom ( resolutionAtom ) ;
31+ const canvasRef = useRef < HTMLCanvasElement > ( null ) ;
32+ const videoRef = useRef < HTMLVideoElement > ( null ) ;
33+ const canvasContextRef = useRef < CanvasRenderingContext2D | null > ( null ) ;
3634
3735 const [ isLoading , setIsLoading ] = useState ( true ) ;
3836 const [ isCameraAvailable , setIsCameraAvailable ] = useState ( false ) ;
3937
38+ const videoStyle = clsx ( 'block select-none origin-center max-w-full max-h-full object-scale-down' , mouseStyle )
39+
40+ const renderFrame = ( frame : VideoFrame ) => {
41+ const canvas = canvasRef . current ;
42+ const ctx = canvasContextRef . current ;
43+ if ( ! canvas || ! ctx ) return ;
44+
45+ ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
46+
47+ ctx . save ( ) ;
48+ ctx . translate ( canvas . width / 2 , canvas . height / 2 ) ;
49+ ctx . rotate ( ( videoRotate * Math . PI ) / 180 ) ;
50+
51+ ctx . drawImage (
52+ frame ,
53+ - frame . displayWidth / 2 ,
54+ - frame . displayHeight / 2 ,
55+ frame . displayWidth ,
56+ frame . displayHeight
57+ ) ;
58+
59+ ctx . restore ( ) ;
60+ } ;
61+
62+ const setCanvas = ( ) => {
63+ const video = videoRef . current ;
64+ const canvas = canvasRef . current ;
65+
66+ if ( ! video || ! canvas || video . videoWidth === 0 ) return ;
67+
68+ if ( videoRotate % 180 === 0 ) {
69+ canvas . width = video . videoWidth ;
70+ canvas . height = video . videoHeight ;
71+ } else {
72+ canvas . width = video . videoHeight ;
73+ canvas . height = video . videoWidth ;
74+ }
75+
76+ if ( videoRotate !== 0 ) {
77+ processVideoFrames ( ) ;
78+ }
79+ }
80+
81+ const processVideoFrames = ( ) => {
82+ const video = videoRef . current ;
83+ if ( video == null || videoRotate === 0 ) return ;
84+ video . requestVideoFrameCallback ( ( ) => {
85+ const frame = new VideoFrame ( video ) ;
86+ renderFrame ( frame ) ;
87+ frame . close ( ) ;
88+ processVideoFrames ( ) ;
89+ } )
90+ }
91+
92+ useEffect ( ( ) => {
93+ if ( videoRotate !== 0 ) {
94+ setCanvas ( ) ;
95+ if ( canvasRef . current !== null )
96+ canvasContextRef . current = canvasRef . current . getContext ( '2d' ) ;
97+ }
98+ } , [ videoRotate ] ) ;
99+
40100 useEffect ( ( ) => {
41101 const resolution = storage . getVideoResolution ( ) ;
42102 if ( resolution ) {
@@ -45,6 +105,16 @@ const App = () => {
45105
46106 requestMediaPermissions ( resolution ) ;
47107
108+ const rotate = storage . getVideoRotate ( ) ;
109+ if ( rotate ) {
110+ setVideoRotate ( rotate ) ;
111+ }
112+
113+ const scale = storage . getVideoScale ( ) ;
114+ if ( scale ) {
115+ setVideoScale ( scale ) ;
116+ }
117+
48118 return ( ) => {
49119 camera . close ( ) ;
50120 device . serialPort . close ( ) ;
@@ -112,18 +182,24 @@ const App = () => {
112182
113183 < video
114184 id = "video"
115- className = { clsx ( 'block min-h-[480px] min-w-[640px] select-none' , mouseStyle ) }
116- style = { {
117- transform : `scale(${ videoScale } )` ,
118- transformOrigin : 'center' ,
119- maxWidth : '100%' ,
120- maxHeight : '100%' ,
121- objectFit : 'scale-down'
122- } }
185+ className = { clsx ( videoRotate === 0 ? [ videoStyle , "min-h-[480px] min-w-[640px]" ] : "hidden" ) }
186+ ref = { videoRef }
123187 autoPlay
124188 playsInline
189+ onLoadedMetadata = { setCanvas }
125190 />
126191
192+ { videoRotate !== 0 &&
193+ < canvas
194+ id = "video-canvas"
195+ ref = { canvasRef }
196+ className = { videoStyle }
197+ style = { {
198+ transform : `scale(${ videoScale } )` ,
199+ } }
200+ />
201+ }
202+
127203 < VirtualKeyboard isBigScreen = { isBigScreen } />
128204 </ >
129205 ) ;
0 commit comments