mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-05 02:39:26 +02:00
refactor(scrcpy): rewrite option classes to improve tree-shaking
This commit is contained in:
parent
db8466f6ee
commit
92472007db
218 changed files with 5412 additions and 2380 deletions
|
@ -10,12 +10,12 @@ import type {
|
||||||
ScrcpyDisplay,
|
ScrcpyDisplay,
|
||||||
ScrcpyEncoder,
|
ScrcpyEncoder,
|
||||||
ScrcpyMediaStreamPacket,
|
ScrcpyMediaStreamPacket,
|
||||||
ScrcpyOptionsInit1_16,
|
ScrcpyOptions1_15,
|
||||||
ScrcpyVideoStreamMetadata,
|
ScrcpyVideoStreamMetadata,
|
||||||
} from "@yume-chan/scrcpy";
|
} from "@yume-chan/scrcpy";
|
||||||
import {
|
import {
|
||||||
Av1,
|
Av1,
|
||||||
DEFAULT_SERVER_PATH,
|
DefaultServerPath,
|
||||||
ScrcpyControlMessageWriter,
|
ScrcpyControlMessageWriter,
|
||||||
ScrcpyVideoCodecId,
|
ScrcpyVideoCodecId,
|
||||||
h264ParseConfiguration,
|
h264ParseConfiguration,
|
||||||
|
@ -104,7 +104,7 @@ export class AdbScrcpyClient {
|
||||||
static async pushServer(
|
static async pushServer(
|
||||||
adb: Adb,
|
adb: Adb,
|
||||||
file: ReadableStream<MaybeConsumable<Uint8Array>>,
|
file: ReadableStream<MaybeConsumable<Uint8Array>>,
|
||||||
filename = DEFAULT_SERVER_PATH,
|
filename = DefaultServerPath,
|
||||||
) {
|
) {
|
||||||
const sync = await adb.sync();
|
const sync = await adb.sync();
|
||||||
try {
|
try {
|
||||||
|
@ -121,7 +121,9 @@ export class AdbScrcpyClient {
|
||||||
adb: Adb,
|
adb: Adb,
|
||||||
path: string,
|
path: string,
|
||||||
version: string,
|
version: string,
|
||||||
options: AdbScrcpyOptions<Pick<ScrcpyOptionsInit1_16, "tunnelForward">>,
|
options: AdbScrcpyOptions<
|
||||||
|
Pick<ScrcpyOptions1_15.Init, "tunnelForward">
|
||||||
|
>,
|
||||||
) {
|
) {
|
||||||
let connection: AdbScrcpyConnection | undefined;
|
let connection: AdbScrcpyConnection | undefined;
|
||||||
let process: AdbSubprocessProtocol | undefined;
|
let process: AdbSubprocessProtocol | undefined;
|
||||||
|
@ -342,7 +344,7 @@ export class AdbScrcpyClient {
|
||||||
type = result[0]!;
|
type = result[0]!;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof ExactReadableEndedError) {
|
if (e instanceof ExactReadableEndedError) {
|
||||||
await this.#options.endDeviceMessageStream();
|
this.#options.endDeviceMessageStream();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -350,7 +352,7 @@ export class AdbScrcpyClient {
|
||||||
await this.#options.parseDeviceMessage(type, buffered);
|
await this.#options.parseDeviceMessage(type, buffered);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.#options.endDeviceMessageStream(e);
|
this.#options.endDeviceMessageStream(e);
|
||||||
buffered.cancel(e).catch(() => {});
|
buffered.cancel(e).catch(() => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { Adb } from "@yume-chan/adb";
|
||||||
import type {
|
import type {
|
||||||
ScrcpyDisplay,
|
ScrcpyDisplay,
|
||||||
ScrcpyEncoder,
|
ScrcpyEncoder,
|
||||||
ScrcpyOptionsInit1_16,
|
ScrcpyOptions1_16Impl,
|
||||||
} from "@yume-chan/scrcpy";
|
} from "@yume-chan/scrcpy";
|
||||||
import { WritableStream } from "@yume-chan/stream-extra";
|
import { WritableStream } from "@yume-chan/stream-extra";
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import { AdbScrcpyOptions } from "./types.js";
|
||||||
export class AdbScrcpyOptions1_16 extends AdbScrcpyOptions<
|
export class AdbScrcpyOptions1_16 extends AdbScrcpyOptions<
|
||||||
// Only pick options that are used in this class,
|
// Only pick options that are used in this class,
|
||||||
// so changes in `ScrcpyOptionsInitX_XX` won't affect type assignability with this class
|
// so changes in `ScrcpyOptionsInitX_XX` won't affect type assignability with this class
|
||||||
Pick<ScrcpyOptionsInit1_16, "tunnelForward">
|
Pick<ScrcpyOptions1_16Impl.Init, "tunnelForward">
|
||||||
> {
|
> {
|
||||||
static createConnection(
|
static createConnection(
|
||||||
adb: Adb,
|
adb: Adb,
|
||||||
|
@ -39,7 +39,9 @@ export class AdbScrcpyOptions1_16 extends AdbScrcpyOptions<
|
||||||
adb: Adb,
|
adb: Adb,
|
||||||
path: string,
|
path: string,
|
||||||
version: string,
|
version: string,
|
||||||
options: AdbScrcpyOptions<Pick<ScrcpyOptionsInit1_16, "tunnelForward">>,
|
options: AdbScrcpyOptions<
|
||||||
|
Pick<ScrcpyOptions1_16Impl.Init, "tunnelForward">
|
||||||
|
>,
|
||||||
): Promise<ScrcpyEncoder[]> {
|
): Promise<ScrcpyEncoder[]> {
|
||||||
const client = await AdbScrcpyClient.start(adb, path, version, options);
|
const client = await AdbScrcpyClient.start(adb, path, version, options);
|
||||||
|
|
||||||
|
@ -62,7 +64,9 @@ export class AdbScrcpyOptions1_16 extends AdbScrcpyOptions<
|
||||||
adb: Adb,
|
adb: Adb,
|
||||||
path: string,
|
path: string,
|
||||||
version: string,
|
version: string,
|
||||||
options: AdbScrcpyOptions<Pick<ScrcpyOptionsInit1_16, "tunnelForward">>,
|
options: AdbScrcpyOptions<
|
||||||
|
Pick<ScrcpyOptions1_16Impl.Init, "tunnelForward">
|
||||||
|
>,
|
||||||
): Promise<ScrcpyDisplay[]> {
|
): Promise<ScrcpyDisplay[]> {
|
||||||
try {
|
try {
|
||||||
// Server will exit before opening connections when an invalid display id was given
|
// Server will exit before opening connections when an invalid display id was given
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { Adb } from "@yume-chan/adb";
|
||||||
import type {
|
import type {
|
||||||
ScrcpyDisplay,
|
ScrcpyDisplay,
|
||||||
ScrcpyEncoder,
|
ScrcpyEncoder,
|
||||||
ScrcpyOptionsInit1_22,
|
ScrcpyOptions1_22Impl,
|
||||||
} from "@yume-chan/scrcpy";
|
} from "@yume-chan/scrcpy";
|
||||||
|
|
||||||
import type { AdbScrcpyConnection } from "../connection.js";
|
import type { AdbScrcpyConnection } from "../connection.js";
|
||||||
|
@ -13,7 +13,10 @@ import { AdbScrcpyOptions } from "./types.js";
|
||||||
export class AdbScrcpyOptions1_22 extends AdbScrcpyOptions<
|
export class AdbScrcpyOptions1_22 extends AdbScrcpyOptions<
|
||||||
// Only pick options that are used in this class,
|
// Only pick options that are used in this class,
|
||||||
// so changes in `ScrcpyOptionsInitX_XX` won't affect type assignability with this class
|
// so changes in `ScrcpyOptionsInitX_XX` won't affect type assignability with this class
|
||||||
Pick<ScrcpyOptionsInit1_22, "tunnelForward" | "control" | "sendDummyByte">
|
Pick<
|
||||||
|
ScrcpyOptions1_22Impl.Init,
|
||||||
|
"tunnelForward" | "control" | "sendDummyByte"
|
||||||
|
>
|
||||||
> {
|
> {
|
||||||
override getEncoders(
|
override getEncoders(
|
||||||
adb: Adb,
|
adb: Adb,
|
||||||
|
|
|
@ -2,8 +2,8 @@ import type { Adb } from "@yume-chan/adb";
|
||||||
import type {
|
import type {
|
||||||
ScrcpyDisplay,
|
ScrcpyDisplay,
|
||||||
ScrcpyEncoder,
|
ScrcpyEncoder,
|
||||||
ScrcpyOptionsInit1_16,
|
ScrcpyOptions1_16Impl,
|
||||||
ScrcpyOptionsInit2_0,
|
ScrcpyOptions2_0Impl,
|
||||||
} from "@yume-chan/scrcpy";
|
} from "@yume-chan/scrcpy";
|
||||||
|
|
||||||
import { AdbScrcpyClient, AdbScrcpyExitedError } from "../client.js";
|
import { AdbScrcpyClient, AdbScrcpyExitedError } from "../client.js";
|
||||||
|
@ -16,7 +16,7 @@ export class AdbScrcpyOptions2_0 extends AdbScrcpyOptions<
|
||||||
// Only pick options that are used in this class,
|
// Only pick options that are used in this class,
|
||||||
// so changes in `ScrcpyOptionsInitX_XX` won't affect type assignability with this class
|
// so changes in `ScrcpyOptionsInitX_XX` won't affect type assignability with this class
|
||||||
Pick<
|
Pick<
|
||||||
ScrcpyOptionsInit2_0,
|
ScrcpyOptions2_0Impl.Init,
|
||||||
"tunnelForward" | "control" | "sendDummyByte" | "scid" | "audio"
|
"tunnelForward" | "control" | "sendDummyByte" | "scid" | "audio"
|
||||||
>
|
>
|
||||||
> {
|
> {
|
||||||
|
@ -24,7 +24,9 @@ export class AdbScrcpyOptions2_0 extends AdbScrcpyOptions<
|
||||||
adb: Adb,
|
adb: Adb,
|
||||||
path: string,
|
path: string,
|
||||||
version: string,
|
version: string,
|
||||||
options: AdbScrcpyOptions<Pick<ScrcpyOptionsInit1_16, "tunnelForward">>,
|
options: AdbScrcpyOptions<
|
||||||
|
Pick<ScrcpyOptions1_16Impl.Init, "tunnelForward">
|
||||||
|
>,
|
||||||
): Promise<ScrcpyEncoder[]> {
|
): Promise<ScrcpyEncoder[]> {
|
||||||
try {
|
try {
|
||||||
// Similar to `AdbScrcpyOptions1_16.getDisplays`,
|
// Similar to `AdbScrcpyOptions1_16.getDisplays`,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { Adb } from "@yume-chan/adb";
|
||||||
import type {
|
import type {
|
||||||
ScrcpyDisplay,
|
ScrcpyDisplay,
|
||||||
ScrcpyEncoder,
|
ScrcpyEncoder,
|
||||||
ScrcpyOptionsInit2_1,
|
ScrcpyOptions2_1Impl,
|
||||||
} from "@yume-chan/scrcpy";
|
} from "@yume-chan/scrcpy";
|
||||||
|
|
||||||
import type { AdbScrcpyConnection } from "../connection.js";
|
import type { AdbScrcpyConnection } from "../connection.js";
|
||||||
|
@ -15,7 +15,7 @@ export class AdbScrcpyOptions2_1 extends AdbScrcpyOptions<
|
||||||
// Only pick options that are used in this class,
|
// Only pick options that are used in this class,
|
||||||
// so changes in `ScrcpyOptionsInitX_XX` won't affect type assignability with this class
|
// so changes in `ScrcpyOptionsInitX_XX` won't affect type assignability with this class
|
||||||
Pick<
|
Pick<
|
||||||
ScrcpyOptionsInit2_1,
|
ScrcpyOptions2_1Impl.Init,
|
||||||
| "tunnelForward"
|
| "tunnelForward"
|
||||||
| "control"
|
| "control"
|
||||||
| "sendDummyByte"
|
| "sendDummyByte"
|
||||||
|
|
|
@ -1,39 +1,12 @@
|
||||||
import type { Adb } from "@yume-chan/adb";
|
import type { Adb } from "@yume-chan/adb";
|
||||||
import type { ScrcpyDisplay, ScrcpyEncoder } from "@yume-chan/scrcpy";
|
import type { ScrcpyDisplay, ScrcpyEncoder } from "@yume-chan/scrcpy";
|
||||||
import { ScrcpyOptions } from "@yume-chan/scrcpy";
|
import { ScrcpyOptionsWrapper } from "@yume-chan/scrcpy";
|
||||||
|
|
||||||
import type { AdbScrcpyConnection } from "../connection.js";
|
import type { AdbScrcpyConnection } from "../connection.js";
|
||||||
|
|
||||||
export abstract class AdbScrcpyOptions<
|
export abstract class AdbScrcpyOptions<
|
||||||
T extends object,
|
T extends object,
|
||||||
> extends ScrcpyOptions<T> {
|
> extends ScrcpyOptionsWrapper<T> {
|
||||||
#base: ScrcpyOptions<T>;
|
|
||||||
|
|
||||||
override get defaults(): Required<T> {
|
|
||||||
return this.#base.defaults;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(base: ScrcpyOptions<T>) {
|
|
||||||
super(
|
|
||||||
// HACK: `ScrcpyOptions`'s constructor requires a constructor for the base class,
|
|
||||||
// but we need to pass an instance here.
|
|
||||||
// A normal `function` can be used as a constructor, and constructors can return
|
|
||||||
// any object to override the default return value.
|
|
||||||
function () {
|
|
||||||
return base;
|
|
||||||
} as never,
|
|
||||||
// HACK: `base.value` contains `SkipDefaultMark`, so it will be used as is,
|
|
||||||
// and `defaults` parameter is not used.
|
|
||||||
base.value,
|
|
||||||
{} as Required<T>,
|
|
||||||
);
|
|
||||||
this.#base = base;
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize(): string[] {
|
|
||||||
return this.#base.serialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract getEncoders(
|
abstract getEncoders(
|
||||||
adb: Adb,
|
adb: Adb,
|
||||||
path: string,
|
path: string,
|
||||||
|
|
|
@ -210,6 +210,8 @@ export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder {
|
||||||
this.#updateSize,
|
this.#updateSize,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported codec: ${this.#codec}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#writable = new WritableStream<ScrcpyMediaStreamPacket>({
|
this.#writable = new WritableStream<ScrcpyMediaStreamPacket>({
|
||||||
|
|
19
libraries/scrcpy/side-effect-test/package.json
Normal file
19
libraries/scrcpy/side-effect-test/package.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "side-effect-test",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"start": "rollup -c rollup.config.ts --configPlugin @rollup/plugin-typescript"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||||
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
|
"@rollup/plugin-typescript": "^12.1.1",
|
||||||
|
"rollup": "^4.27.4"
|
||||||
|
}
|
||||||
|
}
|
24
libraries/scrcpy/side-effect-test/rollup.config.ts
Normal file
24
libraries/scrcpy/side-effect-test/rollup.config.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import node from "@rollup/plugin-node-resolve";
|
||||||
|
import terser from "@rollup/plugin-terser";
|
||||||
|
import typescript from "@rollup/plugin-typescript";
|
||||||
|
import { defineConfig } from "rollup";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
input: "src/index.js",
|
||||||
|
experimentalLogSideEffects: true,
|
||||||
|
output: {
|
||||||
|
name: "index",
|
||||||
|
file: "dist/index.js",
|
||||||
|
format: "esm",
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
typescript(),
|
||||||
|
node(),
|
||||||
|
terser({
|
||||||
|
module: true,
|
||||||
|
format: {
|
||||||
|
beautify: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
1
libraries/scrcpy/side-effect-test/src/index.js
Normal file
1
libraries/scrcpy/side-effect-test/src/index.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from "../../esm/index";
|
21
libraries/scrcpy/src/1_15/impl/back-or-screen-on.ts
Normal file
21
libraries/scrcpy/src/1_15/impl/back-or-screen-on.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import type { StructInit } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import { AndroidKeyEventAction } from "../../android/index.js";
|
||||||
|
import { EmptyControlMessage } from "../../control/index.js";
|
||||||
|
import type { ScrcpyBackOrScreenOnControlMessage } from "../../latest.js";
|
||||||
|
|
||||||
|
export const BackOrScreenOnControlMessage = EmptyControlMessage;
|
||||||
|
|
||||||
|
export type BackOrScreenOnControlMessage = StructInit<
|
||||||
|
typeof BackOrScreenOnControlMessage
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function serializeBackOrScreenOnControlMessage(
|
||||||
|
message: ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
) {
|
||||||
|
if (message.action === AndroidKeyEventAction.Down) {
|
||||||
|
return BackOrScreenOnControlMessage.serialize(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
43
libraries/scrcpy/src/1_15/impl/clipboard-stream.ts
Normal file
43
libraries/scrcpy/src/1_15/impl/clipboard-stream.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import type { PushReadableStreamController } from "@yume-chan/stream-extra";
|
||||||
|
import { PushReadableStream } from "@yume-chan/stream-extra";
|
||||||
|
import type { AsyncExactReadable } from "@yume-chan/struct";
|
||||||
|
import { string, struct, u32 } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type { ScrcpyDeviceMessageParser } from "../../base/index.js";
|
||||||
|
|
||||||
|
export const ClipboardDeviceMessage = struct(
|
||||||
|
{ content: string(u32) },
|
||||||
|
{ littleEndian: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
export class ClipboardStream
|
||||||
|
extends PushReadableStream<string>
|
||||||
|
implements ScrcpyDeviceMessageParser
|
||||||
|
{
|
||||||
|
#controller: PushReadableStreamController<string>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
let controller!: PushReadableStreamController<string>;
|
||||||
|
super((controller_) => {
|
||||||
|
controller = controller_;
|
||||||
|
});
|
||||||
|
this.#controller = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
async parse(id: number, stream: AsyncExactReadable): Promise<boolean> {
|
||||||
|
if (id === 0) {
|
||||||
|
const message = await ClipboardDeviceMessage.deserialize(stream);
|
||||||
|
await this.#controller.enqueue(message.content);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.#controller.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
error(e?: unknown) {
|
||||||
|
this.#controller.error(e);
|
||||||
|
}
|
||||||
|
}
|
16
libraries/scrcpy/src/1_15/impl/control-message-types.ts
Normal file
16
libraries/scrcpy/src/1_15/impl/control-message-types.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { ScrcpyControlMessageType } from "../../base/index.js";
|
||||||
|
|
||||||
|
export const ControlMessageTypes: readonly ScrcpyControlMessageType[] =
|
||||||
|
/* #__PURE__ */ (() => [
|
||||||
|
/* 0 */ ScrcpyControlMessageType.InjectKeyCode,
|
||||||
|
/* 1 */ ScrcpyControlMessageType.InjectText,
|
||||||
|
/* 2 */ ScrcpyControlMessageType.InjectTouch,
|
||||||
|
/* 3 */ ScrcpyControlMessageType.InjectScroll,
|
||||||
|
/* 4 */ ScrcpyControlMessageType.BackOrScreenOn,
|
||||||
|
/* 5 */ ScrcpyControlMessageType.ExpandNotificationPanel,
|
||||||
|
/* 6 */ ScrcpyControlMessageType.CollapseNotificationPanel,
|
||||||
|
/* 7 */ ScrcpyControlMessageType.GetClipboard,
|
||||||
|
/* 8 */ ScrcpyControlMessageType.SetClipboard,
|
||||||
|
/* 9 */ ScrcpyControlMessageType.SetDisplayPower,
|
||||||
|
/* 10 */ ScrcpyControlMessageType.RotateDevice,
|
||||||
|
])();
|
19
libraries/scrcpy/src/1_15/impl/defaults.ts
Normal file
19
libraries/scrcpy/src/1_15/impl/defaults.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
import { CodecOptions, VideoOrientation } from "./init.js";
|
||||||
|
|
||||||
|
export const Defaults = /* #__PURE__ */ (() =>
|
||||||
|
({
|
||||||
|
logLevel: "debug",
|
||||||
|
maxSize: 0,
|
||||||
|
bitRate: 8_000_000,
|
||||||
|
maxFps: 0,
|
||||||
|
lockVideoOrientation: VideoOrientation.Unlocked,
|
||||||
|
tunnelForward: false,
|
||||||
|
crop: undefined,
|
||||||
|
sendFrameMeta: true,
|
||||||
|
control: true,
|
||||||
|
displayId: 0,
|
||||||
|
showTouches: false,
|
||||||
|
stayAwake: false,
|
||||||
|
codecOptions: CodecOptions.Empty,
|
||||||
|
}) as const satisfies Required<Init>)();
|
14
libraries/scrcpy/src/1_15/impl/index.ts
Normal file
14
libraries/scrcpy/src/1_15/impl/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
export * from "./back-or-screen-on.js";
|
||||||
|
export * from "./clipboard-stream.js";
|
||||||
|
export * from "./control-message-types.js";
|
||||||
|
export * from "./defaults.js";
|
||||||
|
export * from "./init.js";
|
||||||
|
export * from "./inject-touch.js";
|
||||||
|
export * from "./media-stream-transformer.js";
|
||||||
|
export * from "./parse-display.js";
|
||||||
|
export * from "./parse-video-stream-metadata.js";
|
||||||
|
export * from "./scroll-controller.js";
|
||||||
|
export * from "./serialize-order.js";
|
||||||
|
export * from "./serialize.js";
|
||||||
|
export * from "./set-clipboard.js";
|
||||||
|
export * from "./set-list-display.js";
|
17
libraries/scrcpy/src/1_15/impl/init.spec.ts
Normal file
17
libraries/scrcpy/src/1_15/impl/init.spec.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import * as assert from "node:assert";
|
||||||
|
import { describe, it } from "node:test";
|
||||||
|
|
||||||
|
import { CodecOptions } from "./init.js";
|
||||||
|
|
||||||
|
describe("CodecOptions", () => {
|
||||||
|
it("should convert empty value to `undefined`", () => {
|
||||||
|
assert.strictEqual(new CodecOptions({}).toOptionValue(), undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should serialize unknown options as integers", () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
new CodecOptions({ profile: 42 }).toOptionValue(),
|
||||||
|
"profile=42",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
150
libraries/scrcpy/src/1_15/impl/init.ts
Normal file
150
libraries/scrcpy/src/1_15/impl/init.ts
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
import type { ScrcpyOptionValue } from "../../base/index.js";
|
||||||
|
|
||||||
|
export const VideoOrientation = {
|
||||||
|
Unlocked: -1,
|
||||||
|
Portrait: 0,
|
||||||
|
Landscape: 1,
|
||||||
|
PortraitFlipped: 2,
|
||||||
|
LandscapeFlipped: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type VideoOrientation =
|
||||||
|
(typeof VideoOrientation)[keyof typeof VideoOrientation];
|
||||||
|
|
||||||
|
export type LogLevel = "debug" | "info" | "warn" | "error";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the option you need is not in this type,
|
||||||
|
* please file an issue on GitHub.
|
||||||
|
*/
|
||||||
|
export interface CodecOptionsInit {
|
||||||
|
profile?: number | undefined;
|
||||||
|
level?: number | undefined;
|
||||||
|
|
||||||
|
iFrameInterval?: number | undefined;
|
||||||
|
maxBframes?: number | undefined;
|
||||||
|
repeatPreviousFrameAfter?: number | undefined;
|
||||||
|
maxPtsGapToEncoder?: number | undefined;
|
||||||
|
intraRefreshPeriod?: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDashCase(input: string) {
|
||||||
|
return input.replace(/([A-Z])/g, "-$1").toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
const CodecOptionTypes: Partial<
|
||||||
|
Record<keyof CodecOptionsInit, "long" | "float" | "string">
|
||||||
|
> = {
|
||||||
|
repeatPreviousFrameAfter: "long",
|
||||||
|
maxPtsGapToEncoder: "long",
|
||||||
|
};
|
||||||
|
|
||||||
|
export class CodecOptions implements ScrcpyOptionValue {
|
||||||
|
static Empty = /* #__PURE__ */ new CodecOptions();
|
||||||
|
|
||||||
|
options: CodecOptionsInit;
|
||||||
|
|
||||||
|
constructor(options: CodecOptionsInit = {}) {
|
||||||
|
for (const [key, value] of Object.entries(options)) {
|
||||||
|
if (value === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== "number") {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid option value for ${key}: ${String(value)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
toOptionValue(): string | undefined {
|
||||||
|
const entries = Object.entries(this.options).filter(
|
||||||
|
([, value]) => value !== undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (entries.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
.map(([key, value]) => {
|
||||||
|
let result = toDashCase(key);
|
||||||
|
|
||||||
|
const type = CodecOptionTypes[key as keyof CodecOptionsInit];
|
||||||
|
if (type) {
|
||||||
|
result += `:${type}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
result += `=${value}`;
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
.join(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Init {
|
||||||
|
logLevel?: LogLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum value of both width and height.
|
||||||
|
*/
|
||||||
|
maxSize?: number;
|
||||||
|
|
||||||
|
bitRate?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 0 for unlimited.
|
||||||
|
*
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
|
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?: VideoOrientation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use ADB forward tunnel instead of reverse tunnel.
|
||||||
|
*
|
||||||
|
* This option is mainly used for working around the bug that on Android <9,
|
||||||
|
* ADB daemon can't create reverse tunnels if connected wirelessly (ADB over WiFi).
|
||||||
|
*
|
||||||
|
* When using `AdbScrcpyClient`, it can detect this situation and enable this option automatically.
|
||||||
|
*/
|
||||||
|
tunnelForward?: boolean;
|
||||||
|
|
||||||
|
crop?: string | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send PTS so that the client may record properly
|
||||||
|
*
|
||||||
|
* Note: When `sendFrameMeta: false` is specified,
|
||||||
|
* the video stream will not contain `configuration` typed packets,
|
||||||
|
* which means it can't be decoded by the companion decoders.
|
||||||
|
* It's still possible to record the stream into a file,
|
||||||
|
* or to decode it with a more tolerant decoder like FFMpeg.
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
sendFrameMeta?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
control?: boolean;
|
||||||
|
|
||||||
|
displayId?: number;
|
||||||
|
|
||||||
|
showTouches?: boolean;
|
||||||
|
|
||||||
|
stayAwake?: boolean;
|
||||||
|
|
||||||
|
codecOptions?: CodecOptions;
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
import { getUint16, setUint16 } from "@yume-chan/no-data-view";
|
import { getUint16, setUint16 } from "@yume-chan/no-data-view";
|
||||||
import type { Field } from "@yume-chan/struct";
|
import type { Field, StructInit } from "@yume-chan/struct";
|
||||||
import { bipedal } from "@yume-chan/struct";
|
import { bipedal, struct, u16, u32, u64, u8 } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type { AndroidMotionEventAction } from "../../android/index.js";
|
||||||
|
import type { ScrcpyInjectTouchControlMessage } from "../../latest.js";
|
||||||
|
|
||||||
export function clamp(value: number, min: number, max: number): number {
|
export function clamp(value: number, min: number, max: number): number {
|
||||||
if (value < min) {
|
if (value < min) {
|
||||||
|
@ -14,7 +17,7 @@ export function clamp(value: number, min: number, max: number): number {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ScrcpyUnsignedFloat: Field<number, never, never> = {
|
export const UnsignedFloat: Field<number, never, never> = {
|
||||||
size: 2,
|
size: 2,
|
||||||
serialize(value, { buffer, index, littleEndian }) {
|
serialize(value, { buffer, index, littleEndian }) {
|
||||||
// https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/app/src/util/binary.h#L51
|
// https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/app/src/util/binary.h#L51
|
||||||
|
@ -29,3 +32,28 @@ export const ScrcpyUnsignedFloat: Field<number, never, never> = {
|
||||||
return value === 0xffff ? 1 : value / 0x10000;
|
return value === 0xffff ? 1 : value / 0x10000;
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const InjectTouchControlMessage = struct(
|
||||||
|
{
|
||||||
|
type: u8,
|
||||||
|
action: u8<AndroidMotionEventAction>(),
|
||||||
|
pointerId: u64,
|
||||||
|
pointerX: u32,
|
||||||
|
pointerY: u32,
|
||||||
|
screenWidth: u16,
|
||||||
|
screenHeight: u16,
|
||||||
|
pressure: UnsignedFloat,
|
||||||
|
buttons: u32,
|
||||||
|
},
|
||||||
|
{ littleEndian: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
export type InjectTouchControlMessage = StructInit<
|
||||||
|
typeof InjectTouchControlMessage
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function serializeInjectTouchControlMessage(
|
||||||
|
message: ScrcpyInjectTouchControlMessage,
|
||||||
|
): Uint8Array {
|
||||||
|
return InjectTouchControlMessage.serialize(message);
|
||||||
|
}
|
56
libraries/scrcpy/src/1_15/impl/media-stream-transformer.ts
Normal file
56
libraries/scrcpy/src/1_15/impl/media-stream-transformer.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import {
|
||||||
|
StructDeserializeStream,
|
||||||
|
TransformStream,
|
||||||
|
} from "@yume-chan/stream-extra";
|
||||||
|
import { buffer, struct, u32, u64 } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type { ScrcpyMediaStreamPacket } from "../../base/index.js";
|
||||||
|
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
|
||||||
|
export const MediaStreamRawPacket = struct(
|
||||||
|
{ pts: u64, data: buffer(u32) },
|
||||||
|
{ littleEndian: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
export const PtsConfig = 1n << 63n;
|
||||||
|
|
||||||
|
export function createMediaStreamTransformer(
|
||||||
|
options: Pick<Init, "sendFrameMeta">,
|
||||||
|
): TransformStream<Uint8Array, ScrcpyMediaStreamPacket> {
|
||||||
|
// Optimized path for video frames only
|
||||||
|
if (!options.sendFrameMeta) {
|
||||||
|
return new TransformStream({
|
||||||
|
transform(chunk, controller) {
|
||||||
|
controller.enqueue({
|
||||||
|
type: "data",
|
||||||
|
data: chunk,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const deserializeStream = new StructDeserializeStream(MediaStreamRawPacket);
|
||||||
|
return {
|
||||||
|
writable: deserializeStream.writable,
|
||||||
|
readable: deserializeStream.readable.pipeThrough(
|
||||||
|
new TransformStream({
|
||||||
|
transform(packet, controller) {
|
||||||
|
if (packet.pts === PtsConfig) {
|
||||||
|
controller.enqueue({
|
||||||
|
type: "configuration",
|
||||||
|
data: packet.data,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.enqueue({
|
||||||
|
type: "data",
|
||||||
|
pts: packet.pts,
|
||||||
|
data: packet.data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
11
libraries/scrcpy/src/1_15/impl/parse-display.ts
Normal file
11
libraries/scrcpy/src/1_15/impl/parse-display.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import type { ScrcpyDisplay } from "../../base/index.js";
|
||||||
|
|
||||||
|
export function parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||||
|
const match = line.match(/^\s+scrcpy --display (\d+)$/);
|
||||||
|
if (match) {
|
||||||
|
return {
|
||||||
|
id: Number.parseInt(match[1]!, 10),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
import {
|
||||||
|
getUint16BigEndian,
|
||||||
|
getUint32BigEndian,
|
||||||
|
} from "@yume-chan/no-data-view";
|
||||||
|
import type { ReadableStream } from "@yume-chan/stream-extra";
|
||||||
|
import { BufferedReadableStream } from "@yume-chan/stream-extra";
|
||||||
|
import type { AsyncExactReadable } from "@yume-chan/struct";
|
||||||
|
import { decodeUtf8 } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ScrcpyVideoStream,
|
||||||
|
ScrcpyVideoStreamMetadata,
|
||||||
|
} from "../../base/index.js";
|
||||||
|
import { ScrcpyVideoCodecId } from "../../base/index.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a fixed-length, null-terminated string.
|
||||||
|
* @param stream The stream to read from
|
||||||
|
* @param maxLength The maximum length of the string, including the null terminator, in bytes
|
||||||
|
* @returns The parsed string, without the null terminator
|
||||||
|
*/
|
||||||
|
export async function readString(
|
||||||
|
stream: AsyncExactReadable,
|
||||||
|
maxLength: number,
|
||||||
|
): Promise<string> {
|
||||||
|
const buffer = await stream.readExactly(maxLength);
|
||||||
|
// If null terminator is not found, `subarray(0, -1)` will remove the last byte
|
||||||
|
// But since it's a invalid case, it's fine
|
||||||
|
return decodeUtf8(buffer.subarray(0, buffer.indexOf(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readU16(stream: AsyncExactReadable): Promise<number> {
|
||||||
|
const buffer = await stream.readExactly(2);
|
||||||
|
return getUint16BigEndian(buffer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readU32(stream: AsyncExactReadable): Promise<number> {
|
||||||
|
const buffer = await stream.readExactly(4);
|
||||||
|
return getUint32BigEndian(buffer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function parseVideoStreamMetadata(
|
||||||
|
stream: ReadableStream<Uint8Array>,
|
||||||
|
): Promise<ScrcpyVideoStream> {
|
||||||
|
const buffered = new BufferedReadableStream(stream);
|
||||||
|
const metadata: ScrcpyVideoStreamMetadata = {
|
||||||
|
codec: ScrcpyVideoCodecId.H264,
|
||||||
|
};
|
||||||
|
metadata.deviceName = await readString(buffered, 64);
|
||||||
|
metadata.width = await readU16(buffered);
|
||||||
|
metadata.height = await readU16(buffered);
|
||||||
|
return { stream: buffered.release(), metadata };
|
||||||
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
import * as assert from "node:assert";
|
import * as assert from "node:assert";
|
||||||
import { describe, it } from "node:test";
|
import { describe, it } from "node:test";
|
||||||
|
|
||||||
import { ScrcpyControlMessageType } from "../../control/index.js";
|
import { ScrcpyControlMessageType } from "../../base/index.js";
|
||||||
|
|
||||||
import { ScrcpyScrollController1_16 } from "./scroll.js";
|
import { ScrollController } from "./scroll-controller.js";
|
||||||
|
|
||||||
describe("ScrcpyScrollController1_16", () => {
|
describe("ScrollController", () => {
|
||||||
it("should return undefined when scroll distance is less than 1", () => {
|
it("should return undefined when scroll distance is less than 1", () => {
|
||||||
const controller = new ScrcpyScrollController1_16();
|
const controller = new ScrollController();
|
||||||
const message = controller.serializeScrollMessage({
|
const message = controller.serializeScrollMessage({
|
||||||
type: ScrcpyControlMessageType.InjectScroll,
|
type: ScrcpyControlMessageType.InjectScroll,
|
||||||
pointerX: 0,
|
pointerX: 0,
|
||||||
|
@ -22,7 +22,7 @@ describe("ScrcpyScrollController1_16", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return a message when scroll distance is greater than 1", () => {
|
it("should return a message when scroll distance is greater than 1", () => {
|
||||||
const controller = new ScrcpyScrollController1_16();
|
const controller = new ScrollController();
|
||||||
const message = controller.serializeScrollMessage({
|
const message = controller.serializeScrollMessage({
|
||||||
type: ScrcpyControlMessageType.InjectScroll,
|
type: ScrcpyControlMessageType.InjectScroll,
|
||||||
pointerX: 0,
|
pointerX: 0,
|
||||||
|
@ -38,7 +38,7 @@ describe("ScrcpyScrollController1_16", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return a message when accumulated scroll distance is greater than 1", () => {
|
it("should return a message when accumulated scroll distance is greater than 1", () => {
|
||||||
const controller = new ScrcpyScrollController1_16();
|
const controller = new ScrollController();
|
||||||
controller.serializeScrollMessage({
|
controller.serializeScrollMessage({
|
||||||
type: ScrcpyControlMessageType.InjectScroll,
|
type: ScrcpyControlMessageType.InjectScroll,
|
||||||
pointerX: 0,
|
pointerX: 0,
|
||||||
|
@ -64,7 +64,7 @@ describe("ScrcpyScrollController1_16", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return a message when accumulated scroll distance is less than -1", () => {
|
it("should return a message when accumulated scroll distance is less than -1", () => {
|
||||||
const controller = new ScrcpyScrollController1_16();
|
const controller = new ScrollController();
|
||||||
controller.serializeScrollMessage({
|
controller.serializeScrollMessage({
|
||||||
type: ScrcpyControlMessageType.InjectScroll,
|
type: ScrcpyControlMessageType.InjectScroll,
|
||||||
pointerX: 0,
|
pointerX: 0,
|
|
@ -1,14 +1,10 @@
|
||||||
|
import type { StructInit } from "@yume-chan/struct";
|
||||||
import { s32, struct, u16, u32, u8 } from "@yume-chan/struct";
|
import { s32, struct, u16, u32, u8 } from "@yume-chan/struct";
|
||||||
|
|
||||||
import type { ScrcpyInjectScrollControlMessage } from "../../control/index.js";
|
import type { ScrcpyScrollController } from "../../base/index.js";
|
||||||
|
import type { ScrcpyInjectScrollControlMessage } from "../../latest.js";
|
||||||
|
|
||||||
export interface ScrcpyScrollController {
|
export const InjectScrollControlMessage = struct(
|
||||||
serializeScrollMessage(
|
|
||||||
message: ScrcpyInjectScrollControlMessage,
|
|
||||||
): Uint8Array | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ScrcpyInjectScrollControlMessage1_16 = struct(
|
|
||||||
{
|
{
|
||||||
type: u8,
|
type: u8,
|
||||||
pointerX: u32,
|
pointerX: u32,
|
||||||
|
@ -21,13 +17,17 @@ export const ScrcpyInjectScrollControlMessage1_16 = struct(
|
||||||
{ littleEndian: false },
|
{ littleEndian: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type InjectScrollControlMessage = StructInit<
|
||||||
|
typeof InjectScrollControlMessage
|
||||||
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Old version of Scrcpy server only supports integer values for scroll.
|
* Old version of Scrcpy server only supports integer values for scroll.
|
||||||
*
|
*
|
||||||
* Accumulate scroll values and send scroll message when accumulated value
|
* Accumulate scroll values and send scroll message when accumulated value
|
||||||
* reaches 1 or -1.
|
* reaches 1 or -1.
|
||||||
*/
|
*/
|
||||||
export class ScrcpyScrollController1_16 implements ScrcpyScrollController {
|
export class ScrollController implements ScrcpyScrollController {
|
||||||
#accumulatedX = 0;
|
#accumulatedX = 0;
|
||||||
#accumulatedY = 0;
|
#accumulatedY = 0;
|
||||||
|
|
||||||
|
@ -72,6 +72,10 @@ export class ScrcpyScrollController1_16 implements ScrcpyScrollController {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ScrcpyInjectScrollControlMessage1_16.serialize(processed);
|
return InjectScrollControlMessage.serialize(processed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createScrollController(): ScrcpyScrollController {
|
||||||
|
return new ScrollController();
|
||||||
|
}
|
17
libraries/scrcpy/src/1_15/impl/serialize-order.ts
Normal file
17
libraries/scrcpy/src/1_15/impl/serialize-order.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
|
||||||
|
export const SerializeOrder = [
|
||||||
|
"logLevel",
|
||||||
|
"maxSize",
|
||||||
|
"bitRate",
|
||||||
|
"maxFps",
|
||||||
|
"lockVideoOrientation",
|
||||||
|
"tunnelForward",
|
||||||
|
"crop",
|
||||||
|
"sendFrameMeta",
|
||||||
|
"control",
|
||||||
|
"displayId",
|
||||||
|
"showTouches",
|
||||||
|
"stayAwake",
|
||||||
|
"codecOptions",
|
||||||
|
] as const satisfies readonly (keyof Init)[];
|
5
libraries/scrcpy/src/1_15/impl/serialize.ts
Normal file
5
libraries/scrcpy/src/1_15/impl/serialize.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { toScrcpyOptionValue } from "../../base/index.js";
|
||||||
|
|
||||||
|
export function serialize<T>(options: T, order: readonly (keyof T)[]) {
|
||||||
|
return order.map((key) => toScrcpyOptionValue(options[key], "-"));
|
||||||
|
}
|
19
libraries/scrcpy/src/1_15/impl/set-clipboard.ts
Normal file
19
libraries/scrcpy/src/1_15/impl/set-clipboard.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import type { StructInit } from "@yume-chan/struct";
|
||||||
|
import { string, struct, u32, u8 } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type { ScrcpySetClipboardControlMessage } from "../../latest.js";
|
||||||
|
|
||||||
|
export const SetClipboardControlMessage = struct(
|
||||||
|
{ type: u8, content: string(u32) },
|
||||||
|
{ littleEndian: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
export type SetClipboardControlMessage = StructInit<
|
||||||
|
typeof SetClipboardControlMessage
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function serializeSetClipboardControlMessage(
|
||||||
|
message: ScrcpySetClipboardControlMessage,
|
||||||
|
): Uint8Array {
|
||||||
|
return SetClipboardControlMessage.serialize(message);
|
||||||
|
}
|
8
libraries/scrcpy/src/1_15/impl/set-list-display.ts
Normal file
8
libraries/scrcpy/src/1_15/impl/set-list-display.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
|
||||||
|
export function setListDisplays(options: Pick<Init, "displayId">): void {
|
||||||
|
// Set to an invalid value
|
||||||
|
// Server will print valid values before crashing
|
||||||
|
// (server will crash before opening sockets)
|
||||||
|
options.displayId = -1;
|
||||||
|
}
|
2
libraries/scrcpy/src/1_15/index.ts
Normal file
2
libraries/scrcpy/src/1_15/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * as ScrcpyOptions1_15Impl from "./impl/index.js";
|
||||||
|
export * from "./options.js";
|
|
@ -1,12 +1,12 @@
|
||||||
import * as assert from "node:assert";
|
import * as assert from "node:assert";
|
||||||
import { describe, it } from "node:test";
|
import { describe, it } from "node:test";
|
||||||
|
|
||||||
import { ScrcpyOptions1_16 } from "./options.js";
|
import { ScrcpyOptions1_15 } from "./options.js";
|
||||||
|
|
||||||
describe("ScrcpyOptions1_16", () => {
|
describe("ScrcpyOptions1_15", () => {
|
||||||
describe("serialize", () => {
|
describe("serialize", () => {
|
||||||
it("should return `-` for default values", () => {
|
it("should return `-` for default values", () => {
|
||||||
assert.deepStrictEqual(new ScrcpyOptions1_16({}).serialize(), [
|
assert.deepStrictEqual(new ScrcpyOptions1_15({}).serialize(), [
|
||||||
"debug",
|
"debug",
|
||||||
"0",
|
"0",
|
||||||
"8000000",
|
"8000000",
|
||||||
|
@ -26,7 +26,7 @@ describe("ScrcpyOptions1_16", () => {
|
||||||
|
|
||||||
describe("setListDisplays", () => {
|
describe("setListDisplays", () => {
|
||||||
it("should set `display` to `-1`", () => {
|
it("should set `display` to `-1`", () => {
|
||||||
const options = new ScrcpyOptions1_16({});
|
const options = new ScrcpyOptions1_15({});
|
||||||
options.setListDisplays();
|
options.setListDisplays();
|
||||||
assert.strictEqual(options.value.displayId, -1);
|
assert.strictEqual(options.value.displayId, -1);
|
||||||
});
|
});
|
127
libraries/scrcpy/src/1_15/options.ts
Normal file
127
libraries/scrcpy/src/1_15/options.ts
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
import type { MaybePromiseLike } from "@yume-chan/async";
|
||||||
|
import type { ReadableStream, TransformStream } from "@yume-chan/stream-extra";
|
||||||
|
import type { AsyncExactReadable } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ScrcpyControlMessageType,
|
||||||
|
ScrcpyDisplay,
|
||||||
|
ScrcpyMediaStreamPacket,
|
||||||
|
ScrcpyOptions,
|
||||||
|
ScrcpyScrollController,
|
||||||
|
ScrcpyVideoStream,
|
||||||
|
} from "../base/index.js";
|
||||||
|
import type {
|
||||||
|
ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
ScrcpyInjectTouchControlMessage,
|
||||||
|
ScrcpySetClipboardControlMessage,
|
||||||
|
} from "../latest.js";
|
||||||
|
|
||||||
|
import type { Init } from "./impl/index.js";
|
||||||
|
import {
|
||||||
|
ClipboardStream,
|
||||||
|
ControlMessageTypes,
|
||||||
|
createMediaStreamTransformer,
|
||||||
|
createScrollController,
|
||||||
|
Defaults,
|
||||||
|
parseDisplay,
|
||||||
|
parseVideoStreamMetadata,
|
||||||
|
serialize,
|
||||||
|
serializeBackOrScreenOnControlMessage,
|
||||||
|
serializeInjectTouchControlMessage,
|
||||||
|
SerializeOrder,
|
||||||
|
serializeSetClipboardControlMessage,
|
||||||
|
setListDisplays,
|
||||||
|
} from "./impl/index.js";
|
||||||
|
|
||||||
|
export class ScrcpyOptions1_15 implements ScrcpyOptions<Init> {
|
||||||
|
readonly value: Required<Init>;
|
||||||
|
|
||||||
|
get controlMessageTypes(): readonly ScrcpyControlMessageType[] {
|
||||||
|
return ControlMessageTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clipboard: ClipboardStream | undefined;
|
||||||
|
get clipboard(): ReadableStream<string> | undefined {
|
||||||
|
return this.#clipboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(init: Init) {
|
||||||
|
this.value = { ...Defaults, ...init };
|
||||||
|
|
||||||
|
if (this.value.control) {
|
||||||
|
this.#clipboard = new ClipboardStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize(): string[] {
|
||||||
|
return serialize(this.value, SerializeOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListDisplays(): void {
|
||||||
|
setListDisplays(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||||
|
return parseDisplay(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseVideoStreamMetadata(
|
||||||
|
stream: ReadableStream<Uint8Array>,
|
||||||
|
): MaybePromiseLike<ScrcpyVideoStream> {
|
||||||
|
return parseVideoStreamMetadata(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseDeviceMessage(
|
||||||
|
id: number,
|
||||||
|
stream: AsyncExactReadable,
|
||||||
|
): Promise<void> {
|
||||||
|
if (await this.#clipboard!.parse(id, stream)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unknown device message");
|
||||||
|
}
|
||||||
|
|
||||||
|
endDeviceMessageStream(e?: unknown): void {
|
||||||
|
if (e) {
|
||||||
|
this.#clipboard!.error(e);
|
||||||
|
} else {
|
||||||
|
this.#clipboard!.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createMediaStreamTransformer(): TransformStream<
|
||||||
|
Uint8Array,
|
||||||
|
ScrcpyMediaStreamPacket
|
||||||
|
> {
|
||||||
|
return createMediaStreamTransformer(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeInjectTouchControlMessage(
|
||||||
|
message: ScrcpyInjectTouchControlMessage,
|
||||||
|
): Uint8Array {
|
||||||
|
return serializeInjectTouchControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeBackOrScreenOnControlMessage(
|
||||||
|
message: ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
): Uint8Array | undefined {
|
||||||
|
return serializeBackOrScreenOnControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeSetClipboardControlMessage(
|
||||||
|
message: ScrcpySetClipboardControlMessage,
|
||||||
|
): Uint8Array {
|
||||||
|
return serializeSetClipboardControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
createScrollController(): ScrcpyScrollController {
|
||||||
|
return createScrollController();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Init_ = Init;
|
||||||
|
|
||||||
|
export namespace ScrcpyOptions1_15 {
|
||||||
|
export type Init = Init_;
|
||||||
|
}
|
4
libraries/scrcpy/src/1_16.ts
Normal file
4
libraries/scrcpy/src/1_16.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export {
|
||||||
|
ScrcpyOptions1_15 as ScrcpyOptions1_16,
|
||||||
|
ScrcpyOptions1_15Impl as ScrcpyOptions1_16Impl,
|
||||||
|
} from "./1_15/index.js";
|
8
libraries/scrcpy/src/1_17/impl/defaults.ts
Normal file
8
libraries/scrcpy/src/1_17/impl/defaults.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export const Defaults = /* #__PURE__ */ (() =>
|
||||||
|
({
|
||||||
|
...PrevImpl.Defaults,
|
||||||
|
encoderName: undefined,
|
||||||
|
}) as const satisfies Required<Init>)();
|
6
libraries/scrcpy/src/1_17/impl/index.ts
Normal file
6
libraries/scrcpy/src/1_17/impl/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export * from "../../1_15/impl/index.js";
|
||||||
|
export { Defaults } from "./defaults.js";
|
||||||
|
export type { Init } from "./init.js";
|
||||||
|
export * from "./parse-encoder.js";
|
||||||
|
export { SerializeOrder } from "./serialize-order.js";
|
||||||
|
export * from "./set-list-encoder.js";
|
5
libraries/scrcpy/src/1_17/impl/init.ts
Normal file
5
libraries/scrcpy/src/1_17/impl/init.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import type { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export interface Init extends PrevImpl.Init {
|
||||||
|
encoderName?: string | undefined;
|
||||||
|
}
|
14
libraries/scrcpy/src/1_17/impl/parse-encoder.ts
Normal file
14
libraries/scrcpy/src/1_17/impl/parse-encoder.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import type { ScrcpyEncoder } from "../../base/index.js";
|
||||||
|
|
||||||
|
export function parseEncoder(
|
||||||
|
line: string,
|
||||||
|
encoderNameRegex: RegExp,
|
||||||
|
): ScrcpyEncoder | undefined {
|
||||||
|
const match = line.match(encoderNameRegex);
|
||||||
|
if (match) {
|
||||||
|
return { type: "video", name: match[1]! };
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EncoderRegex = /^\s+scrcpy --encoder-name '([^']+)'$/;
|
1
libraries/scrcpy/src/1_17/impl/prev.ts
Normal file
1
libraries/scrcpy/src/1_17/impl/prev.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * as PrevImpl from "../../1_15/impl/index.js";
|
8
libraries/scrcpy/src/1_17/impl/serialize-order.ts
Normal file
8
libraries/scrcpy/src/1_17/impl/serialize-order.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export const SerializeOrder = /* #__PURE__ */ (() =>
|
||||||
|
[
|
||||||
|
...PrevImpl.SerializeOrder,
|
||||||
|
"encoderName",
|
||||||
|
] as const satisfies readonly (keyof Init)[])();
|
8
libraries/scrcpy/src/1_17/impl/set-list-encoder.ts
Normal file
8
libraries/scrcpy/src/1_17/impl/set-list-encoder.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
|
||||||
|
export function setListEncoders(options: Pick<Init, "encoderName">): void {
|
||||||
|
// Set to an invalid value
|
||||||
|
// Server will print valid values before crashing
|
||||||
|
// (server will crash after opening video and control sockets)
|
||||||
|
options.encoderName = "_";
|
||||||
|
}
|
2
libraries/scrcpy/src/1_17/index.ts
Normal file
2
libraries/scrcpy/src/1_17/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * as ScrcpyOptions1_17Impl from "./impl/index.js";
|
||||||
|
export * from "./options.js";
|
|
@ -1,7 +1,7 @@
|
||||||
import * as assert from "node:assert";
|
import * as assert from "node:assert";
|
||||||
import { describe, it } from "node:test";
|
import { describe, it } from "node:test";
|
||||||
|
|
||||||
import { ScrcpyOptions1_17 } from "./1_17.js";
|
import { ScrcpyOptions1_17 } from "./options.js";
|
||||||
|
|
||||||
describe("ScrcpyOptions1_17", () => {
|
describe("ScrcpyOptions1_17", () => {
|
||||||
it("should share `value` with `base`", () => {
|
it("should share `value` with `base`", () => {
|
139
libraries/scrcpy/src/1_17/options.ts
Normal file
139
libraries/scrcpy/src/1_17/options.ts
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
import type { MaybePromiseLike } from "@yume-chan/async";
|
||||||
|
import type { ReadableStream, TransformStream } from "@yume-chan/stream-extra";
|
||||||
|
import type { AsyncExactReadable } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ScrcpyControlMessageType,
|
||||||
|
ScrcpyDisplay,
|
||||||
|
ScrcpyEncoder,
|
||||||
|
ScrcpyMediaStreamPacket,
|
||||||
|
ScrcpyOptions,
|
||||||
|
ScrcpyScrollController,
|
||||||
|
ScrcpyVideoStream,
|
||||||
|
} from "../base/index.js";
|
||||||
|
import type {
|
||||||
|
ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
ScrcpyInjectTouchControlMessage,
|
||||||
|
ScrcpySetClipboardControlMessage,
|
||||||
|
} from "../latest.js";
|
||||||
|
|
||||||
|
import type { Init } from "./impl/index.js";
|
||||||
|
import {
|
||||||
|
ClipboardStream,
|
||||||
|
ControlMessageTypes,
|
||||||
|
createMediaStreamTransformer,
|
||||||
|
createScrollController,
|
||||||
|
Defaults,
|
||||||
|
EncoderRegex,
|
||||||
|
parseDisplay,
|
||||||
|
parseEncoder,
|
||||||
|
parseVideoStreamMetadata,
|
||||||
|
serialize,
|
||||||
|
serializeBackOrScreenOnControlMessage,
|
||||||
|
serializeInjectTouchControlMessage,
|
||||||
|
SerializeOrder,
|
||||||
|
serializeSetClipboardControlMessage,
|
||||||
|
setListDisplays,
|
||||||
|
setListEncoders,
|
||||||
|
} from "./impl/index.js";
|
||||||
|
|
||||||
|
export class ScrcpyOptions1_17 implements ScrcpyOptions<Init> {
|
||||||
|
readonly value: Required<Init>;
|
||||||
|
|
||||||
|
get controlMessageTypes(): readonly ScrcpyControlMessageType[] {
|
||||||
|
return ControlMessageTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clipboard: ClipboardStream | undefined;
|
||||||
|
get clipboard(): ReadableStream<string> | undefined {
|
||||||
|
return this.#clipboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(init: Init) {
|
||||||
|
this.value = { ...Defaults, ...init };
|
||||||
|
|
||||||
|
if (this.value.control) {
|
||||||
|
this.#clipboard = new ClipboardStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize(): string[] {
|
||||||
|
return serialize(this.value, SerializeOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListDisplays(): void {
|
||||||
|
setListDisplays(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||||
|
return parseDisplay(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListEncoders() {
|
||||||
|
setListEncoders(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseEncoder(line: string): ScrcpyEncoder | undefined {
|
||||||
|
return parseEncoder(line, EncoderRegex);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseVideoStreamMetadata(
|
||||||
|
stream: ReadableStream<Uint8Array>,
|
||||||
|
): MaybePromiseLike<ScrcpyVideoStream> {
|
||||||
|
return parseVideoStreamMetadata(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseDeviceMessage(
|
||||||
|
id: number,
|
||||||
|
stream: AsyncExactReadable,
|
||||||
|
): Promise<void> {
|
||||||
|
if (await this.#clipboard!.parse(id, stream)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unknown device message");
|
||||||
|
}
|
||||||
|
|
||||||
|
endDeviceMessageStream(e?: unknown): void {
|
||||||
|
if (e) {
|
||||||
|
this.#clipboard!.error(e);
|
||||||
|
} else {
|
||||||
|
this.#clipboard!.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createMediaStreamTransformer(): TransformStream<
|
||||||
|
Uint8Array,
|
||||||
|
ScrcpyMediaStreamPacket
|
||||||
|
> {
|
||||||
|
return createMediaStreamTransformer(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeInjectTouchControlMessage(
|
||||||
|
message: ScrcpyInjectTouchControlMessage,
|
||||||
|
): Uint8Array {
|
||||||
|
return serializeInjectTouchControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeBackOrScreenOnControlMessage(
|
||||||
|
message: ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
): Uint8Array | undefined {
|
||||||
|
return serializeBackOrScreenOnControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeSetClipboardControlMessage(
|
||||||
|
message: ScrcpySetClipboardControlMessage,
|
||||||
|
): Uint8Array {
|
||||||
|
return serializeSetClipboardControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
createScrollController(): ScrcpyScrollController {
|
||||||
|
return createScrollController();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Init_ = Init;
|
||||||
|
|
||||||
|
export namespace ScrcpyOptions1_17 {
|
||||||
|
export type Init = Init_;
|
||||||
|
}
|
26
libraries/scrcpy/src/1_18/impl/back-or-screen-on.ts
Normal file
26
libraries/scrcpy/src/1_18/impl/back-or-screen-on.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import type { StructInit } from "@yume-chan/struct";
|
||||||
|
import { struct, u8 } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type { AndroidKeyEventAction } from "../../android/index.js";
|
||||||
|
import type { ScrcpyBackOrScreenOnControlMessage } from "../../latest.js";
|
||||||
|
|
||||||
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export const BackOrScreenOnControlMessage = /* #__PURE__ */ (() =>
|
||||||
|
struct(
|
||||||
|
{
|
||||||
|
...PrevImpl.BackOrScreenOnControlMessage.fields,
|
||||||
|
action: u8<AndroidKeyEventAction>(),
|
||||||
|
},
|
||||||
|
{ littleEndian: false },
|
||||||
|
))();
|
||||||
|
|
||||||
|
export type BackOrScreenOnControlMessage = StructInit<
|
||||||
|
typeof BackOrScreenOnControlMessage
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function serializeBackOrScreenOnControlMessage(
|
||||||
|
message: ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
) {
|
||||||
|
return BackOrScreenOnControlMessage.serialize(message);
|
||||||
|
}
|
10
libraries/scrcpy/src/1_18/impl/control-message-types.ts
Normal file
10
libraries/scrcpy/src/1_18/impl/control-message-types.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { ScrcpyControlMessageType } from "../../base/index.js";
|
||||||
|
|
||||||
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export const ControlMessageTypes: readonly ScrcpyControlMessageType[] =
|
||||||
|
/* #__PURE__ */ (() => {
|
||||||
|
const result = PrevImpl.ControlMessageTypes.slice();
|
||||||
|
result.splice(6, 0, ScrcpyControlMessageType.ExpandSettingPanel);
|
||||||
|
return result;
|
||||||
|
})();
|
11
libraries/scrcpy/src/1_18/impl/defaults.ts
Normal file
11
libraries/scrcpy/src/1_18/impl/defaults.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
import { VideoOrientation } from "./init.js";
|
||||||
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export const Defaults = /* #__PURE__ */ (() =>
|
||||||
|
({
|
||||||
|
...PrevImpl.Defaults,
|
||||||
|
logLevel: "debug",
|
||||||
|
lockVideoOrientation: VideoOrientation.Unlocked,
|
||||||
|
powerOffOnClose: false,
|
||||||
|
}) as const satisfies Required<Init>)();
|
10
libraries/scrcpy/src/1_18/impl/index.ts
Normal file
10
libraries/scrcpy/src/1_18/impl/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
export * from "../../1_17/impl/index.js";
|
||||||
|
export {
|
||||||
|
BackOrScreenOnControlMessage,
|
||||||
|
serializeBackOrScreenOnControlMessage,
|
||||||
|
} from "./back-or-screen-on.js";
|
||||||
|
export { ControlMessageTypes } from "./control-message-types.js";
|
||||||
|
export { Defaults } from "./defaults.js";
|
||||||
|
export type { Init } from "./init.js";
|
||||||
|
export { EncoderRegex } from "./parse-encoder.js";
|
||||||
|
export { SerializeOrder } from "./serialize-order.js";
|
24
libraries/scrcpy/src/1_18/impl/init.ts
Normal file
24
libraries/scrcpy/src/1_18/impl/init.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import type { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export type LogLevel = "verbose" | "debug" | "info" | "warn" | "error";
|
||||||
|
|
||||||
|
export const VideoOrientation = {
|
||||||
|
Initial: -2,
|
||||||
|
Unlocked: -1,
|
||||||
|
Portrait: 0,
|
||||||
|
Landscape: 1,
|
||||||
|
PortraitFlipped: 2,
|
||||||
|
LandscapeFlipped: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type VideoOrientation =
|
||||||
|
(typeof VideoOrientation)[keyof typeof VideoOrientation];
|
||||||
|
|
||||||
|
export interface Init
|
||||||
|
extends Omit<PrevImpl.Init, "logLevel" | "lockVideoOrientation"> {
|
||||||
|
logLevel?: LogLevel;
|
||||||
|
|
||||||
|
lockVideoOrientation?: VideoOrientation;
|
||||||
|
|
||||||
|
powerOffOnClose?: boolean;
|
||||||
|
}
|
1
libraries/scrcpy/src/1_18/impl/parse-encoder.ts
Normal file
1
libraries/scrcpy/src/1_18/impl/parse-encoder.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const EncoderRegex = /^\s+scrcpy --encoder '([^']+)'$/;
|
1
libraries/scrcpy/src/1_18/impl/prev.ts
Normal file
1
libraries/scrcpy/src/1_18/impl/prev.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * as PrevImpl from "../../1_17/impl/index.js";
|
8
libraries/scrcpy/src/1_18/impl/serialize-order.ts
Normal file
8
libraries/scrcpy/src/1_18/impl/serialize-order.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export const SerializeOrder = /* #__PURE__ */ (() =>
|
||||||
|
[
|
||||||
|
...PrevImpl.SerializeOrder,
|
||||||
|
"powerOffOnClose",
|
||||||
|
] as const satisfies readonly (keyof Init)[])();
|
2
libraries/scrcpy/src/1_18/index.ts
Normal file
2
libraries/scrcpy/src/1_18/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * as ScrcpyOptions1_18Impl from "./impl/index.js";
|
||||||
|
export * from "./options.js";
|
|
@ -1,7 +1,7 @@
|
||||||
import * as assert from "node:assert";
|
import * as assert from "node:assert";
|
||||||
import { describe, it } from "node:test";
|
import { describe, it } from "node:test";
|
||||||
|
|
||||||
import { ScrcpyOptions1_18 } from "./1_18.js";
|
import { ScrcpyOptions1_18 } from "./options.js";
|
||||||
|
|
||||||
describe("ScrcpyOptions1_18", () => {
|
describe("ScrcpyOptions1_18", () => {
|
||||||
it("should share `value` with `base`", () => {
|
it("should share `value` with `base`", () => {
|
139
libraries/scrcpy/src/1_18/options.ts
Normal file
139
libraries/scrcpy/src/1_18/options.ts
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
import type { MaybePromiseLike } from "@yume-chan/async";
|
||||||
|
import type { ReadableStream, TransformStream } from "@yume-chan/stream-extra";
|
||||||
|
import type { AsyncExactReadable } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ScrcpyControlMessageType,
|
||||||
|
ScrcpyDisplay,
|
||||||
|
ScrcpyEncoder,
|
||||||
|
ScrcpyMediaStreamPacket,
|
||||||
|
ScrcpyOptions,
|
||||||
|
ScrcpyScrollController,
|
||||||
|
ScrcpyVideoStream,
|
||||||
|
} from "../base/index.js";
|
||||||
|
import type {
|
||||||
|
ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
ScrcpyInjectTouchControlMessage,
|
||||||
|
ScrcpySetClipboardControlMessage,
|
||||||
|
} from "../latest.js";
|
||||||
|
|
||||||
|
import type { Init } from "./impl/index.js";
|
||||||
|
import {
|
||||||
|
ClipboardStream,
|
||||||
|
ControlMessageTypes,
|
||||||
|
createMediaStreamTransformer,
|
||||||
|
createScrollController,
|
||||||
|
Defaults,
|
||||||
|
EncoderRegex,
|
||||||
|
parseDisplay,
|
||||||
|
parseEncoder,
|
||||||
|
parseVideoStreamMetadata,
|
||||||
|
serialize,
|
||||||
|
serializeBackOrScreenOnControlMessage,
|
||||||
|
serializeInjectTouchControlMessage,
|
||||||
|
SerializeOrder,
|
||||||
|
serializeSetClipboardControlMessage,
|
||||||
|
setListDisplays,
|
||||||
|
setListEncoders,
|
||||||
|
} from "./impl/index.js";
|
||||||
|
|
||||||
|
export class ScrcpyOptions1_18 implements ScrcpyOptions<Init> {
|
||||||
|
readonly value: Required<Init>;
|
||||||
|
|
||||||
|
get controlMessageTypes(): readonly ScrcpyControlMessageType[] {
|
||||||
|
return ControlMessageTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clipboard: ClipboardStream | undefined;
|
||||||
|
get clipboard(): ReadableStream<string> | undefined {
|
||||||
|
return this.#clipboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(init: Init) {
|
||||||
|
this.value = { ...Defaults, ...init };
|
||||||
|
|
||||||
|
if (this.value.control) {
|
||||||
|
this.#clipboard = new ClipboardStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize(): string[] {
|
||||||
|
return serialize(this.value, SerializeOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListDisplays(): void {
|
||||||
|
setListDisplays(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||||
|
return parseDisplay(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListEncoders() {
|
||||||
|
setListEncoders(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseEncoder(line: string): ScrcpyEncoder | undefined {
|
||||||
|
return parseEncoder(line, EncoderRegex);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseVideoStreamMetadata(
|
||||||
|
stream: ReadableStream<Uint8Array>,
|
||||||
|
): MaybePromiseLike<ScrcpyVideoStream> {
|
||||||
|
return parseVideoStreamMetadata(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseDeviceMessage(
|
||||||
|
id: number,
|
||||||
|
stream: AsyncExactReadable,
|
||||||
|
): Promise<void> {
|
||||||
|
if (await this.#clipboard!.parse(id, stream)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unknown device message");
|
||||||
|
}
|
||||||
|
|
||||||
|
endDeviceMessageStream(e?: unknown): void {
|
||||||
|
if (e) {
|
||||||
|
this.#clipboard!.error(e);
|
||||||
|
} else {
|
||||||
|
this.#clipboard!.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createMediaStreamTransformer(): TransformStream<
|
||||||
|
Uint8Array,
|
||||||
|
ScrcpyMediaStreamPacket
|
||||||
|
> {
|
||||||
|
return createMediaStreamTransformer(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeInjectTouchControlMessage(
|
||||||
|
message: ScrcpyInjectTouchControlMessage,
|
||||||
|
): Uint8Array {
|
||||||
|
return serializeInjectTouchControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeBackOrScreenOnControlMessage(
|
||||||
|
message: ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
): Uint8Array | undefined {
|
||||||
|
return serializeBackOrScreenOnControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeSetClipboardControlMessage(
|
||||||
|
message: ScrcpySetClipboardControlMessage,
|
||||||
|
): Uint8Array {
|
||||||
|
return serializeSetClipboardControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
createScrollController(): ScrcpyScrollController {
|
||||||
|
return createScrollController();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Init_ = Init;
|
||||||
|
|
||||||
|
export namespace ScrcpyOptions1_18 {
|
||||||
|
export type Init = Init_;
|
||||||
|
}
|
4
libraries/scrcpy/src/1_19.ts
Normal file
4
libraries/scrcpy/src/1_19.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export {
|
||||||
|
ScrcpyOptions1_18 as ScrcpyOptions1_19,
|
||||||
|
ScrcpyOptions1_18Impl as ScrcpyOptions1_19Impl,
|
||||||
|
} from "./1_18/index.js";
|
4
libraries/scrcpy/src/1_20.ts
Normal file
4
libraries/scrcpy/src/1_20.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export {
|
||||||
|
ScrcpyOptions1_18 as ScrcpyOptions1_20,
|
||||||
|
ScrcpyOptions1_18Impl as ScrcpyOptions1_20Impl,
|
||||||
|
} from "./1_18/index.js";
|
8
libraries/scrcpy/src/1_21/impl/defaults.ts
Normal file
8
libraries/scrcpy/src/1_21/impl/defaults.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export const Defaults = /* #__PURE__ */ (() =>
|
||||||
|
({
|
||||||
|
...PrevImpl.Defaults,
|
||||||
|
clipboardAutosync: true,
|
||||||
|
}) as const satisfies Required<Init>)();
|
10
libraries/scrcpy/src/1_21/impl/index.ts
Normal file
10
libraries/scrcpy/src/1_21/impl/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
export * from "../../1_18/impl/index.js";
|
||||||
|
export { Defaults } from "./defaults.js";
|
||||||
|
export type { Init } from "./init.js";
|
||||||
|
export { EncoderRegex } from "./parse-encoder.js";
|
||||||
|
export { serialize } from "./serialize.js";
|
||||||
|
export {
|
||||||
|
AckClipboardDeviceMessage,
|
||||||
|
AckClipboardHandler,
|
||||||
|
SetClipboardControlMessage,
|
||||||
|
} from "./set-clipboard.js";
|
5
libraries/scrcpy/src/1_21/impl/init.ts
Normal file
5
libraries/scrcpy/src/1_21/impl/init.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import type { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export interface Init extends PrevImpl.Init {
|
||||||
|
clipboardAutosync?: boolean;
|
||||||
|
}
|
1
libraries/scrcpy/src/1_21/impl/parse-encoder.ts
Normal file
1
libraries/scrcpy/src/1_21/impl/parse-encoder.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const EncoderRegex = /^\s+scrcpy --encoder-name '([^']+)'$/;
|
1
libraries/scrcpy/src/1_21/impl/prev.ts
Normal file
1
libraries/scrcpy/src/1_21/impl/prev.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * as PrevImpl from "../../1_18/impl/index.js";
|
30
libraries/scrcpy/src/1_21/impl/serialize.ts
Normal file
30
libraries/scrcpy/src/1_21/impl/serialize.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { toScrcpyOptionValue } from "../../base/option-value.js";
|
||||||
|
|
||||||
|
function toSnakeCase(input: string): string {
|
||||||
|
return input.replace(/([A-Z])/g, "_$1").toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serialize<T extends object>(
|
||||||
|
options: T,
|
||||||
|
defaults: Required<T>,
|
||||||
|
): string[] {
|
||||||
|
// 1.21 changed the format of arguments
|
||||||
|
const result: string[] = [];
|
||||||
|
for (const [key, value] of Object.entries(options)) {
|
||||||
|
const serializedValue = toScrcpyOptionValue(value, undefined);
|
||||||
|
if (!serializedValue) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultValue = toScrcpyOptionValue(
|
||||||
|
defaults[key as keyof T],
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
if (serializedValue == defaultValue) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(`${toSnakeCase(key)}=${serializedValue}`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
80
libraries/scrcpy/src/1_21/impl/set-clipboard.ts
Normal file
80
libraries/scrcpy/src/1_21/impl/set-clipboard.ts
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import { PromiseResolver } from "@yume-chan/async";
|
||||||
|
import type { AsyncExactReadable, StructInit } from "@yume-chan/struct";
|
||||||
|
import { string, struct, u32, u64, u8 } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type { ScrcpyDeviceMessageParser } from "../../base/index.js";
|
||||||
|
import type { ScrcpySetClipboardControlMessage } from "../../latest.js";
|
||||||
|
|
||||||
|
export const AckClipboardDeviceMessage = struct(
|
||||||
|
{ sequence: u64 },
|
||||||
|
{ littleEndian: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SetClipboardControlMessage = struct(
|
||||||
|
{
|
||||||
|
type: u8,
|
||||||
|
sequence: u64,
|
||||||
|
paste: u8<boolean>(),
|
||||||
|
content: string(u32),
|
||||||
|
},
|
||||||
|
{ littleEndian: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
export type SetClipboardControlMessage = StructInit<
|
||||||
|
typeof SetClipboardControlMessage
|
||||||
|
>;
|
||||||
|
|
||||||
|
export class AckClipboardHandler implements ScrcpyDeviceMessageParser {
|
||||||
|
#resolvers = new Map<bigint, PromiseResolver<void>>();
|
||||||
|
|
||||||
|
#closed = false;
|
||||||
|
|
||||||
|
async parse(id: number, stream: AsyncExactReadable) {
|
||||||
|
if (id !== 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = await AckClipboardDeviceMessage.deserialize(stream);
|
||||||
|
const resolver = this.#resolvers.get(message.sequence);
|
||||||
|
if (resolver) {
|
||||||
|
resolver.resolve();
|
||||||
|
this.#resolvers.delete(message.sequence);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
for (const resolver of this.#resolvers.values()) {
|
||||||
|
resolver.reject();
|
||||||
|
}
|
||||||
|
this.#resolvers.clear();
|
||||||
|
this.#closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
error(e?: unknown): void {
|
||||||
|
for (const resolver of this.#resolvers.values()) {
|
||||||
|
resolver.reject(e);
|
||||||
|
}
|
||||||
|
this.#resolvers.clear();
|
||||||
|
this.#closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeSetClipboardControlMessage(
|
||||||
|
message: ScrcpySetClipboardControlMessage,
|
||||||
|
): Uint8Array | [Uint8Array, Promise<void>] {
|
||||||
|
if (message.sequence === 0n) {
|
||||||
|
return SetClipboardControlMessage.serialize(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#closed) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolver = new PromiseResolver<void>();
|
||||||
|
this.#resolvers.set(message.sequence, resolver);
|
||||||
|
return [
|
||||||
|
SetClipboardControlMessage.serialize(message),
|
||||||
|
resolver.promise,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
2
libraries/scrcpy/src/1_21/index.ts
Normal file
2
libraries/scrcpy/src/1_21/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * as ScrcpyOptions1_21Impl from "./impl/index.js";
|
||||||
|
export * from "./options.js";
|
|
@ -3,9 +3,8 @@ import { describe, it } from "node:test";
|
||||||
|
|
||||||
import { AndroidAvcProfile } from "../codec/index.js";
|
import { AndroidAvcProfile } from "../codec/index.js";
|
||||||
|
|
||||||
import { ScrcpyOptions1_21 } from "./1_21.js";
|
import { CodecOptions } from "./impl/index.js";
|
||||||
|
import { ScrcpyOptions1_21 } from "./options.js";
|
||||||
import { CodecOptions } from "./index.js";
|
|
||||||
|
|
||||||
describe("ScrcpyOptions1_21", () => {
|
describe("ScrcpyOptions1_21", () => {
|
||||||
describe("serialize", () => {
|
describe("serialize", () => {
|
||||||
|
@ -35,7 +34,9 @@ describe("ScrcpyOptions1_21", () => {
|
||||||
profile: AndroidAvcProfile.High,
|
profile: AndroidAvcProfile.High,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
assert.deepEqual(options.serialize(), ["codec_options=profile=8"]);
|
assert.deepStrictEqual(options.serialize(), [
|
||||||
|
"codec_options=profile=8",
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
149
libraries/scrcpy/src/1_21/options.ts
Normal file
149
libraries/scrcpy/src/1_21/options.ts
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
import type { MaybePromiseLike } from "@yume-chan/async";
|
||||||
|
import type { ReadableStream, TransformStream } from "@yume-chan/stream-extra";
|
||||||
|
import type { AsyncExactReadable } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ScrcpyControlMessageType,
|
||||||
|
ScrcpyDisplay,
|
||||||
|
ScrcpyEncoder,
|
||||||
|
ScrcpyMediaStreamPacket,
|
||||||
|
ScrcpyOptions,
|
||||||
|
ScrcpyScrollController,
|
||||||
|
ScrcpyVideoStream,
|
||||||
|
} from "../base/index.js";
|
||||||
|
import type {
|
||||||
|
ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
ScrcpyInjectTouchControlMessage,
|
||||||
|
ScrcpySetClipboardControlMessage,
|
||||||
|
} from "../latest.js";
|
||||||
|
|
||||||
|
import type { Init } from "./impl/index.js";
|
||||||
|
import {
|
||||||
|
AckClipboardHandler,
|
||||||
|
ClipboardStream,
|
||||||
|
ControlMessageTypes,
|
||||||
|
createMediaStreamTransformer,
|
||||||
|
createScrollController,
|
||||||
|
Defaults,
|
||||||
|
EncoderRegex,
|
||||||
|
parseDisplay,
|
||||||
|
parseEncoder,
|
||||||
|
parseVideoStreamMetadata,
|
||||||
|
serialize,
|
||||||
|
serializeBackOrScreenOnControlMessage,
|
||||||
|
serializeInjectTouchControlMessage,
|
||||||
|
setListDisplays,
|
||||||
|
setListEncoders,
|
||||||
|
} from "./impl/index.js";
|
||||||
|
|
||||||
|
export class ScrcpyOptions1_21 implements ScrcpyOptions<Init> {
|
||||||
|
readonly value: Required<Init>;
|
||||||
|
|
||||||
|
get controlMessageTypes(): readonly ScrcpyControlMessageType[] {
|
||||||
|
return ControlMessageTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clipboard: ClipboardStream | undefined;
|
||||||
|
get clipboard(): ReadableStream<string> | undefined {
|
||||||
|
return this.#clipboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ackClipboardHandler: AckClipboardHandler | undefined;
|
||||||
|
|
||||||
|
constructor(init: Init) {
|
||||||
|
this.value = { ...Defaults, ...init };
|
||||||
|
|
||||||
|
if (this.value.control && this.value.clipboardAutosync) {
|
||||||
|
this.#clipboard = new ClipboardStream();
|
||||||
|
this.#ackClipboardHandler = new AckClipboardHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize(): string[] {
|
||||||
|
return serialize(this.value, Defaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListDisplays(): void {
|
||||||
|
setListDisplays(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||||
|
return parseDisplay(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListEncoders() {
|
||||||
|
setListEncoders(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseEncoder(line: string): ScrcpyEncoder | undefined {
|
||||||
|
return parseEncoder(line, EncoderRegex);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseVideoStreamMetadata(
|
||||||
|
stream: ReadableStream<Uint8Array>,
|
||||||
|
): MaybePromiseLike<ScrcpyVideoStream> {
|
||||||
|
return parseVideoStreamMetadata(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseDeviceMessage(
|
||||||
|
id: number,
|
||||||
|
stream: AsyncExactReadable,
|
||||||
|
): Promise<void> {
|
||||||
|
if (await this.#clipboard?.parse(id, stream)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await this.#ackClipboardHandler?.parse(id, stream)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unknown device message");
|
||||||
|
}
|
||||||
|
|
||||||
|
endDeviceMessageStream(e?: unknown): void {
|
||||||
|
if (e) {
|
||||||
|
this.#clipboard?.error(e);
|
||||||
|
this.#ackClipboardHandler?.error(e);
|
||||||
|
} else {
|
||||||
|
this.#clipboard?.close();
|
||||||
|
this.#ackClipboardHandler?.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createMediaStreamTransformer(): TransformStream<
|
||||||
|
Uint8Array,
|
||||||
|
ScrcpyMediaStreamPacket
|
||||||
|
> {
|
||||||
|
return createMediaStreamTransformer(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeInjectTouchControlMessage(
|
||||||
|
message: ScrcpyInjectTouchControlMessage,
|
||||||
|
): Uint8Array {
|
||||||
|
return serializeInjectTouchControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeBackOrScreenOnControlMessage(
|
||||||
|
message: ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
): Uint8Array | undefined {
|
||||||
|
return serializeBackOrScreenOnControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeSetClipboardControlMessage(
|
||||||
|
message: ScrcpySetClipboardControlMessage,
|
||||||
|
): Uint8Array | [Uint8Array, Promise<void>] {
|
||||||
|
return this.#ackClipboardHandler!.serializeSetClipboardControlMessage(
|
||||||
|
message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createScrollController(): ScrcpyScrollController {
|
||||||
|
return createScrollController();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Init_ = Init;
|
||||||
|
|
||||||
|
export namespace ScrcpyOptions1_21 {
|
||||||
|
export type Init = Init_;
|
||||||
|
}
|
10
libraries/scrcpy/src/1_22/impl/defaults.ts
Normal file
10
libraries/scrcpy/src/1_22/impl/defaults.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export const Defaults = /* #__PURE__ */ (() =>
|
||||||
|
({
|
||||||
|
...PrevImpl.Defaults,
|
||||||
|
downsizeOnError: true,
|
||||||
|
sendDeviceMeta: true,
|
||||||
|
sendDummyByte: true,
|
||||||
|
}) as const satisfies Required<Init>)();
|
8
libraries/scrcpy/src/1_22/impl/index.ts
Normal file
8
libraries/scrcpy/src/1_22/impl/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export * from "../../1_21/impl/index.js";
|
||||||
|
export { Defaults } from "./defaults.js";
|
||||||
|
export type { Init } from "./init.js";
|
||||||
|
export { parseVideoStreamMetadata } from "./parse-video-stream-metadata.js";
|
||||||
|
export {
|
||||||
|
InjectScrollControlMessage,
|
||||||
|
ScrollController,
|
||||||
|
} from "./scroll-controller.js";
|
|
@ -1,6 +1,6 @@
|
||||||
import type { ScrcpyOptionsInit1_21 } from "../1_21.js";
|
import type { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
export interface ScrcpyOptionsInit1_22 extends ScrcpyOptionsInit1_21 {
|
export interface Init extends PrevImpl.Init {
|
||||||
downsizeOnError?: boolean;
|
downsizeOnError?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -0,0 +1,18 @@
|
||||||
|
import type { ReadableStream } from "@yume-chan/stream-extra";
|
||||||
|
|
||||||
|
import type { ScrcpyVideoStream } from "../../base/video.js";
|
||||||
|
import { ScrcpyVideoCodecId } from "../../base/video.js";
|
||||||
|
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export async function parseVideoStreamMetadata(
|
||||||
|
options: Pick<Init, "sendDeviceMeta">,
|
||||||
|
stream: ReadableStream<Uint8Array>,
|
||||||
|
): Promise<ScrcpyVideoStream> {
|
||||||
|
if (!options.sendDeviceMeta) {
|
||||||
|
return { stream, metadata: { codec: ScrcpyVideoCodecId.H264 } };
|
||||||
|
} else {
|
||||||
|
return PrevImpl.parseVideoStreamMetadata(stream);
|
||||||
|
}
|
||||||
|
}
|
1
libraries/scrcpy/src/1_22/impl/prev.ts
Normal file
1
libraries/scrcpy/src/1_22/impl/prev.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * as PrevImpl from "../../1_21/impl/index.js";
|
|
@ -1,13 +1,13 @@
|
||||||
import * as assert from "node:assert";
|
import * as assert from "node:assert";
|
||||||
import { describe, it } from "node:test";
|
import { describe, it } from "node:test";
|
||||||
|
|
||||||
import { ScrcpyControlMessageType } from "../../control/index.js";
|
import { ScrcpyControlMessageType } from "../../base/index.js";
|
||||||
|
|
||||||
import { ScrcpyScrollController1_22 } from "./scroll.js";
|
import { ScrollController } from "./scroll-controller.js";
|
||||||
|
|
||||||
describe("ScrcpyScrollController1_22", () => {
|
describe("ScrollController", () => {
|
||||||
it("should return correct message length", () => {
|
it("should return correct message length", () => {
|
||||||
const controller = new ScrcpyScrollController1_22();
|
const controller = new ScrollController();
|
||||||
const message = controller.serializeScrollMessage({
|
const message = controller.serializeScrollMessage({
|
||||||
type: ScrcpyControlMessageType.InjectScroll,
|
type: ScrcpyControlMessageType.InjectScroll,
|
||||||
pointerX: 0,
|
pointerX: 0,
|
30
libraries/scrcpy/src/1_22/impl/scroll-controller.ts
Normal file
30
libraries/scrcpy/src/1_22/impl/scroll-controller.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import type { StructInit } from "@yume-chan/struct";
|
||||||
|
import { s32, struct } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export const InjectScrollControlMessage = /* #__PURE__ */ (() =>
|
||||||
|
struct(
|
||||||
|
{
|
||||||
|
...PrevImpl.InjectScrollControlMessage.fields,
|
||||||
|
buttons: s32,
|
||||||
|
},
|
||||||
|
{ littleEndian: false },
|
||||||
|
))();
|
||||||
|
|
||||||
|
export type InjectScrollControlMessage = StructInit<
|
||||||
|
typeof InjectScrollControlMessage
|
||||||
|
>;
|
||||||
|
|
||||||
|
export class ScrollController extends PrevImpl.ScrollController {
|
||||||
|
override serializeScrollMessage(
|
||||||
|
message: InjectScrollControlMessage,
|
||||||
|
): Uint8Array | undefined {
|
||||||
|
const processed = this.processMessage(message);
|
||||||
|
if (!processed) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return InjectScrollControlMessage.serialize(processed);
|
||||||
|
}
|
||||||
|
}
|
2
libraries/scrcpy/src/1_22/index.ts
Normal file
2
libraries/scrcpy/src/1_22/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * as ScrcpyOptions1_22Impl from "./impl/index.js";
|
||||||
|
export * from "./options.js";
|
|
@ -1,7 +1,7 @@
|
||||||
import * as assert from "node:assert";
|
import * as assert from "node:assert";
|
||||||
import { describe, it } from "node:test";
|
import { describe, it } from "node:test";
|
||||||
|
|
||||||
import { ScrcpyOptions1_21 } from "../1_21.js";
|
import { ScrcpyOptions1_21 } from "../1_21/index.js";
|
||||||
|
|
||||||
import { ScrcpyOptions1_22 } from "./options.js";
|
import { ScrcpyOptions1_22 } from "./options.js";
|
||||||
|
|
149
libraries/scrcpy/src/1_22/options.ts
Normal file
149
libraries/scrcpy/src/1_22/options.ts
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
import type { MaybePromiseLike } from "@yume-chan/async";
|
||||||
|
import type { ReadableStream, TransformStream } from "@yume-chan/stream-extra";
|
||||||
|
import type { AsyncExactReadable } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ScrcpyControlMessageType,
|
||||||
|
ScrcpyDisplay,
|
||||||
|
ScrcpyEncoder,
|
||||||
|
ScrcpyMediaStreamPacket,
|
||||||
|
ScrcpyOptions,
|
||||||
|
ScrcpyScrollController,
|
||||||
|
ScrcpyVideoStream,
|
||||||
|
} from "../base/index.js";
|
||||||
|
import type {
|
||||||
|
ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
ScrcpyInjectTouchControlMessage,
|
||||||
|
ScrcpySetClipboardControlMessage,
|
||||||
|
} from "../latest.js";
|
||||||
|
|
||||||
|
import type { Init } from "./impl/index.js";
|
||||||
|
import {
|
||||||
|
AckClipboardHandler,
|
||||||
|
ClipboardStream,
|
||||||
|
ControlMessageTypes,
|
||||||
|
createMediaStreamTransformer,
|
||||||
|
createScrollController,
|
||||||
|
Defaults,
|
||||||
|
EncoderRegex,
|
||||||
|
parseDisplay,
|
||||||
|
parseEncoder,
|
||||||
|
parseVideoStreamMetadata,
|
||||||
|
serialize,
|
||||||
|
serializeBackOrScreenOnControlMessage,
|
||||||
|
serializeInjectTouchControlMessage,
|
||||||
|
setListDisplays,
|
||||||
|
setListEncoders,
|
||||||
|
} from "./impl/index.js";
|
||||||
|
|
||||||
|
export class ScrcpyOptions1_22 implements ScrcpyOptions<Init> {
|
||||||
|
readonly value: Required<Init>;
|
||||||
|
|
||||||
|
get controlMessageTypes(): readonly ScrcpyControlMessageType[] {
|
||||||
|
return ControlMessageTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clipboard: ClipboardStream | undefined;
|
||||||
|
get clipboard(): ReadableStream<string> | undefined {
|
||||||
|
return this.#clipboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ackClipboardHandler: AckClipboardHandler | undefined;
|
||||||
|
|
||||||
|
constructor(init: Init) {
|
||||||
|
this.value = { ...Defaults, ...init };
|
||||||
|
|
||||||
|
if (this.value.control && this.value.clipboardAutosync) {
|
||||||
|
this.#clipboard = new ClipboardStream();
|
||||||
|
this.#ackClipboardHandler = new AckClipboardHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize(): string[] {
|
||||||
|
return serialize(this.value, Defaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListDisplays(): void {
|
||||||
|
setListDisplays(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||||
|
return parseDisplay(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListEncoders() {
|
||||||
|
setListEncoders(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseEncoder(line: string): ScrcpyEncoder | undefined {
|
||||||
|
return parseEncoder(line, EncoderRegex);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseVideoStreamMetadata(
|
||||||
|
stream: ReadableStream<Uint8Array>,
|
||||||
|
): MaybePromiseLike<ScrcpyVideoStream> {
|
||||||
|
return parseVideoStreamMetadata(this.value, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseDeviceMessage(
|
||||||
|
id: number,
|
||||||
|
stream: AsyncExactReadable,
|
||||||
|
): Promise<void> {
|
||||||
|
if (await this.#clipboard?.parse(id, stream)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await this.#ackClipboardHandler?.parse(id, stream)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unknown device message");
|
||||||
|
}
|
||||||
|
|
||||||
|
endDeviceMessageStream(e?: unknown): void {
|
||||||
|
if (e) {
|
||||||
|
this.#clipboard?.error(e);
|
||||||
|
this.#ackClipboardHandler?.error(e);
|
||||||
|
} else {
|
||||||
|
this.#clipboard?.close();
|
||||||
|
this.#ackClipboardHandler?.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createMediaStreamTransformer(): TransformStream<
|
||||||
|
Uint8Array,
|
||||||
|
ScrcpyMediaStreamPacket
|
||||||
|
> {
|
||||||
|
return createMediaStreamTransformer(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeInjectTouchControlMessage(
|
||||||
|
message: ScrcpyInjectTouchControlMessage,
|
||||||
|
): Uint8Array {
|
||||||
|
return serializeInjectTouchControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeBackOrScreenOnControlMessage(
|
||||||
|
message: ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
): Uint8Array | undefined {
|
||||||
|
return serializeBackOrScreenOnControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeSetClipboardControlMessage(
|
||||||
|
message: ScrcpySetClipboardControlMessage,
|
||||||
|
): Uint8Array | [Uint8Array, Promise<void>] {
|
||||||
|
return this.#ackClipboardHandler!.serializeSetClipboardControlMessage(
|
||||||
|
message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createScrollController(): ScrcpyScrollController {
|
||||||
|
return createScrollController();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Init_ = Init;
|
||||||
|
|
||||||
|
export namespace ScrcpyOptions1_22 {
|
||||||
|
export type Init = Init_;
|
||||||
|
}
|
8
libraries/scrcpy/src/1_23/impl/defaults.ts
Normal file
8
libraries/scrcpy/src/1_23/impl/defaults.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export const Defaults = /* #__PURE__ */ (() =>
|
||||||
|
({
|
||||||
|
...PrevImpl.Defaults,
|
||||||
|
cleanup: true,
|
||||||
|
}) as const satisfies Required<Init>)();
|
7
libraries/scrcpy/src/1_23/impl/index.ts
Normal file
7
libraries/scrcpy/src/1_23/impl/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export * from "../../1_22/impl/index.js";
|
||||||
|
export { Defaults } from "./defaults.js";
|
||||||
|
export type { Init } from "./init.js";
|
||||||
|
export {
|
||||||
|
PtsKeyframe,
|
||||||
|
createMediaStreamTransformer,
|
||||||
|
} from "./media-stream-transformer.js";
|
5
libraries/scrcpy/src/1_23/impl/init.ts
Normal file
5
libraries/scrcpy/src/1_23/impl/init.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import type { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export interface Init extends PrevImpl.Init {
|
||||||
|
cleanup?: boolean;
|
||||||
|
}
|
64
libraries/scrcpy/src/1_23/impl/media-stream-transformer.ts
Normal file
64
libraries/scrcpy/src/1_23/impl/media-stream-transformer.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import {
|
||||||
|
StructDeserializeStream,
|
||||||
|
TransformStream,
|
||||||
|
} from "@yume-chan/stream-extra";
|
||||||
|
|
||||||
|
import type { ScrcpyMediaStreamPacket } from "../../base/index.js";
|
||||||
|
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export const PtsKeyframe = 1n << 62n;
|
||||||
|
|
||||||
|
export function createMediaStreamTransformer(
|
||||||
|
options: Pick<Init, "sendFrameMeta">,
|
||||||
|
): TransformStream<Uint8Array, ScrcpyMediaStreamPacket> {
|
||||||
|
// Optimized path for video frames only
|
||||||
|
if (!options.sendFrameMeta) {
|
||||||
|
return new TransformStream({
|
||||||
|
transform(chunk, controller) {
|
||||||
|
controller.enqueue({
|
||||||
|
type: "data",
|
||||||
|
data: chunk,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const deserializeStream = new StructDeserializeStream(
|
||||||
|
PrevImpl.MediaStreamRawPacket,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
writable: deserializeStream.writable,
|
||||||
|
readable: deserializeStream.readable.pipeThrough(
|
||||||
|
new TransformStream({
|
||||||
|
transform(packet, controller) {
|
||||||
|
if (packet.pts === PrevImpl.PtsConfig) {
|
||||||
|
controller.enqueue({
|
||||||
|
type: "configuration",
|
||||||
|
data: packet.data,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.pts & PtsKeyframe) {
|
||||||
|
controller.enqueue({
|
||||||
|
type: "data",
|
||||||
|
keyframe: true,
|
||||||
|
pts: packet.pts & ~PtsKeyframe,
|
||||||
|
data: packet.data,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.enqueue({
|
||||||
|
type: "data",
|
||||||
|
keyframe: false,
|
||||||
|
pts: packet.pts,
|
||||||
|
data: packet.data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
1
libraries/scrcpy/src/1_23/impl/prev.ts
Normal file
1
libraries/scrcpy/src/1_23/impl/prev.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * as PrevImpl from "../../1_22/impl/index.js";
|
2
libraries/scrcpy/src/1_23/index.ts
Normal file
2
libraries/scrcpy/src/1_23/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * as ScrcpyOptions1_23Impl from "./impl/index.js";
|
||||||
|
export * from "./options.js";
|
149
libraries/scrcpy/src/1_23/options.ts
Normal file
149
libraries/scrcpy/src/1_23/options.ts
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
import type { MaybePromiseLike } from "@yume-chan/async";
|
||||||
|
import type { ReadableStream, TransformStream } from "@yume-chan/stream-extra";
|
||||||
|
import type { AsyncExactReadable } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ScrcpyControlMessageType,
|
||||||
|
ScrcpyDisplay,
|
||||||
|
ScrcpyEncoder,
|
||||||
|
ScrcpyMediaStreamPacket,
|
||||||
|
ScrcpyOptions,
|
||||||
|
ScrcpyScrollController,
|
||||||
|
ScrcpyVideoStream,
|
||||||
|
} from "../base/index.js";
|
||||||
|
import type {
|
||||||
|
ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
ScrcpyInjectTouchControlMessage,
|
||||||
|
ScrcpySetClipboardControlMessage,
|
||||||
|
} from "../latest.js";
|
||||||
|
|
||||||
|
import type { Init } from "./impl/index.js";
|
||||||
|
import {
|
||||||
|
AckClipboardHandler,
|
||||||
|
ClipboardStream,
|
||||||
|
ControlMessageTypes,
|
||||||
|
createMediaStreamTransformer,
|
||||||
|
createScrollController,
|
||||||
|
Defaults,
|
||||||
|
EncoderRegex,
|
||||||
|
parseDisplay,
|
||||||
|
parseEncoder,
|
||||||
|
parseVideoStreamMetadata,
|
||||||
|
serialize,
|
||||||
|
serializeBackOrScreenOnControlMessage,
|
||||||
|
serializeInjectTouchControlMessage,
|
||||||
|
setListDisplays,
|
||||||
|
setListEncoders,
|
||||||
|
} from "./impl/index.js";
|
||||||
|
|
||||||
|
export class ScrcpyOptions1_23 implements ScrcpyOptions<Init> {
|
||||||
|
readonly value: Required<Init>;
|
||||||
|
|
||||||
|
get controlMessageTypes(): readonly ScrcpyControlMessageType[] {
|
||||||
|
return ControlMessageTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clipboard: ClipboardStream | undefined;
|
||||||
|
get clipboard(): ReadableStream<string> | undefined {
|
||||||
|
return this.#clipboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ackClipboardHandler: AckClipboardHandler | undefined;
|
||||||
|
|
||||||
|
constructor(init: Init) {
|
||||||
|
this.value = { ...Defaults, ...init };
|
||||||
|
|
||||||
|
if (this.value.control && this.value.clipboardAutosync) {
|
||||||
|
this.#clipboard = new ClipboardStream();
|
||||||
|
this.#ackClipboardHandler = new AckClipboardHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize(): string[] {
|
||||||
|
return serialize(this.value, Defaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListDisplays(): void {
|
||||||
|
setListDisplays(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||||
|
return parseDisplay(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListEncoders() {
|
||||||
|
setListEncoders(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseEncoder(line: string): ScrcpyEncoder | undefined {
|
||||||
|
return parseEncoder(line, EncoderRegex);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseVideoStreamMetadata(
|
||||||
|
stream: ReadableStream<Uint8Array>,
|
||||||
|
): MaybePromiseLike<ScrcpyVideoStream> {
|
||||||
|
return parseVideoStreamMetadata(this.value, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseDeviceMessage(
|
||||||
|
id: number,
|
||||||
|
stream: AsyncExactReadable,
|
||||||
|
): Promise<void> {
|
||||||
|
if (await this.#clipboard?.parse(id, stream)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await this.#ackClipboardHandler?.parse(id, stream)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unknown device message");
|
||||||
|
}
|
||||||
|
|
||||||
|
endDeviceMessageStream(e?: unknown): void {
|
||||||
|
if (e) {
|
||||||
|
this.#clipboard?.error(e);
|
||||||
|
this.#ackClipboardHandler?.error(e);
|
||||||
|
} else {
|
||||||
|
this.#clipboard?.close();
|
||||||
|
this.#ackClipboardHandler?.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createMediaStreamTransformer(): TransformStream<
|
||||||
|
Uint8Array,
|
||||||
|
ScrcpyMediaStreamPacket
|
||||||
|
> {
|
||||||
|
return createMediaStreamTransformer(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeInjectTouchControlMessage(
|
||||||
|
message: ScrcpyInjectTouchControlMessage,
|
||||||
|
): Uint8Array {
|
||||||
|
return serializeInjectTouchControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeBackOrScreenOnControlMessage(
|
||||||
|
message: ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
): Uint8Array | undefined {
|
||||||
|
return serializeBackOrScreenOnControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeSetClipboardControlMessage(
|
||||||
|
message: ScrcpySetClipboardControlMessage,
|
||||||
|
): Uint8Array | [Uint8Array, Promise<void>] {
|
||||||
|
return this.#ackClipboardHandler!.serializeSetClipboardControlMessage(
|
||||||
|
message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createScrollController(): ScrcpyScrollController {
|
||||||
|
return createScrollController();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Init_ = Init;
|
||||||
|
|
||||||
|
export namespace ScrcpyOptions1_23 {
|
||||||
|
export type Init = Init_;
|
||||||
|
}
|
0
libraries/scrcpy/src/1_24/impl/control-message-types.ts
Normal file
0
libraries/scrcpy/src/1_24/impl/control-message-types.ts
Normal file
8
libraries/scrcpy/src/1_24/impl/defaults.ts
Normal file
8
libraries/scrcpy/src/1_24/impl/defaults.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export const Defaults = /* #__PURE__ */ (() =>
|
||||||
|
({
|
||||||
|
...PrevImpl.Defaults,
|
||||||
|
powerOn: true,
|
||||||
|
}) as const satisfies Required<Init>)();
|
3
libraries/scrcpy/src/1_24/impl/index.ts
Normal file
3
libraries/scrcpy/src/1_24/impl/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from "../../1_23/impl/index.js";
|
||||||
|
export { Defaults } from "./defaults.js";
|
||||||
|
export type { Init } from "./init.js";
|
5
libraries/scrcpy/src/1_24/impl/init.ts
Normal file
5
libraries/scrcpy/src/1_24/impl/init.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import type { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export interface Init extends PrevImpl.Init {
|
||||||
|
powerOn?: boolean;
|
||||||
|
}
|
1
libraries/scrcpy/src/1_24/impl/prev.ts
Normal file
1
libraries/scrcpy/src/1_24/impl/prev.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * as PrevImpl from "../../1_23/impl/index.js";
|
2
libraries/scrcpy/src/1_24/index.ts
Normal file
2
libraries/scrcpy/src/1_24/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * as ScrcpyOptions1_24Impl from "./impl/index.js";
|
||||||
|
export * from "./options.js";
|
149
libraries/scrcpy/src/1_24/options.ts
Normal file
149
libraries/scrcpy/src/1_24/options.ts
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
import type { MaybePromiseLike } from "@yume-chan/async";
|
||||||
|
import type { ReadableStream, TransformStream } from "@yume-chan/stream-extra";
|
||||||
|
import type { AsyncExactReadable } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ScrcpyControlMessageType,
|
||||||
|
ScrcpyDisplay,
|
||||||
|
ScrcpyEncoder,
|
||||||
|
ScrcpyMediaStreamPacket,
|
||||||
|
ScrcpyOptions,
|
||||||
|
ScrcpyScrollController,
|
||||||
|
ScrcpyVideoStream,
|
||||||
|
} from "../base/index.js";
|
||||||
|
import type {
|
||||||
|
ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
ScrcpyInjectTouchControlMessage,
|
||||||
|
ScrcpySetClipboardControlMessage,
|
||||||
|
} from "../latest.js";
|
||||||
|
|
||||||
|
import type { Init } from "./impl/index.js";
|
||||||
|
import {
|
||||||
|
AckClipboardHandler,
|
||||||
|
ClipboardStream,
|
||||||
|
ControlMessageTypes,
|
||||||
|
createMediaStreamTransformer,
|
||||||
|
createScrollController,
|
||||||
|
Defaults,
|
||||||
|
EncoderRegex,
|
||||||
|
parseDisplay,
|
||||||
|
parseEncoder,
|
||||||
|
parseVideoStreamMetadata,
|
||||||
|
serialize,
|
||||||
|
serializeBackOrScreenOnControlMessage,
|
||||||
|
serializeInjectTouchControlMessage,
|
||||||
|
setListDisplays,
|
||||||
|
setListEncoders,
|
||||||
|
} from "./impl/index.js";
|
||||||
|
|
||||||
|
export class ScrcpyOptions1_24 implements ScrcpyOptions<Init> {
|
||||||
|
readonly value: Required<Init>;
|
||||||
|
|
||||||
|
get controlMessageTypes(): readonly ScrcpyControlMessageType[] {
|
||||||
|
return ControlMessageTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clipboard: ClipboardStream | undefined;
|
||||||
|
get clipboard(): ReadableStream<string> | undefined {
|
||||||
|
return this.#clipboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ackClipboardHandler: AckClipboardHandler | undefined;
|
||||||
|
|
||||||
|
constructor(init: Init) {
|
||||||
|
this.value = { ...Defaults, ...init };
|
||||||
|
|
||||||
|
if (this.value.control && this.value.clipboardAutosync) {
|
||||||
|
this.#clipboard = new ClipboardStream();
|
||||||
|
this.#ackClipboardHandler = new AckClipboardHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize(): string[] {
|
||||||
|
return serialize(this.value, Defaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListDisplays(): void {
|
||||||
|
setListDisplays(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||||
|
return parseDisplay(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListEncoders() {
|
||||||
|
setListEncoders(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseEncoder(line: string): ScrcpyEncoder | undefined {
|
||||||
|
return parseEncoder(line, EncoderRegex);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseVideoStreamMetadata(
|
||||||
|
stream: ReadableStream<Uint8Array>,
|
||||||
|
): MaybePromiseLike<ScrcpyVideoStream> {
|
||||||
|
return parseVideoStreamMetadata(this.value, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseDeviceMessage(
|
||||||
|
id: number,
|
||||||
|
stream: AsyncExactReadable,
|
||||||
|
): Promise<void> {
|
||||||
|
if (await this.#clipboard?.parse(id, stream)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await this.#ackClipboardHandler?.parse(id, stream)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unknown device message");
|
||||||
|
}
|
||||||
|
|
||||||
|
endDeviceMessageStream(e?: unknown): void {
|
||||||
|
if (e) {
|
||||||
|
this.#clipboard?.error(e);
|
||||||
|
this.#ackClipboardHandler?.error(e);
|
||||||
|
} else {
|
||||||
|
this.#clipboard?.close();
|
||||||
|
this.#ackClipboardHandler?.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createMediaStreamTransformer(): TransformStream<
|
||||||
|
Uint8Array,
|
||||||
|
ScrcpyMediaStreamPacket
|
||||||
|
> {
|
||||||
|
return createMediaStreamTransformer(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeInjectTouchControlMessage(
|
||||||
|
message: ScrcpyInjectTouchControlMessage,
|
||||||
|
): Uint8Array {
|
||||||
|
return serializeInjectTouchControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeBackOrScreenOnControlMessage(
|
||||||
|
message: ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
): Uint8Array | undefined {
|
||||||
|
return serializeBackOrScreenOnControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeSetClipboardControlMessage(
|
||||||
|
message: ScrcpySetClipboardControlMessage,
|
||||||
|
): Uint8Array | [Uint8Array, Promise<void>] {
|
||||||
|
return this.#ackClipboardHandler!.serializeSetClipboardControlMessage(
|
||||||
|
message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createScrollController(): ScrcpyScrollController {
|
||||||
|
return createScrollController();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Init_ = Init;
|
||||||
|
|
||||||
|
export namespace ScrcpyOptions1_24 {
|
||||||
|
export type Init = Init_;
|
||||||
|
}
|
6
libraries/scrcpy/src/1_25/impl/index.ts
Normal file
6
libraries/scrcpy/src/1_25/impl/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export * from "../../1_24/impl/index.js";
|
||||||
|
export {
|
||||||
|
InjectScrollControlMessage,
|
||||||
|
ScrollController,
|
||||||
|
SignedFloat,
|
||||||
|
} from "./scroll-controller.js";
|
1
libraries/scrcpy/src/1_25/impl/prev.ts
Normal file
1
libraries/scrcpy/src/1_25/impl/prev.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * as PrevImpl from "../../1_24/impl/index.js";
|
|
@ -1,14 +1,14 @@
|
||||||
import * as assert from "node:assert";
|
import * as assert from "node:assert";
|
||||||
import { describe, it } from "node:test";
|
import { describe, it } from "node:test";
|
||||||
|
|
||||||
import { ScrcpyControlMessageType } from "../../control/index.js";
|
import { ScrcpyControlMessageType } from "../../base/index.js";
|
||||||
|
|
||||||
import { ScrcpyScrollController1_25, ScrcpySignedFloat } from "./scroll.js";
|
import { ScrollController, SignedFloat } from "./scroll-controller.js";
|
||||||
|
|
||||||
describe("ScrcpySignedFloat", () => {
|
describe("SignedFloat", () => {
|
||||||
it("should serialize", () => {
|
it("should serialize", () => {
|
||||||
const array = new Uint8Array(2);
|
const array = new Uint8Array(2);
|
||||||
ScrcpySignedFloat.serialize(-1, {
|
SignedFloat.serialize(-1, {
|
||||||
buffer: array,
|
buffer: array,
|
||||||
index: 0,
|
index: 0,
|
||||||
littleEndian: true,
|
littleEndian: true,
|
||||||
|
@ -18,14 +18,14 @@ describe("ScrcpySignedFloat", () => {
|
||||||
-0x8000,
|
-0x8000,
|
||||||
);
|
);
|
||||||
|
|
||||||
ScrcpySignedFloat.serialize(0, {
|
SignedFloat.serialize(0, {
|
||||||
buffer: array,
|
buffer: array,
|
||||||
index: 0,
|
index: 0,
|
||||||
littleEndian: true,
|
littleEndian: true,
|
||||||
});
|
});
|
||||||
assert.strictEqual(new DataView(array.buffer).getInt16(0, true), 0);
|
assert.strictEqual(new DataView(array.buffer).getInt16(0, true), 0);
|
||||||
|
|
||||||
ScrcpySignedFloat.serialize(1, {
|
SignedFloat.serialize(1, {
|
||||||
buffer: array,
|
buffer: array,
|
||||||
index: 0,
|
index: 0,
|
||||||
littleEndian: true,
|
littleEndian: true,
|
||||||
|
@ -38,7 +38,7 @@ describe("ScrcpySignedFloat", () => {
|
||||||
|
|
||||||
it("should clamp input values", () => {
|
it("should clamp input values", () => {
|
||||||
const array = new Uint8Array(2);
|
const array = new Uint8Array(2);
|
||||||
ScrcpySignedFloat.serialize(-2, {
|
SignedFloat.serialize(-2, {
|
||||||
buffer: array,
|
buffer: array,
|
||||||
index: 0,
|
index: 0,
|
||||||
littleEndian: true,
|
littleEndian: true,
|
||||||
|
@ -48,7 +48,7 @@ describe("ScrcpySignedFloat", () => {
|
||||||
-0x8000,
|
-0x8000,
|
||||||
);
|
);
|
||||||
|
|
||||||
ScrcpySignedFloat.serialize(2, {
|
SignedFloat.serialize(2, {
|
||||||
buffer: array,
|
buffer: array,
|
||||||
index: 0,
|
index: 0,
|
||||||
littleEndian: true,
|
littleEndian: true,
|
||||||
|
@ -65,7 +65,7 @@ describe("ScrcpySignedFloat", () => {
|
||||||
|
|
||||||
dataView.setInt16(0, -0x8000, true);
|
dataView.setInt16(0, -0x8000, true);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
ScrcpySignedFloat.deserialize({
|
SignedFloat.deserialize({
|
||||||
runtimeStruct: {} as never,
|
runtimeStruct: {} as never,
|
||||||
reader: { position: 0, readExactly: () => view },
|
reader: { position: 0, readExactly: () => view },
|
||||||
littleEndian: true,
|
littleEndian: true,
|
||||||
|
@ -75,7 +75,7 @@ describe("ScrcpySignedFloat", () => {
|
||||||
|
|
||||||
dataView.setInt16(0, 0, true);
|
dataView.setInt16(0, 0, true);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
ScrcpySignedFloat.deserialize({
|
SignedFloat.deserialize({
|
||||||
runtimeStruct: {} as never,
|
runtimeStruct: {} as never,
|
||||||
reader: { position: 0, readExactly: () => view },
|
reader: { position: 0, readExactly: () => view },
|
||||||
littleEndian: true,
|
littleEndian: true,
|
||||||
|
@ -85,7 +85,7 @@ describe("ScrcpySignedFloat", () => {
|
||||||
|
|
||||||
dataView.setInt16(0, 0x7fff, true);
|
dataView.setInt16(0, 0x7fff, true);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
ScrcpySignedFloat.deserialize({
|
SignedFloat.deserialize({
|
||||||
runtimeStruct: {} as never,
|
runtimeStruct: {} as never,
|
||||||
reader: { position: 0, readExactly: () => view },
|
reader: { position: 0, readExactly: () => view },
|
||||||
littleEndian: true,
|
littleEndian: true,
|
||||||
|
@ -95,9 +95,9 @@ describe("ScrcpySignedFloat", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("ScrcpyScrollController1_25", () => {
|
describe("ScrollController", () => {
|
||||||
it("should return a message for each scroll event", () => {
|
it("should return a message for each scroll event", () => {
|
||||||
const controller = new ScrcpyScrollController1_25();
|
const controller = new ScrollController();
|
||||||
const message1 = controller.serializeScrollMessage({
|
const message1 = controller.serializeScrollMessage({
|
||||||
type: ScrcpyControlMessageType.InjectScroll,
|
type: ScrcpyControlMessageType.InjectScroll,
|
||||||
pointerX: 0,
|
pointerX: 0,
|
|
@ -2,15 +2,16 @@ import { getInt16, setInt16 } from "@yume-chan/no-data-view";
|
||||||
import type { Field, StructInit } from "@yume-chan/struct";
|
import type { Field, StructInit } from "@yume-chan/struct";
|
||||||
import { bipedal, struct, u16, u32, u8 } from "@yume-chan/struct";
|
import { bipedal, struct, u16, u32, u8 } from "@yume-chan/struct";
|
||||||
|
|
||||||
import type { ScrcpyInjectScrollControlMessage } from "../../control/index.js";
|
import type { ScrcpyScrollController } from "../../base/index.js";
|
||||||
import type { ScrcpyScrollController } from "../1_16/index.js";
|
import type { ScrcpyInjectScrollControlMessage } from "../../latest.js";
|
||||||
import { clamp } from "../1_16/index.js";
|
|
||||||
|
|
||||||
export const ScrcpySignedFloat: Field<number, never, never> = {
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export const SignedFloat: Field<number, never, never> = {
|
||||||
size: 2,
|
size: 2,
|
||||||
serialize(value, { buffer, index, littleEndian }) {
|
serialize(value, { buffer, index, littleEndian }) {
|
||||||
// https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/app/src/util/binary.h#L51
|
// https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/app/src/util/binary.h#L51
|
||||||
value = clamp(value, -1, 1);
|
value = PrevImpl.clamp(value, -1, 1);
|
||||||
value = value === 1 ? 0x7fff : value * 0x8000;
|
value = value === 1 ? 0x7fff : value * 0x8000;
|
||||||
setInt16(buffer, index, value, littleEndian);
|
setInt16(buffer, index, value, littleEndian);
|
||||||
},
|
},
|
||||||
|
@ -22,28 +23,28 @@ export const ScrcpySignedFloat: Field<number, never, never> = {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ScrcpyInjectScrollControlMessage1_25 = struct(
|
export const InjectScrollControlMessage = struct(
|
||||||
{
|
{
|
||||||
type: u8,
|
type: u8,
|
||||||
pointerX: u32,
|
pointerX: u32,
|
||||||
pointerY: u32,
|
pointerY: u32,
|
||||||
screenWidth: u16,
|
screenWidth: u16,
|
||||||
screenHeight: u16,
|
screenHeight: u16,
|
||||||
scrollX: ScrcpySignedFloat,
|
scrollX: SignedFloat,
|
||||||
scrollY: ScrcpySignedFloat,
|
scrollY: SignedFloat,
|
||||||
buttons: u32,
|
buttons: u32,
|
||||||
},
|
},
|
||||||
{ littleEndian: false },
|
{ littleEndian: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ScrcpyInjectScrollControlMessage1_25 = StructInit<
|
export type InjectScrollControlMessage = StructInit<
|
||||||
typeof ScrcpyInjectScrollControlMessage1_25
|
typeof InjectScrollControlMessage
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export class ScrcpyScrollController1_25 implements ScrcpyScrollController {
|
export class ScrollController implements ScrcpyScrollController {
|
||||||
serializeScrollMessage(
|
serializeScrollMessage(
|
||||||
message: ScrcpyInjectScrollControlMessage,
|
message: ScrcpyInjectScrollControlMessage,
|
||||||
): Uint8Array | undefined {
|
): Uint8Array | undefined {
|
||||||
return ScrcpyInjectScrollControlMessage1_25.serialize(message);
|
return InjectScrollControlMessage.serialize(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
2
libraries/scrcpy/src/1_25/index.ts
Normal file
2
libraries/scrcpy/src/1_25/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * as ScrcpyOptions1_25Impl from "./impl/index.js";
|
||||||
|
export * from "./options.js";
|
149
libraries/scrcpy/src/1_25/options.ts
Normal file
149
libraries/scrcpy/src/1_25/options.ts
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
import type { MaybePromiseLike } from "@yume-chan/async";
|
||||||
|
import type { ReadableStream, TransformStream } from "@yume-chan/stream-extra";
|
||||||
|
import type { AsyncExactReadable } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ScrcpyControlMessageType,
|
||||||
|
ScrcpyDisplay,
|
||||||
|
ScrcpyEncoder,
|
||||||
|
ScrcpyMediaStreamPacket,
|
||||||
|
ScrcpyOptions,
|
||||||
|
ScrcpyScrollController,
|
||||||
|
ScrcpyVideoStream,
|
||||||
|
} from "../base/index.js";
|
||||||
|
import type {
|
||||||
|
ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
ScrcpyInjectTouchControlMessage,
|
||||||
|
ScrcpySetClipboardControlMessage,
|
||||||
|
} from "../latest.js";
|
||||||
|
|
||||||
|
import type { Init } from "./impl/index.js";
|
||||||
|
import {
|
||||||
|
AckClipboardHandler,
|
||||||
|
ClipboardStream,
|
||||||
|
ControlMessageTypes,
|
||||||
|
createMediaStreamTransformer,
|
||||||
|
createScrollController,
|
||||||
|
Defaults,
|
||||||
|
EncoderRegex,
|
||||||
|
parseDisplay,
|
||||||
|
parseEncoder,
|
||||||
|
parseVideoStreamMetadata,
|
||||||
|
serialize,
|
||||||
|
serializeBackOrScreenOnControlMessage,
|
||||||
|
serializeInjectTouchControlMessage,
|
||||||
|
setListDisplays,
|
||||||
|
setListEncoders,
|
||||||
|
} from "./impl/index.js";
|
||||||
|
|
||||||
|
export class ScrcpyOptions1_25 implements ScrcpyOptions<Init> {
|
||||||
|
readonly value: Required<Init>;
|
||||||
|
|
||||||
|
get controlMessageTypes(): readonly ScrcpyControlMessageType[] {
|
||||||
|
return ControlMessageTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clipboard: ClipboardStream | undefined;
|
||||||
|
get clipboard(): ReadableStream<string> | undefined {
|
||||||
|
return this.#clipboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ackClipboardHandler: AckClipboardHandler | undefined;
|
||||||
|
|
||||||
|
constructor(init: Init) {
|
||||||
|
this.value = { ...Defaults, ...init };
|
||||||
|
|
||||||
|
if (this.value.control && this.value.clipboardAutosync) {
|
||||||
|
this.#clipboard = new ClipboardStream();
|
||||||
|
this.#ackClipboardHandler = new AckClipboardHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize(): string[] {
|
||||||
|
return serialize(this.value, Defaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListDisplays(): void {
|
||||||
|
setListDisplays(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||||
|
return parseDisplay(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListEncoders() {
|
||||||
|
setListEncoders(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseEncoder(line: string): ScrcpyEncoder | undefined {
|
||||||
|
return parseEncoder(line, EncoderRegex);
|
||||||
|
}
|
||||||
|
|
||||||
|
parseVideoStreamMetadata(
|
||||||
|
stream: ReadableStream<Uint8Array>,
|
||||||
|
): MaybePromiseLike<ScrcpyVideoStream> {
|
||||||
|
return parseVideoStreamMetadata(this.value, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseDeviceMessage(
|
||||||
|
id: number,
|
||||||
|
stream: AsyncExactReadable,
|
||||||
|
): Promise<void> {
|
||||||
|
if (await this.#clipboard?.parse(id, stream)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await this.#ackClipboardHandler?.parse(id, stream)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unknown device message");
|
||||||
|
}
|
||||||
|
|
||||||
|
endDeviceMessageStream(e?: unknown): void {
|
||||||
|
if (e) {
|
||||||
|
this.#clipboard?.error(e);
|
||||||
|
this.#ackClipboardHandler?.error(e);
|
||||||
|
} else {
|
||||||
|
this.#clipboard?.close();
|
||||||
|
this.#ackClipboardHandler?.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createMediaStreamTransformer(): TransformStream<
|
||||||
|
Uint8Array,
|
||||||
|
ScrcpyMediaStreamPacket
|
||||||
|
> {
|
||||||
|
return createMediaStreamTransformer(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeInjectTouchControlMessage(
|
||||||
|
message: ScrcpyInjectTouchControlMessage,
|
||||||
|
): Uint8Array {
|
||||||
|
return serializeInjectTouchControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeBackOrScreenOnControlMessage(
|
||||||
|
message: ScrcpyBackOrScreenOnControlMessage,
|
||||||
|
): Uint8Array | undefined {
|
||||||
|
return serializeBackOrScreenOnControlMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializeSetClipboardControlMessage(
|
||||||
|
message: ScrcpySetClipboardControlMessage,
|
||||||
|
): Uint8Array | [Uint8Array, Promise<void>] {
|
||||||
|
return this.#ackClipboardHandler!.serializeSetClipboardControlMessage(
|
||||||
|
message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createScrollController(): ScrcpyScrollController {
|
||||||
|
return createScrollController();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Init_ = Init;
|
||||||
|
|
||||||
|
export namespace ScrcpyOptions1_25 {
|
||||||
|
export type Init = Init_;
|
||||||
|
}
|
24
libraries/scrcpy/src/2_0/impl/defaults.ts
Normal file
24
libraries/scrcpy/src/2_0/impl/defaults.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import type { Init } from "./init.js";
|
||||||
|
import { InstanceId } from "./init.js";
|
||||||
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export const Defaults = /* #__PURE__ */ (() =>
|
||||||
|
({
|
||||||
|
...PrevImpl.Defaults,
|
||||||
|
scid: InstanceId.NONE,
|
||||||
|
|
||||||
|
videoCodec: "h264",
|
||||||
|
videoBitRate: 8000000,
|
||||||
|
videoCodecOptions: PrevImpl.CodecOptions.Empty,
|
||||||
|
videoEncoder: undefined,
|
||||||
|
|
||||||
|
audio: true,
|
||||||
|
audioCodec: "opus",
|
||||||
|
audioBitRate: 128000,
|
||||||
|
audioCodecOptions: PrevImpl.CodecOptions.Empty,
|
||||||
|
audioEncoder: undefined,
|
||||||
|
|
||||||
|
listEncoders: false,
|
||||||
|
listDisplays: false,
|
||||||
|
sendCodecMeta: true,
|
||||||
|
}) as const satisfies Required<Init>)();
|
12
libraries/scrcpy/src/2_0/impl/index.ts
Normal file
12
libraries/scrcpy/src/2_0/impl/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
export * from "../../1_25/impl/index.js";
|
||||||
|
export { Defaults } from "./defaults.js";
|
||||||
|
export { InstanceId, type Init } from "./init.js";
|
||||||
|
export {
|
||||||
|
InjectTouchControlMessage,
|
||||||
|
serializeInjectTouchControlMessage,
|
||||||
|
} from "./inject-touch.js";
|
||||||
|
export * from "./parse-audio-stream-metadata.js";
|
||||||
|
export { parseEncoder } from "./parse-encoder.js";
|
||||||
|
export { parseVideoStreamMetadata } from "./parse-video-stream-metadata.js";
|
||||||
|
export { setListDisplays } from "./set-list-display.js";
|
||||||
|
export { setListEncoders } from "./set-list-encoder.js";
|
45
libraries/scrcpy/src/2_0/impl/init.ts
Normal file
45
libraries/scrcpy/src/2_0/impl/init.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import type { ScrcpyOptionValue } from "../../base/index.js";
|
||||||
|
|
||||||
|
import type { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export class InstanceId implements ScrcpyOptionValue {
|
||||||
|
static readonly NONE = /* #__PURE__ */ new InstanceId(-1);
|
||||||
|
|
||||||
|
static random(): InstanceId {
|
||||||
|
// A random 31-bit unsigned integer
|
||||||
|
return new InstanceId((Math.random() * 0x80000000) | 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
value: number;
|
||||||
|
|
||||||
|
constructor(value: number) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toOptionValue(): string | undefined {
|
||||||
|
if (this.value < 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return this.value.toString(16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Init
|
||||||
|
extends Omit<PrevImpl.Init, "bitRate" | "codecOptions" | "encoderName"> {
|
||||||
|
scid?: InstanceId;
|
||||||
|
|
||||||
|
videoCodec?: "h264" | "h265" | "av1";
|
||||||
|
videoBitRate?: number;
|
||||||
|
videoCodecOptions?: PrevImpl.CodecOptions;
|
||||||
|
videoEncoder?: string | undefined;
|
||||||
|
|
||||||
|
audio?: boolean;
|
||||||
|
audioCodec?: "raw" | "opus" | "aac";
|
||||||
|
audioBitRate?: number;
|
||||||
|
audioCodecOptions?: PrevImpl.CodecOptions;
|
||||||
|
audioEncoder?: string | undefined;
|
||||||
|
|
||||||
|
listEncoders?: boolean;
|
||||||
|
listDisplays?: boolean;
|
||||||
|
sendCodecMeta?: boolean;
|
||||||
|
}
|
33
libraries/scrcpy/src/2_0/impl/inject-touch.ts
Normal file
33
libraries/scrcpy/src/2_0/impl/inject-touch.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import type { StructInit } from "@yume-chan/struct";
|
||||||
|
import { struct, u16, u32, u64, u8 } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type { AndroidMotionEventAction } from "../../android/motion-event.js";
|
||||||
|
import type { ScrcpyInjectTouchControlMessage } from "../../latest.js";
|
||||||
|
|
||||||
|
import { PrevImpl } from "./prev.js";
|
||||||
|
|
||||||
|
export const InjectTouchControlMessage = struct(
|
||||||
|
{
|
||||||
|
type: u8,
|
||||||
|
action: u8<AndroidMotionEventAction>(),
|
||||||
|
pointerId: u64,
|
||||||
|
pointerX: u32,
|
||||||
|
pointerY: u32,
|
||||||
|
screenWidth: u16,
|
||||||
|
screenHeight: u16,
|
||||||
|
pressure: PrevImpl.UnsignedFloat,
|
||||||
|
actionButton: u32,
|
||||||
|
buttons: u32,
|
||||||
|
},
|
||||||
|
{ littleEndian: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
export type InjectTouchControlMessage = StructInit<
|
||||||
|
typeof InjectTouchControlMessage
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function serializeInjectTouchControlMessage(
|
||||||
|
message: ScrcpyInjectTouchControlMessage,
|
||||||
|
): Uint8Array {
|
||||||
|
return InjectTouchControlMessage.serialize(message);
|
||||||
|
}
|
99
libraries/scrcpy/src/2_0/impl/parse-audio-stream-metadata.ts
Normal file
99
libraries/scrcpy/src/2_0/impl/parse-audio-stream-metadata.ts
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import { getUint32BigEndian } from "@yume-chan/no-data-view";
|
||||||
|
import type { ReadableStream } from "@yume-chan/stream-extra";
|
||||||
|
import {
|
||||||
|
BufferedReadableStream,
|
||||||
|
PushReadableStream,
|
||||||
|
} from "@yume-chan/stream-extra";
|
||||||
|
|
||||||
|
import type { Init } from "../../2_3/impl/init.js";
|
||||||
|
import type { ScrcpyAudioStreamMetadata } from "../../base/index.js";
|
||||||
|
import { ScrcpyAudioCodec } from "../../base/index.js";
|
||||||
|
|
||||||
|
export async function parseAudioStreamMetadata(
|
||||||
|
stream: ReadableStream<Uint8Array>,
|
||||||
|
options: Pick<Required<Init>, "sendCodecMeta" | "audioCodec">,
|
||||||
|
): Promise<ScrcpyAudioStreamMetadata> {
|
||||||
|
const buffered = new BufferedReadableStream(stream);
|
||||||
|
|
||||||
|
const buffer = await buffered.readExactly(4);
|
||||||
|
// Treat it as a 32-bit number for simpler comparisons
|
||||||
|
const codecMetadataValue = getUint32BigEndian(buffer, 0);
|
||||||
|
// Server will send `0x00_00_00_00` and `0x00_00_00_01` even if `sendCodecMeta` is false
|
||||||
|
switch (codecMetadataValue) {
|
||||||
|
case 0x00_00_00_00:
|
||||||
|
return {
|
||||||
|
type: "disabled",
|
||||||
|
};
|
||||||
|
case 0x00_00_00_01:
|
||||||
|
return {
|
||||||
|
type: "errored",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.sendCodecMeta) {
|
||||||
|
let codec: ScrcpyAudioCodec;
|
||||||
|
switch (codecMetadataValue) {
|
||||||
|
case ScrcpyAudioCodec.Raw.metadataValue:
|
||||||
|
codec = ScrcpyAudioCodec.Raw;
|
||||||
|
break;
|
||||||
|
case ScrcpyAudioCodec.Opus.metadataValue:
|
||||||
|
codec = ScrcpyAudioCodec.Opus;
|
||||||
|
break;
|
||||||
|
case ScrcpyAudioCodec.Aac.metadataValue:
|
||||||
|
codec = ScrcpyAudioCodec.Aac;
|
||||||
|
break;
|
||||||
|
case ScrcpyAudioCodec.Flac.metadataValue:
|
||||||
|
codec = ScrcpyAudioCodec.Flac;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unknown audio codec metadata value: ${codecMetadataValue}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: "success",
|
||||||
|
codec,
|
||||||
|
stream: buffered.release(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infer codec from `audioCodec` option
|
||||||
|
let codec: ScrcpyAudioCodec;
|
||||||
|
switch (options.audioCodec as string) {
|
||||||
|
case "raw":
|
||||||
|
codec = ScrcpyAudioCodec.Raw;
|
||||||
|
break;
|
||||||
|
case "opus":
|
||||||
|
codec = ScrcpyAudioCodec.Opus;
|
||||||
|
break;
|
||||||
|
case "aac":
|
||||||
|
codec = ScrcpyAudioCodec.Aac;
|
||||||
|
break;
|
||||||
|
case "flac":
|
||||||
|
codec = ScrcpyAudioCodec.Flac;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unknown audio codec metadata value: ${codecMetadataValue}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "success",
|
||||||
|
codec,
|
||||||
|
stream: new PushReadableStream<Uint8Array>(async (controller) => {
|
||||||
|
// Put the first 4 bytes back
|
||||||
|
await controller.enqueue(buffer);
|
||||||
|
|
||||||
|
const stream = buffered.release();
|
||||||
|
const reader = stream.getReader();
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await controller.enqueue(value);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
15
libraries/scrcpy/src/2_0/impl/parse-display.ts
Normal file
15
libraries/scrcpy/src/2_0/impl/parse-display.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import type { ScrcpyDisplay } from "../../base/index.js";
|
||||||
|
|
||||||
|
export function parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||||
|
const match = line.match(/^\s+--display=(\d+)\s+\(([^)]+)\)$/);
|
||||||
|
if (match) {
|
||||||
|
const display: ScrcpyDisplay = {
|
||||||
|
id: Number.parseInt(match[1]!, 10),
|
||||||
|
};
|
||||||
|
if (match[2] !== "size unknown") {
|
||||||
|
display.resolution = match[2]!;
|
||||||
|
}
|
||||||
|
return display;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue