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",
|
"arrowParens": "avoid",
|
||||||
"endOfLine": "lf",
|
"endOfLine": "lf",
|
||||||
"tabWidth": 2,
|
"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 { AdbScrcpyClient, AdbScrcpyOptions1_22 } from '@yume-chan/adb-scrcpy'
|
||||||
import { WebCodecsDecoder } from '@yume-chan/scrcpy-decoder-webcodecs'
|
import { WebCodecsDecoder } from '@yume-chan/scrcpy-decoder-webcodecs'
|
||||||
import {
|
import {
|
||||||
|
AndroidKeyCode,
|
||||||
|
AndroidKeyEventAction,
|
||||||
AndroidMotionEventAction,
|
AndroidMotionEventAction,
|
||||||
ScrcpyLogLevel1_18,
|
ScrcpyLogLevel1_18,
|
||||||
ScrcpyOptions1_25,
|
ScrcpyOptions1_25,
|
||||||
ScrcpyPointerId,
|
ScrcpyPointerId,
|
||||||
ScrcpyVideoCodecId
|
ScrcpyVideoCodecId
|
||||||
} from '@yume-chan/scrcpy'
|
} from '@yume-chan/scrcpy'
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useRef, useState } from 'react'
|
||||||
import {
|
import { ReadableStream, WritableStream, Consumable, DecodeUtf8Stream } from '@yume-chan/stream-extra'
|
||||||
ReadableStream,
|
|
||||||
WritableStream,
|
|
||||||
Consumable,
|
|
||||||
DecodeUtf8Stream
|
|
||||||
} from '@yume-chan/stream-extra'
|
|
||||||
import { useBeforeUnload } from 'react-router-dom'
|
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 AndroidPage: React.FC = () => {
|
||||||
const { adb, manager, device, setAdb, setDevice } = useAdb(
|
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() {
|
async function createNewConnection() {
|
||||||
device?.raw.forget()
|
device?.raw.forget()
|
||||||
setDevice(undefined)
|
setDevice(undefined)
|
||||||
|
|
@ -118,9 +62,6 @@ const AndroidPage: React.FC = () => {
|
||||||
|
|
||||||
const adb: Adb = new Adb(transport)
|
const adb: Adb = new Adb(transport)
|
||||||
setAdb(adb)
|
setAdb(adb)
|
||||||
|
|
||||||
setDeviceSerial(selectedDevice.serial)
|
|
||||||
setIsAlreadyConnected(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [client, setClient] = useState<AdbScrcpyClient | undefined>()
|
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
|
// when user close or refresh the page, close the adb connection
|
||||||
useBeforeUnload(
|
useBeforeUnload(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
|
decoder?.dispose()
|
||||||
client?.close()
|
client?.close()
|
||||||
adb?.close()
|
adb?.close()
|
||||||
device?.raw.forget()
|
|
||||||
|
setDecoder(undefined)
|
||||||
|
setClient(undefined)
|
||||||
|
setAdb(undefined)
|
||||||
}, [])
|
}, [])
|
||||||
)
|
)
|
||||||
|
|
||||||
async function scrcpyConnect() {
|
async function scrcpyConnect() {
|
||||||
const server: ArrayBuffer = await fetch(
|
const server: ArrayBuffer = await fetch(new URL('../scrcpy/scrcpy_server_v1.25', import.meta.url)).then(res => res.arrayBuffer())
|
||||||
new URL('../scrcpy/scrcpy_server_v1.25', import.meta.url)
|
|
||||||
).then(res => res.arrayBuffer())
|
|
||||||
|
|
||||||
await AdbScrcpyClient.pushServer(
|
await AdbScrcpyClient.pushServer(
|
||||||
adb!,
|
adb!,
|
||||||
|
|
@ -151,9 +94,7 @@ const AndroidPage: React.FC = () => {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const res = await adb!.subprocess.spawn(
|
const res = await adb!.subprocess.spawn('CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 1.25')
|
||||||
'CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 1.25'
|
|
||||||
)
|
|
||||||
|
|
||||||
res.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo(
|
res.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo(
|
||||||
new WritableStream({
|
new WritableStream({
|
||||||
|
|
@ -165,77 +106,90 @@ const AndroidPage: React.FC = () => {
|
||||||
|
|
||||||
const scrcpyOption = new ScrcpyOptions1_25({
|
const scrcpyOption = new ScrcpyOptions1_25({
|
||||||
maxFps: 60,
|
maxFps: 60,
|
||||||
|
bitRate: 4000000,
|
||||||
|
stayAwake: true,
|
||||||
control: true,
|
control: true,
|
||||||
logLevel: ScrcpyLogLevel1_18.Debug
|
logLevel: ScrcpyLogLevel1_18.Debug
|
||||||
})
|
})
|
||||||
const _client = await AdbScrcpyClient.start(
|
const _client = await AdbScrcpyClient.start(adb!, '/data/local/tmp/scrcpy-server.jar', '1.25', new AdbScrcpyOptions1_22(scrcpyOption))
|
||||||
adb!,
|
|
||||||
'/data/local/tmp/scrcpy-server.jar',
|
|
||||||
'1.25',
|
|
||||||
new AdbScrcpyOptions1_22(scrcpyOption)
|
|
||||||
)
|
|
||||||
|
|
||||||
const videoStream: AdbScrcpyVideoStream | undefined =
|
const videoStream: AdbScrcpyVideoStream | undefined = await _client?.videoStream
|
||||||
await _client?.videoStream
|
|
||||||
|
|
||||||
if (videoStream) {
|
if (videoStream) {
|
||||||
const _decoder = new WebCodecsDecoder(ScrcpyVideoCodecId.H264)
|
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)
|
screenRef.current?.appendChild(_decoder.renderer)
|
||||||
videoStream?.stream.pipeTo(_decoder.writable)
|
videoStream?.stream.pipeTo(_decoder.writable)
|
||||||
setDecoder(_decoder)
|
setDecoder(_decoder)
|
||||||
|
|
||||||
if (_client.controlMessageWriter) {
|
if (_client.controlMessageWriter) {
|
||||||
_decoder.renderer.addEventListener('mousedown', e => {
|
_decoder.renderer.addEventListener('mousedown', e => {
|
||||||
|
// client width and height 700 x 400
|
||||||
const react = _decoder.renderer.getBoundingClientRect()
|
const react = _decoder.renderer.getBoundingClientRect()
|
||||||
const x = e.clientX - react.left
|
|
||||||
const y = e.clientY - react.top
|
// normalize to _decoder.renderer.width and height 1080 x 1920
|
||||||
console.log('mouse down at ' + x + ' ' + y)
|
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({
|
_client.controlMessageWriter?.injectTouch({
|
||||||
action: AndroidMotionEventAction.Down,
|
action: AndroidMotionEventAction.Down,
|
||||||
pointerId: ScrcpyPointerId.Mouse,
|
pointerId: ScrcpyPointerId.Mouse | ScrcpyPointerId.Finger,
|
||||||
pointerX: x,
|
pointerX: x,
|
||||||
pointerY: y,
|
pointerY: y,
|
||||||
pressure: 1,
|
pressure: 1,
|
||||||
screenWidth: _decoder.renderer.clientWidth,
|
screenWidth: _decoder.renderer.width,
|
||||||
screenHeight: _decoder.renderer.clientHeight,
|
screenHeight: _decoder.renderer.height,
|
||||||
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,
|
|
||||||
buttons: 0,
|
buttons: 0,
|
||||||
actionButton: 0
|
actionButton: 0
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
_decoder.renderer.addEventListener('mouseup', e => {
|
_decoder.renderer.addEventListener('mouseup', e => {
|
||||||
|
// client width and height 700 x 400
|
||||||
const react = _decoder.renderer.getBoundingClientRect()
|
const react = _decoder.renderer.getBoundingClientRect()
|
||||||
const x = e.clientX - react.left
|
|
||||||
const y = e.clientY - react.top
|
// normalize to _decoder.renderer.width and height 1080 x 1920
|
||||||
console.log('mouse up at ' + x + ' ' + y)
|
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({
|
_client.controlMessageWriter?.injectTouch({
|
||||||
action: AndroidMotionEventAction.Up,
|
action: AndroidMotionEventAction.Up,
|
||||||
pointerId: ScrcpyPointerId.Mouse,
|
pointerId: ScrcpyPointerId.Mouse,
|
||||||
pointerX: x,
|
pointerX: x,
|
||||||
pointerY: y,
|
pointerY: y,
|
||||||
pressure: 1,
|
pressure: 1,
|
||||||
screenWidth: _decoder.renderer.clientWidth,
|
screenWidth: _decoder.renderer.width,
|
||||||
screenHeight: _decoder.renderer.clientHeight,
|
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,
|
buttons: 0,
|
||||||
actionButton: 0
|
actionButton: 0
|
||||||
})
|
})
|
||||||
|
|
@ -251,9 +205,6 @@ const AndroidPage: React.FC = () => {
|
||||||
adb?.close()
|
adb?.close()
|
||||||
setClient(undefined)
|
setClient(undefined)
|
||||||
setAdb(undefined)
|
setAdb(undefined)
|
||||||
setIsAlreadyConnected(false)
|
|
||||||
setDeviceSerial('')
|
|
||||||
device?.raw.forget()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrcpyDisconnect() {
|
function scrcpyDisconnect() {
|
||||||
|
|
@ -271,40 +222,62 @@ const AndroidPage: React.FC = () => {
|
||||||
console.log('[rebootDevice] res: ', res)
|
console.log('[rebootDevice] res: ', res)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function killScrcpyServer() {
|
function goHome() {
|
||||||
const res = await adb?.subprocess.spawn(
|
client?.controlMessageWriter?.injectKeyCode({
|
||||||
'kill -9 $(pidof -s com.genymobile.scrcpy.Server)'
|
action: AndroidKeyEventAction.Up,
|
||||||
)
|
keyCode: AndroidKeyCode.AndroidHome,
|
||||||
res?.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo(
|
metaState: 0,
|
||||||
new WritableStream({
|
repeat: 0
|
||||||
write(chunk) {
|
})
|
||||||
console.log(chunk)
|
}
|
||||||
}
|
|
||||||
|
function goBack() {
|
||||||
|
client?.controlMessageWriter?.injectKeyCode({
|
||||||
|
action: AndroidKeyEventAction.Up,
|
||||||
|
keyCode: AndroidKeyCode.AndroidBack,
|
||||||
|
metaState: 0,
|
||||||
|
repeat: 0
|
||||||
})
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-y-auto flex flex-row w-[1080px] h-full">
|
<div className="flex justify-center items-center mx-auto">
|
||||||
<div ref={screenRef} className="flex-grow-[0.7] h-full"></div>
|
<div className="flex justify-around items-start mx-auto w-full h-full">
|
||||||
<div className="justify-center items-center flex-grow-[0.3]">
|
<div>
|
||||||
<div className="flex flex-col">
|
<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
|
<button
|
||||||
className="btn btn-primary"
|
|
||||||
onClick={adb ? disconnectAdb : createNewConnection}
|
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'}
|
{adb ? 'Disconnect' : 'Connect'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary"
|
onClick={client ? scrcpyDisconnect : scrcpyConnect}
|
||||||
onClick={adb && !client ? scrcpyConnect : scrcpyDisconnect}
|
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>
|
</button>
|
||||||
{adb ? <button onClick={rebootDevice}>Reboot</button> : ''}
|
|
||||||
{adb ? <button onClick={killScrcpyServer}>Kill Scrcpy</button> : ''}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white">{adb && device ? device.name : ''}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue