mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-05 02:39:26 +02:00
parent
6750bbc367
commit
4d0f1a11cb
6 changed files with 135 additions and 59 deletions
|
@ -417,6 +417,8 @@ class ScrcpyPageState {
|
|||
logLevel: ScrcpyLogLevel.Debug,
|
||||
bitRate: 4_000_000,
|
||||
tunnelForward: this.tunnelForward,
|
||||
sendDeviceMeta: false,
|
||||
sendDummyByte: false,
|
||||
})
|
||||
);
|
||||
if (encoders.length === 0) {
|
||||
|
@ -468,6 +470,8 @@ class ScrcpyPageState {
|
|||
lockVideoOrientation: ScrcpyScreenOrientation.Unlocked,
|
||||
tunnelForward: this.tunnelForward,
|
||||
encoderName: this.selectedEncoder ?? encoders[0],
|
||||
sendDeviceMeta: false,
|
||||
sendDummyByte: false,
|
||||
codecOptions: new CodecOptions({
|
||||
profile: decoder.maxProfile,
|
||||
level: decoder.maxLevel,
|
||||
|
|
0
libraries/scrcpy/scripts/fetch-server.cjs
Normal file → Executable file
0
libraries/scrcpy/scripts/fetch-server.cjs
Normal file → Executable file
|
@ -25,11 +25,6 @@ function* splitLines(text: string): Generator<string, void, void> {
|
|||
}
|
||||
}
|
||||
|
||||
const Size =
|
||||
new Struct()
|
||||
.uint16('width')
|
||||
.uint16('height');
|
||||
|
||||
const VideoPacket =
|
||||
new Struct()
|
||||
.int64('pts')
|
||||
|
@ -95,6 +90,8 @@ export class ScrcpyClient {
|
|||
// Provide an invalid encoder name
|
||||
// So the server will return all available encoders
|
||||
options.value.encoderName = '_';
|
||||
// Disable control for faster connection in 1.22+
|
||||
options.value.control = false;
|
||||
|
||||
// Scrcpy server will open connections, before initializing encoder
|
||||
// Thus although an invalid encoder name is given, the start process will success
|
||||
|
@ -227,14 +224,6 @@ export class ScrcpyClient {
|
|||
}
|
||||
|
||||
try {
|
||||
// Device name, we don't need it
|
||||
await this.videoStream.read(64);
|
||||
|
||||
// Initial video size
|
||||
const { width, height } = await Size.deserialize(this.videoStream);
|
||||
this._screenWidth = width;
|
||||
this._screenHeight = height;
|
||||
|
||||
let buffer: ArrayBuffer | undefined;
|
||||
while (this._running) {
|
||||
const { pts, data } = await VideoPacket.deserialize(this.videoStream);
|
||||
|
@ -307,7 +296,8 @@ export class ScrcpyClient {
|
|||
|
||||
private async receiveControl() {
|
||||
if (!this.controlStream) {
|
||||
throw new Error('receiveControl started before initialization');
|
||||
// control disabled
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -329,32 +319,38 @@ export class ScrcpyClient {
|
|||
}
|
||||
}
|
||||
|
||||
public async injectKeyCode(message: Omit<ScrcpyInjectKeyCodeControlMessage, 'type'>) {
|
||||
if (!this.controlStream) {
|
||||
throw new Error('injectKeyCode called before initialization');
|
||||
private checkControlStream(caller: string) {
|
||||
if (!this._running) {
|
||||
throw new Error(`${caller} called before start`);
|
||||
}
|
||||
|
||||
await this.controlStream.write(ScrcpyInjectKeyCodeControlMessage.serialize({
|
||||
if (!this.controlStream) {
|
||||
throw new Error(`${caller} called with control disabled`);
|
||||
}
|
||||
|
||||
return this.controlStream;
|
||||
}
|
||||
|
||||
public async injectKeyCode(message: Omit<ScrcpyInjectKeyCodeControlMessage, 'type'>) {
|
||||
const controlStream = this.checkControlStream('injectKeyCode');
|
||||
|
||||
await controlStream.write(ScrcpyInjectKeyCodeControlMessage.serialize({
|
||||
...message,
|
||||
type: ScrcpyControlMessageType.InjectKeycode,
|
||||
}));
|
||||
}
|
||||
|
||||
public async injectText(text: string) {
|
||||
if (!this.controlStream) {
|
||||
throw new Error('injectText called before initialization');
|
||||
}
|
||||
const controlStream = this.checkControlStream('injectText');
|
||||
|
||||
await this.controlStream.write(ScrcpyInjectTextControlMessage.serialize({
|
||||
await controlStream.write(ScrcpyInjectTextControlMessage.serialize({
|
||||
type: ScrcpyControlMessageType.InjectText,
|
||||
text,
|
||||
}));
|
||||
}
|
||||
|
||||
public async injectTouch(message: Omit<ScrcpyInjectTouchControlMessage, 'type' | 'screenWidth' | 'screenHeight'>) {
|
||||
if (!this.controlStream) {
|
||||
throw new Error('injectTouch called before initialization');
|
||||
}
|
||||
const controlStream = this.checkControlStream('injectTouch');
|
||||
|
||||
if (!this.screenWidth || !this.screenHeight) {
|
||||
return;
|
||||
|
@ -369,20 +365,17 @@ export class ScrcpyClient {
|
|||
}
|
||||
|
||||
this.sendingTouchMessage = true;
|
||||
const buffer = ScrcpyInjectTouchControlMessage.serialize({
|
||||
await controlStream.write(ScrcpyInjectTouchControlMessage.serialize({
|
||||
...message,
|
||||
type: ScrcpyControlMessageType.InjectTouch,
|
||||
screenWidth: this.screenWidth,
|
||||
screenHeight: this.screenHeight,
|
||||
});
|
||||
await this.controlStream.write(buffer);
|
||||
}));
|
||||
this.sendingTouchMessage = false;
|
||||
}
|
||||
|
||||
public async injectScroll(message: Omit<ScrcpyInjectScrollControlMessage1_22, 'type' | 'screenWidth' | 'screenHeight'>) {
|
||||
if (!this.controlStream) {
|
||||
throw new Error('injectScroll called before initialization');
|
||||
}
|
||||
const controlStream = this.checkControlStream('injectScroll');
|
||||
|
||||
if (!this.screenWidth || !this.screenHeight) {
|
||||
return;
|
||||
|
@ -394,17 +387,15 @@ export class ScrcpyClient {
|
|||
screenWidth: this.screenWidth,
|
||||
screenHeight: this.screenHeight,
|
||||
});
|
||||
await this.controlStream.write(buffer);
|
||||
await controlStream.write(buffer);
|
||||
}
|
||||
|
||||
public async pressBackOrTurnOnScreen(action: AndroidKeyEventAction) {
|
||||
if (!this.controlStream) {
|
||||
throw new Error('pressBackOrTurnOnScreen called before initialization');
|
||||
}
|
||||
const controlStream = this.checkControlStream('pressBackOrTurnOnScreen');
|
||||
|
||||
const buffer = this.options!.serializeBackOrScreenOnControlMessage(action, this.device);
|
||||
if (buffer) {
|
||||
await this.controlStream.write(buffer);
|
||||
await controlStream.write(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -414,8 +405,13 @@ export class ScrcpyClient {
|
|||
}
|
||||
|
||||
this._running = false;
|
||||
|
||||
this.videoStream?.close();
|
||||
this.videoStream = undefined;
|
||||
|
||||
this.controlStream?.close();
|
||||
this.controlStream = undefined;
|
||||
|
||||
await this.process?.kill();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,16 +3,33 @@ import { Disposable } from "@yume-chan/event";
|
|||
import { ValueOrPromise } from "@yume-chan/struct";
|
||||
import { delay } from "./utils";
|
||||
|
||||
export interface ScrcpyClientConnectionOptions {
|
||||
control: boolean;
|
||||
|
||||
/**
|
||||
* Write a byte on start to detect connection issues
|
||||
*/
|
||||
sendDummyByte: boolean;
|
||||
|
||||
/**
|
||||
* Send device name and size
|
||||
*/
|
||||
sendDeviceMeta: boolean;
|
||||
}
|
||||
|
||||
export abstract class ScrcpyClientConnection implements Disposable {
|
||||
protected device: Adb;
|
||||
|
||||
public constructor(device: Adb) {
|
||||
protected options: ScrcpyClientConnectionOptions;
|
||||
|
||||
public constructor(device: Adb, options: ScrcpyClientConnectionOptions) {
|
||||
this.device = device;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public initialize(): ValueOrPromise<void> { }
|
||||
|
||||
public abstract getStreams(): ValueOrPromise<[videoSteam: AdbBufferedStream, controlStream: AdbBufferedStream]>;
|
||||
public abstract getStreams(): ValueOrPromise<[videoSteam: AdbBufferedStream, controlStream: AdbBufferedStream | undefined]>;
|
||||
|
||||
public dispose(): void { }
|
||||
}
|
||||
|
@ -33,18 +50,26 @@ export class ScrcpyClientForwardConnection extends ScrcpyClientConnection {
|
|||
throw new Error(`Can't connect to server after 100 retries`);
|
||||
}
|
||||
|
||||
private async connectAndReadByte(): Promise<AdbBufferedStream> {
|
||||
private async connectVideoStream(): Promise<AdbBufferedStream> {
|
||||
const stream = await this.connectAndRetry();
|
||||
if (this.options.sendDummyByte) {
|
||||
// server will write a `0` to signal connection success
|
||||
await stream.read(1);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
public async getStreams(): Promise<[videoSteam: AdbBufferedStream, controlStream: AdbBufferedStream]> {
|
||||
return [
|
||||
await this.connectAndReadByte(),
|
||||
await this.connectAndRetry()
|
||||
];
|
||||
public async getStreams(): Promise<[videoSteam: AdbBufferedStream, controlStream: AdbBufferedStream | undefined]> {
|
||||
const videoStream = await this.connectVideoStream();
|
||||
let controlStream: AdbBufferedStream | undefined;
|
||||
if (this.options.control) {
|
||||
controlStream = await this.connectAndRetry();
|
||||
}
|
||||
if (this.options.sendDeviceMeta) {
|
||||
// 64 bytes device name + 2 bytes video width + 2 bytes video height
|
||||
await videoStream.read(64 + 2 + 2);
|
||||
}
|
||||
return [videoStream, controlStream];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,11 +98,17 @@ export class ScrcpyClientReverseConnection extends ScrcpyClientConnection {
|
|||
return new AdbBufferedStream(await this.streams.dequeue());
|
||||
}
|
||||
|
||||
public async getStreams(): Promise<[videoSteam: AdbBufferedStream, controlStream: AdbBufferedStream]> {
|
||||
return [
|
||||
await this.accept(),
|
||||
await this.accept(),
|
||||
];
|
||||
public async getStreams(): Promise<[videoSteam: AdbBufferedStream, controlStream: AdbBufferedStream | undefined]> {
|
||||
const videoStream = await this.accept();
|
||||
let controlStream: AdbBufferedStream | undefined;
|
||||
if (this.options.control) {
|
||||
controlStream = await this.accept();
|
||||
}
|
||||
if (this.options.sendDeviceMeta) {
|
||||
// 64 bytes device name + 2 bytes video width + 2 bytes video height
|
||||
await videoStream.read(64 + 2 + 2);
|
||||
}
|
||||
return [videoStream, controlStream];
|
||||
}
|
||||
|
||||
public override dispose() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { Adb } from "@yume-chan/adb";
|
||||
import Struct, { placeholder } from "@yume-chan/struct";
|
||||
import { AndroidCodecLevel, AndroidCodecProfile } from "../codec";
|
||||
import { ScrcpyClientConnection, ScrcpyClientForwardConnection, ScrcpyClientReverseConnection } from "../connection";
|
||||
import { ScrcpyClientConnection, ScrcpyClientConnectionOptions, ScrcpyClientForwardConnection, ScrcpyClientReverseConnection } from "../connection";
|
||||
import { AndroidKeyEventAction, ScrcpyControlMessageType } from "../message";
|
||||
import type { ScrcpyInjectScrollControlMessage1_22 } from "./1_22";
|
||||
import { ScrcpyLogLevel, ScrcpyOptions, ScrcpyOptionValue, ScrcpyScreenOrientation, toScrcpyOptionValue } from "./common";
|
||||
|
@ -43,6 +43,11 @@ export interface ScrcpyOptions1_16Type {
|
|||
|
||||
bitRate: number;
|
||||
|
||||
/**
|
||||
* 0 for unlimited.
|
||||
*
|
||||
* @default 0
|
||||
*/
|
||||
maxFps: number;
|
||||
|
||||
/**
|
||||
|
@ -60,12 +65,14 @@ export interface ScrcpyOptions1_16Type {
|
|||
/**
|
||||
* Send PTS so that the client may record properly
|
||||
*
|
||||
* TODO: This is not implemented yet
|
||||
* @default true
|
||||
*
|
||||
* TODO: Add support for `sendFrameMeta: false`
|
||||
*/
|
||||
sendFrameMeta: boolean;
|
||||
|
||||
/**
|
||||
* TODO: Scrcpy 1.22 changed how `control: false` works, and it's not supported yet
|
||||
* @default true
|
||||
*/
|
||||
control: boolean;
|
||||
|
||||
|
@ -156,10 +163,16 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptions1_16Type = ScrcpyOptions1_
|
|||
}
|
||||
|
||||
public createConnection(device: Adb): ScrcpyClientConnection {
|
||||
const options: ScrcpyClientConnectionOptions = {
|
||||
// Old scrcpy connection always have control stream no matter what the option is
|
||||
control: true,
|
||||
sendDummyByte: true,
|
||||
sendDeviceMeta: true,
|
||||
};
|
||||
if (this.value.tunnelForward) {
|
||||
return new ScrcpyClientForwardConnection(device);
|
||||
return new ScrcpyClientForwardConnection(device, options);
|
||||
} else {
|
||||
return new ScrcpyClientReverseConnection(device);
|
||||
return new ScrcpyClientReverseConnection(device, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { Adb } from "@yume-chan/adb";
|
||||
import Struct from "@yume-chan/struct";
|
||||
import { ScrcpyClientConnection, ScrcpyClientConnectionOptions, ScrcpyClientForwardConnection, ScrcpyClientReverseConnection } from "../connection";
|
||||
import { ScrcpyInjectScrollControlMessage1_16 } from "./1_16";
|
||||
import { ScrcpyOptions1_21, ScrcpyOptions1_21Type } from "./1_21";
|
||||
|
||||
|
@ -8,21 +10,23 @@ export interface ScrcpyOptions1_22Type extends ScrcpyOptions1_21Type {
|
|||
/**
|
||||
* Send device name and size
|
||||
*
|
||||
* TODO: This is not implemented yet
|
||||
* @default true
|
||||
*/
|
||||
sendDeviceMeta: boolean;
|
||||
|
||||
/**
|
||||
* Write a byte on start to detect connection issues
|
||||
*
|
||||
* TODO: This is not implemented yet
|
||||
* @default true
|
||||
*/
|
||||
sendDummyByte: boolean;
|
||||
|
||||
/**
|
||||
* Implies `sendDeviceMeta: false`, `sendFrameMeta: false` and `sendDummyByte: false`
|
||||
*
|
||||
* TODO: This is not implemented yet
|
||||
* @default false
|
||||
*
|
||||
* TODO: Add support for `sendFrameMeta: false`
|
||||
*/
|
||||
rawVideoStream: boolean;
|
||||
}
|
||||
|
@ -36,6 +40,20 @@ export type ScrcpyInjectScrollControlMessage1_22 = typeof ScrcpyInjectScrollCont
|
|||
|
||||
export class ScrcpyOptions1_22<T extends ScrcpyOptions1_22Type = ScrcpyOptions1_22Type> extends ScrcpyOptions1_21<T> {
|
||||
public constructor(init: Partial<ScrcpyOptions1_22Type>) {
|
||||
if (init.rawVideoStream) {
|
||||
// Set implied options for client-side processing
|
||||
init.sendDeviceMeta = false;
|
||||
init.sendFrameMeta = false;
|
||||
init.sendDummyByte = false;
|
||||
// TODO: Add support for `sendFrameMeta: false`
|
||||
throw new Error('`rawVideoStream:true` is not supported');
|
||||
}
|
||||
|
||||
if (!init.sendFrameMeta) {
|
||||
// TODO: Add support for `sendFrameMeta: false`
|
||||
throw new Error('`sendFrameMeta:false` is not supported');
|
||||
}
|
||||
|
||||
super(init);
|
||||
}
|
||||
|
||||
|
@ -49,6 +67,20 @@ export class ScrcpyOptions1_22<T extends ScrcpyOptions1_22Type = ScrcpyOptions1_
|
|||
};
|
||||
}
|
||||
|
||||
public override createConnection(device: Adb): ScrcpyClientConnection {
|
||||
const defaultValue = this.getDefaultValue();
|
||||
const options: ScrcpyClientConnectionOptions = {
|
||||
control: this.value.control ?? defaultValue.control,
|
||||
sendDummyByte: this.value.sendDummyByte ?? defaultValue.sendDummyByte,
|
||||
sendDeviceMeta: this.value.sendDeviceMeta ?? defaultValue.sendDeviceMeta,
|
||||
};
|
||||
if (this.value.tunnelForward) {
|
||||
return new ScrcpyClientForwardConnection(device, options);
|
||||
} else {
|
||||
return new ScrcpyClientReverseConnection(device, options);
|
||||
}
|
||||
}
|
||||
|
||||
public override serializeInjectScrollControlMessage(
|
||||
message: ScrcpyInjectScrollControlMessage1_22,
|
||||
): ArrayBuffer {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue