update scrcpy
This commit is contained in:
parent
bdd3762cd1
commit
ad8913f3c5
3 changed files with 109 additions and 162 deletions
|
|
@ -6,5 +6,5 @@
|
|||
"arrowParens": "avoid",
|
||||
"endOfLine": "lf",
|
||||
"tabWidth": 2,
|
||||
"proseWrap": "preserve"
|
||||
"printWidth": 150
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import { create } from 'zustand'
|
||||
import { createJSONStorage, persist } from 'zustand/middleware'
|
||||
|
||||
interface AdbStore {
|
||||
deviceSerial: string
|
||||
isAlreadyConnected: boolean
|
||||
setDeviceSerial: (deviceSerial: string) => void
|
||||
setIsAlreadyConnected: (isAlreadyConnected: boolean) => void
|
||||
}
|
||||
|
||||
const useAdbStore = create(
|
||||
persist<AdbStore>(
|
||||
set => ({
|
||||
deviceSerial: '',
|
||||
isAlreadyConnected: false,
|
||||
setDeviceSerial: deviceSerial => set({ deviceSerial }),
|
||||
setIsAlreadyConnected: isAlreadyConnected => set({ isAlreadyConnected })
|
||||
}),
|
||||
{
|
||||
name: 'adb',
|
||||
storage: createJSONStorage(() => sessionStorage)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
export default useAdbStore
|
||||
|
|
@ -7,21 +7,19 @@ import type { AdbScrcpyVideoStream } from '@yume-chan/adb-scrcpy'
|
|||
import { AdbScrcpyClient, AdbScrcpyOptions1_22 } from '@yume-chan/adb-scrcpy'
|
||||
import { WebCodecsDecoder } from '@yume-chan/scrcpy-decoder-webcodecs'
|
||||
import {
|
||||
AndroidKeyCode,
|
||||
AndroidKeyEventAction,
|
||||
AndroidMotionEventAction,
|
||||
ScrcpyLogLevel1_18,
|
||||
ScrcpyOptions1_25,
|
||||
ScrcpyPointerId,
|
||||
ScrcpyVideoCodecId
|
||||
} from '@yume-chan/scrcpy'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
ReadableStream,
|
||||
WritableStream,
|
||||
Consumable,
|
||||
DecodeUtf8Stream
|
||||
} from '@yume-chan/stream-extra'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { ReadableStream, WritableStream, Consumable, DecodeUtf8Stream } from '@yume-chan/stream-extra'
|
||||
import { useBeforeUnload } from 'react-router-dom'
|
||||
import useAdbStore from '../hooks/adbStore'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { ArrowLeftIcon, HomeIcon } from '@radix-ui/react-icons'
|
||||
|
||||
const AndroidPage: React.FC = () => {
|
||||
const { adb, manager, device, setAdb, setDevice } = useAdb(
|
||||
|
|
@ -34,60 +32,6 @@ const AndroidPage: React.FC = () => {
|
|||
}))
|
||||
)
|
||||
|
||||
const {
|
||||
isAlreadyConnected,
|
||||
deviceSerial,
|
||||
setIsAlreadyConnected,
|
||||
setDeviceSerial
|
||||
} = useAdbStore(
|
||||
useShallow(state => ({
|
||||
isAlreadyConnected: state.isAlreadyConnected,
|
||||
setIsAlreadyConnected: state.setIsAlreadyConnected,
|
||||
deviceSerial: state.deviceSerial,
|
||||
setDeviceSerial: state.setDeviceSerial
|
||||
}))
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (isAlreadyConnected) {
|
||||
const reconnectAdb = async () => {
|
||||
const device = await manager
|
||||
?.getDevices([
|
||||
{
|
||||
...ADB_DEFAULT_DEVICE_FILTER,
|
||||
serialNumber: deviceSerial
|
||||
}
|
||||
])
|
||||
.then(devices => devices[0])
|
||||
|
||||
if (!device) {
|
||||
// clear state
|
||||
setIsAlreadyConnected(false)
|
||||
setDeviceSerial('')
|
||||
return
|
||||
}
|
||||
|
||||
const connection = await device.connect()
|
||||
|
||||
const credentialStore: AdbWebCredentialStore =
|
||||
new AdbWebCredentialStore()
|
||||
|
||||
const transport = await AdbDaemonTransport.authenticate({
|
||||
serial: deviceSerial,
|
||||
connection: connection,
|
||||
credentialStore: credentialStore
|
||||
})
|
||||
|
||||
const adb: Adb = new Adb(transport)
|
||||
|
||||
setAdb(adb)
|
||||
setDevice(device)
|
||||
}
|
||||
|
||||
reconnectAdb()
|
||||
}
|
||||
}, [])
|
||||
|
||||
async function createNewConnection() {
|
||||
device?.raw.forget()
|
||||
setDevice(undefined)
|
||||
|
|
@ -118,9 +62,6 @@ const AndroidPage: React.FC = () => {
|
|||
|
||||
const adb: Adb = new Adb(transport)
|
||||
setAdb(adb)
|
||||
|
||||
setDeviceSerial(selectedDevice.serial)
|
||||
setIsAlreadyConnected(true)
|
||||
}
|
||||
|
||||
const [client, setClient] = useState<AdbScrcpyClient | undefined>()
|
||||
|
|
@ -130,16 +71,18 @@ const AndroidPage: React.FC = () => {
|
|||
// when user close or refresh the page, close the adb connection
|
||||
useBeforeUnload(
|
||||
useCallback(() => {
|
||||
decoder?.dispose()
|
||||
client?.close()
|
||||
adb?.close()
|
||||
device?.raw.forget()
|
||||
|
||||
setDecoder(undefined)
|
||||
setClient(undefined)
|
||||
setAdb(undefined)
|
||||
}, [])
|
||||
)
|
||||
|
||||
async function scrcpyConnect() {
|
||||
const server: ArrayBuffer = await fetch(
|
||||
new URL('../scrcpy/scrcpy_server_v1.25', import.meta.url)
|
||||
).then(res => res.arrayBuffer())
|
||||
const server: ArrayBuffer = await fetch(new URL('../scrcpy/scrcpy_server_v1.25', import.meta.url)).then(res => res.arrayBuffer())
|
||||
|
||||
await AdbScrcpyClient.pushServer(
|
||||
adb!,
|
||||
|
|
@ -151,9 +94,7 @@ const AndroidPage: React.FC = () => {
|
|||
})
|
||||
)
|
||||
|
||||
const res = await adb!.subprocess.spawn(
|
||||
'CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 1.25'
|
||||
)
|
||||
const res = await adb!.subprocess.spawn('CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 1.25')
|
||||
|
||||
res.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo(
|
||||
new WritableStream({
|
||||
|
|
@ -165,77 +106,90 @@ const AndroidPage: React.FC = () => {
|
|||
|
||||
const scrcpyOption = new ScrcpyOptions1_25({
|
||||
maxFps: 60,
|
||||
bitRate: 4000000,
|
||||
stayAwake: true,
|
||||
control: true,
|
||||
logLevel: ScrcpyLogLevel1_18.Debug
|
||||
})
|
||||
const _client = await AdbScrcpyClient.start(
|
||||
adb!,
|
||||
'/data/local/tmp/scrcpy-server.jar',
|
||||
'1.25',
|
||||
new AdbScrcpyOptions1_22(scrcpyOption)
|
||||
)
|
||||
const _client = await AdbScrcpyClient.start(adb!, '/data/local/tmp/scrcpy-server.jar', '1.25', new AdbScrcpyOptions1_22(scrcpyOption))
|
||||
|
||||
const videoStream: AdbScrcpyVideoStream | undefined =
|
||||
await _client?.videoStream
|
||||
const videoStream: AdbScrcpyVideoStream | undefined = await _client?.videoStream
|
||||
|
||||
if (videoStream) {
|
||||
const _decoder = new WebCodecsDecoder(ScrcpyVideoCodecId.H264)
|
||||
|
||||
_decoder.renderer.style.maxHeight = '900px'
|
||||
_decoder.renderer.style.width = '100%'
|
||||
_decoder.renderer.style.height = '100%'
|
||||
|
||||
if (screenRef.current && screenRef.current.firstChild) {
|
||||
screenRef.current.removeChild(screenRef.current.firstChild)
|
||||
}
|
||||
screenRef.current?.appendChild(_decoder.renderer)
|
||||
videoStream?.stream.pipeTo(_decoder.writable)
|
||||
setDecoder(_decoder)
|
||||
|
||||
if (_client.controlMessageWriter) {
|
||||
_decoder.renderer.addEventListener('mousedown', e => {
|
||||
// client width and height 700 x 400
|
||||
const react = _decoder.renderer.getBoundingClientRect()
|
||||
const x = e.clientX - react.left
|
||||
const y = e.clientY - react.top
|
||||
console.log('mouse down at ' + x + ' ' + y)
|
||||
|
||||
// normalize to _decoder.renderer.width and height 1080 x 1920
|
||||
const x = ((e.clientX - react.left) * _decoder.renderer.width) / react.width
|
||||
const y = ((e.clientY - react.top) * _decoder.renderer.height) / react.height
|
||||
|
||||
//console.log('mouse down at ' + x + ' ' + y)
|
||||
_client.controlMessageWriter?.injectTouch({
|
||||
action: AndroidMotionEventAction.Down,
|
||||
pointerId: ScrcpyPointerId.Mouse,
|
||||
pointerId: ScrcpyPointerId.Mouse | ScrcpyPointerId.Finger,
|
||||
pointerX: x,
|
||||
pointerY: y,
|
||||
pressure: 1,
|
||||
screenWidth: _decoder.renderer.clientWidth,
|
||||
screenHeight: _decoder.renderer.clientHeight,
|
||||
buttons: 0,
|
||||
actionButton: 0
|
||||
})
|
||||
})
|
||||
|
||||
_decoder.renderer.addEventListener('mousemove', e => {
|
||||
const react = _decoder.renderer.getBoundingClientRect()
|
||||
const x = e.clientX - react.left
|
||||
const y = e.clientY - react.top
|
||||
console.log('mouse move at ' + x + ' ' + y)
|
||||
_client.controlMessageWriter?.injectTouch({
|
||||
action: AndroidMotionEventAction.Move,
|
||||
pointerId: ScrcpyPointerId.Mouse,
|
||||
pointerX: x,
|
||||
pointerY: y,
|
||||
pressure: 1,
|
||||
screenWidth: _decoder.renderer.clientWidth,
|
||||
screenHeight: _decoder.renderer.clientHeight,
|
||||
screenWidth: _decoder.renderer.width,
|
||||
screenHeight: _decoder.renderer.height,
|
||||
buttons: 0,
|
||||
actionButton: 0
|
||||
})
|
||||
})
|
||||
|
||||
_decoder.renderer.addEventListener('mouseup', e => {
|
||||
// client width and height 700 x 400
|
||||
const react = _decoder.renderer.getBoundingClientRect()
|
||||
const x = e.clientX - react.left
|
||||
const y = e.clientY - react.top
|
||||
console.log('mouse up at ' + x + ' ' + y)
|
||||
|
||||
// normalize to _decoder.renderer.width and height 1080 x 1920
|
||||
const x = ((e.clientX - react.left) * _decoder.renderer.width) / react.width
|
||||
const y = ((e.clientY - react.top) * _decoder.renderer.height) / react.height
|
||||
|
||||
//console.log('mouse up at ' + x + ' ' + y)
|
||||
_client.controlMessageWriter?.injectTouch({
|
||||
action: AndroidMotionEventAction.Up,
|
||||
pointerId: ScrcpyPointerId.Mouse,
|
||||
pointerX: x,
|
||||
pointerY: y,
|
||||
pressure: 1,
|
||||
screenWidth: _decoder.renderer.clientWidth,
|
||||
screenHeight: _decoder.renderer.clientHeight,
|
||||
screenWidth: _decoder.renderer.width,
|
||||
screenHeight: _decoder.renderer.height,
|
||||
buttons: 0,
|
||||
actionButton: 0
|
||||
})
|
||||
})
|
||||
|
||||
_decoder.renderer.addEventListener('mousemove', e => {
|
||||
// client width and height 700 x 400
|
||||
const react = _decoder.renderer.getBoundingClientRect()
|
||||
|
||||
// normalize to _decoder.renderer.width and height 1080 x 1920
|
||||
const x = ((e.clientX - react.left) * _decoder.renderer.width) / react.width
|
||||
const y = ((e.clientY - react.top) * _decoder.renderer.height) / react.height
|
||||
|
||||
//console.log('mouse move at ' + x + ' ' + y)
|
||||
_client.controlMessageWriter?.injectTouch({
|
||||
action: AndroidMotionEventAction.Move,
|
||||
pointerId: ScrcpyPointerId.Mouse,
|
||||
pointerX: x,
|
||||
pointerY: y,
|
||||
pressure: 1,
|
||||
screenWidth: _decoder.renderer.width,
|
||||
screenHeight: _decoder.renderer.height,
|
||||
buttons: 0,
|
||||
actionButton: 0
|
||||
})
|
||||
|
|
@ -251,9 +205,6 @@ const AndroidPage: React.FC = () => {
|
|||
adb?.close()
|
||||
setClient(undefined)
|
||||
setAdb(undefined)
|
||||
setIsAlreadyConnected(false)
|
||||
setDeviceSerial('')
|
||||
device?.raw.forget()
|
||||
}
|
||||
|
||||
function scrcpyDisconnect() {
|
||||
|
|
@ -271,40 +222,62 @@ const AndroidPage: React.FC = () => {
|
|||
console.log('[rebootDevice] res: ', res)
|
||||
}
|
||||
|
||||
async function killScrcpyServer() {
|
||||
const res = await adb?.subprocess.spawn(
|
||||
'kill -9 $(pidof -s com.genymobile.scrcpy.Server)'
|
||||
)
|
||||
res?.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo(
|
||||
new WritableStream({
|
||||
write(chunk) {
|
||||
console.log(chunk)
|
||||
}
|
||||
})
|
||||
)
|
||||
function goHome() {
|
||||
client?.controlMessageWriter?.injectKeyCode({
|
||||
action: AndroidKeyEventAction.Up,
|
||||
keyCode: AndroidKeyCode.AndroidHome,
|
||||
metaState: 0,
|
||||
repeat: 0
|
||||
})
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
client?.controlMessageWriter?.injectKeyCode({
|
||||
action: AndroidKeyEventAction.Up,
|
||||
keyCode: AndroidKeyCode.AndroidBack,
|
||||
metaState: 0,
|
||||
repeat: 0
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-y-auto flex flex-row w-[1080px] h-full">
|
||||
<div ref={screenRef} className="flex-grow-[0.7] h-full"></div>
|
||||
<div className="justify-center items-center flex-grow-[0.3]">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex justify-center items-center mx-auto">
|
||||
<div className="flex justify-around items-start mx-auto w-full h-full">
|
||||
<div>
|
||||
<div ref={screenRef} className="min-h-[700px] min-w-[400px] max-h-[700px] max-w-[400px] bg-gray-700"></div>
|
||||
{screenRef.current?.firstChild ? (
|
||||
<div className="flex justify-around gap-4 mt-3">
|
||||
<Button onClick={goBack} className="flex-grow">
|
||||
<ArrowLeftIcon className="mr-3" />
|
||||
Back
|
||||
</Button>
|
||||
<Button onClick={goHome} className="flex-grow">
|
||||
<HomeIcon className="mr-3" />
|
||||
Home
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={adb ? disconnectAdb : createNewConnection}
|
||||
className="bg-white px-4 py-2 border flex gap-2 border-slate-200 rounded-lg text-slate-700 hover:border-slate-400 hover:text-slate-900 hover:shadow transition duration-150"
|
||||
>
|
||||
{adb ? 'Disconnect' : 'Connect'}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={adb && !client ? scrcpyConnect : scrcpyDisconnect}
|
||||
onClick={client ? scrcpyDisconnect : scrcpyConnect}
|
||||
className="bg-white px-4 py-2 border flex gap-2 border-slate-200 rounded-lg text-slate-700 hover:border-slate-400 hover:text-slate-900 hover:shadow transition duration-150"
|
||||
>
|
||||
{client ? 'Disconnect' : 'Connect'}
|
||||
{client ? 'Disconnect Scrcpy' : 'Connect Scrcpy'}
|
||||
</button>
|
||||
<button
|
||||
onClick={rebootDevice}
|
||||
className="bg-white px-4 py-2 border flex gap-2 border-slate-200 rounded-lg text-slate-700 hover:border-slate-400 hover:text-slate-900 hover:shadow transition duration-150"
|
||||
>
|
||||
Reboot Device
|
||||
</button>
|
||||
{adb ? <button onClick={rebootDevice}>Reboot</button> : ''}
|
||||
{adb ? <button onClick={killScrcpyServer}>Kill Scrcpy</button> : ''}
|
||||
</div>
|
||||
<div className="bg-white">{adb && device ? device.name : ''}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue