@9b9387/android-stream-scrcpy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +351 -0
  3. package/assets/scrcpy-server-v4.0.jar +0 -0
  4. package/dist/backend/adb/scrcpy-adb-client.d.ts +13 -0
  5. package/dist/backend/adb/scrcpy-adb-client.js +49 -0
  6. package/dist/backend/index.d.ts +6 -0
  7. package/dist/backend/index.js +6 -0
  8. package/dist/backend/io/buffered-stream-reader.d.ts +10 -0
  9. package/dist/backend/io/buffered-stream-reader.js +40 -0
  10. package/dist/backend/scrcpy-backend.d.ts +42 -0
  11. package/dist/backend/scrcpy-backend.js +250 -0
  12. package/dist/backend/server/options.d.ts +26 -0
  13. package/dist/backend/server/options.js +31 -0
  14. package/dist/backend/server/server-command.d.ts +3 -0
  15. package/dist/backend/server/server-command.js +24 -0
  16. package/dist/backend/server/socket-name.d.ts +2 -0
  17. package/dist/backend/server/socket-name.js +4 -0
  18. package/dist/index.d.ts +4 -0
  19. package/dist/index.js +4 -0
  20. package/dist/protocol/core/binary.d.ts +5 -0
  21. package/dist/protocol/core/binary.js +23 -0
  22. package/dist/protocol/core/types.d.ts +15 -0
  23. package/dist/protocol/core/types.js +1 -0
  24. package/dist/protocol/index.d.ts +3 -0
  25. package/dist/protocol/index.js +3 -0
  26. package/dist/protocol/registry.d.ts +3 -0
  27. package/dist/protocol/registry.js +16 -0
  28. package/dist/protocol/v4_0/codecs.d.ts +14 -0
  29. package/dist/protocol/v4_0/codecs.js +22 -0
  30. package/dist/protocol/v4_0/control-message-types.d.ts +115 -0
  31. package/dist/protocol/v4_0/control-message-types.js +47 -0
  32. package/dist/protocol/v4_0/control-message.d.ts +2 -0
  33. package/dist/protocol/v4_0/control-message.js +149 -0
  34. package/dist/protocol/v4_0/device-message.d.ts +17 -0
  35. package/dist/protocol/v4_0/device-message.js +33 -0
  36. package/dist/protocol/v4_0/frame-header.d.ts +19 -0
  37. package/dist/protocol/v4_0/frame-header.js +29 -0
  38. package/dist/protocol/v4_0/index.d.ts +9 -0
  39. package/dist/protocol/v4_0/index.js +21 -0
  40. package/dist/protocol/v4_0/server-options.d.ts +19 -0
  41. package/dist/protocol/v4_0/server-options.js +38 -0
  42. package/dist/protocol/v4_0/string-payload.d.ts +2 -0
  43. package/dist/protocol/v4_0/string-payload.js +23 -0
  44. package/dist/service/index.d.ts +3 -0
  45. package/dist/service/index.js +3 -0
  46. package/dist/service/packet-queue-subscriber.d.ts +11 -0
  47. package/dist/service/packet-queue-subscriber.js +56 -0
  48. package/dist/service/scrcpy-stream-service.d.ts +26 -0
  49. package/dist/service/scrcpy-stream-service.js +178 -0
  50. package/dist/service/types.d.ts +25 -0
  51. package/dist/service/types.js +14 -0
  52. package/dist/snapshot/ffmpeg-snapshot-cache.d.ts +34 -0
  53. package/dist/snapshot/ffmpeg-snapshot-cache.js +195 -0
  54. package/dist/snapshot/index.d.ts +2 -0
  55. package/dist/snapshot/index.js +2 -0
  56. package/dist/snapshot/types.d.ts +15 -0
  57. package/dist/snapshot/types.js +1 -0
  58. package/dist/websocket/binary-packet.d.ts +2 -0
  59. package/dist/websocket/binary-packet.js +33 -0
  60. package/dist/websocket/control-json.d.ts +2 -0
  61. package/dist/websocket/control-json.js +129 -0
  62. package/dist/websocket/index.d.ts +4 -0
  63. package/dist/websocket/index.js +4 -0
  64. package/dist/websocket/scrcpy-websocket-bridge.d.ts +10 -0
  65. package/dist/websocket/scrcpy-websocket-bridge.js +75 -0
  66. package/dist/websocket/types.d.ts +9 -0
  67. package/dist/websocket/types.js +4 -0
  68. package/package.json +90 -0
@@ -0,0 +1,250 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { AUDIO_CODEC_IDS, VIDEO_CODEC_IDS, } from "../protocol/index.js";
3
+ import { dataView, utf8Decode, zeroTerminatedUtf8Decode, } from "../protocol/core/binary.js";
4
+ import { ScrcpyAdbClient } from "./adb/scrcpy-adb-client.js";
5
+ import { BufferedStreamReader } from "./io/buffered-stream-reader.js";
6
+ import { normalizeBackendOptions, } from "./server/options.js";
7
+ import { buildServerCommand, getDeviceServerPath, } from "./server/server-command.js";
8
+ export class ScrcpyBackend extends EventEmitter {
9
+ options;
10
+ adbClient;
11
+ device = null;
12
+ serverStream = null;
13
+ videoSocket = null;
14
+ audioSocket = null;
15
+ controlSocket = null;
16
+ videoReader = null;
17
+ audioReader = null;
18
+ controlReader = null;
19
+ running = false;
20
+ meta = null;
21
+ constructor(options = {}, adbClient = new ScrcpyAdbClient()) {
22
+ super();
23
+ this.options = normalizeBackendOptions(options);
24
+ this.adbClient = adbClient;
25
+ }
26
+ async start() {
27
+ if (this.running)
28
+ throw new Error("backend already running");
29
+ const selected = await this.adbClient.selectDevice(this.options.deviceSerial);
30
+ this.device = selected.device;
31
+ await this.spawnServer();
32
+ await this.connectSockets();
33
+ this.meta = await this.handshake();
34
+ this.running = true;
35
+ this.startWorkerLoops();
36
+ return this.meta;
37
+ }
38
+ stop() {
39
+ this.running = false;
40
+ this.cleanup();
41
+ }
42
+ sendControlMessage(msg) {
43
+ if (!this.running || !this.controlSocket) {
44
+ throw new Error("control channel is not available");
45
+ }
46
+ this.controlSocket.write(this.options.protocol.serializeControlMessage(msg));
47
+ }
48
+ async spawnServer() {
49
+ const devicePath = getDeviceServerPath(this.options.protocol.serverVersion);
50
+ await this.adbClient.pushServerJar(this.device, this.options.serverJarPath, devicePath, this.options.pushTimeoutMs);
51
+ const command = buildServerCommand(devicePath, this.options);
52
+ this.serverStream = await this.adbClient.startServer(this.device, command);
53
+ this.serverStream.on("data", (chunk) => {
54
+ this.emit("serverLog", utf8Decode(chunk));
55
+ });
56
+ await this.waitForServerStartProbe();
57
+ }
58
+ async waitForServerStartProbe() {
59
+ await new Promise((resolve, reject) => {
60
+ let timeout;
61
+ const onData = () => {
62
+ clearTimeout(timeout);
63
+ cleanup();
64
+ resolve();
65
+ };
66
+ const onError = (e) => {
67
+ clearTimeout(timeout);
68
+ cleanup();
69
+ reject(e);
70
+ };
71
+ const onEnd = () => {
72
+ clearTimeout(timeout);
73
+ cleanup();
74
+ reject(new Error("server exited prematurely"));
75
+ };
76
+ const cleanup = () => {
77
+ this.serverStream.removeListener("data", onData);
78
+ this.serverStream.removeListener("error", onError);
79
+ this.serverStream.removeListener("end", onEnd);
80
+ };
81
+ timeout = setTimeout(() => {
82
+ cleanup();
83
+ resolve();
84
+ }, Math.min(500, this.options.deployTimeoutMs));
85
+ this.serverStream.on("data", onData);
86
+ this.serverStream.once("error", onError);
87
+ this.serverStream.once("end", onEnd);
88
+ });
89
+ }
90
+ async connectSockets() {
91
+ const order = [];
92
+ if (this.options.video)
93
+ order.push("video");
94
+ if (this.options.audio)
95
+ order.push("audio");
96
+ if (this.options.control)
97
+ order.push("control");
98
+ const deadline = Date.now() + this.options.connectionTimeoutMs;
99
+ for (const kind of order) {
100
+ const socket = await this.connectSocket(kind, deadline);
101
+ this.setSocket(kind, socket);
102
+ }
103
+ }
104
+ async connectSocket(kind, deadline) {
105
+ let attempts = 0;
106
+ while (Date.now() < deadline) {
107
+ attempts++;
108
+ try {
109
+ return await this.adbClient.openLocal(this.device, this.options.socketName);
110
+ }
111
+ catch {
112
+ await new Promise((resolve) => setTimeout(resolve, 500));
113
+ }
114
+ }
115
+ throw new Error(`failed to connect scrcpy ${kind} socket after ${attempts} attempts`);
116
+ }
117
+ setSocket(kind, socket) {
118
+ if (kind === "video")
119
+ this.videoSocket = socket;
120
+ if (kind === "audio")
121
+ this.audioSocket = socket;
122
+ if (kind === "control")
123
+ this.controlSocket = socket;
124
+ }
125
+ async handshake() {
126
+ const firstSocket = this.videoSocket || this.audioSocket || this.controlSocket;
127
+ if (!firstSocket)
128
+ throw new Error("no sockets connected");
129
+ // scrcpy writes the dummy byte and device name to the first enabled socket,
130
+ // then writes per-stream metadata on each stream socket in connection order.
131
+ const sharedReader = new BufferedStreamReader(firstSocket);
132
+ const dummy = await sharedReader.readExact(1);
133
+ if (dummy[0] !== 0)
134
+ throw new Error("invalid dummy byte");
135
+ const deviceNameRaw = await sharedReader.readExact(64);
136
+ const deviceName = zeroTerminatedUtf8Decode(deviceNameRaw);
137
+ let videoCodec;
138
+ let audioCodec;
139
+ let width = 0;
140
+ let height = 0;
141
+ if (this.videoSocket) {
142
+ this.videoReader =
143
+ this.videoSocket === firstSocket
144
+ ? sharedReader
145
+ : new BufferedStreamReader(this.videoSocket);
146
+ const codecIdBuf = await this.videoReader.readExact(4);
147
+ videoCodec = VIDEO_CODEC_IDS[dataView(codecIdBuf).getUint32(0)];
148
+ const sessionBuf = await this.videoReader.readExact(12);
149
+ const session = this.options.protocol.parseFrameHeader(sessionBuf);
150
+ width = session.width;
151
+ height = session.height;
152
+ }
153
+ if (this.audioSocket) {
154
+ this.audioReader =
155
+ this.audioSocket === firstSocket
156
+ ? sharedReader
157
+ : new BufferedStreamReader(this.audioSocket);
158
+ const codecIdBuf = await this.audioReader.readExact(4);
159
+ const codecId = dataView(codecIdBuf).getUint32(0);
160
+ if (codecId === 0) {
161
+ this.audioSocket.destroy();
162
+ this.audioSocket = null;
163
+ this.audioReader = null;
164
+ }
165
+ else {
166
+ audioCodec = AUDIO_CODEC_IDS[codecId];
167
+ }
168
+ }
169
+ if (this.controlSocket) {
170
+ this.controlReader =
171
+ this.controlSocket === firstSocket
172
+ ? sharedReader
173
+ : new BufferedStreamReader(this.controlSocket);
174
+ }
175
+ return { deviceName, videoCodec, audioCodec, width, height };
176
+ }
177
+ async socketReadExact(socket, n) {
178
+ if (this.videoSocket === socket && this.videoReader)
179
+ return this.videoReader.readExact(n);
180
+ if (this.audioSocket === socket && this.audioReader)
181
+ return this.audioReader.readExact(n);
182
+ if (this.controlSocket === socket && this.controlReader) {
183
+ return this.controlReader.readExact(n);
184
+ }
185
+ throw new Error("no reader for socket");
186
+ }
187
+ startWorkerLoops() {
188
+ if (this.videoSocket) {
189
+ this.workerLoop("video", this.videoSocket, this.handleVideoHeader.bind(this));
190
+ }
191
+ if (this.audioSocket) {
192
+ this.workerLoop("audio", this.audioSocket, this.handleAudioHeader.bind(this));
193
+ }
194
+ if (this.controlSocket)
195
+ this.controlWorkerLoop();
196
+ }
197
+ async workerLoop(kind, socket, headerHandler) {
198
+ try {
199
+ while (this.running) {
200
+ const header = await this.socketReadExact(socket, 12);
201
+ await headerHandler(header);
202
+ }
203
+ }
204
+ catch (e) {
205
+ if (this.running)
206
+ this.emit("error", e);
207
+ }
208
+ }
209
+ async handleVideoHeader(header) {
210
+ const parsed = this.options.protocol.parseFrameHeader(header);
211
+ if (parsed.kind === "session") {
212
+ // Session packets carry resolution changes and do not have a payload.
213
+ this.emit("session", parsed);
214
+ return;
215
+ }
216
+ const payload = await this.socketReadExact(this.videoSocket, parsed.size);
217
+ this.emit("video", parsed, payload);
218
+ }
219
+ async handleAudioHeader(header) {
220
+ const parsed = this.options.protocol.parseFrameHeader(header);
221
+ if (parsed.kind === "session")
222
+ return;
223
+ const payload = await this.socketReadExact(this.audioSocket, parsed.size);
224
+ this.emit("audio", parsed, payload);
225
+ }
226
+ async controlWorkerLoop() {
227
+ const readExact = (n) => this.socketReadExact(this.controlSocket, n);
228
+ try {
229
+ while (this.running) {
230
+ const msg = await this.options.protocol.parseDeviceMessage(readExact);
231
+ this.emit("deviceMessage", msg);
232
+ }
233
+ }
234
+ catch (e) {
235
+ if (this.running)
236
+ this.emit("error", e);
237
+ }
238
+ }
239
+ cleanup() {
240
+ [this.videoSocket, this.audioSocket, this.controlSocket].forEach((socket) => socket?.destroy());
241
+ this.videoSocket = null;
242
+ this.audioSocket = null;
243
+ this.controlSocket = null;
244
+ this.videoReader = null;
245
+ this.audioReader = null;
246
+ this.controlReader = null;
247
+ this.serverStream?.destroy();
248
+ this.serverStream = null;
249
+ }
250
+ }
@@ -0,0 +1,26 @@
1
+ import { AudioCodec, VideoCodec } from "../../protocol/index.js";
2
+ import { ScrcpyProtocol, ScrcpyProtocolVersion } from "../../protocol/core/types.js";
3
+ export interface ScrcpyBackendOptions {
4
+ deviceSerial?: string;
5
+ protocolVersion?: ScrcpyProtocolVersion;
6
+ scid?: number;
7
+ maxSize?: number;
8
+ maxFps?: number;
9
+ videoBitRate?: number;
10
+ audioBitRate?: number;
11
+ video?: boolean;
12
+ audio?: boolean;
13
+ control?: boolean;
14
+ videoCodec?: VideoCodec;
15
+ audioCodec?: AudioCodec;
16
+ connectionTimeoutMs?: number;
17
+ deployTimeoutMs?: number;
18
+ pushTimeoutMs?: number;
19
+ serverJarPath?: string;
20
+ }
21
+ export interface NormalizedScrcpyBackendOptions extends Required<Omit<ScrcpyBackendOptions, "serverJarPath">> {
22
+ protocol: ScrcpyProtocol;
23
+ socketName: string;
24
+ serverJarPath: string;
25
+ }
26
+ export declare function normalizeBackendOptions(options?: ScrcpyBackendOptions): NormalizedScrcpyBackendOptions;
@@ -0,0 +1,31 @@
1
+ import * as path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { AudioCodec, VideoCodec } from "../../protocol/index.js";
4
+ import { getScrcpyProtocol } from "../../protocol/registry.js";
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ export function normalizeBackendOptions(options = {}) {
7
+ const protocolVersion = options.protocolVersion ?? "4.0";
8
+ const protocol = getScrcpyProtocol(protocolVersion);
9
+ const scid = options.scid ?? 0x0000000a;
10
+ return {
11
+ deviceSerial: options.deviceSerial ?? "",
12
+ protocolVersion,
13
+ protocol,
14
+ scid,
15
+ socketName: protocol.getSocketName(scid),
16
+ maxSize: options.maxSize ?? 0,
17
+ maxFps: options.maxFps ?? 30,
18
+ videoBitRate: options.videoBitRate ?? 8000000,
19
+ audioBitRate: options.audioBitRate ?? 128000,
20
+ video: options.video !== false,
21
+ audio: options.audio !== false,
22
+ control: options.control !== false,
23
+ videoCodec: options.videoCodec ?? VideoCodec.H264,
24
+ audioCodec: options.audioCodec ?? AudioCodec.OPUS,
25
+ connectionTimeoutMs: options.connectionTimeoutMs ?? 8000,
26
+ deployTimeoutMs: options.deployTimeoutMs ?? 5000,
27
+ pushTimeoutMs: options.pushTimeoutMs ?? 60000,
28
+ serverJarPath: options.serverJarPath ??
29
+ path.join(__dirname, "..", "..", "..", "assets", protocol.serverJarAssetName),
30
+ };
31
+ }
@@ -0,0 +1,3 @@
1
+ import { NormalizedScrcpyBackendOptions } from "./options.js";
2
+ export declare function getDeviceServerPath(serverVersion: string): string;
3
+ export declare function buildServerCommand(deviceServerPath: string, options: NormalizedScrcpyBackendOptions): string;
@@ -0,0 +1,24 @@
1
+ export function getDeviceServerPath(serverVersion) {
2
+ return `/data/local/tmp/scrcpy-server-v${serverVersion}.jar`;
3
+ }
4
+ export function buildServerCommand(deviceServerPath, options) {
5
+ const args = [
6
+ `CLASSPATH=${deviceServerPath}`,
7
+ "app_process",
8
+ "/",
9
+ "com.genymobile.scrcpy.Server",
10
+ ...options.protocol.buildServerOptions({
11
+ scid: options.scid,
12
+ video: options.video,
13
+ audio: options.audio,
14
+ control: options.control,
15
+ videoCodec: options.videoCodec,
16
+ audioCodec: options.audioCodec,
17
+ maxSize: options.maxSize,
18
+ maxFps: options.maxFps,
19
+ videoBitRate: options.videoBitRate,
20
+ audioBitRate: options.audioBitRate,
21
+ }),
22
+ ];
23
+ return args.join(" ");
24
+ }
@@ -0,0 +1,2 @@
1
+ import { ScrcpyProtocolVersion } from "../../protocol/core/types.js";
2
+ export declare function getScrcpySocketName(scid?: number, protocolVersion?: ScrcpyProtocolVersion): string;
@@ -0,0 +1,4 @@
1
+ import { getScrcpyProtocol } from "../../protocol/registry.js";
2
+ export function getScrcpySocketName(scid, protocolVersion = "4.0") {
3
+ return getScrcpyProtocol(protocolVersion).getSocketName(scid);
4
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./protocol/index.js";
2
+ export * from "./backend/index.js";
3
+ export * from "./service/index.js";
4
+ export * from "./snapshot/index.js";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./protocol/index.js";
2
+ export * from "./backend/index.js";
3
+ export * from "./service/index.js";
4
+ export * from "./snapshot/index.js";
@@ -0,0 +1,5 @@
1
+ export declare function dataView(bytes: Uint8Array): DataView;
2
+ export declare function concatBytes(chunks: readonly Uint8Array[]): Uint8Array;
3
+ export declare function utf8Encode(text: string): Uint8Array;
4
+ export declare function utf8Decode(bytes: Uint8Array): string;
5
+ export declare function zeroTerminatedUtf8Decode(bytes: Uint8Array): string;
@@ -0,0 +1,23 @@
1
+ export function dataView(bytes) {
2
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
3
+ }
4
+ export function concatBytes(chunks) {
5
+ const length = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
6
+ const result = new Uint8Array(length);
7
+ let offset = 0;
8
+ for (const chunk of chunks) {
9
+ result.set(chunk, offset);
10
+ offset += chunk.byteLength;
11
+ }
12
+ return result;
13
+ }
14
+ export function utf8Encode(text) {
15
+ return new TextEncoder().encode(text);
16
+ }
17
+ export function utf8Decode(bytes) {
18
+ return new TextDecoder().decode(bytes);
19
+ }
20
+ export function zeroTerminatedUtf8Decode(bytes) {
21
+ const end = bytes.indexOf(0);
22
+ return utf8Decode(end === -1 ? bytes : bytes.subarray(0, end));
23
+ }
@@ -0,0 +1,15 @@
1
+ import { ControlMessage } from "../v4_0/control-message-types.js";
2
+ import { DeviceMessage } from "../v4_0/device-message.js";
3
+ import { StreamPacketMeta } from "../v4_0/frame-header.js";
4
+ import { ScrcpyServerOptionInput } from "../v4_0/server-options.js";
5
+ export type ScrcpyProtocolVersion = "4.0";
6
+ export interface ScrcpyProtocol {
7
+ version: ScrcpyProtocolVersion;
8
+ serverVersion: string;
9
+ serverJarAssetName: string;
10
+ serializeControlMessage(message: ControlMessage): Uint8Array;
11
+ parseFrameHeader(header: Uint8Array): StreamPacketMeta;
12
+ parseDeviceMessage(readExact: (n: number) => Promise<Uint8Array>): Promise<DeviceMessage>;
13
+ buildServerOptions(input: ScrcpyServerOptionInput): string[];
14
+ getSocketName(scid?: number): string;
15
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export * from "./core/types.js";
2
+ export * from "./registry.js";
3
+ export * from "./v4_0/index.js";
@@ -0,0 +1,3 @@
1
+ export * from "./core/types.js";
2
+ export * from "./registry.js";
3
+ export * from "./v4_0/index.js";
@@ -0,0 +1,3 @@
1
+ import { ScrcpyProtocol, ScrcpyProtocolVersion } from "./core/types.js";
2
+ export declare function getScrcpyProtocol(version?: ScrcpyProtocolVersion): ScrcpyProtocol;
3
+ export declare function listScrcpyProtocolVersions(): ScrcpyProtocolVersion[];
@@ -0,0 +1,16 @@
1
+ import { SCRCPY_4_0_PROTOCOL } from "./v4_0/index.js";
2
+ const PROTOCOLS = {
3
+ "4.0": SCRCPY_4_0_PROTOCOL,
4
+ };
5
+ export function getScrcpyProtocol(version = "4.0") {
6
+ // Keep version selection centralized so future protocol versions can be
7
+ // registered without changing backend or service orchestration code.
8
+ const protocol = PROTOCOLS[version];
9
+ if (!protocol) {
10
+ throw new Error(`unsupported scrcpy protocol version: ${version}`);
11
+ }
12
+ return protocol;
13
+ }
14
+ export function listScrcpyProtocolVersions() {
15
+ return Object.keys(PROTOCOLS);
16
+ }
@@ -0,0 +1,14 @@
1
+ export declare enum VideoCodec {
2
+ H264 = "h264",
3
+ H265 = "h265",
4
+ AV1 = "av1"
5
+ }
6
+ export declare enum AudioCodec {
7
+ OPUS = "opus",
8
+ AAC = "aac",
9
+ FLAC = "flac",
10
+ RAW = "raw"
11
+ }
12
+ export declare function getCodecId(name: string): number;
13
+ export declare const VIDEO_CODEC_IDS: Record<number, VideoCodec>;
14
+ export declare const AUDIO_CODEC_IDS: Record<number, AudioCodec>;
@@ -0,0 +1,22 @@
1
+ import { dataView, utf8Encode } from "../core/binary.js";
2
+ export var VideoCodec;
3
+ (function (VideoCodec) {
4
+ VideoCodec["H264"] = "h264";
5
+ VideoCodec["H265"] = "h265";
6
+ VideoCodec["AV1"] = "av1";
7
+ })(VideoCodec || (VideoCodec = {}));
8
+ export var AudioCodec;
9
+ (function (AudioCodec) {
10
+ AudioCodec["OPUS"] = "opus";
11
+ AudioCodec["AAC"] = "aac";
12
+ AudioCodec["FLAC"] = "flac";
13
+ AudioCodec["RAW"] = "raw";
14
+ })(AudioCodec || (AudioCodec = {}));
15
+ export function getCodecId(name) {
16
+ const bytes = new Uint8Array(4);
17
+ const nameBytes = utf8Encode(name);
18
+ bytes.set(nameBytes, 4 - nameBytes.byteLength);
19
+ return dataView(bytes).getUint32(0);
20
+ }
21
+ export const VIDEO_CODEC_IDS = Object.fromEntries(Object.values(VideoCodec).map((codec) => [getCodecId(codec), codec]));
22
+ export const AUDIO_CODEC_IDS = Object.fromEntries(Object.values(AudioCodec).map((codec) => [getCodecId(codec), codec]));
@@ -0,0 +1,115 @@
1
+ export declare enum ControlMessageType {
2
+ INJECT_KEYCODE = 0,
3
+ INJECT_TEXT = 1,
4
+ INJECT_TOUCH_EVENT = 2,
5
+ INJECT_SCROLL_EVENT = 3,
6
+ BACK_OR_SCREEN_ON = 4,
7
+ EXPAND_NOTIFICATION_PANEL = 5,
8
+ EXPAND_SETTINGS_PANEL = 6,
9
+ COLLAPSE_PANELS = 7,
10
+ GET_CLIPBOARD = 8,
11
+ SET_CLIPBOARD = 9,
12
+ SET_DISPLAY_POWER = 10,
13
+ ROTATE_DEVICE = 11,
14
+ UHID_CREATE = 12,
15
+ UHID_INPUT = 13,
16
+ UHID_DESTROY = 14,
17
+ OPEN_HARD_KEYBOARD_SETTINGS = 15,
18
+ START_APP = 16,
19
+ RESET_VIDEO = 17,
20
+ CAMERA_SET_TORCH = 18,
21
+ CAMERA_ZOOM_IN = 19,
22
+ CAMERA_ZOOM_OUT = 20,
23
+ RESIZE_DISPLAY = 21
24
+ }
25
+ export declare enum CopyKey {
26
+ NONE = 0,
27
+ COPY = 1,
28
+ CUT = 2
29
+ }
30
+ export declare const ACTION_DOWN = 0;
31
+ export declare const ACTION_UP = 1;
32
+ export declare const ACTION_MOVE = 2;
33
+ export declare const KEY_ACTION_DOWN = 0;
34
+ export declare const KEY_ACTION_UP = 1;
35
+ export declare const BUTTON_PRIMARY: number;
36
+ export declare const BUTTON_SECONDARY: number;
37
+ export declare const BUTTON_TERTIARY: number;
38
+ export declare const POINTER_ID_MOUSE = -1n;
39
+ export declare const POINTER_ID_GENERIC_FINGER = -2n;
40
+ export declare const POINTER_ID_VIRTUAL_FINGER = -3n;
41
+ export declare const INJECT_TEXT_MAX_LENGTH = 300;
42
+ export declare const CONTROL_MESSAGE_MAX_SIZE: number;
43
+ export declare const SET_CLIPBOARD_TEXT_MAX_LENGTH: number;
44
+ export declare const UHID_NAME_MAX_LENGTH = 127;
45
+ export declare const START_APP_NAME_MAX_LENGTH = 255;
46
+ export type ControlMessage = {
47
+ type: ControlMessageType.INJECT_KEYCODE;
48
+ action: number;
49
+ keycode: number;
50
+ repeat?: number;
51
+ metaState?: number;
52
+ } | {
53
+ type: ControlMessageType.INJECT_TEXT;
54
+ text: string;
55
+ } | {
56
+ type: ControlMessageType.INJECT_TOUCH_EVENT;
57
+ action: number;
58
+ pointerId: bigint;
59
+ x: number;
60
+ y: number;
61
+ screenWidth: number;
62
+ screenHeight: number;
63
+ pressure?: number;
64
+ actionButton?: number;
65
+ buttons?: number;
66
+ } | {
67
+ type: ControlMessageType.INJECT_SCROLL_EVENT;
68
+ x: number;
69
+ y: number;
70
+ screenWidth: number;
71
+ screenHeight: number;
72
+ hScroll?: number;
73
+ vScroll?: number;
74
+ buttons?: number;
75
+ } | {
76
+ type: ControlMessageType.BACK_OR_SCREEN_ON;
77
+ action: number;
78
+ } | {
79
+ type: ControlMessageType.GET_CLIPBOARD;
80
+ copyKey: CopyKey | number;
81
+ } | {
82
+ type: ControlMessageType.SET_CLIPBOARD;
83
+ sequence: bigint;
84
+ text: string;
85
+ paste: boolean;
86
+ } | {
87
+ type: ControlMessageType.SET_DISPLAY_POWER;
88
+ on: boolean;
89
+ } | {
90
+ type: ControlMessageType.UHID_CREATE;
91
+ id: number;
92
+ vendorId: number;
93
+ productId: number;
94
+ name: string;
95
+ reportDescriptor: Uint8Array;
96
+ } | {
97
+ type: ControlMessageType.UHID_INPUT;
98
+ id: number;
99
+ data: Uint8Array;
100
+ } | {
101
+ type: ControlMessageType.UHID_DESTROY;
102
+ id: number;
103
+ } | {
104
+ type: ControlMessageType.START_APP;
105
+ name: string;
106
+ } | {
107
+ type: ControlMessageType.CAMERA_SET_TORCH;
108
+ on: boolean;
109
+ } | {
110
+ type: ControlMessageType.RESIZE_DISPLAY;
111
+ width: number;
112
+ height: number;
113
+ } | {
114
+ type: ControlMessageType.EXPAND_NOTIFICATION_PANEL | ControlMessageType.EXPAND_SETTINGS_PANEL | ControlMessageType.COLLAPSE_PANELS | ControlMessageType.ROTATE_DEVICE | ControlMessageType.OPEN_HARD_KEYBOARD_SETTINGS | ControlMessageType.RESET_VIDEO | ControlMessageType.CAMERA_ZOOM_IN | ControlMessageType.CAMERA_ZOOM_OUT;
115
+ };
@@ -0,0 +1,47 @@
1
+ export var ControlMessageType;
2
+ (function (ControlMessageType) {
3
+ ControlMessageType[ControlMessageType["INJECT_KEYCODE"] = 0] = "INJECT_KEYCODE";
4
+ ControlMessageType[ControlMessageType["INJECT_TEXT"] = 1] = "INJECT_TEXT";
5
+ ControlMessageType[ControlMessageType["INJECT_TOUCH_EVENT"] = 2] = "INJECT_TOUCH_EVENT";
6
+ ControlMessageType[ControlMessageType["INJECT_SCROLL_EVENT"] = 3] = "INJECT_SCROLL_EVENT";
7
+ ControlMessageType[ControlMessageType["BACK_OR_SCREEN_ON"] = 4] = "BACK_OR_SCREEN_ON";
8
+ ControlMessageType[ControlMessageType["EXPAND_NOTIFICATION_PANEL"] = 5] = "EXPAND_NOTIFICATION_PANEL";
9
+ ControlMessageType[ControlMessageType["EXPAND_SETTINGS_PANEL"] = 6] = "EXPAND_SETTINGS_PANEL";
10
+ ControlMessageType[ControlMessageType["COLLAPSE_PANELS"] = 7] = "COLLAPSE_PANELS";
11
+ ControlMessageType[ControlMessageType["GET_CLIPBOARD"] = 8] = "GET_CLIPBOARD";
12
+ ControlMessageType[ControlMessageType["SET_CLIPBOARD"] = 9] = "SET_CLIPBOARD";
13
+ ControlMessageType[ControlMessageType["SET_DISPLAY_POWER"] = 10] = "SET_DISPLAY_POWER";
14
+ ControlMessageType[ControlMessageType["ROTATE_DEVICE"] = 11] = "ROTATE_DEVICE";
15
+ ControlMessageType[ControlMessageType["UHID_CREATE"] = 12] = "UHID_CREATE";
16
+ ControlMessageType[ControlMessageType["UHID_INPUT"] = 13] = "UHID_INPUT";
17
+ ControlMessageType[ControlMessageType["UHID_DESTROY"] = 14] = "UHID_DESTROY";
18
+ ControlMessageType[ControlMessageType["OPEN_HARD_KEYBOARD_SETTINGS"] = 15] = "OPEN_HARD_KEYBOARD_SETTINGS";
19
+ ControlMessageType[ControlMessageType["START_APP"] = 16] = "START_APP";
20
+ ControlMessageType[ControlMessageType["RESET_VIDEO"] = 17] = "RESET_VIDEO";
21
+ ControlMessageType[ControlMessageType["CAMERA_SET_TORCH"] = 18] = "CAMERA_SET_TORCH";
22
+ ControlMessageType[ControlMessageType["CAMERA_ZOOM_IN"] = 19] = "CAMERA_ZOOM_IN";
23
+ ControlMessageType[ControlMessageType["CAMERA_ZOOM_OUT"] = 20] = "CAMERA_ZOOM_OUT";
24
+ ControlMessageType[ControlMessageType["RESIZE_DISPLAY"] = 21] = "RESIZE_DISPLAY";
25
+ })(ControlMessageType || (ControlMessageType = {}));
26
+ export var CopyKey;
27
+ (function (CopyKey) {
28
+ CopyKey[CopyKey["NONE"] = 0] = "NONE";
29
+ CopyKey[CopyKey["COPY"] = 1] = "COPY";
30
+ CopyKey[CopyKey["CUT"] = 2] = "CUT";
31
+ })(CopyKey || (CopyKey = {}));
32
+ export const ACTION_DOWN = 0;
33
+ export const ACTION_UP = 1;
34
+ export const ACTION_MOVE = 2;
35
+ export const KEY_ACTION_DOWN = 0;
36
+ export const KEY_ACTION_UP = 1;
37
+ export const BUTTON_PRIMARY = 1 << 0;
38
+ export const BUTTON_SECONDARY = 1 << 1;
39
+ export const BUTTON_TERTIARY = 1 << 2;
40
+ export const POINTER_ID_MOUSE = -1n;
41
+ export const POINTER_ID_GENERIC_FINGER = -2n;
42
+ export const POINTER_ID_VIRTUAL_FINGER = -3n;
43
+ export const INJECT_TEXT_MAX_LENGTH = 300;
44
+ export const CONTROL_MESSAGE_MAX_SIZE = 1 << 18;
45
+ export const SET_CLIPBOARD_TEXT_MAX_LENGTH = CONTROL_MESSAGE_MAX_SIZE - 14;
46
+ export const UHID_NAME_MAX_LENGTH = 127;
47
+ export const START_APP_NAME_MAX_LENGTH = 255;
@@ -0,0 +1,2 @@
1
+ import { ControlMessage } from "./control-message-types.js";
2
+ export declare function serializeControlMessage(msg: ControlMessage): Uint8Array;