1- import { ReactElement , useEffect , useState } from 'react'
1+ import { ReactElement , useEffect , useRef , useState } from 'react'
22import { Result , Spin } from 'antd'
33import clsx from 'clsx'
4- import { useAtomValue , useSetAtom } from 'jotai'
4+ import { useAtom , useAtomValue , useSetAtom } from 'jotai'
55import { useTranslation } from 'react-i18next'
66import { useMediaQuery } from 'react-responsive'
77
@@ -14,13 +14,14 @@ import { VirtualKeyboard } from '@renderer/components/virtual-keyboard'
1414import {
1515 resolutionAtom ,
1616 serialPortStateAtom ,
17+ videoRotateAtom ,
1718 videoScaleAtom ,
1819 videoStateAtom
1920} from '@renderer/jotai/device'
2021import { isKeyboardEnableAtom } from '@renderer/jotai/keyboard'
2122import { mouseStyleAtom } from '@renderer/jotai/mouse'
2223import { camera } from '@renderer/libs/camera'
23- import { getVideoResolution } from '@renderer/libs/storage'
24+ import * as storage from '@renderer/libs/storage'
2425import type { Resolution } from '@renderer/types'
2526
2627type State = 'loading' | 'success' | 'failed'
@@ -30,17 +31,93 @@ const App = (): ReactElement => {
3031 const isBigScreen = useMediaQuery ( { minWidth : 850 } )
3132
3233 const videoScale = useAtomValue ( videoScaleAtom )
34+ const [ videoRotate , setVideoRotate ] = useAtom ( videoRotateAtom )
3335 const videoState = useAtomValue ( videoStateAtom )
3436 const serialPortState = useAtomValue ( serialPortStateAtom )
3537 const mouseStyle = useAtomValue ( mouseStyleAtom )
3638 const isKeyboardEnable = useAtomValue ( isKeyboardEnableAtom )
3739 const setResolution = useSetAtom ( resolutionAtom )
40+ const canvasRef = useRef < HTMLCanvasElement > ( null )
41+ const videoRef = useRef < HTMLVideoElement > ( null )
42+ const canvasContextRef = useRef < CanvasRenderingContext2D | null > ( null )
3843
3944 const [ state , setState ] = useState < State > ( 'loading' )
4045 const [ isConnected , setIsConnected ] = useState ( false )
4146
47+ const videoStyle = clsx (
48+ 'block select-none origin-center max-w-full max-h-full object-scale-down' ,
49+ mouseStyle
50+ )
51+
52+ const renderFrame = ( frame : VideoFrame ) => {
53+ const canvas = canvasRef . current
54+ const ctx = canvasContextRef . current
55+ if ( ! canvas || ! ctx ) return
56+
57+ ctx . clearRect ( 0 , 0 , canvas . width , canvas . height )
58+
59+ ctx . save ( )
60+ ctx . translate ( canvas . width / 2 , canvas . height / 2 )
61+ ctx . rotate ( ( videoRotate * Math . PI ) / 180 )
62+
63+ ctx . drawImage (
64+ frame ,
65+ - frame . displayWidth / 2 ,
66+ - frame . displayHeight / 2 ,
67+ frame . displayWidth ,
68+ frame . displayHeight
69+ )
70+
71+ ctx . restore ( )
72+ }
73+
74+ const setCanvas = ( ) => {
75+ const video = videoRef . current
76+ const canvas = canvasRef . current
77+
78+ if ( ! video || ! canvas || video . videoWidth === 0 ) return
79+
80+ if ( videoRotate % 180 === 0 ) {
81+ canvas . width = video . videoWidth
82+ canvas . height = video . videoHeight
83+ } else {
84+ canvas . width = video . videoHeight
85+ canvas . height = video . videoWidth
86+ }
87+
88+ if ( canvasRef . current !== null ) {
89+ canvasContextRef . current = canvasRef . current . getContext ( '2d' )
90+ }
91+
92+ if ( videoRotate !== 0 ) {
93+ processVideoFrames ( )
94+ }
95+ }
96+
97+ const processVideoFrames = ( ) => {
98+ const video = videoRef . current
99+ if ( video == null || videoRotate === 0 ) return
100+ video . requestVideoFrameCallback ( ( ) => {
101+ const frame = new VideoFrame ( video )
102+ renderFrame ( frame )
103+ frame . close ( )
104+ processVideoFrames ( )
105+ } )
106+ }
107+
42108 useEffect ( ( ) => {
43- const resolution = getVideoResolution ( )
109+ if ( videoRotate !== 0 ) {
110+ setCanvas ( )
111+ }
112+ } , [ videoRotate ] )
113+
114+ useEffect ( ( ) => {
115+ const rotate = storage . getVideoRotate ( )
116+ if ( rotate ) {
117+ setVideoRotate ( rotate )
118+ }
119+
120+ const resolution = storage . getVideoResolution ( )
44121 if ( resolution ) {
45122 setResolution ( resolution )
46123 }
@@ -120,18 +197,24 @@ const App = (): ReactElement => {
120197
121198 < video
122199 id = "video"
123- className = { clsx ( 'block min-h-[480px] min-w-[640px] select-none' , mouseStyle ) }
124- style = { {
125- transform : `scale(${ videoScale } )` ,
126- transformOrigin : 'center' ,
127- maxWidth : '100%' ,
128- maxHeight : '100%' ,
129- objectFit : 'scale-down'
130- } }
200+ className = { clsx ( videoRotate === 0 ? [ videoStyle , 'min-h-[480px] min-w-[640px]' ] : 'hidden' ) }
201+ ref = { videoRef }
131202 autoPlay
132203 playsInline
204+ onLoadedMetadata = { setCanvas }
133205 />
134206
207+ { videoRotate !== 0 && (
208+ < canvas
209+ id = "video-canvas"
210+ ref = { canvasRef }
211+ className = { videoStyle }
212+ style = { {
213+ transform : `scale(${ videoScale } )`
214+ } }
215+ />
216+ ) }
217+
135218 < VirtualKeyboard isBigScreen = { isBigScreen } />
136219 </ >
137220 )
0 commit comments