@yume-chan/scrcpy 0.0.16 → 0.0.17
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.
- package/CHANGELOG.json +15 -0
- package/CHANGELOG.md +9 -1
- package/README.md +275 -38
- package/esm/adb/client.d.ts +40 -0
- package/esm/adb/client.d.ts.map +1 -0
- package/esm/adb/client.js +218 -0
- package/esm/adb/client.js.map +1 -0
- package/esm/adb/connection.d.ts +38 -0
- package/esm/adb/connection.d.ts.map +1 -0
- package/esm/{connection.js → adb/connection.js} +20 -24
- package/esm/adb/connection.js.map +1 -0
- package/esm/adb/index.d.ts +4 -0
- package/esm/adb/index.d.ts.map +1 -0
- package/esm/adb/index.js +4 -0
- package/esm/adb/index.js.map +1 -0
- package/esm/adb/options/1_16.d.ts +8 -0
- package/esm/adb/options/1_16.d.ts.map +1 -0
- package/esm/adb/options/1_16.js +20 -0
- package/esm/adb/options/1_16.js.map +1 -0
- package/esm/adb/options/1_22.d.ts +8 -0
- package/esm/adb/options/1_22.d.ts.map +1 -0
- package/esm/adb/options/1_22.js +17 -0
- package/esm/adb/options/1_22.js.map +1 -0
- package/esm/adb/options/index.d.ts +4 -0
- package/esm/adb/options/index.d.ts.map +1 -0
- package/esm/adb/options/index.js +4 -0
- package/esm/adb/options/index.js.map +1 -0
- package/esm/adb/options/types.d.ts +23 -0
- package/esm/adb/options/types.d.ts.map +1 -0
- package/esm/adb/options/types.js +30 -0
- package/esm/adb/options/types.js.map +1 -0
- package/esm/codec.js +2 -2
- package/esm/codec.js.map +1 -1
- package/esm/control/index.d.ts +8 -0
- package/esm/control/index.d.ts.map +1 -0
- package/esm/control/index.js +8 -0
- package/esm/control/index.js.map +1 -0
- package/esm/control/inject-keycode.d.ts +53 -0
- package/esm/control/inject-keycode.d.ts.map +1 -0
- package/esm/control/inject-keycode.js +54 -0
- package/esm/control/inject-keycode.js.map +1 -0
- package/esm/control/inject-text.d.ts +9 -0
- package/esm/control/inject-text.d.ts.map +1 -0
- package/esm/control/inject-text.js +7 -0
- package/esm/control/inject-text.js.map +1 -0
- package/esm/control/inject-touch.d.ts +30 -0
- package/esm/control/inject-touch.d.ts.map +1 -0
- package/esm/control/inject-touch.js +30 -0
- package/esm/control/inject-touch.js.map +1 -0
- package/esm/control/rotate-device.d.ts +7 -0
- package/esm/control/rotate-device.d.ts.map +1 -0
- package/esm/control/rotate-device.js +5 -0
- package/esm/control/rotate-device.js.map +1 -0
- package/esm/control/serializer.d.ts +23 -0
- package/esm/control/serializer.d.ts.map +1 -0
- package/esm/control/serializer.js +73 -0
- package/esm/control/serializer.js.map +1 -0
- package/esm/control/set-screen-power-mode.d.ts +12 -0
- package/esm/control/set-screen-power-mode.d.ts.map +1 -0
- package/esm/control/set-screen-power-mode.js +12 -0
- package/esm/control/set-screen-power-mode.js.map +1 -0
- package/esm/control/type.d.ts +15 -0
- package/esm/control/type.d.ts.map +1 -0
- package/esm/control/type.js +19 -0
- package/esm/control/type.js.map +1 -0
- package/esm/decoder/index.d.ts +27 -3
- package/esm/decoder/index.d.ts.map +1 -1
- package/esm/decoder/index.js +1 -3
- package/esm/decoder/index.js.map +1 -1
- package/esm/decoder/tinyh264/index.d.ts +5 -5
- package/esm/decoder/tinyh264/index.d.ts.map +1 -1
- package/esm/decoder/tinyh264/index.js +4 -4
- package/esm/decoder/tinyh264/index.js.map +1 -1
- package/esm/decoder/tinyh264/worker.js.map +1 -1
- package/esm/decoder/tinyh264/wrapper.d.ts.map +1 -1
- package/esm/decoder/tinyh264/wrapper.js +14 -8
- package/esm/decoder/tinyh264/wrapper.js.map +1 -1
- package/esm/decoder/types.d.ts +1 -27
- package/esm/decoder/types.d.ts.map +1 -1
- package/esm/decoder/web-codecs/index.d.ts +4 -4
- package/esm/decoder/web-codecs/index.d.ts.map +1 -1
- package/esm/decoder/web-codecs/index.js +1 -1
- package/esm/decoder/web-codecs/index.js.map +1 -1
- package/esm/device-message/clipboard.d.ts +10 -0
- package/esm/device-message/clipboard.d.ts.map +1 -0
- package/esm/device-message/clipboard.js +7 -0
- package/esm/device-message/clipboard.js.map +1 -0
- package/esm/device-message/index.d.ts +4 -0
- package/esm/device-message/index.d.ts.map +1 -0
- package/esm/device-message/index.js +4 -0
- package/esm/device-message/index.js.map +1 -0
- package/esm/device-message/stream.d.ts +7 -0
- package/esm/device-message/stream.d.ts.map +1 -0
- package/esm/device-message/stream.js +16 -0
- package/esm/device-message/stream.js.map +1 -0
- package/esm/device-message/type.d.ts +5 -0
- package/esm/device-message/type.d.ts.map +1 -0
- package/esm/device-message/type.js +7 -0
- package/esm/device-message/type.js.map +1 -0
- package/esm/index.d.ts +3 -6
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +3 -6
- package/esm/index.js.map +1 -1
- package/esm/options/1_16/codec-options.d.ts +19 -0
- package/esm/options/1_16/codec-options.d.ts.map +1 -0
- package/esm/options/1_16/codec-options.js +27 -0
- package/esm/options/1_16/codec-options.js.map +1 -0
- package/esm/options/1_16/index.d.ts +3 -110
- package/esm/options/1_16/index.d.ts.map +1 -1
- package/esm/options/1_16/index.js +3 -226
- package/esm/options/1_16/index.js.map +1 -1
- package/esm/options/1_16/options.d.ts +97 -0
- package/esm/options/1_16/options.d.ts.map +1 -0
- package/esm/options/1_16/options.js +188 -0
- package/esm/options/1_16/options.js.map +1 -0
- package/esm/options/1_18.d.ts +8 -8
- package/esm/options/1_18.d.ts.map +1 -1
- package/esm/options/1_18.js +6 -6
- package/esm/options/1_18.js.map +1 -1
- package/esm/options/1_21.d.ts +1 -1
- package/esm/options/1_21.d.ts.map +1 -1
- package/esm/options/1_21.js +1 -1
- package/esm/options/1_21.js.map +1 -1
- package/esm/options/1_22.d.ts +5 -8
- package/esm/options/1_22.d.ts.map +1 -1
- package/esm/options/1_22.js +4 -17
- package/esm/options/1_22.js.map +1 -1
- package/esm/options/1_23.d.ts +4 -4
- package/esm/options/1_23.d.ts.map +1 -1
- package/esm/options/1_23.js +1 -1
- package/esm/options/1_23.js.map +1 -1
- package/esm/options/1_24.d.ts +1 -1
- package/esm/options/1_24.d.ts.map +1 -1
- package/esm/options/1_24.js.map +1 -1
- package/esm/options/index.d.ts +1 -1
- package/esm/options/index.d.ts.map +1 -1
- package/esm/options/index.js +1 -1
- package/esm/options/index.js.map +1 -1
- package/esm/options/types.d.ts +45 -0
- package/esm/options/types.d.ts.map +1 -0
- package/esm/options/{common.js → types.js} +1 -1
- package/esm/options/types.js.map +1 -0
- package/package.json +24 -24
- package/src/adb/client.ts +292 -0
- package/src/{connection.ts → adb/connection.ts} +35 -38
- package/src/adb/index.ts +3 -0
- package/src/adb/options/1_16.ts +23 -0
- package/src/adb/options/1_22.ts +20 -0
- package/src/adb/options/index.ts +3 -0
- package/src/adb/options/types.ts +55 -0
- package/src/control/index.ts +7 -0
- package/src/control/inject-keycode.ts +59 -0
- package/src/control/inject-text.ts +12 -0
- package/src/control/inject-touch.ts +34 -0
- package/src/control/rotate-device.ts +10 -0
- package/src/control/serializer.ts +86 -0
- package/src/control/set-screen-power-mode.ts +17 -0
- package/src/control/type.ts +17 -0
- package/src/device-message/clipboard.ts +11 -0
- package/src/device-message/index.ts +3 -0
- package/src/device-message/stream.ts +19 -0
- package/src/device-message/type.ts +5 -0
- package/src/index.ts +3 -6
- package/src/options/1_16/codec-options.ts +48 -0
- package/src/options/1_16/index.ts +3 -345
- package/src/options/1_16/options.ts +294 -0
- package/src/options/1_18.ts +12 -10
- package/src/options/1_21.ts +16 -6
- package/src/options/1_22.ts +10 -21
- package/src/options/1_23.ts +7 -5
- package/src/options/1_24.ts +3 -2
- package/src/options/index.ts +1 -1
- package/src/options/{common.ts → types.ts} +32 -13
- package/tsconfig.build.json +3 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.test.tsbuildinfo +1 -0
- package/esm/client.d.ts +0 -48
- package/esm/client.d.ts.map +0 -1
- package/esm/client.js +0 -341
- package/esm/client.js.map +0 -1
- package/esm/connection.d.ts +0 -37
- package/esm/connection.d.ts.map +0 -1
- package/esm/connection.js.map +0 -1
- package/esm/message.d.ts +0 -98
- package/esm/message.d.ts.map +0 -1
- package/esm/message.js +0 -98
- package/esm/message.js.map +0 -1
- package/esm/options/common.d.ts +0 -34
- package/esm/options/common.d.ts.map +0 -1
- package/esm/options/common.js.map +0 -1
- package/esm/push-server.d.ts +0 -6
- package/esm/push-server.d.ts.map +0 -1
- package/esm/push-server.js +0 -16
- package/esm/push-server.js.map +0 -1
- package/esm/utils.d.ts +0 -2
- package/esm/utils.d.ts.map +0 -1
- package/esm/utils.js +0 -6
- package/esm/utils.js.map +0 -1
- package/src/client.ts +0 -435
- package/src/decoder/index.ts +0 -3
- package/src/decoder/tinyh264/index.ts +0 -112
- package/src/decoder/tinyh264/types.d.ts +0 -137
- package/src/decoder/tinyh264/worker.ts +0 -2
- package/src/decoder/tinyh264/wrapper.ts +0 -89
- package/src/decoder/types.ts +0 -35
- package/src/decoder/web-codecs/index.ts +0 -99
- package/src/message.ts +0 -113
- package/src/push-server.ts +0 -24
- package/src/utils.ts +0 -5
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { Adb, AdbSubprocessNoneProtocol, AdbSubprocessProtocol, AdbSync } from '@yume-chan/adb';
|
|
2
|
+
import { AbortController, DecodeUtf8Stream, InspectStream, ReadableStream, SplitStringStream, WrapWritableStream, WritableStream, type ReadableStreamDefaultController, type ReadableStreamDefaultReader, type ReadableWritablePair } from '@yume-chan/stream-extra';
|
|
3
|
+
|
|
4
|
+
import { ScrcpyControlMessageSerializer } from '../control/index.js';
|
|
5
|
+
import { ScrcpyDeviceMessageDeserializeStream, type ScrcpyDeviceMessage } from '../device-message/index.js';
|
|
6
|
+
import { DEFAULT_SERVER_PATH, type ScrcpyVideoStreamPacket } from '../options/index.js';
|
|
7
|
+
import type { AdbScrcpyOptions } from './options/index.js';
|
|
8
|
+
|
|
9
|
+
class ArrayToStream<T> extends ReadableStream<T>{
|
|
10
|
+
private array!: T[];
|
|
11
|
+
private index = 0;
|
|
12
|
+
|
|
13
|
+
constructor(array: T[]) {
|
|
14
|
+
super({
|
|
15
|
+
start: async () => {
|
|
16
|
+
await Promise.resolve();
|
|
17
|
+
this.array = array;
|
|
18
|
+
},
|
|
19
|
+
pull: (controller) => {
|
|
20
|
+
if (this.index < this.array.length) {
|
|
21
|
+
controller.enqueue(this.array[this.index]!);
|
|
22
|
+
this.index += 1;
|
|
23
|
+
} else {
|
|
24
|
+
controller.close();
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class ConcatStream<T> extends ReadableStream<T>{
|
|
32
|
+
private streams!: ReadableStream<T>[];
|
|
33
|
+
private index = 0;
|
|
34
|
+
private reader!: ReadableStreamDefaultReader<T>;
|
|
35
|
+
|
|
36
|
+
constructor(...streams: ReadableStream<T>[]) {
|
|
37
|
+
super({
|
|
38
|
+
start: async (controller) => {
|
|
39
|
+
await Promise.resolve();
|
|
40
|
+
|
|
41
|
+
this.streams = streams;
|
|
42
|
+
this.advance(controller);
|
|
43
|
+
},
|
|
44
|
+
pull: async (controller) => {
|
|
45
|
+
const result = await this.reader.read();
|
|
46
|
+
if (!result.done) {
|
|
47
|
+
controller.enqueue(result.value);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
this.advance(controller);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private advance(controller: ReadableStreamDefaultController<T>) {
|
|
56
|
+
if (this.index < this.streams.length) {
|
|
57
|
+
this.reader = this.streams[this.index]!.getReader();
|
|
58
|
+
this.index += 1;
|
|
59
|
+
} else {
|
|
60
|
+
controller.close();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class AdbScrcpyClient {
|
|
66
|
+
public static pushServer(
|
|
67
|
+
adb: Adb,
|
|
68
|
+
path = DEFAULT_SERVER_PATH,
|
|
69
|
+
) {
|
|
70
|
+
let sync!: AdbSync;
|
|
71
|
+
return new WrapWritableStream<Uint8Array>({
|
|
72
|
+
async start() {
|
|
73
|
+
sync = await adb.sync();
|
|
74
|
+
return sync.write(path);
|
|
75
|
+
},
|
|
76
|
+
async close() {
|
|
77
|
+
await sync.dispose();
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public static async start(
|
|
83
|
+
adb: Adb,
|
|
84
|
+
path: string,
|
|
85
|
+
version: string,
|
|
86
|
+
options: AdbScrcpyOptions<any>
|
|
87
|
+
) {
|
|
88
|
+
const connection = options.createConnection(adb);
|
|
89
|
+
let process: AdbSubprocessProtocol | undefined;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
await connection.initialize();
|
|
93
|
+
|
|
94
|
+
process = await adb.subprocess.spawn(
|
|
95
|
+
[
|
|
96
|
+
// cspell: disable-next-line
|
|
97
|
+
`CLASSPATH=${path}`,
|
|
98
|
+
'app_process',
|
|
99
|
+
/* unused */ '/',
|
|
100
|
+
'com.genymobile.scrcpy.Server',
|
|
101
|
+
version,
|
|
102
|
+
...options.formatServerArguments(),
|
|
103
|
+
],
|
|
104
|
+
{
|
|
105
|
+
// Scrcpy server doesn't use stderr,
|
|
106
|
+
// so disable Shell Protocol to simplify processing
|
|
107
|
+
protocols: [AdbSubprocessNoneProtocol],
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const stdout = process.stdout
|
|
112
|
+
.pipeThrough(new DecodeUtf8Stream())
|
|
113
|
+
.pipeThrough(new SplitStringStream('\n'));
|
|
114
|
+
|
|
115
|
+
// Read stdout, otherwise `process.exit` won't resolve.
|
|
116
|
+
const output: string[] = [];
|
|
117
|
+
const abortController = new AbortController();
|
|
118
|
+
const pipe = stdout
|
|
119
|
+
.pipeTo(new WritableStream({
|
|
120
|
+
write(chunk) {
|
|
121
|
+
output.push(chunk);
|
|
122
|
+
}
|
|
123
|
+
}), {
|
|
124
|
+
signal: abortController.signal,
|
|
125
|
+
preventCancel: true,
|
|
126
|
+
})
|
|
127
|
+
.catch(() => { });
|
|
128
|
+
|
|
129
|
+
const result = await Promise.race([
|
|
130
|
+
process.exit,
|
|
131
|
+
connection.getStreams(),
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
if (typeof result === 'number') {
|
|
135
|
+
const error = new Error('scrcpy server exited prematurely');
|
|
136
|
+
(error as any).output = output;
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
abortController.abort();
|
|
141
|
+
await pipe;
|
|
142
|
+
|
|
143
|
+
const [videoStream, controlStream] = result;
|
|
144
|
+
return new AdbScrcpyClient(
|
|
145
|
+
options,
|
|
146
|
+
process,
|
|
147
|
+
new ConcatStream(
|
|
148
|
+
new ArrayToStream(output),
|
|
149
|
+
stdout,
|
|
150
|
+
),
|
|
151
|
+
videoStream,
|
|
152
|
+
controlStream
|
|
153
|
+
);
|
|
154
|
+
} catch (e) {
|
|
155
|
+
await process?.kill();
|
|
156
|
+
throw e;
|
|
157
|
+
} finally {
|
|
158
|
+
connection.dispose();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* This method will modify the given `options`,
|
|
164
|
+
* so don't reuse it elsewhere.
|
|
165
|
+
*/
|
|
166
|
+
public static async getEncoders(
|
|
167
|
+
adb: Adb,
|
|
168
|
+
path: string,
|
|
169
|
+
version: string,
|
|
170
|
+
options: AdbScrcpyOptions<any>
|
|
171
|
+
): Promise<string[]> {
|
|
172
|
+
// Provide an invalid encoder name
|
|
173
|
+
// So the server will return all available encoders
|
|
174
|
+
options.value.encoderName = '_';
|
|
175
|
+
// Disable control for faster connection in 1.22+
|
|
176
|
+
options.value.control = false;
|
|
177
|
+
options.value.sendDeviceMeta = false;
|
|
178
|
+
options.value.sendDummyByte = false;
|
|
179
|
+
|
|
180
|
+
// Scrcpy server will open connections, before initializing encoder
|
|
181
|
+
// Thus although an invalid encoder name is given, the start process will success
|
|
182
|
+
const client = await AdbScrcpyClient.start(
|
|
183
|
+
adb,
|
|
184
|
+
path,
|
|
185
|
+
version,
|
|
186
|
+
options
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const encoderNameRegex = options.getOutputEncoderNameRegex();
|
|
190
|
+
const encoders: string[] = [];
|
|
191
|
+
await client.stdout.pipeTo(new WritableStream({
|
|
192
|
+
write(line) {
|
|
193
|
+
const match = line.match(encoderNameRegex);
|
|
194
|
+
if (match) {
|
|
195
|
+
encoders.push(match[1]!);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
}));
|
|
199
|
+
|
|
200
|
+
return encoders;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* This method will modify the given `options`,
|
|
205
|
+
* so don't reuse it elsewhere.
|
|
206
|
+
*/
|
|
207
|
+
public static async getDisplays(
|
|
208
|
+
adb: Adb,
|
|
209
|
+
path: string,
|
|
210
|
+
version: string,
|
|
211
|
+
options: AdbScrcpyOptions<any>
|
|
212
|
+
): Promise<number[]> {
|
|
213
|
+
// Similar to `getEncoders`, pass an invalid option and parse the output
|
|
214
|
+
options.value.displayId = -1;
|
|
215
|
+
|
|
216
|
+
options.value.control = false;
|
|
217
|
+
options.value.sendDeviceMeta = false;
|
|
218
|
+
options.value.sendDummyByte = false;
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
// Server will exit before opening connections when an invalid display id was given.
|
|
222
|
+
await AdbScrcpyClient.start(adb, path, version, options);
|
|
223
|
+
} catch (e) {
|
|
224
|
+
if (e instanceof Error) {
|
|
225
|
+
const output = (e as any).output as string[];
|
|
226
|
+
|
|
227
|
+
const displayIdRegex = /\s+scrcpy --display (\d+)/;
|
|
228
|
+
const displays: number[] = [];
|
|
229
|
+
for (const line of output) {
|
|
230
|
+
const match = line.match(displayIdRegex);
|
|
231
|
+
if (match) {
|
|
232
|
+
displays.push(Number.parseInt(match[1]!, 10));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return displays;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
throw new Error('failed to get displays');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private process: AdbSubprocessProtocol;
|
|
243
|
+
|
|
244
|
+
private _stdout: ReadableStream<string>;
|
|
245
|
+
public get stdout() { return this._stdout; }
|
|
246
|
+
|
|
247
|
+
public get exit() { return this.process.exit; }
|
|
248
|
+
|
|
249
|
+
private _screenWidth: number | undefined;
|
|
250
|
+
public get screenWidth() { return this._screenWidth; }
|
|
251
|
+
|
|
252
|
+
private _screenHeight: number | undefined;
|
|
253
|
+
public get screenHeight() { return this._screenHeight; }
|
|
254
|
+
|
|
255
|
+
private _videoStream: ReadableStream<ScrcpyVideoStreamPacket>;
|
|
256
|
+
public get videoStream() { return this._videoStream; }
|
|
257
|
+
|
|
258
|
+
private _controlMessageSerializer: ScrcpyControlMessageSerializer | undefined;
|
|
259
|
+
public get controlMessageSerializer() { return this._controlMessageSerializer; }
|
|
260
|
+
|
|
261
|
+
private _deviceMessageStream: ReadableStream<ScrcpyDeviceMessage> | undefined;
|
|
262
|
+
public get deviceMessageStream() { return this._deviceMessageStream; }
|
|
263
|
+
|
|
264
|
+
public constructor(
|
|
265
|
+
options: AdbScrcpyOptions<any>,
|
|
266
|
+
process: AdbSubprocessProtocol,
|
|
267
|
+
stdout: ReadableStream<string>,
|
|
268
|
+
videoStream: ReadableStream<Uint8Array>,
|
|
269
|
+
controlStream: ReadableWritablePair<Uint8Array, Uint8Array> | undefined,
|
|
270
|
+
) {
|
|
271
|
+
this.process = process;
|
|
272
|
+
this._stdout = stdout;
|
|
273
|
+
|
|
274
|
+
this._videoStream = videoStream
|
|
275
|
+
.pipeThrough(options.createVideoStreamTransformer())
|
|
276
|
+
.pipeThrough(new InspectStream(packet => {
|
|
277
|
+
if (packet.type === 'configuration') {
|
|
278
|
+
this._screenWidth = packet.data.croppedWidth;
|
|
279
|
+
this._screenHeight = packet.data.croppedHeight;
|
|
280
|
+
}
|
|
281
|
+
}));
|
|
282
|
+
|
|
283
|
+
if (controlStream) {
|
|
284
|
+
this._controlMessageSerializer = new ScrcpyControlMessageSerializer(controlStream.writable, options);
|
|
285
|
+
this._deviceMessageStream = controlStream.readable.pipeThrough(new ScrcpyDeviceMessageDeserializeStream());
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
public async close() {
|
|
290
|
+
await this.process.kill();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { Adb
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
1
|
+
import type { Adb } from '@yume-chan/adb';
|
|
2
|
+
import { delay } from '@yume-chan/async';
|
|
3
|
+
import type { Disposable } from '@yume-chan/event';
|
|
4
|
+
import { TransformStream, type ReadableStream, type ReadableStreamDefaultReader, type ReadableWritablePair } from '@yume-chan/stream-extra';
|
|
5
|
+
import type { ValueOrPromise } from '@yume-chan/struct';
|
|
5
6
|
|
|
6
|
-
export interface
|
|
7
|
+
export interface AdbScrcpyConnectionOptions {
|
|
7
8
|
control: boolean;
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -17,29 +18,29 @@ export interface ScrcpyClientConnectionOptions {
|
|
|
17
18
|
sendDeviceMeta: boolean;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
export abstract class
|
|
21
|
-
protected
|
|
21
|
+
export abstract class AdbScrcpyConnection implements Disposable {
|
|
22
|
+
protected adb: Adb;
|
|
22
23
|
|
|
23
|
-
protected options:
|
|
24
|
+
protected options: AdbScrcpyConnectionOptions;
|
|
24
25
|
|
|
25
|
-
public constructor(
|
|
26
|
-
this.
|
|
26
|
+
public constructor(adb: Adb, options: AdbScrcpyConnectionOptions) {
|
|
27
|
+
this.adb = adb;
|
|
27
28
|
this.options = options;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
public initialize(): ValueOrPromise<void> { }
|
|
31
32
|
|
|
32
|
-
public abstract getStreams(): ValueOrPromise<[videoSteam:
|
|
33
|
+
public abstract getStreams(): ValueOrPromise<[videoSteam: ReadableStream<Uint8Array>, controlStream: ReadableWritablePair<Uint8Array, Uint8Array> | undefined]>;
|
|
33
34
|
|
|
34
35
|
public dispose(): void { }
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
export class
|
|
38
|
-
private
|
|
39
|
-
return
|
|
38
|
+
export class AdbScrcpyForwardConnection extends AdbScrcpyConnection {
|
|
39
|
+
private connect(): Promise<ReadableWritablePair<Uint8Array, Uint8Array>> {
|
|
40
|
+
return this.adb.createSocket('localabstract:scrcpy');
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
private async connectAndRetry(): Promise<
|
|
43
|
+
private async connectAndRetry(): Promise<ReadableWritablePair<Uint8Array, Uint8Array>> {
|
|
43
44
|
for (let i = 0; i < 100; i++) {
|
|
44
45
|
try {
|
|
45
46
|
return await this.connect();
|
|
@@ -50,10 +51,10 @@ export class ScrcpyClientForwardConnection extends ScrcpyClientConnection {
|
|
|
50
51
|
throw new Error(`Can't connect to server after 100 retries`);
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
private async connectVideoStream(): Promise<
|
|
54
|
-
const
|
|
54
|
+
private async connectVideoStream(): Promise<ReadableStream<Uint8Array>> {
|
|
55
|
+
const { readable: videoStream } = await this.connectAndRetry();
|
|
55
56
|
if (this.options.sendDummyByte) {
|
|
56
|
-
const reader =
|
|
57
|
+
const reader = videoStream.getReader();
|
|
57
58
|
const { done, value } = await reader.read();
|
|
58
59
|
// server will write a `0` to signal connection success
|
|
59
60
|
if (done || value.byteLength !== 1 || value[0] !== 0) {
|
|
@@ -61,20 +62,20 @@ export class ScrcpyClientForwardConnection extends ScrcpyClientConnection {
|
|
|
61
62
|
}
|
|
62
63
|
reader.releaseLock();
|
|
63
64
|
}
|
|
64
|
-
return
|
|
65
|
+
return videoStream;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
public async getStreams(): Promise<[videoSteam:
|
|
68
|
+
public async getStreams(): Promise<[videoSteam: ReadableStream<Uint8Array>, controlStream: ReadableWritablePair<Uint8Array, Uint8Array> | undefined]> {
|
|
68
69
|
const videoStream = await this.connectVideoStream();
|
|
69
70
|
|
|
70
|
-
let controlStream:
|
|
71
|
+
let controlStream: ReadableWritablePair<Uint8Array, Uint8Array> | undefined;
|
|
71
72
|
if (this.options.control) {
|
|
72
73
|
controlStream = await this.connectAndRetry();
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
// Server only writes device meta after control socket is connected (if enabled)
|
|
76
77
|
if (this.options.sendDeviceMeta) {
|
|
77
|
-
const reader = videoStream.
|
|
78
|
+
const reader = videoStream.getReader();
|
|
78
79
|
const { done, value } = await reader.read();
|
|
79
80
|
// 64 bytes device name + 2 bytes video width + 2 bytes video height
|
|
80
81
|
if (done || value.byteLength !== 64 + 2 + 2) {
|
|
@@ -87,23 +88,19 @@ export class ScrcpyClientForwardConnection extends ScrcpyClientConnection {
|
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
export class
|
|
91
|
-
private streams!: ReadableStreamDefaultReader<
|
|
91
|
+
export class AdbScrcpyReverseConnection extends AdbScrcpyConnection {
|
|
92
|
+
private streams!: ReadableStreamDefaultReader<ReadableWritablePair<Uint8Array, Uint8Array>>;
|
|
92
93
|
|
|
93
94
|
private address!: string;
|
|
94
95
|
|
|
95
96
|
public override async initialize(): Promise<void> {
|
|
96
|
-
try
|
|
97
|
-
|
|
98
|
-
await this.device.reverse.remove('localabstract:scrcpy');
|
|
99
|
-
} catch {
|
|
100
|
-
// ignore error
|
|
101
|
-
}
|
|
97
|
+
// try to unbind first, ignore errors
|
|
98
|
+
await this.adb.reverse.remove('localabstract:scrcpy').catch(() => { });
|
|
102
99
|
|
|
103
|
-
const queue = new TransformStream<
|
|
100
|
+
const queue = new TransformStream<ReadableWritablePair<Uint8Array, Uint8Array>>();
|
|
104
101
|
this.streams = queue.readable.getReader();
|
|
105
102
|
const writer = queue.writable.getWriter();
|
|
106
|
-
this.address = await this.
|
|
103
|
+
this.address = await this.adb.reverse.add(
|
|
107
104
|
'localabstract:scrcpy',
|
|
108
105
|
'tcp:27183',
|
|
109
106
|
socket => {
|
|
@@ -113,21 +110,21 @@ export class ScrcpyClientReverseConnection extends ScrcpyClientConnection {
|
|
|
113
110
|
);
|
|
114
111
|
}
|
|
115
112
|
|
|
116
|
-
private async accept(): Promise<
|
|
113
|
+
private async accept(): Promise<ReadableWritablePair<Uint8Array, Uint8Array>> {
|
|
117
114
|
return (await this.streams.read()).value!;
|
|
118
115
|
}
|
|
119
116
|
|
|
120
|
-
public async getStreams(): Promise<[videoSteam:
|
|
121
|
-
const videoStream = await this.accept();
|
|
117
|
+
public async getStreams(): Promise<[videoSteam: ReadableStream<Uint8Array>, controlStream: ReadableWritablePair<Uint8Array, Uint8Array> | undefined]> {
|
|
118
|
+
const { readable: videoStream } = await this.accept();
|
|
122
119
|
|
|
123
|
-
let controlStream:
|
|
120
|
+
let controlStream: ReadableWritablePair<Uint8Array, Uint8Array> | undefined;
|
|
124
121
|
if (this.options.control) {
|
|
125
122
|
controlStream = await this.accept();
|
|
126
123
|
}
|
|
127
124
|
|
|
128
125
|
// Server only writes device meta after control socket is connected (if enabled)
|
|
129
126
|
if (this.options.sendDeviceMeta) {
|
|
130
|
-
const reader = videoStream.
|
|
127
|
+
const reader = videoStream.getReader();
|
|
131
128
|
const { done, value } = await reader.read();
|
|
132
129
|
// 64 bytes device name + 2 bytes video width + 2 bytes video height
|
|
133
130
|
if (done || value.byteLength !== 64 + 2 + 2) {
|
|
@@ -143,6 +140,6 @@ export class ScrcpyClientReverseConnection extends ScrcpyClientConnection {
|
|
|
143
140
|
// Don't await this!
|
|
144
141
|
// `reverse.remove`'s response will never arrive
|
|
145
142
|
// before we read all pending data from `videoStream`
|
|
146
|
-
this.
|
|
143
|
+
this.adb.reverse.remove(this.address);
|
|
147
144
|
}
|
|
148
145
|
}
|
package/src/adb/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Adb } from '@yume-chan/adb';
|
|
2
|
+
|
|
3
|
+
import type { ScrcpyOptionsInit1_16 } from '../../options/index.js';
|
|
4
|
+
import { AdbScrcpyConnection, AdbScrcpyConnectionOptions, AdbScrcpyForwardConnection, AdbScrcpyReverseConnection } from '../connection.js';
|
|
5
|
+
import { AdbScrcpyOptionsBase } from './types.js';
|
|
6
|
+
|
|
7
|
+
export class AdbScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsInit1_16>
|
|
8
|
+
extends AdbScrcpyOptionsBase<T> {
|
|
9
|
+
public override createConnection(adb: Adb): AdbScrcpyConnection {
|
|
10
|
+
const options: AdbScrcpyConnectionOptions = {
|
|
11
|
+
// Old versions always have control stream no matter what the option is
|
|
12
|
+
// Pass `control: false` to `Connection` will disable the control stream
|
|
13
|
+
control: true,
|
|
14
|
+
sendDummyByte: true,
|
|
15
|
+
sendDeviceMeta: true,
|
|
16
|
+
};
|
|
17
|
+
if (this.value.tunnelForward) {
|
|
18
|
+
return new AdbScrcpyForwardConnection(adb, options);
|
|
19
|
+
} else {
|
|
20
|
+
return new AdbScrcpyReverseConnection(adb, options);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Adb } from '@yume-chan/adb';
|
|
2
|
+
|
|
3
|
+
import type { ScrcpyOptionsInit1_22 } from '../../options/index.js';
|
|
4
|
+
import { AdbScrcpyConnection, AdbScrcpyForwardConnection, AdbScrcpyReverseConnection } from '../connection.js';
|
|
5
|
+
import { AdbScrcpyOptions1_16 } from './1_16.js';
|
|
6
|
+
|
|
7
|
+
export class AdbScrcpyOptions1_22<T extends ScrcpyOptionsInit1_22 = ScrcpyOptionsInit1_22>
|
|
8
|
+
extends AdbScrcpyOptions1_16<T> {
|
|
9
|
+
public override createConnection(adb: Adb): AdbScrcpyConnection {
|
|
10
|
+
const options = {
|
|
11
|
+
...this.getDefaultValue(),
|
|
12
|
+
...this.value,
|
|
13
|
+
};
|
|
14
|
+
if (this.value.tunnelForward) {
|
|
15
|
+
return new AdbScrcpyForwardConnection(adb, options);
|
|
16
|
+
} else {
|
|
17
|
+
return new AdbScrcpyReverseConnection(adb, options);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Adb } from '@yume-chan/adb';
|
|
2
|
+
import type { TransformStream } from '@yume-chan/stream-extra';
|
|
3
|
+
|
|
4
|
+
import type { ScrcpyControlMessageType } from '../../control/index.js';
|
|
5
|
+
import type { ScrcpyBackOrScreenOnControlMessage1_18, ScrcpyInjectScrollControlMessage1_22, ScrcpyOptions, ScrcpyVideoStreamPacket } from '../../options/index.js';
|
|
6
|
+
import type { AdbScrcpyConnection } from '../connection.js';
|
|
7
|
+
|
|
8
|
+
export interface AdbScrcpyOptions<T> extends ScrcpyOptions<T> {
|
|
9
|
+
createConnection(adb: Adb): AdbScrcpyConnection;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export abstract class AdbScrcpyOptionsBase<T> implements ScrcpyOptions<T> {
|
|
13
|
+
private raw: ScrcpyOptions<T>;
|
|
14
|
+
|
|
15
|
+
public get value(): Partial<T> { return this.raw.value; }
|
|
16
|
+
public set value(value: Partial<T>) { this.raw.value = value; }
|
|
17
|
+
|
|
18
|
+
public constructor(raw: ScrcpyOptions<T>) {
|
|
19
|
+
this.raw = raw;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public getDefaultValue(): T {
|
|
23
|
+
return this.raw.getDefaultValue();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public formatServerArguments(): string[] {
|
|
27
|
+
return this.raw.formatServerArguments();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public getOutputEncoderNameRegex(): RegExp {
|
|
31
|
+
return this.raw.getOutputEncoderNameRegex();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public createVideoStreamTransformer(): TransformStream<Uint8Array, ScrcpyVideoStreamPacket> {
|
|
35
|
+
return this.raw.createVideoStreamTransformer();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public getControlMessageTypes(): ScrcpyControlMessageType[] {
|
|
39
|
+
return this.raw.getControlMessageTypes();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public serializeBackOrScreenOnControlMessage(
|
|
43
|
+
message: ScrcpyBackOrScreenOnControlMessage1_18,
|
|
44
|
+
): Uint8Array | undefined {
|
|
45
|
+
return this.raw.serializeBackOrScreenOnControlMessage(message);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public serializeInjectScrollControlMessage(
|
|
49
|
+
message: ScrcpyInjectScrollControlMessage1_22,
|
|
50
|
+
): Uint8Array {
|
|
51
|
+
return this.raw.serializeInjectScrollControlMessage(message);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public abstract createConnection(adb: Adb): AdbScrcpyConnection;
|
|
55
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import Struct, { placeholder } from '@yume-chan/struct';
|
|
2
|
+
|
|
3
|
+
import { ScrcpyControlMessageType } from './type.js';
|
|
4
|
+
|
|
5
|
+
export enum AndroidKeyEventAction {
|
|
6
|
+
Down = 0,
|
|
7
|
+
Up = 1,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// https://github.com/Genymobile/scrcpy/blob/cabb102a04153f9c237b18257d3269a058558c63/app/src/android/keycodes.h#L26
|
|
11
|
+
export enum AndroidKeyCode {
|
|
12
|
+
Home = 3,
|
|
13
|
+
Back = 4,
|
|
14
|
+
VolumeUp = 24,
|
|
15
|
+
VolumeDown,
|
|
16
|
+
A = 29,
|
|
17
|
+
B,
|
|
18
|
+
C,
|
|
19
|
+
D,
|
|
20
|
+
E,
|
|
21
|
+
F,
|
|
22
|
+
G,
|
|
23
|
+
H,
|
|
24
|
+
I,
|
|
25
|
+
J,
|
|
26
|
+
K,
|
|
27
|
+
L,
|
|
28
|
+
M,
|
|
29
|
+
N,
|
|
30
|
+
O,
|
|
31
|
+
P,
|
|
32
|
+
Q,
|
|
33
|
+
R,
|
|
34
|
+
S,
|
|
35
|
+
T,
|
|
36
|
+
U,
|
|
37
|
+
V,
|
|
38
|
+
W,
|
|
39
|
+
X,
|
|
40
|
+
Y,
|
|
41
|
+
Z,
|
|
42
|
+
Tab = 61,
|
|
43
|
+
Space,
|
|
44
|
+
Enter = 66,
|
|
45
|
+
Delete,
|
|
46
|
+
VolumeMute = 164,
|
|
47
|
+
AppSwitch = 187,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const ScrcpyInjectKeyCodeControlMessage =
|
|
51
|
+
new Struct()
|
|
52
|
+
.uint8('type', ScrcpyControlMessageType.InjectKeyCode as const)
|
|
53
|
+
.uint8('action', placeholder<AndroidKeyEventAction>())
|
|
54
|
+
.uint32('keyCode')
|
|
55
|
+
.uint32('repeat')
|
|
56
|
+
.uint32('metaState');
|
|
57
|
+
|
|
58
|
+
export type ScrcpyInjectKeyCodeControlMessage =
|
|
59
|
+
typeof ScrcpyInjectKeyCodeControlMessage['TInit'];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Struct from '@yume-chan/struct';
|
|
2
|
+
|
|
3
|
+
import { ScrcpyControlMessageType } from './type.js';
|
|
4
|
+
|
|
5
|
+
export const ScrcpyInjectTextControlMessage =
|
|
6
|
+
new Struct()
|
|
7
|
+
.uint8('type', ScrcpyControlMessageType.InjectText as const)
|
|
8
|
+
.uint32('length')
|
|
9
|
+
.string('text', { lengthField: 'length' });
|
|
10
|
+
|
|
11
|
+
export type ScrcpyInjectTextControlMessage =
|
|
12
|
+
typeof ScrcpyInjectTextControlMessage['TInit'];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import Struct, { placeholder } from '@yume-chan/struct';
|
|
2
|
+
import { ScrcpyControlMessageType } from './type.js';
|
|
3
|
+
|
|
4
|
+
// https://developer.android.com/reference/android/view/MotionEvent#constants_1
|
|
5
|
+
export enum AndroidMotionEventAction {
|
|
6
|
+
Down,
|
|
7
|
+
Up,
|
|
8
|
+
Move,
|
|
9
|
+
Cancel,
|
|
10
|
+
Outside,
|
|
11
|
+
PointerDown,
|
|
12
|
+
PointerUp,
|
|
13
|
+
HoverMove,
|
|
14
|
+
Scroll,
|
|
15
|
+
HoverEnter,
|
|
16
|
+
HoverExit,
|
|
17
|
+
ButtonPress,
|
|
18
|
+
ButtonRelease,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const ScrcpyInjectTouchControlMessage =
|
|
22
|
+
new Struct()
|
|
23
|
+
.uint8('type', ScrcpyControlMessageType.InjectTouch as const)
|
|
24
|
+
.uint8('action', placeholder<AndroidMotionEventAction>())
|
|
25
|
+
.uint64('pointerId')
|
|
26
|
+
.uint32('pointerX')
|
|
27
|
+
.uint32('pointerY')
|
|
28
|
+
.uint16('screenWidth')
|
|
29
|
+
.uint16('screenHeight')
|
|
30
|
+
.uint16('pressure')
|
|
31
|
+
.uint32('buttons');
|
|
32
|
+
|
|
33
|
+
export type ScrcpyInjectTouchControlMessage =
|
|
34
|
+
typeof ScrcpyInjectTouchControlMessage['TInit'];
|