mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-05 19:42:15 +02:00
feat(scrcpy): support server v1.21
This commit is contained in:
parent
89bda8fa96
commit
916405c8b7
12 changed files with 352 additions and 232 deletions
|
@ -3,6 +3,7 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"postinstall": "fetch-scrcpy-server 1.21",
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { CommandBar, Dialog, Dropdown, ICommandBarItemProps, Icon, IconButton, IDropdownOption, LayerHost, Position, ProgressIndicator, SpinButton, Stack, Toggle, TooltipHost } from "@fluentui/react";
|
||||
import { useId } from "@fluentui/react-hooks";
|
||||
import { EventEmitter } from "@yume-chan/event";
|
||||
import { AndroidKeyCode, AndroidKeyEventAction, AndroidMotionEventAction, H264Decoder, H264DecoderConstructor, pushServer, ScrcpyClient, ScrcpyLogLevel, ScrcpyOptions1_18, ScrcpyScreenOrientation, TinyH264Decoder, WebCodecsDecoder } from "@yume-chan/scrcpy";
|
||||
import { AndroidKeyCode, AndroidKeyEventAction, AndroidMotionEventAction, CodecOptions, DEFAULT_SERVER_PATH, H264Decoder, H264DecoderConstructor, pushServer, ScrcpyClient, ScrcpyLogLevel, ScrcpyOptions1_21, ScrcpyScreenOrientation, TinyH264Decoder, WebCodecsDecoder } from "@yume-chan/scrcpy";
|
||||
import { action, autorun, makeAutoObservable, observable, runInAction } from "mobx";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { NextPage } from "next";
|
||||
|
@ -10,11 +10,10 @@ import React, { useEffect, useState } from "react";
|
|||
import { DemoMode, DeviceView, DeviceViewRef, ExternalLink } from "../components";
|
||||
import { global } from "../state";
|
||||
import { CommonStackTokens, formatSpeed, Icons, RouteStackProps } from "../utils";
|
||||
import SCRCPY_SERVER_VERSION from '@yume-chan/scrcpy/bin/version';
|
||||
|
||||
const SERVER_URL = new URL('@yume-chan/scrcpy/bin/scrcpy-server?url', import.meta.url).toString();
|
||||
|
||||
export const ScrcpyServerVersion = '1.19';
|
||||
|
||||
class FetchWithProgress {
|
||||
public readonly promise: Promise<ArrayBuffer>;
|
||||
|
||||
|
@ -400,8 +399,9 @@ class ScrcpyPageState {
|
|||
|
||||
const encoders = await ScrcpyClient.getEncoders(
|
||||
global.device,
|
||||
new ScrcpyOptions1_18({
|
||||
version: ScrcpyServerVersion,
|
||||
DEFAULT_SERVER_PATH,
|
||||
SCRCPY_SERVER_VERSION,
|
||||
new ScrcpyOptions1_21({
|
||||
logLevel: ScrcpyLogLevel.Debug,
|
||||
bitRate: 4_000_000,
|
||||
tunnelForward: this.tunnelForward,
|
||||
|
@ -456,17 +456,20 @@ class ScrcpyPageState {
|
|||
});
|
||||
|
||||
await client.start(
|
||||
new ScrcpyOptions1_18({
|
||||
version: ScrcpyServerVersion,
|
||||
DEFAULT_SERVER_PATH,
|
||||
SCRCPY_SERVER_VERSION,
|
||||
new ScrcpyOptions1_21({
|
||||
logLevel: ScrcpyLogLevel.Debug,
|
||||
maxSize: this.resolution,
|
||||
bitRate: this.bitRate,
|
||||
orientation: ScrcpyScreenOrientation.Unlocked,
|
||||
lockVideoOrientation: ScrcpyScreenOrientation.Unlocked,
|
||||
tunnelForward: this.tunnelForward,
|
||||
encoder: this.selectedEncoder ?? encoders[0],
|
||||
encoderName: this.selectedEncoder ?? encoders[0],
|
||||
codecOptions: new CodecOptions({
|
||||
profile: decoder.maxProfile,
|
||||
level: decoder.maxLevel,
|
||||
})
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
runInAction(() => {
|
||||
|
|
|
@ -3,3 +3,49 @@
|
|||
TypeScript implementation of [Scrcpy](https://github.com/Genymobile/scrcpy) client.
|
||||
|
||||
It uses the official Scrcpy server releases.
|
||||
|
||||
## Download Server Binary
|
||||
|
||||
This package has a script `fetch-scrcpy-server` to help you download the official server binary.
|
||||
|
||||
The server binary is subject to [Apache License 2.0](https://github.com/Genymobile/scrcpy/blob/master/LICENSE).
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
$ npx fetch-scrcpy-server <version>
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
$ npx fetch-scrcpy-server 1.21
|
||||
```
|
||||
|
||||
You can also add it to the `postinstall` script of your `package.json` so it will run automatically when you do `npm install`:
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
"postinstall": "fetch-scrcpy-server 1.21",
|
||||
},
|
||||
```
|
||||
|
||||
It will download the binary to `bin/scrcpy` and write the version string to `bin/version.js`. You can import the version string with
|
||||
|
||||
```js
|
||||
import SCRCPY_SERVER_VERSION from '@yume-chan/scrcpy/bin/version';
|
||||
```
|
||||
|
||||
And import the server binary with [file-loader](https://v4.webpack.js.org/loaders/file-loader/) (Webpack 4) or [Asset Modules](https://webpack.js.org/guides/asset-modules/) (Webpack 5).
|
||||
|
||||
## Option versions
|
||||
|
||||
Scrcpy server has no backward compatibility on options input format. Currently the following versions are supported:
|
||||
|
||||
| versions | type |
|
||||
| --------- | ------------------- |
|
||||
| 1.16~1.17 | `ScrcpyOptions1_16` |
|
||||
| 1.18~1.20 | `ScrcpyOptions1_18` |
|
||||
| 1.21 | `ScrcpyOptions1_21` |
|
||||
|
||||
You must use the correct type according to the server version.
|
||||
|
|
|
@ -22,11 +22,13 @@
|
|||
"bugs": {
|
||||
"url": "https://github.com/yume-chan/ya-webadb/issues"
|
||||
},
|
||||
"bin": {
|
||||
"fetch-scrcpy-server": "scripts/fetch-server.js"
|
||||
},
|
||||
"main": "cjs/index.js",
|
||||
"module": "esm/index.js",
|
||||
"types": "dts/index.d.ts",
|
||||
"scripts": {
|
||||
"postinstall": "node scripts/fetch-server",
|
||||
"build": "build-ts-package",
|
||||
"build:watch": "build-ts-package --incremental",
|
||||
"test": "jest --coverage",
|
||||
|
|
16
libraries/scrcpy/scripts/fetch-server.js
Normal file → Executable file
16
libraries/scrcpy/scripts/fetch-server.js
Normal file → Executable file
|
@ -1,26 +1,28 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const { fetchVersion } = require('gh-release-fetch');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
|
||||
const SERVER_VERSION = '1.19';
|
||||
|
||||
(async () => {
|
||||
console.log('Downloading scrcpy server binary...');
|
||||
const serverVerision = process.argv[2];
|
||||
console.log(`Downloading Scrcpy server binary version ${serverVerision}...`);
|
||||
|
||||
const binFolder = path.resolve(__dirname, '..', 'bin');
|
||||
|
||||
await fetchVersion({
|
||||
repository: 'Genymobile/scrcpy',
|
||||
version: `v${SERVER_VERSION}`,
|
||||
package: `scrcpy-server-v${SERVER_VERSION}`,
|
||||
version: `v${serverVerision}`,
|
||||
package: `scrcpy-server-v${serverVerision}`,
|
||||
destination: binFolder,
|
||||
extract: false,
|
||||
});
|
||||
|
||||
await fs.rename(
|
||||
path.resolve(binFolder, `scrcpy-server-v${SERVER_VERSION}`),
|
||||
path.resolve(binFolder, `scrcpy-server-v${serverVerision}`),
|
||||
path.resolve(binFolder, 'scrcpy-server')
|
||||
);
|
||||
|
||||
fs.writeFile(path.resolve(binFolder, 'version'), SERVER_VERSION);
|
||||
fs.writeFile(path.resolve(binFolder, 'version.js'), `export default '${serverVerision}';`);
|
||||
fs.writeFile(path.resolve(binFolder, 'version.d.ts'), `export default '${serverVerision}';`);
|
||||
})();
|
||||
|
|
|
@ -66,7 +66,7 @@ class LineReader {
|
|||
function* parseScrcpyOutput(text: string): Generator<ScrcpyOutput> {
|
||||
const lines = new LineReader(text);
|
||||
let line: string | undefined;
|
||||
while (line = lines.next()) {
|
||||
while ((line = lines.next()) !== undefined) {
|
||||
if (line === '') {
|
||||
continue;
|
||||
}
|
||||
|
@ -176,7 +176,12 @@ export class ScrcpyClient {
|
|||
pushServer(device, file, options);
|
||||
}
|
||||
|
||||
public static async getEncoders(device: Adb, options: ScrcpyOptions): Promise<string[]> {
|
||||
public static async getEncoders(
|
||||
device: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
options: ScrcpyOptions
|
||||
): Promise<string[]> {
|
||||
const client = new ScrcpyClient(device);
|
||||
const encoderNameRegex = options.getOutputEncoderNameRegex();
|
||||
|
||||
|
@ -201,6 +206,8 @@ export class ScrcpyClient {
|
|||
// Scrcpy server will open connections, before initializing encoder
|
||||
// Thus although an invalid encoder name is given, the start process will success
|
||||
await client.startCore(
|
||||
path,
|
||||
version,
|
||||
options.formatGetEncoderListArguments(),
|
||||
options.createConnection(device)
|
||||
);
|
||||
|
@ -256,6 +263,8 @@ export class ScrcpyClient {
|
|||
}
|
||||
|
||||
private async startCore(
|
||||
path: string,
|
||||
version: string,
|
||||
serverArguments: string[],
|
||||
connection: ScrcpyClientConnection
|
||||
): Promise<void> {
|
||||
|
@ -265,7 +274,14 @@ export class ScrcpyClient {
|
|||
await connection.initialize();
|
||||
|
||||
process = await this.device.childProcess.spawn(
|
||||
serverArguments,
|
||||
[
|
||||
`CLASSPATH=${path}`,
|
||||
'app_process',
|
||||
/* unused */ '/',
|
||||
'com.genymobile.scrcpy.Server',
|
||||
version,
|
||||
...serverArguments
|
||||
],
|
||||
{
|
||||
// Scrcpy server doesn't split stdout and stderr,
|
||||
// so disable Shell Protocol to simplify processing
|
||||
|
@ -302,9 +318,15 @@ export class ScrcpyClient {
|
|||
}
|
||||
}
|
||||
|
||||
public start(options: ScrcpyOptions) {
|
||||
public start(
|
||||
path: string,
|
||||
version: string,
|
||||
options: ScrcpyOptions
|
||||
) {
|
||||
this.options = options;
|
||||
return this.startCore(
|
||||
path,
|
||||
version,
|
||||
options.formatServerArguments(),
|
||||
options.createConnection(this.device)
|
||||
);
|
||||
|
|
183
libraries/scrcpy/src/options/1_16.ts
Normal file
183
libraries/scrcpy/src/options/1_16.ts
Normal file
|
@ -0,0 +1,183 @@
|
|||
import { Adb } from "@yume-chan/adb";
|
||||
import Struct, { placeholder } from "@yume-chan/struct";
|
||||
import { AndroidCodecLevel, AndroidCodecProfile } from "../codec";
|
||||
import { ScrcpyClientConnection, ScrcpyClientForwardConnection, ScrcpyClientReverseConnection } from "../connection";
|
||||
import { AndroidKeyEventAction, ScrcpyControlMessageType } from "../message";
|
||||
import { ScrcpyLogLevel, ScrcpyOptions, ScrcpyScreenOrientation, toScrcpyOption, ToScrcpyOption } from "./common";
|
||||
|
||||
export interface CodecOptionsType {
|
||||
profile: AndroidCodecProfile;
|
||||
|
||||
level: AndroidCodecLevel;
|
||||
}
|
||||
|
||||
export class CodecOptions implements ToScrcpyOption {
|
||||
public value: CodecOptionsType;
|
||||
|
||||
public constructor({
|
||||
profile = AndroidCodecProfile.Baseline,
|
||||
level = AndroidCodecLevel.Level4,
|
||||
}: Partial<CodecOptionsType>) {
|
||||
this.value = {
|
||||
profile,
|
||||
level,
|
||||
};
|
||||
}
|
||||
|
||||
public toScrcpyOption(): string {
|
||||
return Object.entries(this.value)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join(',');
|
||||
}
|
||||
}
|
||||
|
||||
export interface ScrcpyOptions1_16Type {
|
||||
logLevel: ScrcpyLogLevel;
|
||||
|
||||
/**
|
||||
* The maximum value of both width and height.
|
||||
*/
|
||||
maxSize: number;
|
||||
|
||||
bitRate: number;
|
||||
|
||||
maxFps: number;
|
||||
|
||||
/**
|
||||
* The orientation of the video stream.
|
||||
*
|
||||
* It will not keep the device screen in specific orientation,
|
||||
* only the captured video will in this orientation.
|
||||
*/
|
||||
lockVideoOrientation: ScrcpyScreenOrientation;
|
||||
|
||||
tunnelForward: boolean;
|
||||
|
||||
// Because Scrcpy 1.21 changed the empty value from '-' to '',
|
||||
// We mark properties which can be empty with `| undefined`
|
||||
crop: string | undefined;
|
||||
|
||||
sendFrameMeta: boolean;
|
||||
|
||||
control: boolean;
|
||||
|
||||
displayId: number;
|
||||
|
||||
showTouches: boolean;
|
||||
|
||||
stayAwake: boolean;
|
||||
|
||||
codecOptions: CodecOptions | undefined;
|
||||
|
||||
encoderName: string | undefined;
|
||||
}
|
||||
|
||||
export const ScrcpyBackOrScreenOnEvent1_16 =
|
||||
new Struct()
|
||||
.uint8('type', placeholder<ScrcpyControlMessageType.BackOrScreenOn>());
|
||||
|
||||
export class ScrcpyOptions1_16<T extends ScrcpyOptions1_16Type = ScrcpyOptions1_16Type> implements ScrcpyOptions {
|
||||
public value: T;
|
||||
|
||||
public constructor({
|
||||
logLevel = ScrcpyLogLevel.Error,
|
||||
maxSize = 0,
|
||||
bitRate = 8_000_000,
|
||||
maxFps = 0,
|
||||
lockVideoOrientation = ScrcpyScreenOrientation.Unlocked,
|
||||
tunnelForward = false,
|
||||
crop,
|
||||
sendFrameMeta = true,
|
||||
control = true,
|
||||
displayId = 0,
|
||||
showTouches = false,
|
||||
stayAwake = true,
|
||||
codecOptions,
|
||||
encoderName,
|
||||
}: Partial<ScrcpyOptions1_16Type>) {
|
||||
if (new.target === ScrcpyOptions1_16 &&
|
||||
logLevel === ScrcpyLogLevel.Verbose) {
|
||||
logLevel = ScrcpyLogLevel.Debug;
|
||||
}
|
||||
if (new.target === ScrcpyOptions1_16 &&
|
||||
lockVideoOrientation === ScrcpyScreenOrientation.Initial) {
|
||||
lockVideoOrientation = ScrcpyScreenOrientation.Unlocked;
|
||||
}
|
||||
this.value = {
|
||||
logLevel,
|
||||
maxSize,
|
||||
bitRate,
|
||||
maxFps,
|
||||
lockVideoOrientation,
|
||||
tunnelForward,
|
||||
crop,
|
||||
sendFrameMeta,
|
||||
control,
|
||||
displayId,
|
||||
showTouches,
|
||||
stayAwake,
|
||||
codecOptions,
|
||||
encoderName,
|
||||
} as T;
|
||||
}
|
||||
|
||||
protected getArgumnetOrder(): (keyof T)[] {
|
||||
return [
|
||||
'logLevel',
|
||||
'maxSize',
|
||||
'bitRate',
|
||||
'maxFps',
|
||||
'lockVideoOrientation',
|
||||
'tunnelForward',
|
||||
'crop',
|
||||
'sendFrameMeta',
|
||||
'control',
|
||||
'displayId',
|
||||
'showTouches',
|
||||
'stayAwake',
|
||||
'codecOptions',
|
||||
'encoderName',
|
||||
];
|
||||
}
|
||||
|
||||
public formatServerArguments(): string[] {
|
||||
return this.getArgumnetOrder().map(key => {
|
||||
return toScrcpyOption(this.value[key], '-');
|
||||
});
|
||||
}
|
||||
|
||||
public formatGetEncoderListArguments(): string[] {
|
||||
return this.getArgumnetOrder().map(key => {
|
||||
if (key === 'encoderName') {
|
||||
// Provide an invalid encoder name
|
||||
// So the server will return all available encoders
|
||||
return '_';
|
||||
}
|
||||
|
||||
return toScrcpyOption(this.value[key], '-');
|
||||
});
|
||||
}
|
||||
|
||||
public createConnection(device: Adb): ScrcpyClientConnection {
|
||||
if (this.value.tunnelForward) {
|
||||
return new ScrcpyClientForwardConnection(device);
|
||||
} else {
|
||||
return new ScrcpyClientReverseConnection(device);
|
||||
}
|
||||
}
|
||||
|
||||
public getOutputEncoderNameRegex(): RegExp {
|
||||
return /^\s+scrcpy --encoder-name '(.*?)'/;
|
||||
}
|
||||
|
||||
public createBackOrScreenOnEvent(action: AndroidKeyEventAction, device: Adb) {
|
||||
if (action === AndroidKeyEventAction.Down) {
|
||||
return ScrcpyBackOrScreenOnEvent1_16.serialize(
|
||||
{ type: ScrcpyControlMessageType.BackOrScreenOn },
|
||||
device.backend
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
|
@ -1,181 +0,0 @@
|
|||
import { Adb } from "@yume-chan/adb";
|
||||
import Struct, { placeholder } from "@yume-chan/struct";
|
||||
import { AndroidCodecLevel, AndroidCodecProfile } from "../codec";
|
||||
import { ScrcpyClientConnection, ScrcpyClientForwardConnection, ScrcpyClientReverseConnection } from "../connection";
|
||||
import { AndroidKeyEventAction, ScrcpyControlMessageType } from "../message";
|
||||
import { DEFAULT_SERVER_PATH, ScrcpyLogLevel, ScrcpyOptions, ScrcpyScreenOrientation } from "./common";
|
||||
|
||||
export interface ScrcpyOptions1_17Init {
|
||||
path?: string,
|
||||
|
||||
version?: string;
|
||||
|
||||
logLevel?: ScrcpyLogLevel;
|
||||
|
||||
/**
|
||||
* The maximum value of both width and height.
|
||||
*/
|
||||
maxSize?: number | undefined;
|
||||
|
||||
bitRate: number;
|
||||
|
||||
maxFps?: number;
|
||||
|
||||
/**
|
||||
* The orientation of the video stream.
|
||||
*
|
||||
* It will not keep the device screen in specific orientation,
|
||||
* only the captured video will in this orientation.
|
||||
*/
|
||||
orientation?: ScrcpyScreenOrientation;
|
||||
|
||||
tunnelForward?: boolean;
|
||||
|
||||
profile?: AndroidCodecProfile;
|
||||
|
||||
level?: AndroidCodecLevel;
|
||||
|
||||
encoder?: string;
|
||||
}
|
||||
|
||||
export const ScrcpyBackOrScreenOnEvent1_17 =
|
||||
new Struct()
|
||||
.uint8('type', placeholder<ScrcpyControlMessageType.BackOrScreenOn>());
|
||||
|
||||
export class ScrcpyOptions1_17 implements ScrcpyOptions {
|
||||
path: string;
|
||||
|
||||
version: string;
|
||||
|
||||
logLevel: ScrcpyLogLevel;
|
||||
|
||||
/**
|
||||
* The maximum value of both width and height.
|
||||
*/
|
||||
maxSize: number;
|
||||
|
||||
bitRate: number;
|
||||
|
||||
maxFps: number;
|
||||
|
||||
/**
|
||||
* The orientation of the video stream.
|
||||
*
|
||||
* It will not keep the device screen in specific orientation,
|
||||
* only the captured video will in this orientation.
|
||||
*/
|
||||
orientation: ScrcpyScreenOrientation;
|
||||
|
||||
tunnelForward: boolean;
|
||||
|
||||
profile: AndroidCodecProfile;
|
||||
|
||||
level: AndroidCodecLevel;
|
||||
|
||||
encoder: string;
|
||||
|
||||
public constructor({
|
||||
path = DEFAULT_SERVER_PATH,
|
||||
version = '1.17',
|
||||
logLevel = ScrcpyLogLevel.Error,
|
||||
maxSize = 0,
|
||||
bitRate,
|
||||
maxFps = 0,
|
||||
orientation = ScrcpyScreenOrientation.Unlocked,
|
||||
tunnelForward = false,
|
||||
profile = AndroidCodecProfile.Baseline,
|
||||
level = AndroidCodecLevel.Level4,
|
||||
encoder = '-',
|
||||
}: ScrcpyOptions1_17Init) {
|
||||
this.path = path;
|
||||
this.version = version;
|
||||
if (logLevel === ScrcpyLogLevel.Verbose) {
|
||||
logLevel = ScrcpyLogLevel.Debug;
|
||||
}
|
||||
this.logLevel = logLevel;
|
||||
this.maxSize = maxSize;
|
||||
this.bitRate = bitRate;
|
||||
this.maxFps = maxFps;
|
||||
if (orientation === ScrcpyScreenOrientation.Initial) {
|
||||
orientation = ScrcpyScreenOrientation.Unlocked;
|
||||
}
|
||||
this.orientation = orientation;
|
||||
this.tunnelForward = tunnelForward;
|
||||
this.profile = profile;
|
||||
this.level = level;
|
||||
this.encoder = encoder;
|
||||
}
|
||||
|
||||
public formatServerArguments() {
|
||||
return [
|
||||
`CLASSPATH=${this.path}`,
|
||||
'app_process',
|
||||
/* unused */ '/',
|
||||
'com.genymobile.scrcpy.Server',
|
||||
this.version,
|
||||
this.logLevel,
|
||||
this.maxSize.toString(), // (0: unlimited)
|
||||
this.bitRate.toString(),
|
||||
this.maxFps.toString(),
|
||||
this.orientation.toString(),
|
||||
this.tunnelForward.toString(),
|
||||
/* crop */ '-',
|
||||
/* send_frame_meta */ 'true', // always send frame meta (packet boundaries + timestamp)
|
||||
/* control */ 'true',
|
||||
/* display_id */ '0',
|
||||
/* show_touches */ 'false',
|
||||
/* stay_awake */ 'true',
|
||||
/* codec_options */ `profile=${this.profile},level=${this.level}`,
|
||||
this.encoder,
|
||||
];
|
||||
}
|
||||
|
||||
public formatGetEncoderListArguments() {
|
||||
return [
|
||||
`CLASSPATH=${this.path}`,
|
||||
'app_process',
|
||||
/* unused */ '/',
|
||||
'com.genymobile.scrcpy.Server',
|
||||
this.version,
|
||||
this.logLevel,
|
||||
this.maxSize.toString(), // (0: unlimited)
|
||||
this.bitRate.toString(),
|
||||
this.maxFps.toString(),
|
||||
this.orientation.toString(),
|
||||
this.tunnelForward.toString(),
|
||||
/* crop */ '-',
|
||||
/* send_frame_meta */ 'true', // always send frame meta (packet boundaries + timestamp)
|
||||
/* control */ 'true',
|
||||
/* display_id */ '0',
|
||||
/* show_touches */ 'false',
|
||||
/* stay_awake */ 'true',
|
||||
/* codec_options */ `profile=${this.profile},level=${this.level}`,
|
||||
// Provide an invalid encoder name
|
||||
// So the server will return all available encoders
|
||||
/* encoder_name */ '_',
|
||||
];
|
||||
}
|
||||
|
||||
public createConnection(device: Adb): ScrcpyClientConnection {
|
||||
if (this.tunnelForward) {
|
||||
return new ScrcpyClientForwardConnection(device);
|
||||
} else {
|
||||
return new ScrcpyClientReverseConnection(device);
|
||||
}
|
||||
}
|
||||
|
||||
public getOutputEncoderNameRegex(): RegExp {
|
||||
return /^\s+scrcpy --encoder-name '(.*?)'/;
|
||||
}
|
||||
|
||||
public createBackOrScreenOnEvent(action: AndroidKeyEventAction, device: Adb) {
|
||||
if (action === AndroidKeyEventAction.Down) {
|
||||
return ScrcpyBackOrScreenOnEvent1_17.serialize(
|
||||
{ type: ScrcpyControlMessageType.BackOrScreenOn },
|
||||
device.backend
|
||||
);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
import { Adb } from "@yume-chan/adb";
|
||||
import Struct, { placeholder } from "@yume-chan/struct";
|
||||
import { AndroidKeyEventAction, ScrcpyControlMessageType } from "../message";
|
||||
import { ScrcpyOptions1_17, ScrcpyOptions1_17Init } from "./1_17";
|
||||
import { ScrcpyLogLevel, ScrcpyScreenOrientation } from "./common";
|
||||
import { ScrcpyOptions1_16, ScrcpyOptions1_16Type } from "./1_16";
|
||||
|
||||
export interface ScrcpyOptions1_18Init extends ScrcpyOptions1_17Init {
|
||||
powerOffOnClose?: boolean;
|
||||
export interface ScrcpyOptions1_18Type extends ScrcpyOptions1_16Type {
|
||||
powerOffOnClose: boolean;
|
||||
}
|
||||
|
||||
export const ScrcpyBackOrScreenOnEvent1_18 =
|
||||
|
@ -13,40 +12,24 @@ export const ScrcpyBackOrScreenOnEvent1_18 =
|
|||
.uint8('type', placeholder<ScrcpyControlMessageType.BackOrScreenOn>())
|
||||
.uint8('action', placeholder<AndroidKeyEventAction>());
|
||||
|
||||
export class ScrcpyOptions1_18 extends ScrcpyOptions1_17 {
|
||||
powerOffOnClose: boolean;
|
||||
|
||||
constructor(init: ScrcpyOptions1_18Init) {
|
||||
export class ScrcpyOptions1_18<T extends ScrcpyOptions1_18Type = ScrcpyOptions1_18Type> extends ScrcpyOptions1_16<T> {
|
||||
constructor(init: Partial<ScrcpyOptions1_18Type>) {
|
||||
super(init);
|
||||
const {
|
||||
logLevel = ScrcpyLogLevel.Error,
|
||||
orientation = ScrcpyScreenOrientation.Unlocked,
|
||||
powerOffOnClose = false,
|
||||
} = init;
|
||||
this.logLevel = logLevel;
|
||||
this.orientation = orientation;
|
||||
this.powerOffOnClose = powerOffOnClose;
|
||||
this.value.powerOffOnClose = powerOffOnClose;
|
||||
}
|
||||
|
||||
public override formatServerArguments(): string[] {
|
||||
return [
|
||||
...super.formatServerArguments(),
|
||||
this.powerOffOnClose.toString()
|
||||
];
|
||||
}
|
||||
|
||||
public override formatGetEncoderListArguments(): string[] {
|
||||
return [
|
||||
...super.formatGetEncoderListArguments(),
|
||||
this.powerOffOnClose.toString()
|
||||
];
|
||||
protected override getArgumnetOrder(): (keyof T)[] {
|
||||
return super.getArgumnetOrder().concat(['powerOffOnClose']);
|
||||
}
|
||||
|
||||
public override getOutputEncoderNameRegex(): RegExp {
|
||||
return /^\s+scrcpy --encoder '(.*?)'/;
|
||||
}
|
||||
|
||||
public createBackOrScreenOnEvent(action: AndroidKeyEventAction, device: Adb) {
|
||||
public override createBackOrScreenOnEvent(action: AndroidKeyEventAction, device: Adb) {
|
||||
return ScrcpyBackOrScreenOnEvent1_18.serialize(
|
||||
{
|
||||
type: ScrcpyControlMessageType.BackOrScreenOn,
|
||||
|
|
36
libraries/scrcpy/src/options/1_21.ts
Normal file
36
libraries/scrcpy/src/options/1_21.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { ScrcpyOptions1_18, ScrcpyOptions1_18Type } from './1_18';
|
||||
import { toScrcpyOption } from "./common";
|
||||
|
||||
export interface ScrcpyOptions1_21Type extends ScrcpyOptions1_18Type {
|
||||
clipboardAutosync?: boolean;
|
||||
}
|
||||
|
||||
function toSnakeCase(input: string): string {
|
||||
return input.replace(/([A-Z])/g, '_$1').toLowerCase();
|
||||
}
|
||||
|
||||
export class ScrcpyOptions1_21<T extends ScrcpyOptions1_21Type = ScrcpyOptions1_21Type> extends ScrcpyOptions1_18<T> {
|
||||
public constructor(init: Partial<ScrcpyOptions1_21Type>) {
|
||||
super(init);
|
||||
const {
|
||||
clipboardAutosync = true,
|
||||
} = init;
|
||||
this.value.clipboardAutosync = clipboardAutosync;
|
||||
}
|
||||
|
||||
public override formatServerArguments(): string[] {
|
||||
return Object.entries(this.value).map(([key, value]) => {
|
||||
return `${toSnakeCase(key)}=${toScrcpyOption(value, '')}`;
|
||||
});
|
||||
}
|
||||
|
||||
public formatGetEncoderListArguments(): string[] {
|
||||
return Object.entries(this.value).map(([key, value]) => {
|
||||
if (key === 'encoderName') {
|
||||
value = '_';
|
||||
}
|
||||
|
||||
return `${toSnakeCase(key)}=${toScrcpyOption(value, '')}`;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -32,3 +32,25 @@ export interface ScrcpyOptions {
|
|||
|
||||
createBackOrScreenOnEvent(action: AndroidKeyEventAction, device: Adb): ArrayBuffer | undefined;
|
||||
}
|
||||
|
||||
export interface ToScrcpyOption {
|
||||
toScrcpyOption(): string;
|
||||
}
|
||||
|
||||
export function isToScrcpyOption(value: any): value is ToScrcpyOption {
|
||||
return typeof value === 'object' &&
|
||||
value !== null &&
|
||||
typeof value.toScrcpyOption === 'function';
|
||||
}
|
||||
|
||||
export function toScrcpyOption(value: any, empty: string): string {
|
||||
if (value === undefined) {
|
||||
return empty;
|
||||
}
|
||||
|
||||
if (isToScrcpyOption(value)) {
|
||||
return value.toScrcpyOption();
|
||||
}
|
||||
|
||||
return `${value}`;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from './1_17';
|
||||
export * from './1_16';
|
||||
export * from './1_18';
|
||||
export * from './1_21';
|
||||
export * from './common';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue