diff --git a/.vscode/settings.json b/.vscode/settings.json index bdd49b66..a98a69e1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -115,7 +115,7 @@ "editor.defaultFormatter": "esbenp.prettier-vscode" }, "explorer.sortOrder": "mixed", - "prettier.prettierPath": "./node_modules/prettier/index.cjs", + "prettier.prettierPath": "./toolchain/eslint-config/node_modules/prettier/index.cjs", "cSpell.numSuggestions": 4, "cSpell.ignoreRegExpList": [ "0x[0-9a-f_]+" diff --git a/libraries/adb-scrcpy/package.json b/libraries/adb-scrcpy/package.json index a8fbb0e0..22d0def8 100644 --- a/libraries/adb-scrcpy/package.json +++ b/libraries/adb-scrcpy/package.json @@ -29,7 +29,8 @@ "scripts": { "build": "tsc -b tsconfig.build.json", "lint": "run-eslint && prettier src/**/*.ts --write --tab-width 4", - "prepublishOnly": "npm run build" + "prepublishOnly": "npm run build", + "test": "run-test" }, "dependencies": { "@yume-chan/adb": "workspace:^", @@ -40,7 +41,9 @@ "@yume-chan/struct": "workspace:^" }, "devDependencies": { + "@types/node": "^22.10.10", "@yume-chan/eslint-config": "workspace:^", + "@yume-chan/test-runner": "workspace:^", "@yume-chan/tsconfig": "workspace:^", "prettier": "^3.4.2", "typescript": "^5.7.3" diff --git a/libraries/adb-scrcpy/src/2_1/impl/create-connection.ts b/libraries/adb-scrcpy/src/2_1/impl/create-connection.ts index 64cb6023..598ba1db 100644 --- a/libraries/adb-scrcpy/src/2_1/impl/create-connection.ts +++ b/libraries/adb-scrcpy/src/2_1/impl/create-connection.ts @@ -15,7 +15,7 @@ export function createConnection( adb: Adb, options: Required< Pick< - ScrcpyOptions2_1.Init, + ScrcpyOptions2_1.Init, | "tunnelForward" | "control" | "sendDummyByte" diff --git a/libraries/adb-scrcpy/src/2_1/options.ts b/libraries/adb-scrcpy/src/2_1/options.ts index b8dc8818..8887cbd7 100644 --- a/libraries/adb-scrcpy/src/2_1/options.ts +++ b/libraries/adb-scrcpy/src/2_1/options.ts @@ -10,8 +10,10 @@ import { import type { AdbScrcpyConnection } from "../connection.js"; import { AdbScrcpyOptions } from "../types.js"; -export class AdbScrcpyOptions2_1 extends AdbScrcpyOptions { - constructor(init: ScrcpyOptions2_1.Init, version?: string) { +export class AdbScrcpyOptions2_1< + TVideo extends boolean, +> extends AdbScrcpyOptions> { + constructor(init: ScrcpyOptions2_1.Init, version?: string) { super(new ScrcpyOptions2_1(init, version)); } @@ -29,5 +31,6 @@ export class AdbScrcpyOptions2_1 extends AdbScrcpyOptions } export namespace AdbScrcpyOptions2_1 { - export type Init = ScrcpyOptions2_1.Init; + export type Init = + ScrcpyOptions2_1.Init; } diff --git a/libraries/adb-scrcpy/src/2_1_1.ts b/libraries/adb-scrcpy/src/2_1_1.ts index 231a7248..95abafc4 100644 --- a/libraries/adb-scrcpy/src/2_1_1.ts +++ b/libraries/adb-scrcpy/src/2_1_1.ts @@ -10,8 +10,10 @@ import { import type { AdbScrcpyConnection } from "./connection.js"; import { AdbScrcpyOptions } from "./types.js"; -export class AdbScrcpyOptions2_1_1 extends AdbScrcpyOptions { - constructor(init: ScrcpyOptions2_1_1.Init, version?: string) { +export class AdbScrcpyOptions2_1_1< + TVideo extends boolean, +> extends AdbScrcpyOptions> { + constructor(init: ScrcpyOptions2_1_1.Init, version?: string) { super(new ScrcpyOptions2_1_1(init, version)); } @@ -29,5 +31,6 @@ export class AdbScrcpyOptions2_1_1 extends AdbScrcpyOptions = + ScrcpyOptions2_1_1.Init; } diff --git a/libraries/adb-scrcpy/src/2_2.ts b/libraries/adb-scrcpy/src/2_2.ts index 2f41915d..da7440f3 100644 --- a/libraries/adb-scrcpy/src/2_2.ts +++ b/libraries/adb-scrcpy/src/2_2.ts @@ -10,8 +10,10 @@ import { import type { AdbScrcpyConnection } from "./connection.js"; import { AdbScrcpyOptions } from "./types.js"; -export class AdbScrcpyOptions2_2 extends AdbScrcpyOptions { - constructor(init: ScrcpyOptions2_2.Init, version?: string) { +export class AdbScrcpyOptions2_2< + TVideo extends boolean, +> extends AdbScrcpyOptions> { + constructor(init: ScrcpyOptions2_2.Init, version?: string) { super(new ScrcpyOptions2_2(init, version)); } @@ -29,5 +31,6 @@ export class AdbScrcpyOptions2_2 extends AdbScrcpyOptions } export namespace AdbScrcpyOptions2_2 { - export type Init = ScrcpyOptions2_2.Init; + export type Init = + ScrcpyOptions2_2.Init; } diff --git a/libraries/adb-scrcpy/src/2_3.ts b/libraries/adb-scrcpy/src/2_3.ts index f8ca3b1e..24e4b8ea 100644 --- a/libraries/adb-scrcpy/src/2_3.ts +++ b/libraries/adb-scrcpy/src/2_3.ts @@ -10,8 +10,10 @@ import { import type { AdbScrcpyConnection } from "./connection.js"; import { AdbScrcpyOptions } from "./types.js"; -export class AdbScrcpyOptions2_3 extends AdbScrcpyOptions { - constructor(init: ScrcpyOptions2_3.Init, version?: string) { +export class AdbScrcpyOptions2_3< + TVideo extends boolean, +> extends AdbScrcpyOptions> { + constructor(init: ScrcpyOptions2_3.Init, version?: string) { super(new ScrcpyOptions2_3(init, version)); } @@ -29,5 +31,6 @@ export class AdbScrcpyOptions2_3 extends AdbScrcpyOptions } export namespace AdbScrcpyOptions2_3 { - export type Init = ScrcpyOptions2_3.Init; + export type Init = + ScrcpyOptions2_3.Init; } diff --git a/libraries/adb-scrcpy/src/2_3_1.ts b/libraries/adb-scrcpy/src/2_3_1.ts index 14e029cd..90e1a838 100644 --- a/libraries/adb-scrcpy/src/2_3_1.ts +++ b/libraries/adb-scrcpy/src/2_3_1.ts @@ -10,8 +10,10 @@ import { import type { AdbScrcpyConnection } from "./connection.js"; import { AdbScrcpyOptions } from "./types.js"; -export class AdbScrcpyOptions2_3_1 extends AdbScrcpyOptions { - constructor(init: ScrcpyOptions2_3_1.Init, version?: string) { +export class AdbScrcpyOptions2_3_1< + TVideo extends boolean, +> extends AdbScrcpyOptions> { + constructor(init: ScrcpyOptions2_3_1.Init, version?: string) { super(new ScrcpyOptions2_3_1(init, version)); } @@ -29,5 +31,6 @@ export class AdbScrcpyOptions2_3_1 extends AdbScrcpyOptions = + ScrcpyOptions2_3_1.Init; } diff --git a/libraries/adb-scrcpy/src/2_4.ts b/libraries/adb-scrcpy/src/2_4.ts index 7f522516..b3f1fdc1 100644 --- a/libraries/adb-scrcpy/src/2_4.ts +++ b/libraries/adb-scrcpy/src/2_4.ts @@ -10,8 +10,10 @@ import { import type { AdbScrcpyConnection } from "./connection.js"; import { AdbScrcpyOptions } from "./types.js"; -export class AdbScrcpyOptions2_4 extends AdbScrcpyOptions { - constructor(init: ScrcpyOptions2_4.Init, version?: string) { +export class AdbScrcpyOptions2_4< + TVideo extends boolean, +> extends AdbScrcpyOptions> { + constructor(init: ScrcpyOptions2_4.Init, version?: string) { super(new ScrcpyOptions2_4(init, version)); } @@ -29,5 +31,6 @@ export class AdbScrcpyOptions2_4 extends AdbScrcpyOptions } export namespace AdbScrcpyOptions2_4 { - export type Init = ScrcpyOptions2_4.Init; + export type Init = + ScrcpyOptions2_4.Init; } diff --git a/libraries/adb-scrcpy/src/2_5.ts b/libraries/adb-scrcpy/src/2_5.ts index 1f864314..454cbc1b 100644 --- a/libraries/adb-scrcpy/src/2_5.ts +++ b/libraries/adb-scrcpy/src/2_5.ts @@ -10,8 +10,10 @@ import { import type { AdbScrcpyConnection } from "./connection.js"; import { AdbScrcpyOptions } from "./types.js"; -export class AdbScrcpyOptions2_5 extends AdbScrcpyOptions { - constructor(init: ScrcpyOptions2_5.Init, version?: string) { +export class AdbScrcpyOptions2_5< + TVideo extends boolean, +> extends AdbScrcpyOptions> { + constructor(init: ScrcpyOptions2_5.Init, version?: string) { super(new ScrcpyOptions2_5(init, version)); } @@ -29,5 +31,6 @@ export class AdbScrcpyOptions2_5 extends AdbScrcpyOptions } export namespace AdbScrcpyOptions2_5 { - export type Init = ScrcpyOptions2_5.Init; + export type Init = + ScrcpyOptions2_5.Init; } diff --git a/libraries/adb-scrcpy/src/2_6.ts b/libraries/adb-scrcpy/src/2_6.ts index a5cfd509..d5ae445c 100644 --- a/libraries/adb-scrcpy/src/2_6.ts +++ b/libraries/adb-scrcpy/src/2_6.ts @@ -10,8 +10,10 @@ import { import type { AdbScrcpyConnection } from "./connection.js"; import { AdbScrcpyOptions } from "./types.js"; -export class AdbScrcpyOptions2_6 extends AdbScrcpyOptions { - constructor(init: ScrcpyOptions2_6.Init, version?: string) { +export class AdbScrcpyOptions2_6< + TVideo extends boolean, +> extends AdbScrcpyOptions> { + constructor(init: ScrcpyOptions2_6.Init, version?: string) { super(new ScrcpyOptions2_6(init, version)); } @@ -29,5 +31,6 @@ export class AdbScrcpyOptions2_6 extends AdbScrcpyOptions } export namespace AdbScrcpyOptions2_6 { - export type Init = ScrcpyOptions2_6.Init; + export type Init = + ScrcpyOptions2_6.Init; } diff --git a/libraries/adb-scrcpy/src/2_7.ts b/libraries/adb-scrcpy/src/2_7.ts index 45c998f8..820b2241 100644 --- a/libraries/adb-scrcpy/src/2_7.ts +++ b/libraries/adb-scrcpy/src/2_7.ts @@ -10,8 +10,10 @@ import { import type { AdbScrcpyConnection } from "./connection.js"; import { AdbScrcpyOptions } from "./types.js"; -export class AdbScrcpyOptions2_7 extends AdbScrcpyOptions { - constructor(init: ScrcpyOptions2_7.Init, version?: string) { +export class AdbScrcpyOptions2_7< + TVideo extends boolean, +> extends AdbScrcpyOptions> { + constructor(init: ScrcpyOptions2_7.Init, version?: string) { super(new ScrcpyOptions2_7(init, version)); } @@ -29,5 +31,6 @@ export class AdbScrcpyOptions2_7 extends AdbScrcpyOptions } export namespace AdbScrcpyOptions2_7 { - export type Init = ScrcpyOptions2_7.Init; + export type Init = + ScrcpyOptions2_7.Init; } diff --git a/libraries/adb-scrcpy/src/3_0.ts b/libraries/adb-scrcpy/src/3_0.ts index 3fb7e03f..7e9d2578 100644 --- a/libraries/adb-scrcpy/src/3_0.ts +++ b/libraries/adb-scrcpy/src/3_0.ts @@ -10,8 +10,10 @@ import { import type { AdbScrcpyConnection } from "./connection.js"; import { AdbScrcpyOptions } from "./types.js"; -export class AdbScrcpyOptions3_0 extends AdbScrcpyOptions { - constructor(init: ScrcpyOptions3_0.Init, version?: string) { +export class AdbScrcpyOptions3_0< + TVideo extends boolean, +> extends AdbScrcpyOptions> { + constructor(init: ScrcpyOptions3_0.Init, version?: string) { super(new ScrcpyOptions3_0(init, version)); } @@ -29,5 +31,6 @@ export class AdbScrcpyOptions3_0 extends AdbScrcpyOptions } export namespace AdbScrcpyOptions3_0 { - export type Init = ScrcpyOptions3_0.Init; + export type Init = + ScrcpyOptions3_0.Init; } diff --git a/libraries/adb-scrcpy/src/3_0_1.ts b/libraries/adb-scrcpy/src/3_0_1.ts index 28671a11..ed7528d3 100644 --- a/libraries/adb-scrcpy/src/3_0_1.ts +++ b/libraries/adb-scrcpy/src/3_0_1.ts @@ -10,8 +10,10 @@ import { import type { AdbScrcpyConnection } from "./connection.js"; import { AdbScrcpyOptions } from "./types.js"; -export class AdbScrcpyOptions3_0_1 extends AdbScrcpyOptions { - constructor(init: ScrcpyOptions3_0_1.Init, version?: string) { +export class AdbScrcpyOptions3_0_1< + TVideo extends boolean, +> extends AdbScrcpyOptions> { + constructor(init: ScrcpyOptions3_0_1.Init, version?: string) { super(new ScrcpyOptions3_0_1(init, version)); } @@ -29,5 +31,6 @@ export class AdbScrcpyOptions3_0_1 extends AdbScrcpyOptions = + ScrcpyOptions3_0_1.Init; } diff --git a/libraries/adb-scrcpy/src/3_0_2.ts b/libraries/adb-scrcpy/src/3_0_2.ts index c666ac3d..fbe70542 100644 --- a/libraries/adb-scrcpy/src/3_0_2.ts +++ b/libraries/adb-scrcpy/src/3_0_2.ts @@ -10,8 +10,10 @@ import { import type { AdbScrcpyConnection } from "./connection.js"; import { AdbScrcpyOptions } from "./types.js"; -export class AdbScrcpyOptions3_0_2 extends AdbScrcpyOptions { - constructor(init: ScrcpyOptions3_0_2.Init, version?: string) { +export class AdbScrcpyOptions3_0_2< + TVideo extends boolean, +> extends AdbScrcpyOptions> { + constructor(init: ScrcpyOptions3_0_2.Init, version?: string) { super(new ScrcpyOptions3_0_2(init, version)); } @@ -29,5 +31,6 @@ export class AdbScrcpyOptions3_0_2 extends AdbScrcpyOptions = + ScrcpyOptions3_0_2.Init; } diff --git a/libraries/adb-scrcpy/src/3_1.ts b/libraries/adb-scrcpy/src/3_1.ts index 5a38e207..f81dc10f 100644 --- a/libraries/adb-scrcpy/src/3_1.ts +++ b/libraries/adb-scrcpy/src/3_1.ts @@ -10,8 +10,10 @@ import { import type { AdbScrcpyConnection } from "./connection.js"; import { AdbScrcpyOptions } from "./types.js"; -export class AdbScrcpyOptions3_1 extends AdbScrcpyOptions { - constructor(init: ScrcpyOptions3_1.Init, version?: string) { +export class AdbScrcpyOptions3_1< + TVideo extends boolean, +> extends AdbScrcpyOptions> { + constructor(init: ScrcpyOptions3_1.Init, version?: string) { super(new ScrcpyOptions3_1(init, version)); } @@ -29,5 +31,6 @@ export class AdbScrcpyOptions3_1 extends AdbScrcpyOptions } export namespace AdbScrcpyOptions3_1 { - export type Init = ScrcpyOptions3_1.Init; + export type Init = + ScrcpyOptions3_1.Init; } diff --git a/libraries/adb-scrcpy/src/client.spec.ts b/libraries/adb-scrcpy/src/client.spec.ts new file mode 100644 index 00000000..ea5636d6 --- /dev/null +++ b/libraries/adb-scrcpy/src/client.spec.ts @@ -0,0 +1,154 @@ +import { describe, it } from "node:test"; + +import type { Adb } from "@yume-chan/adb"; +import { DefaultServerPath } from "@yume-chan/scrcpy"; + +import { AdbScrcpyOptions1_15 } from "./1_15/options.js"; +import { AdbScrcpyOptions2_0 } from "./2_0/options.js"; +import { AdbScrcpyOptions2_1 } from "./2_1/options.js"; +import { AdbScrcpyOptions3_1 } from "./3_1.js"; +import { AdbScrcpyClient } from "./client.js"; +import type { AdbScrcpyVideoStream } from "./video.js"; + +const TypeOnlyTest = false; +declare const adb: Adb; + +function expect(value: true): void { + void value; +} + +function equal(): ( + value: Y, +) => (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 + ? true + : false { + return (() => {}) as never; +} + +describe("AdbScrcpyClient", () => { + describe("videoStream", () => { + it("should have value in lower versions", async () => { + if (TypeOnlyTest) { + const client = await AdbScrcpyClient.start( + adb, + DefaultServerPath, + new AdbScrcpyOptions1_15({}), + ); + expect( + equal>()(client.videoStream), + ); + } + + if (TypeOnlyTest) { + const client = await AdbScrcpyClient.start( + adb, + DefaultServerPath, + new AdbScrcpyOptions2_0({}), + ); + expect( + equal>()(client.videoStream), + ); + } + }); + + it("should have value when video: true", async () => { + if (TypeOnlyTest) { + const client = await AdbScrcpyClient.start( + adb, + DefaultServerPath, + new AdbScrcpyOptions2_1({ video: true }), + ); + expect( + equal>()(client.videoStream), + ); + } + + if (TypeOnlyTest) { + const client = await AdbScrcpyClient.start( + adb, + DefaultServerPath, + new AdbScrcpyOptions3_1({ video: true }), + ); + expect( + equal>()(client.videoStream), + ); + } + }); + + it("should be undefined when video: false", async () => { + if (TypeOnlyTest) { + const client = await AdbScrcpyClient.start( + adb, + DefaultServerPath, + new AdbScrcpyOptions2_1({ video: false }), + ); + expect(equal()(client.videoStream)); + } + + if (TypeOnlyTest) { + const client = await AdbScrcpyClient.start( + adb, + DefaultServerPath, + new AdbScrcpyOptions3_1({ video: false }), + ); + expect(equal()(client.videoStream)); + } + }); + + it("should be a union when video: undefined", async () => { + if (TypeOnlyTest) { + const client = await AdbScrcpyClient.start( + adb, + DefaultServerPath, + new AdbScrcpyOptions2_1({}), + ); + expect( + equal | undefined>()( + client.videoStream, + ), + ); + } + + if (TypeOnlyTest) { + const client = await AdbScrcpyClient.start( + adb, + DefaultServerPath, + new AdbScrcpyOptions3_1({}), + ); + expect( + equal | undefined>()( + client.videoStream, + ), + ); + } + }); + + it("should be a union when video: boolean", async () => { + if (TypeOnlyTest) { + const client = await AdbScrcpyClient.start( + adb, + DefaultServerPath, + new AdbScrcpyOptions2_1({ video: true as boolean }), + ); + expect( + equal | undefined>()( + client.videoStream, + ), + ); + } + + if (TypeOnlyTest) { + const client = await AdbScrcpyClient.start( + adb, + DefaultServerPath, + new AdbScrcpyOptions3_1({ video: true as boolean }), + ); + expect( + equal | undefined>()( + client.videoStream, + ), + ); + } + }); + }); +}); diff --git a/libraries/adb-scrcpy/src/client.ts b/libraries/adb-scrcpy/src/client.ts index 330cbce6..2d0a94df 100644 --- a/libraries/adb-scrcpy/src/client.ts +++ b/libraries/adb-scrcpy/src/client.ts @@ -11,15 +11,10 @@ import type { ScrcpyEncoder, ScrcpyMediaStreamPacket, ScrcpyOptions1_15, - ScrcpyVideoStreamMetadata, } from "@yume-chan/scrcpy"; import { - Av1, DefaultServerPath, ScrcpyControlMessageWriter, - ScrcpyVideoCodecId, - h264ParseConfiguration, - h265ParseConfiguration, } from "@yume-chan/scrcpy"; import type { Consumable, @@ -30,7 +25,6 @@ import type { import { AbortController, BufferedReadableStream, - InspectStream, PushReadableStream, SplitStringStream, TextDecoderStream, @@ -40,6 +34,7 @@ import { ExactReadableEndedError } from "@yume-chan/struct"; import type { AdbScrcpyConnection } from "./connection.js"; import type { AdbScrcpyOptions } from "./types.js"; +import { AdbScrcpyVideoStream } from "./video.js"; function arrayToStream(array: T[]): ReadableStream { return new PushReadableStream(async (controller) => { @@ -73,8 +68,8 @@ export class AdbScrcpyExitedError extends Error { } } -interface AdbScrcpyClientInit { - options: AdbScrcpyOptions; +interface AdbScrcpyClientInit> { + options: TOptions; process: AdbSubprocessProtocol; stdout: ReadableStream; @@ -85,11 +80,6 @@ interface AdbScrcpyClientInit { | undefined; } -export interface AdbScrcpyVideoStream { - stream: ReadableStream; - metadata: ScrcpyVideoStreamMetadata; -} - export interface AdbScrcpyAudioStreamSuccessMetadata extends Omit { readonly stream: ReadableStream; @@ -100,7 +90,7 @@ export type AdbScrcpyAudioStreamMetadata = | ScrcpyAudioStreamErroredMetadata | AdbScrcpyAudioStreamSuccessMetadata; -export class AdbScrcpyClient { +export class AdbScrcpyClient> { static async pushServer( adb: Adb, file: ReadableStream>, @@ -117,13 +107,15 @@ export class AdbScrcpyClient { } } - static async start( - adb: Adb, - path: string, - options: AdbScrcpyOptions< + static async start< + TOptions extends AdbScrcpyOptions< Pick >, - ) { + >( + adb: Adb, + path: string, + options: TOptions, + ): Promise> { let connection: AdbScrcpyConnection | undefined; let process: AdbSubprocessProtocol | undefined; @@ -239,7 +231,7 @@ export class AdbScrcpyClient { return options.getDisplays(adb, path); } - #options: AdbScrcpyOptions; + #options: TOptions; #process: AdbSubprocessProtocol; #stdout: ReadableStream; @@ -251,16 +243,6 @@ export class AdbScrcpyClient { return this.#process.exit; } - #screenWidth: number | undefined; - get screenWidth() { - return this.#screenWidth; - } - - #screenHeight: number | undefined; - get screenHeight() { - return this.#screenHeight; - } - #videoStream: Promise | undefined; /** * Gets a `Promise` that resolves to the parsed video stream. @@ -271,8 +253,12 @@ export class AdbScrcpyClient { * Note: if it's not `undefined`, it must be consumed to prevent * the connection from being blocked. */ - get videoStream() { - return this.#videoStream; + get videoStream(): TOptions["value"] extends { video: infer T } + ? T extends false + ? undefined + : Promise + : Promise { + return this.#videoStream as never; } #audioStream: Promise | undefined; @@ -312,7 +298,7 @@ export class AdbScrcpyClient { videoStream, audioStream, controlStream, - }: AdbScrcpyClientInit) { + }: AdbScrcpyClientInit) { this.#options = options; this.#process = process; this.#stdout = stdout; @@ -358,65 +344,10 @@ export class AdbScrcpyClient { } } - #configureH264(data: Uint8Array) { - const { croppedWidth, croppedHeight } = h264ParseConfiguration(data); - - this.#screenWidth = croppedWidth; - this.#screenHeight = croppedHeight; - } - - #configureH265(data: Uint8Array) { - const { croppedWidth, croppedHeight } = h265ParseConfiguration(data); - - this.#screenWidth = croppedWidth; - this.#screenHeight = croppedHeight; - } - - #configureAv1(data: Uint8Array) { - const parser = new Av1(data); - const sequenceHeader = parser.searchSequenceHeaderObu(); - if (!sequenceHeader) { - return; - } - - const { max_frame_width_minus_1, max_frame_height_minus_1 } = - sequenceHeader; - - const width = max_frame_width_minus_1 + 1; - const height = max_frame_height_minus_1 + 1; - - this.#screenWidth = width; - this.#screenHeight = height; - } - async #createVideoStream(initialStream: ReadableStream) { - const { stream, metadata } = + const { metadata, stream } = await this.#options.parseVideoStreamMetadata(initialStream); - - return { - stream: stream - .pipeThrough(this.#options.createMediaStreamTransformer()) - .pipeThrough( - new InspectStream((packet) => { - if (packet.type === "configuration") { - switch (metadata.codec) { - case ScrcpyVideoCodecId.H264: - this.#configureH264(packet.data); - break; - case ScrcpyVideoCodecId.H265: - this.#configureH265(packet.data); - break; - case ScrcpyVideoCodecId.AV1: - // AV1 configuration is in normal stream - break; - } - } else if (metadata.codec === ScrcpyVideoCodecId.AV1) { - this.#configureAv1(packet.data); - } - }), - ), - metadata, - }; + return new AdbScrcpyVideoStream(this.#options, metadata, stream); } async #createAudioStream( diff --git a/libraries/adb-scrcpy/src/latest.ts b/libraries/adb-scrcpy/src/latest.ts index 266ecd09..0df62861 100644 --- a/libraries/adb-scrcpy/src/latest.ts +++ b/libraries/adb-scrcpy/src/latest.ts @@ -1,11 +1,14 @@ import { AdbScrcpyOptions3_1 } from "./3_1.js"; -export class AdbScrcpyOptionsLatest extends AdbScrcpyOptions3_1 { - constructor(init: AdbScrcpyOptions3_1.Init, version: string) { +export class AdbScrcpyOptionsLatest< + TVideo extends boolean, +> extends AdbScrcpyOptions3_1 { + constructor(init: AdbScrcpyOptions3_1.Init, version: string) { super(init, version); } } export namespace AdbScrcpyOptionsLatest { - export type Init = AdbScrcpyOptions3_1.Init; + export type Init = + AdbScrcpyOptions3_1.Init; } diff --git a/libraries/adb-scrcpy/src/video.ts b/libraries/adb-scrcpy/src/video.ts new file mode 100644 index 00000000..c9198084 --- /dev/null +++ b/libraries/adb-scrcpy/src/video.ts @@ -0,0 +1,108 @@ +import { EventEmitter } from "@yume-chan/event"; +import type { + ScrcpyMediaStreamPacket, + ScrcpyVideoStreamMetadata, +} from "@yume-chan/scrcpy"; +import { + Av1, + h264ParseConfiguration, + h265ParseConfiguration, + ScrcpyVideoCodecId, +} from "@yume-chan/scrcpy"; +import type { ReadableStream } from "@yume-chan/stream-extra"; +import { InspectStream } from "@yume-chan/stream-extra"; + +import type { AdbScrcpyOptions } from "./types.js"; + +export class AdbScrcpyVideoStream { + #options: AdbScrcpyOptions; + + #metadata: ScrcpyVideoStreamMetadata; + get metadata(): ScrcpyVideoStreamMetadata { + return this.#metadata; + } + + #stream: ReadableStream; + get stream(): ReadableStream { + return this.#stream; + } + + #sizeChanged = new EventEmitter<{ width: number; height: number }>(); + get sizeChanged() { + return this.#sizeChanged.event; + } + + #width: number = 0; + get width() { + return this.#width; + } + + #height: number = 0; + get height() { + return this.#height; + } + + constructor( + options: AdbScrcpyOptions, + metadata: ScrcpyVideoStreamMetadata, + stream: ReadableStream, + ) { + this.#options = options; + this.#metadata = metadata; + this.#stream = stream + .pipeThrough(this.#options.createMediaStreamTransformer()) + .pipeThrough( + new InspectStream((packet) => { + if (packet.type === "configuration") { + switch (metadata.codec) { + case ScrcpyVideoCodecId.H264: + this.#configureH264(packet.data); + break; + case ScrcpyVideoCodecId.H265: + this.#configureH265(packet.data); + break; + case ScrcpyVideoCodecId.AV1: + // AV1 configuration is in data packet + break; + } + } else if (metadata.codec === ScrcpyVideoCodecId.AV1) { + this.#configureAv1(packet.data); + } + }), + ); + } + + #configureH264(data: Uint8Array) { + const { croppedWidth, croppedHeight } = h264ParseConfiguration(data); + + this.#width = croppedWidth; + this.#height = croppedHeight; + this.#sizeChanged.fire({ width: croppedWidth, height: croppedHeight }); + } + + #configureH265(data: Uint8Array) { + const { croppedWidth, croppedHeight } = h265ParseConfiguration(data); + + this.#width = croppedWidth; + this.#height = croppedHeight; + this.#sizeChanged.fire({ width: croppedWidth, height: croppedHeight }); + } + + #configureAv1(data: Uint8Array) { + const parser = new Av1(data); + const sequenceHeader = parser.searchSequenceHeaderObu(); + if (!sequenceHeader) { + return; + } + + const { max_frame_width_minus_1, max_frame_height_minus_1 } = + sequenceHeader; + + const width = max_frame_width_minus_1 + 1; + const height = max_frame_height_minus_1 + 1; + + this.#width = width; + this.#height = height; + this.#sizeChanged.fire({ width, height }); + } +} diff --git a/libraries/adb-scrcpy/tsconfig.test.json b/libraries/adb-scrcpy/tsconfig.test.json index e987f757..6a105912 100644 --- a/libraries/adb-scrcpy/tsconfig.test.json +++ b/libraries/adb-scrcpy/tsconfig.test.json @@ -2,6 +2,7 @@ "extends": "./tsconfig.build.json", "compilerOptions": { "types": [ + "node" ], }, "exclude": [] diff --git a/libraries/scrcpy-decoder-tinyh264/src/decoder.ts b/libraries/scrcpy-decoder-tinyh264/src/decoder.ts index a7a758f6..3d8fc26d 100644 --- a/libraries/scrcpy-decoder-tinyh264/src/decoder.ts +++ b/libraries/scrcpy-decoder-tinyh264/src/decoder.ts @@ -50,6 +50,16 @@ export class TinyH264Decoder implements ScrcpyVideoDecoder { return this.#sizeChanged.event; } + #width: number = 0; + get width() { + return this.#width; + } + + #height: number = 0; + get height() { + return this.#height; + } + #frameRendered = 0; get framesRendered() { return this.#frameRendered; @@ -124,12 +134,16 @@ export class TinyH264Decoder implements ScrcpyVideoDecoder { cropLeft, cropTop, } = h264ParseConfiguration(data); + + this.#width = croppedWidth; + this.#height = croppedHeight; this.#sizeChanged.fire({ width: croppedWidth, height: croppedHeight, }); // H.264 Baseline profile only supports YUV 420 pixel format + // So chroma width/height is each half of video width/height const chromaWidth = encodedWidth / 2; const chromaHeight = encodedHeight / 2; diff --git a/libraries/scrcpy-decoder-tinyh264/src/types.ts b/libraries/scrcpy-decoder-tinyh264/src/types.ts index c384dee7..32a00dd9 100644 --- a/libraries/scrcpy-decoder-tinyh264/src/types.ts +++ b/libraries/scrcpy-decoder-tinyh264/src/types.ts @@ -12,8 +12,12 @@ export interface ScrcpyVideoDecoderCapability { export interface ScrcpyVideoDecoder extends Disposable { readonly sizeChanged: Event<{ width: number; height: number }>; + readonly width: number; + readonly height: number; + readonly framesRendered: number; readonly framesSkipped: number; + readonly writable: WritableStream; } diff --git a/libraries/scrcpy-decoder-webcodecs/src/video/decoder.ts b/libraries/scrcpy-decoder-webcodecs/src/video/decoder.ts index 7b8ebe1e..433ca2cc 100644 --- a/libraries/scrcpy-decoder-webcodecs/src/video/decoder.ts +++ b/libraries/scrcpy-decoder-webcodecs/src/video/decoder.ts @@ -10,87 +10,9 @@ import { WritableStream } from "@yume-chan/stream-extra"; import { Av1Codec, H264Decoder, H265Decoder } from "./codec/index.js"; import type { CodecDecoder } from "./codec/type.js"; +import { Pool } from "./pool.js"; import type { VideoFrameRenderer } from "./render/index.js"; - -class Pool { - #controller!: ReadableStreamDefaultController; - #readable = new ReadableStream( - { - start: (controller) => { - this.#controller = controller; - }, - pull: (controller) => { - controller.enqueue(this.#initializer()); - }, - }, - { highWaterMark: 0 }, - ); - #reader = this.#readable.getReader(); - - #initializer: () => T; - - #size = 0; - #capacity: number; - - constructor(initializer: () => T, capacity: number) { - this.#initializer = initializer; - this.#capacity = capacity; - } - - async borrow() { - const result = await this.#reader.read(); - return result.value!; - } - - return(value: T) { - if (this.#size < this.#capacity) { - this.#controller.enqueue(value); - this.#size += 1; - } - } -} - -class VideoFrameCapturer { - #canvas: OffscreenCanvas | HTMLCanvasElement; - #context: ImageBitmapRenderingContext; - - constructor() { - if (typeof OffscreenCanvas !== "undefined") { - this.#canvas = new OffscreenCanvas(1, 1); - } else { - this.#canvas = document.createElement("canvas"); - this.#canvas.width = 1; - this.#canvas.height = 1; - } - this.#context = this.#canvas.getContext("bitmaprenderer", { - alpha: false, - })!; - } - - async capture(frame: VideoFrame): Promise { - this.#canvas.width = frame.displayWidth; - this.#canvas.height = frame.displayHeight; - - const bitmap = await createImageBitmap(frame); - this.#context.transferFromImageBitmap(bitmap); - - if (this.#canvas instanceof OffscreenCanvas) { - return await this.#canvas.convertToBlob({ - type: "image/png", - }); - } else { - return new Promise((resolve, reject) => { - (this.#canvas as HTMLCanvasElement).toBlob((blob) => { - if (!blob) { - reject(new Error("Failed to convert canvas to blob")); - } else { - resolve(blob); - } - }, "image/png"); - }); - } - } -} +import { VideoFrameCapturer } from "./snapshot.js"; const VideoFrameCapturerPool = /* #__PURE__ */ @@ -144,6 +66,16 @@ export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder { return this.#sizeChanged.event; } + #width: number = 0; + get width() { + return this.#width; + } + + #height: number = 0; + get height() { + return this.#height; + } + #decoder: VideoDecoder; #drawing = false; @@ -218,7 +150,7 @@ export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder { }, }); - this.#onVerticalSync(); + this.#handleAnimationFrame(); } #setError(error: Error) { @@ -261,16 +193,20 @@ export class WebCodecsVideoDecoder implements ScrcpyVideoDecoder { #updateSize = (width: number, height: number) => { this.#renderer.setSize(width, height); + this.#width = width; + this.#height = height; this.#sizeChanged.fire({ width, height }); }; - #onVerticalSync = () => { + #handleAnimationFrame = () => { if (this.#framesDraw > 0) { this.#framesPresented += 1; this.#framesSkipped += this.#framesDraw - 1; this.#framesDraw = 0; } - this.#animationFrameId = requestAnimationFrame(this.#onVerticalSync); + this.#animationFrameId = requestAnimationFrame( + this.#handleAnimationFrame, + ); }; async snapshot() { diff --git a/libraries/scrcpy-decoder-webcodecs/src/video/pool.ts b/libraries/scrcpy-decoder-webcodecs/src/video/pool.ts new file mode 100644 index 00000000..9a3fa48f --- /dev/null +++ b/libraries/scrcpy-decoder-webcodecs/src/video/pool.ts @@ -0,0 +1,37 @@ +export class Pool { + #controller!: ReadableStreamDefaultController; + #readable = new ReadableStream( + { + start: (controller) => { + this.#controller = controller; + }, + pull: (controller) => { + controller.enqueue(this.#initializer()); + }, + }, + { highWaterMark: 0 }, + ); + #reader = this.#readable.getReader(); + + #initializer: () => T; + + #size = 0; + #capacity: number; + + constructor(initializer: () => T, capacity: number) { + this.#initializer = initializer; + this.#capacity = capacity; + } + + async borrow() { + const result = await this.#reader.read(); + return result.value!; + } + + return(value: T) { + if (this.#size < this.#capacity) { + this.#controller.enqueue(value); + this.#size += 1; + } + } +} diff --git a/libraries/scrcpy-decoder-webcodecs/src/video/snapshot.ts b/libraries/scrcpy-decoder-webcodecs/src/video/snapshot.ts new file mode 100644 index 00000000..e10dbc1e --- /dev/null +++ b/libraries/scrcpy-decoder-webcodecs/src/video/snapshot.ts @@ -0,0 +1,41 @@ +export class VideoFrameCapturer { + #canvas: OffscreenCanvas | HTMLCanvasElement; + #context: ImageBitmapRenderingContext; + + constructor() { + if (typeof OffscreenCanvas !== "undefined") { + this.#canvas = new OffscreenCanvas(1, 1); + } else { + this.#canvas = document.createElement("canvas"); + this.#canvas.width = 1; + this.#canvas.height = 1; + } + this.#context = this.#canvas.getContext("bitmaprenderer", { + alpha: false, + })!; + } + + async capture(frame: VideoFrame): Promise { + this.#canvas.width = frame.displayWidth; + this.#canvas.height = frame.displayHeight; + + const bitmap = await createImageBitmap(frame); + this.#context.transferFromImageBitmap(bitmap); + + if (this.#canvas instanceof OffscreenCanvas) { + return await this.#canvas.convertToBlob({ + type: "image/png", + }); + } else { + return new Promise((resolve, reject) => { + (this.#canvas as HTMLCanvasElement).toBlob((blob) => { + if (!blob) { + reject(new Error("Failed to convert canvas to blob")); + } else { + resolve(blob); + } + }, "image/png"); + }); + } + } +} diff --git a/libraries/scrcpy/src/1_15/impl/inject-touch.ts b/libraries/scrcpy/src/1_15/impl/inject-touch.ts index eca781e9..0afaa68a 100644 --- a/libraries/scrcpy/src/1_15/impl/inject-touch.ts +++ b/libraries/scrcpy/src/1_15/impl/inject-touch.ts @@ -36,8 +36,8 @@ export const InjectTouchControlMessage = struct( pointerId: u64, pointerX: u32, pointerY: u32, - screenWidth: u16, - screenHeight: u16, + videoWidth: u16, + videoHeight: u16, pressure: UnsignedFloat, buttons: u32, }, diff --git a/libraries/scrcpy/src/1_15/impl/scroll-controller.spec.ts b/libraries/scrcpy/src/1_15/impl/scroll-controller.spec.ts index 6370abe9..b9319a81 100644 --- a/libraries/scrcpy/src/1_15/impl/scroll-controller.spec.ts +++ b/libraries/scrcpy/src/1_15/impl/scroll-controller.spec.ts @@ -12,8 +12,8 @@ describe("ScrollController", () => { type: ScrcpyControlMessageType.InjectScroll, pointerX: 0, pointerY: 0, - screenWidth: 0, - screenHeight: 0, + videoWidth: 0, + videoHeight: 0, scrollX: 0.5, scrollY: 0.5, buttons: 0, @@ -27,8 +27,8 @@ describe("ScrollController", () => { type: ScrcpyControlMessageType.InjectScroll, pointerX: 0, pointerY: 0, - screenWidth: 0, - screenHeight: 0, + videoWidth: 0, + videoHeight: 0, scrollX: 1.5, scrollY: 1.5, buttons: 0, @@ -43,8 +43,8 @@ describe("ScrollController", () => { type: ScrcpyControlMessageType.InjectScroll, pointerX: 0, pointerY: 0, - screenWidth: 0, - screenHeight: 0, + videoWidth: 0, + videoHeight: 0, scrollX: 0.5, scrollY: 0.5, buttons: 0, @@ -53,8 +53,8 @@ describe("ScrollController", () => { type: ScrcpyControlMessageType.InjectScroll, pointerX: 0, pointerY: 0, - screenWidth: 0, - screenHeight: 0, + videoWidth: 0, + videoHeight: 0, scrollX: 0.5, scrollY: 0.5, buttons: 0, @@ -69,8 +69,8 @@ describe("ScrollController", () => { type: ScrcpyControlMessageType.InjectScroll, pointerX: 0, pointerY: 0, - screenWidth: 0, - screenHeight: 0, + videoWidth: 0, + videoHeight: 0, scrollX: -0.5, scrollY: -0.5, buttons: 0, @@ -79,8 +79,8 @@ describe("ScrollController", () => { type: ScrcpyControlMessageType.InjectScroll, pointerX: 0, pointerY: 0, - screenWidth: 0, - screenHeight: 0, + videoWidth: 0, + videoHeight: 0, scrollX: -0.5, scrollY: -0.5, buttons: 0, diff --git a/libraries/scrcpy/src/1_15/impl/scroll-controller.ts b/libraries/scrcpy/src/1_15/impl/scroll-controller.ts index e9e97413..25505630 100644 --- a/libraries/scrcpy/src/1_15/impl/scroll-controller.ts +++ b/libraries/scrcpy/src/1_15/impl/scroll-controller.ts @@ -9,8 +9,8 @@ export const InjectScrollControlMessage = struct( type: u8, pointerX: u32, pointerY: u32, - screenWidth: u16, - screenHeight: u16, + videoWidth: u16, + videoHeight: u16, scrollX: s32, scrollY: s32, }, diff --git a/libraries/scrcpy/src/1_22/impl/scroll-controller.spec.ts b/libraries/scrcpy/src/1_22/impl/scroll-controller.spec.ts index df6928c1..400a8557 100644 --- a/libraries/scrcpy/src/1_22/impl/scroll-controller.spec.ts +++ b/libraries/scrcpy/src/1_22/impl/scroll-controller.spec.ts @@ -12,8 +12,8 @@ describe("ScrollController", () => { type: ScrcpyControlMessageType.InjectScroll, pointerX: 0, pointerY: 0, - screenWidth: 0, - screenHeight: 0, + videoWidth: 0, + videoHeight: 0, scrollX: 1.5, scrollY: 1.5, buttons: 0, diff --git a/libraries/scrcpy/src/1_25/impl/scroll-controller.spec.ts b/libraries/scrcpy/src/1_25/impl/scroll-controller.spec.ts index 01cb7509..e5eb5faf 100644 --- a/libraries/scrcpy/src/1_25/impl/scroll-controller.spec.ts +++ b/libraries/scrcpy/src/1_25/impl/scroll-controller.spec.ts @@ -102,8 +102,8 @@ describe("ScrollController", () => { type: ScrcpyControlMessageType.InjectScroll, pointerX: 0, pointerY: 0, - screenWidth: 0, - screenHeight: 0, + videoWidth: 0, + videoHeight: 0, scrollX: 0.5, scrollY: 0.5, buttons: 0, @@ -115,8 +115,8 @@ describe("ScrollController", () => { type: ScrcpyControlMessageType.InjectScroll, pointerX: 0, pointerY: 0, - screenWidth: 0, - screenHeight: 0, + videoWidth: 0, + videoHeight: 0, scrollX: 1.5, scrollY: 1.5, buttons: 0, diff --git a/libraries/scrcpy/src/1_25/impl/scroll-controller.ts b/libraries/scrcpy/src/1_25/impl/scroll-controller.ts index 1d487aa9..5bbeaea3 100644 --- a/libraries/scrcpy/src/1_25/impl/scroll-controller.ts +++ b/libraries/scrcpy/src/1_25/impl/scroll-controller.ts @@ -29,8 +29,8 @@ export const InjectScrollControlMessage = /* #__PURE__ */ (() => type: u8(ScrcpyControlMessageType.InjectScroll), pointerX: u32, pointerY: u32, - screenWidth: u16, - screenHeight: u16, + videoWidth: u16, + videoHeight: u16, scrollX: SignedFloat, scrollY: SignedFloat, buttons: u32, diff --git a/libraries/scrcpy/src/2_0/impl/inject-touch.ts b/libraries/scrcpy/src/2_0/impl/inject-touch.ts index 3f8c75de..133ee692 100644 --- a/libraries/scrcpy/src/2_0/impl/inject-touch.ts +++ b/libraries/scrcpy/src/2_0/impl/inject-touch.ts @@ -15,8 +15,8 @@ export const InjectTouchControlMessage = /* #__PURE__ */ (() => pointerId: u64, pointerX: u32, pointerY: u32, - screenWidth: u16, - screenHeight: u16, + videoWidth: u16, + videoHeight: u16, pressure: PrevImpl.UnsignedFloat, actionButton: u32, buttons: u32, diff --git a/libraries/scrcpy/src/2_0/impl/parse-audio-stream-metadata.ts b/libraries/scrcpy/src/2_0/impl/parse-audio-stream-metadata.ts index 4ebe557e..7adc44f0 100644 --- a/libraries/scrcpy/src/2_0/impl/parse-audio-stream-metadata.ts +++ b/libraries/scrcpy/src/2_0/impl/parse-audio-stream-metadata.ts @@ -11,7 +11,7 @@ import { ScrcpyAudioCodec } from "../../base/index.js"; export async function parseAudioStreamMetadata( stream: ReadableStream, - options: Pick, "sendCodecMeta" | "audioCodec">, + options: Pick>, "sendCodecMeta" | "audioCodec">, ): Promise { const buffered = new BufferedReadableStream(stream); diff --git a/libraries/scrcpy/src/2_1/impl/defaults.ts b/libraries/scrcpy/src/2_1/impl/defaults.ts index 5ce1ab8f..99b0ed27 100644 --- a/libraries/scrcpy/src/2_1/impl/defaults.ts +++ b/libraries/scrcpy/src/2_1/impl/defaults.ts @@ -6,4 +6,4 @@ export const Defaults = /* #__PURE__ */ (() => ...PrevImpl.Defaults, video: true, audioSource: "output", - }) as const satisfies Required)(); + }) as const satisfies Required>)(); diff --git a/libraries/scrcpy/src/2_1/impl/init.ts b/libraries/scrcpy/src/2_1/impl/init.ts index a1745f75..717fdce3 100644 --- a/libraries/scrcpy/src/2_1/impl/init.ts +++ b/libraries/scrcpy/src/2_1/impl/init.ts @@ -1,6 +1,6 @@ import type { PrevImpl } from "./prev.js"; -export interface Init extends PrevImpl.Init { - video?: boolean; +export interface Init extends PrevImpl.Init { + video?: TVideo; audioSource?: "output" | "mic"; } diff --git a/libraries/scrcpy/src/2_1/options.ts b/libraries/scrcpy/src/2_1/options.ts index d711e42a..6dda49e6 100644 --- a/libraries/scrcpy/src/2_1/options.ts +++ b/libraries/scrcpy/src/2_1/options.ts @@ -37,12 +37,14 @@ import { setListEncoders, } from "./impl/index.js"; -export class ScrcpyOptions2_1 implements ScrcpyOptions { +export class ScrcpyOptions2_1 + implements ScrcpyOptions> +{ static readonly Defaults = Defaults; readonly version: string; - readonly value: Required; + readonly value: Required>; get controlMessageTypes(): readonly ScrcpyControlMessageType[] { return ControlMessageTypes; @@ -55,8 +57,8 @@ export class ScrcpyOptions2_1 implements ScrcpyOptions { #ackClipboardHandler: AckClipboardHandler | undefined; - constructor(init: Init, version = "2.1") { - this.value = { ...Defaults, ...init }; + constructor(init: Init, version = "2.1") { + this.value = { ...Defaults, ...init } as never; this.version = version; if (this.value.control && this.value.clipboardAutosync) { @@ -66,7 +68,7 @@ export class ScrcpyOptions2_1 implements ScrcpyOptions { } serialize(): string[] { - return serialize(this.value, Defaults); + return serialize>(this.value, Defaults); } setListDisplays(): void { @@ -154,8 +156,8 @@ export class ScrcpyOptions2_1 implements ScrcpyOptions { } } -type Init_ = Init; +type Init_ = Init; export namespace ScrcpyOptions2_1 { - export type Init = Init_; + export type Init = Init_; } diff --git a/libraries/scrcpy/src/2_1_1.ts b/libraries/scrcpy/src/2_1_1.ts index 8cb232ec..0aeb173f 100644 --- a/libraries/scrcpy/src/2_1_1.ts +++ b/libraries/scrcpy/src/2_1_1.ts @@ -1,11 +1,14 @@ import { ScrcpyOptions2_1 } from "./2_1/index.js"; -export class ScrcpyOptions2_1_1 extends ScrcpyOptions2_1 { - constructor(init: ScrcpyOptions2_1.Init, version = "2.1.1") { +export class ScrcpyOptions2_1_1< + TVideo extends boolean, +> extends ScrcpyOptions2_1 { + constructor(init: ScrcpyOptions2_1.Init, version = "2.1.1") { super(init, version); } } export namespace ScrcpyOptions2_1_1 { - export type Init = ScrcpyOptions2_1.Init; + export type Init = + ScrcpyOptions2_1.Init; } diff --git a/libraries/scrcpy/src/2_2/impl/defaults.ts b/libraries/scrcpy/src/2_2/impl/defaults.ts index 154f7f55..3bac3c9a 100644 --- a/libraries/scrcpy/src/2_2/impl/defaults.ts +++ b/libraries/scrcpy/src/2_2/impl/defaults.ts @@ -14,4 +14,4 @@ export const Defaults = /* #__PURE__ */ (() => cameraHighSpeed: false, listCameras: false, listCameraSizes: false, - }) as const satisfies Required)(); + }) as const satisfies Required>)(); diff --git a/libraries/scrcpy/src/2_2/impl/init.ts b/libraries/scrcpy/src/2_2/impl/init.ts index 2adb52a6..de171b8b 100644 --- a/libraries/scrcpy/src/2_2/impl/init.ts +++ b/libraries/scrcpy/src/2_2/impl/init.ts @@ -1,6 +1,6 @@ import type { PrevImpl } from "./prev.js"; -export interface Init extends PrevImpl.Init { +export interface Init extends PrevImpl.Init { videoSource?: "display" | "camera"; cameraId?: string | undefined; cameraSize?: string | undefined; diff --git a/libraries/scrcpy/src/2_2/options.ts b/libraries/scrcpy/src/2_2/options.ts index 578ac9fb..f4afe795 100644 --- a/libraries/scrcpy/src/2_2/options.ts +++ b/libraries/scrcpy/src/2_2/options.ts @@ -37,12 +37,14 @@ import { setListEncoders, } from "./impl/index.js"; -export class ScrcpyOptions2_2 implements ScrcpyOptions { +export class ScrcpyOptions2_2 + implements ScrcpyOptions> +{ static readonly Defaults = Defaults; readonly version: string; - readonly value: Required; + readonly value: Required>; get controlMessageTypes(): readonly ScrcpyControlMessageType[] { return ControlMessageTypes; @@ -55,8 +57,8 @@ export class ScrcpyOptions2_2 implements ScrcpyOptions { #ackClipboardHandler: AckClipboardHandler | undefined; - constructor(init: Init, version = "v2.2") { - this.value = { ...Defaults, ...init }; + constructor(init: Init, version = "v2.2") { + this.value = { ...Defaults, ...init } as never; this.version = version; if (this.value.videoSource === "camera") { @@ -70,7 +72,7 @@ export class ScrcpyOptions2_2 implements ScrcpyOptions { } serialize(): string[] { - return serialize(this.value, Defaults); + return serialize>(this.value, Defaults); } setListDisplays(): void { @@ -158,8 +160,8 @@ export class ScrcpyOptions2_2 implements ScrcpyOptions { } } -type Init_ = Init; +type Init_ = Init; export namespace ScrcpyOptions2_2 { - export type Init = Init_; + export type Init = Init_; } diff --git a/libraries/scrcpy/src/2_3/impl/init.ts b/libraries/scrcpy/src/2_3/impl/init.ts index 3b1dadcd..dc9e6698 100644 --- a/libraries/scrcpy/src/2_3/impl/init.ts +++ b/libraries/scrcpy/src/2_3/impl/init.ts @@ -1,5 +1,6 @@ import type { PrevImpl } from "./prev.js"; -export interface Init extends Omit { - audioCodec?: PrevImpl.Init["audioCodec"] | "flac"; +export interface Init + extends Omit, "audioCodec"> { + audioCodec?: PrevImpl.Init["audioCodec"] | "flac"; } diff --git a/libraries/scrcpy/src/2_3/options.ts b/libraries/scrcpy/src/2_3/options.ts index cafd2110..1b581111 100644 --- a/libraries/scrcpy/src/2_3/options.ts +++ b/libraries/scrcpy/src/2_3/options.ts @@ -37,12 +37,14 @@ import { setListEncoders, } from "./impl/index.js"; -export class ScrcpyOptions2_3 implements ScrcpyOptions { +export class ScrcpyOptions2_3 + implements ScrcpyOptions> +{ static readonly Defaults = Defaults; readonly version: string; - readonly value: Required; + readonly value: Required>; get controlMessageTypes(): readonly ScrcpyControlMessageType[] { return ControlMessageTypes; @@ -55,8 +57,8 @@ export class ScrcpyOptions2_3 implements ScrcpyOptions { #ackClipboardHandler: AckClipboardHandler | undefined; - constructor(init: Init, version = "2.3") { - this.value = { ...Defaults, ...init }; + constructor(init: Init, version = "2.3") { + this.value = { ...Defaults, ...init } as never; this.version = version; if (this.value.videoSource === "camera") { @@ -70,7 +72,7 @@ export class ScrcpyOptions2_3 implements ScrcpyOptions { } serialize(): string[] { - return serialize(this.value, Defaults); + return serialize>(this.value, Defaults); } setListDisplays(): void { @@ -158,8 +160,8 @@ export class ScrcpyOptions2_3 implements ScrcpyOptions { } } -type Init_ = Init; +type Init_ = Init; export namespace ScrcpyOptions2_3 { - export type Init = Init_; + export type Init = Init_; } diff --git a/libraries/scrcpy/src/2_3_1.ts b/libraries/scrcpy/src/2_3_1.ts index e5670c9f..a97fe03f 100644 --- a/libraries/scrcpy/src/2_3_1.ts +++ b/libraries/scrcpy/src/2_3_1.ts @@ -1,11 +1,14 @@ import { ScrcpyOptions2_3 } from "./2_3/index.js"; -export class ScrcpyOptions2_3_1 extends ScrcpyOptions2_3 { - constructor(init: ScrcpyOptions2_3.Init, version = "2.3.1") { +export class ScrcpyOptions2_3_1< + TVideo extends boolean, +> extends ScrcpyOptions2_3 { + constructor(init: ScrcpyOptions2_3.Init, version = "2.3.1") { super(init, version); } } export namespace ScrcpyOptions2_3_1 { - export type Init = ScrcpyOptions2_3.Init; + export type Init = + ScrcpyOptions2_3.Init; } diff --git a/libraries/scrcpy/src/2_4/options.ts b/libraries/scrcpy/src/2_4/options.ts index c9ce8f10..0b78ed65 100644 --- a/libraries/scrcpy/src/2_4/options.ts +++ b/libraries/scrcpy/src/2_4/options.ts @@ -41,12 +41,14 @@ import { UHidOutputStream, } from "./impl/index.js"; -export class ScrcpyOptions2_4 implements ScrcpyOptions { +export class ScrcpyOptions2_4 + implements ScrcpyOptions> +{ static readonly Defaults = Defaults; readonly version: string; - readonly value: Required; + readonly value: Required>; get controlMessageTypes(): readonly ScrcpyControlMessageType[] { return ControlMessageTypes; @@ -66,8 +68,8 @@ export class ScrcpyOptions2_4 implements ScrcpyOptions { return this.#uHidOutput; } - constructor(init: Init, version = "2.4") { - this.value = { ...Defaults, ...init }; + constructor(init: Init, version = "2.4") { + this.value = { ...Defaults, ...init } as never; this.version = version; if (this.value.videoSource === "camera") { @@ -85,7 +87,7 @@ export class ScrcpyOptions2_4 implements ScrcpyOptions { } serialize(): string[] { - return serialize(this.value, Defaults); + return serialize>(this.value, Defaults); } setListDisplays(): void { @@ -185,8 +187,8 @@ export class ScrcpyOptions2_4 implements ScrcpyOptions { } } -type Init_ = Init; +type Init_ = Init; export namespace ScrcpyOptions2_4 { - export type Init = Init_; + export type Init = Init_; } diff --git a/libraries/scrcpy/src/2_5.ts b/libraries/scrcpy/src/2_5.ts index 990e5dd4..9cb7e104 100644 --- a/libraries/scrcpy/src/2_5.ts +++ b/libraries/scrcpy/src/2_5.ts @@ -1,11 +1,14 @@ import { ScrcpyOptions2_4 } from "./2_4/index.js"; -export class ScrcpyOptions2_5 extends ScrcpyOptions2_4 { - constructor(init: ScrcpyOptions2_4.Init, version = "2.5") { +export class ScrcpyOptions2_5< + TVideo extends boolean, +> extends ScrcpyOptions2_4 { + constructor(init: ScrcpyOptions2_4.Init, version = "2.5") { super(init, version); } } export namespace ScrcpyOptions2_5 { - export type Init = ScrcpyOptions2_4.Init; + export type Init = + ScrcpyOptions2_4.Init; } diff --git a/libraries/scrcpy/src/2_6/impl/defaults.ts b/libraries/scrcpy/src/2_6/impl/defaults.ts index e0f9c99b..9b710a5d 100644 --- a/libraries/scrcpy/src/2_6/impl/defaults.ts +++ b/libraries/scrcpy/src/2_6/impl/defaults.ts @@ -5,4 +5,4 @@ export const Defaults = /* #__PURE__ */ (() => ({ ...PrevImpl.Defaults, audioDup: false, - }) as const satisfies Required)(); + }) as const satisfies Required>)(); diff --git a/libraries/scrcpy/src/2_6/impl/init.ts b/libraries/scrcpy/src/2_6/impl/init.ts index 31fdc694..35954b79 100644 --- a/libraries/scrcpy/src/2_6/impl/init.ts +++ b/libraries/scrcpy/src/2_6/impl/init.ts @@ -1,6 +1,7 @@ import type { PrevImpl } from "./prev.js"; -export interface Init extends Omit { - audioSource?: PrevImpl.Init["audioSource"] | "playback"; +export interface Init + extends Omit, "audioSource"> { + audioSource?: PrevImpl.Init["audioSource"] | "playback"; audioDup?: boolean; } diff --git a/libraries/scrcpy/src/2_6/options.ts b/libraries/scrcpy/src/2_6/options.ts index 9b544572..83994a26 100644 --- a/libraries/scrcpy/src/2_6/options.ts +++ b/libraries/scrcpy/src/2_6/options.ts @@ -41,12 +41,14 @@ import { UHidOutputStream, } from "./impl/index.js"; -export class ScrcpyOptions2_6 implements ScrcpyOptions { +export class ScrcpyOptions2_6 + implements ScrcpyOptions> +{ static readonly Defaults = Defaults; readonly version: string; - readonly value: Required; + readonly value: Required>; get controlMessageTypes(): readonly ScrcpyControlMessageType[] { return ControlMessageTypes; @@ -66,8 +68,8 @@ export class ScrcpyOptions2_6 implements ScrcpyOptions { return this.#uHidOutput; } - constructor(init: Init, version = "2.6") { - this.value = { ...Defaults, ...init }; + constructor(init: Init, version = "2.6") { + this.value = { ...Defaults, ...init } as never; this.version = version; if (this.value.videoSource === "camera") { @@ -89,7 +91,7 @@ export class ScrcpyOptions2_6 implements ScrcpyOptions { } serialize(): string[] { - return serialize(this.value, Defaults); + return serialize>(this.value, Defaults); } setListDisplays(): void { @@ -183,8 +185,8 @@ export class ScrcpyOptions2_6 implements ScrcpyOptions { } } -type Init_ = Init; +type Init_ = Init; export namespace ScrcpyOptions2_6 { - export type Init = Init_; + export type Init = Init_; } diff --git a/libraries/scrcpy/src/2_6_1.ts b/libraries/scrcpy/src/2_6_1.ts index aae1842e..ebf70c11 100644 --- a/libraries/scrcpy/src/2_6_1.ts +++ b/libraries/scrcpy/src/2_6_1.ts @@ -1,11 +1,14 @@ import { ScrcpyOptions2_6 } from "./2_6/index.js"; -export class ScrcpyOptions2_6_1 extends ScrcpyOptions2_6 { - constructor(init: ScrcpyOptions2_6.Init, version = "2.6.1") { +export class ScrcpyOptions2_6_1< + TVideo extends boolean, +> extends ScrcpyOptions2_6 { + constructor(init: ScrcpyOptions2_6.Init, version = "2.6.1") { super(init, version); } } export namespace ScrcpyOptions2_6_1 { - export type Init = ScrcpyOptions2_6.Init; + export type Init = + ScrcpyOptions2_6.Init; } diff --git a/libraries/scrcpy/src/2_7/options.ts b/libraries/scrcpy/src/2_7/options.ts index e4d9def0..6af5c653 100644 --- a/libraries/scrcpy/src/2_7/options.ts +++ b/libraries/scrcpy/src/2_7/options.ts @@ -41,12 +41,14 @@ import { UHidOutputStream, } from "./impl/index.js"; -export class ScrcpyOptions2_7 implements ScrcpyOptions { +export class ScrcpyOptions2_7 + implements ScrcpyOptions> +{ static readonly Defaults = Defaults; readonly version: string; - readonly value: Required; + readonly value: Required>; get controlMessageTypes(): readonly ScrcpyControlMessageType[] { return ControlMessageTypes; @@ -66,8 +68,8 @@ export class ScrcpyOptions2_7 implements ScrcpyOptions { return this.#uHidOutput; } - constructor(init: Init, version = "2.7") { - this.value = { ...Defaults, ...init }; + constructor(init: Init, version = "2.7") { + this.value = { ...Defaults, ...init } as never; this.version = version; if (this.value.videoSource === "camera") { @@ -89,7 +91,7 @@ export class ScrcpyOptions2_7 implements ScrcpyOptions { } serialize(): string[] { - return serialize(this.value, Defaults); + return serialize>(this.value, Defaults); } setListDisplays(): void { @@ -183,8 +185,8 @@ export class ScrcpyOptions2_7 implements ScrcpyOptions { } } -type Init_ = Init; +type Init_ = Init; export namespace ScrcpyOptions2_7 { - export type Init = Init_; + export type Init = Init_; } diff --git a/libraries/scrcpy/src/3_0/impl/defaults.ts b/libraries/scrcpy/src/3_0/impl/defaults.ts index 13e49dd7..58faa2b7 100644 --- a/libraries/scrcpy/src/3_0/impl/defaults.ts +++ b/libraries/scrcpy/src/3_0/impl/defaults.ts @@ -12,4 +12,4 @@ export const Defaults = /* #__PURE__ */ (() => listApps: false, newDisplay: undefined, vdSystemDecorations: true, - }) as const satisfies Required)(); + }) as const satisfies Required>)(); diff --git a/libraries/scrcpy/src/3_0/impl/init.ts b/libraries/scrcpy/src/3_0/impl/init.ts index e1815136..2f8133db 100644 --- a/libraries/scrcpy/src/3_0/impl/init.ts +++ b/libraries/scrcpy/src/3_0/impl/init.ts @@ -106,7 +106,8 @@ export class NewDisplay implements ScrcpyOptionValue { } } -export interface Init extends Omit { +export interface Init + extends Omit, "lockVideoOrientation"> { captureOrientation?: CaptureOrientation | string | undefined; angle?: number; screenOffTimeout?: number | undefined; diff --git a/libraries/scrcpy/src/3_0/options.ts b/libraries/scrcpy/src/3_0/options.ts index b0a08dfb..4399cc0a 100644 --- a/libraries/scrcpy/src/3_0/options.ts +++ b/libraries/scrcpy/src/3_0/options.ts @@ -41,12 +41,14 @@ import { UHidOutputStream, } from "./impl/index.js"; -export class ScrcpyOptions3_0 implements ScrcpyOptions { +export class ScrcpyOptions3_0 + implements ScrcpyOptions> +{ static readonly Defaults = Defaults; readonly version: string; - readonly value: Required; + readonly value: Required>; get controlMessageTypes(): readonly ScrcpyControlMessageType[] { return ControlMessageTypes; @@ -66,8 +68,8 @@ export class ScrcpyOptions3_0 implements ScrcpyOptions { return this.#uHidOutput; } - constructor(init: Init, version = "3.0") { - this.value = { ...Defaults, ...init }; + constructor(init: Init, version = "3.0") { + this.value = { ...Defaults, ...init } as never; this.version = version; if (this.value.videoSource === "camera") { @@ -89,7 +91,7 @@ export class ScrcpyOptions3_0 implements ScrcpyOptions { } serialize(): string[] { - return serialize(this.value, Defaults); + return serialize>(this.value, Defaults); } setListDisplays(): void { @@ -183,8 +185,8 @@ export class ScrcpyOptions3_0 implements ScrcpyOptions { } } -type Init_ = Init; +type Init_ = Init; export namespace ScrcpyOptions3_0 { - export type Init = Init_; + export type Init = Init_; } diff --git a/libraries/scrcpy/src/3_0_1.ts b/libraries/scrcpy/src/3_0_1.ts index 4645d4ef..929f72d4 100644 --- a/libraries/scrcpy/src/3_0_1.ts +++ b/libraries/scrcpy/src/3_0_1.ts @@ -1,11 +1,14 @@ import { ScrcpyOptions3_0 } from "./3_0/index.js"; -export class ScrcpyOptions3_0_1 extends ScrcpyOptions3_0 { - constructor(init: ScrcpyOptions3_0.Init, version = "3.0.1") { +export class ScrcpyOptions3_0_1< + TVideo extends boolean, +> extends ScrcpyOptions3_0 { + constructor(init: ScrcpyOptions3_0.Init, version = "3.0.1") { super(init, version); } } export namespace ScrcpyOptions3_0_1 { - export type Init = ScrcpyOptions3_0.Init; + export type Init = + ScrcpyOptions3_0.Init; } diff --git a/libraries/scrcpy/src/3_0_2.ts b/libraries/scrcpy/src/3_0_2.ts index 6989e4cb..2c588020 100644 --- a/libraries/scrcpy/src/3_0_2.ts +++ b/libraries/scrcpy/src/3_0_2.ts @@ -1,11 +1,14 @@ import { ScrcpyOptions3_0 } from "./3_0/index.js"; -export class ScrcpyOptions3_0_2 extends ScrcpyOptions3_0 { - constructor(init: ScrcpyOptions3_0.Init, version = "3.0.2") { +export class ScrcpyOptions3_0_2< + TVideo extends boolean, +> extends ScrcpyOptions3_0 { + constructor(init: ScrcpyOptions3_0.Init, version = "3.0.2") { super(init, version); } } export namespace ScrcpyOptions3_0_2 { - export type Init = ScrcpyOptions3_0.Init; + export type Init = + ScrcpyOptions3_0.Init; } diff --git a/libraries/scrcpy/src/3_1/impl/defaults.ts b/libraries/scrcpy/src/3_1/impl/defaults.ts index 5869af35..2882aae0 100644 --- a/libraries/scrcpy/src/3_1/impl/defaults.ts +++ b/libraries/scrcpy/src/3_1/impl/defaults.ts @@ -5,4 +5,4 @@ export const Defaults = /* #__PURE__ */ (() => ({ ...PrevImpl.Defaults, vdDestroyContent: false, - }) as const satisfies Required)(); + }) as const satisfies Required>)(); diff --git a/libraries/scrcpy/src/3_1/impl/init.ts b/libraries/scrcpy/src/3_1/impl/init.ts index 79b03abf..68c051e5 100644 --- a/libraries/scrcpy/src/3_1/impl/init.ts +++ b/libraries/scrcpy/src/3_1/impl/init.ts @@ -1,5 +1,5 @@ import type { PrevImpl } from "./prev.js"; -export interface Init extends PrevImpl.Init { +export interface Init extends PrevImpl.Init { vdDestroyContent?: boolean; } diff --git a/libraries/scrcpy/src/3_1/options.ts b/libraries/scrcpy/src/3_1/options.ts index 288ac623..4ca6bc10 100644 --- a/libraries/scrcpy/src/3_1/options.ts +++ b/libraries/scrcpy/src/3_1/options.ts @@ -41,12 +41,14 @@ import { UHidOutputStream, } from "./impl/index.js"; -export class ScrcpyOptions3_1 implements ScrcpyOptions { +export class ScrcpyOptions3_1 + implements ScrcpyOptions> +{ static readonly Defaults = Defaults; readonly version: string; - readonly value: Required; + readonly value: Required>; get controlMessageTypes(): readonly ScrcpyControlMessageType[] { return ControlMessageTypes; @@ -66,8 +68,8 @@ export class ScrcpyOptions3_1 implements ScrcpyOptions { return this.#uHidOutput; } - constructor(init: Init, version = "3.1") { - this.value = { ...Defaults, ...init }; + constructor(init: Init, version = "3.1") { + this.value = { ...Defaults, ...init } as never; this.version = version; if (this.value.videoSource === "camera") { @@ -89,7 +91,7 @@ export class ScrcpyOptions3_1 implements ScrcpyOptions { } serialize(): string[] { - return serialize(this.value, Defaults); + return serialize>(this.value, Defaults); } setListDisplays(): void { @@ -183,8 +185,8 @@ export class ScrcpyOptions3_1 implements ScrcpyOptions { } } -type Init_ = Init; +type Init_ = Init; export namespace ScrcpyOptions3_1 { - export type Init = Init_; + export type Init = Init_; } diff --git a/libraries/scrcpy/src/latest.ts b/libraries/scrcpy/src/latest.ts index 73412232..60847fcc 100644 --- a/libraries/scrcpy/src/latest.ts +++ b/libraries/scrcpy/src/latest.ts @@ -1,13 +1,16 @@ import { ScrcpyOptions3_1 } from "./3_1/options.js"; -export class ScrcpyOptionsLatest extends ScrcpyOptions3_1 { - constructor(init: ScrcpyOptions3_1.Init, version: string) { +export class ScrcpyOptionsLatest< + TVideo extends boolean, +> extends ScrcpyOptions3_1 { + constructor(init: ScrcpyOptions3_1.Init, version: string) { super(init, version); } } export namespace ScrcpyOptionsLatest { - export type Init = ScrcpyOptions3_1.Init; + export type Init = + ScrcpyOptions3_1.Init; } export { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f1c9a3df..d73bc1d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -163,9 +163,15 @@ importers: specifier: workspace:^ version: link:../struct devDependencies: + '@types/node': + specifier: ^22.10.10 + version: 22.10.10 '@yume-chan/eslint-config': specifier: workspace:^ version: link:../../toolchain/eslint-config + '@yume-chan/test-runner': + specifier: workspace:^ + version: link:../../toolchain/test-runner '@yume-chan/tsconfig': specifier: workspace:^ version: link:../../toolchain/tsconfig