@yume-chan/scrcpy 0.0.15 → 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.
Files changed (214) hide show
  1. package/CHANGELOG.json +33 -0
  2. package/CHANGELOG.md +18 -1
  3. package/LICENSE +21 -21
  4. package/README.md +327 -89
  5. package/esm/adb/client.d.ts +40 -0
  6. package/esm/adb/client.d.ts.map +1 -0
  7. package/esm/adb/client.js +218 -0
  8. package/esm/adb/client.js.map +1 -0
  9. package/esm/adb/connection.d.ts +38 -0
  10. package/esm/adb/connection.d.ts.map +1 -0
  11. package/esm/{connection.js → adb/connection.js} +20 -24
  12. package/esm/adb/connection.js.map +1 -0
  13. package/esm/adb/index.d.ts +4 -0
  14. package/esm/adb/index.d.ts.map +1 -0
  15. package/esm/adb/index.js +4 -0
  16. package/esm/adb/index.js.map +1 -0
  17. package/esm/adb/options/1_16.d.ts +8 -0
  18. package/esm/adb/options/1_16.d.ts.map +1 -0
  19. package/esm/adb/options/1_16.js +20 -0
  20. package/esm/adb/options/1_16.js.map +1 -0
  21. package/esm/adb/options/1_22.d.ts +8 -0
  22. package/esm/adb/options/1_22.d.ts.map +1 -0
  23. package/esm/adb/options/1_22.js +17 -0
  24. package/esm/adb/options/1_22.js.map +1 -0
  25. package/esm/adb/options/index.d.ts +4 -0
  26. package/esm/adb/options/index.d.ts.map +1 -0
  27. package/esm/adb/options/index.js +4 -0
  28. package/esm/adb/options/index.js.map +1 -0
  29. package/esm/adb/options/types.d.ts +23 -0
  30. package/esm/adb/options/types.d.ts.map +1 -0
  31. package/esm/adb/options/types.js +30 -0
  32. package/esm/adb/options/types.js.map +1 -0
  33. package/esm/codec.js +2 -2
  34. package/esm/codec.js.map +1 -1
  35. package/esm/control/index.d.ts +8 -0
  36. package/esm/control/index.d.ts.map +1 -0
  37. package/esm/control/index.js +8 -0
  38. package/esm/control/index.js.map +1 -0
  39. package/esm/control/inject-keycode.d.ts +53 -0
  40. package/esm/control/inject-keycode.d.ts.map +1 -0
  41. package/esm/control/inject-keycode.js +54 -0
  42. package/esm/control/inject-keycode.js.map +1 -0
  43. package/esm/control/inject-text.d.ts +9 -0
  44. package/esm/control/inject-text.d.ts.map +1 -0
  45. package/esm/control/inject-text.js +7 -0
  46. package/esm/control/inject-text.js.map +1 -0
  47. package/esm/control/inject-touch.d.ts +30 -0
  48. package/esm/control/inject-touch.d.ts.map +1 -0
  49. package/esm/control/inject-touch.js +30 -0
  50. package/esm/control/inject-touch.js.map +1 -0
  51. package/esm/control/rotate-device.d.ts +7 -0
  52. package/esm/control/rotate-device.d.ts.map +1 -0
  53. package/esm/control/rotate-device.js +5 -0
  54. package/esm/control/rotate-device.js.map +1 -0
  55. package/esm/control/serializer.d.ts +23 -0
  56. package/esm/control/serializer.d.ts.map +1 -0
  57. package/esm/control/serializer.js +73 -0
  58. package/esm/control/serializer.js.map +1 -0
  59. package/esm/control/set-screen-power-mode.d.ts +12 -0
  60. package/esm/control/set-screen-power-mode.d.ts.map +1 -0
  61. package/esm/control/set-screen-power-mode.js +12 -0
  62. package/esm/control/set-screen-power-mode.js.map +1 -0
  63. package/esm/control/type.d.ts +15 -0
  64. package/esm/control/type.d.ts.map +1 -0
  65. package/esm/control/type.js +19 -0
  66. package/esm/control/type.js.map +1 -0
  67. package/esm/decoder/index.d.ts +27 -3
  68. package/esm/decoder/index.d.ts.map +1 -1
  69. package/esm/decoder/index.js +1 -3
  70. package/esm/decoder/index.js.map +1 -1
  71. package/esm/decoder/tinyh264/index.d.ts +5 -5
  72. package/esm/decoder/tinyh264/index.d.ts.map +1 -1
  73. package/esm/decoder/tinyh264/index.js +4 -4
  74. package/esm/decoder/tinyh264/index.js.map +1 -1
  75. package/esm/decoder/tinyh264/worker.js.map +1 -1
  76. package/esm/decoder/tinyh264/wrapper.d.ts.map +1 -1
  77. package/esm/decoder/tinyh264/wrapper.js +14 -8
  78. package/esm/decoder/tinyh264/wrapper.js.map +1 -1
  79. package/esm/decoder/types.d.ts +1 -27
  80. package/esm/decoder/types.d.ts.map +1 -1
  81. package/esm/decoder/web-codecs/index.d.ts +4 -4
  82. package/esm/decoder/web-codecs/index.d.ts.map +1 -1
  83. package/esm/decoder/web-codecs/index.js +1 -1
  84. package/esm/decoder/web-codecs/index.js.map +1 -1
  85. package/esm/device-message/clipboard.d.ts +10 -0
  86. package/esm/device-message/clipboard.d.ts.map +1 -0
  87. package/esm/device-message/clipboard.js +7 -0
  88. package/esm/device-message/clipboard.js.map +1 -0
  89. package/esm/device-message/index.d.ts +4 -0
  90. package/esm/device-message/index.d.ts.map +1 -0
  91. package/esm/device-message/index.js +4 -0
  92. package/esm/device-message/index.js.map +1 -0
  93. package/esm/device-message/stream.d.ts +7 -0
  94. package/esm/device-message/stream.d.ts.map +1 -0
  95. package/esm/device-message/stream.js +16 -0
  96. package/esm/device-message/stream.js.map +1 -0
  97. package/esm/device-message/type.d.ts +5 -0
  98. package/esm/device-message/type.d.ts.map +1 -0
  99. package/esm/device-message/type.js +7 -0
  100. package/esm/device-message/type.js.map +1 -0
  101. package/esm/index.d.ts +3 -6
  102. package/esm/index.d.ts.map +1 -1
  103. package/esm/index.js +3 -6
  104. package/esm/index.js.map +1 -1
  105. package/esm/options/1_16/codec-options.d.ts +19 -0
  106. package/esm/options/1_16/codec-options.d.ts.map +1 -0
  107. package/esm/options/1_16/codec-options.js +27 -0
  108. package/esm/options/1_16/codec-options.js.map +1 -0
  109. package/esm/options/1_16/index.d.ts +3 -107
  110. package/esm/options/1_16/index.d.ts.map +1 -1
  111. package/esm/options/1_16/index.js +3 -216
  112. package/esm/options/1_16/index.js.map +1 -1
  113. package/esm/options/1_16/options.d.ts +97 -0
  114. package/esm/options/1_16/options.d.ts.map +1 -0
  115. package/esm/options/1_16/options.js +188 -0
  116. package/esm/options/1_16/options.js.map +1 -0
  117. package/esm/options/1_18.d.ts +8 -8
  118. package/esm/options/1_18.d.ts.map +1 -1
  119. package/esm/options/1_18.js +6 -6
  120. package/esm/options/1_18.js.map +1 -1
  121. package/esm/options/1_21.d.ts +1 -1
  122. package/esm/options/1_21.d.ts.map +1 -1
  123. package/esm/options/1_21.js +1 -1
  124. package/esm/options/1_21.js.map +1 -1
  125. package/esm/options/1_22.d.ts +5 -8
  126. package/esm/options/1_22.d.ts.map +1 -1
  127. package/esm/options/1_22.js +4 -17
  128. package/esm/options/1_22.js.map +1 -1
  129. package/esm/options/1_23.d.ts +4 -4
  130. package/esm/options/1_23.d.ts.map +1 -1
  131. package/esm/options/1_23.js +1 -1
  132. package/esm/options/1_23.js.map +1 -1
  133. package/esm/options/1_24.d.ts +1 -1
  134. package/esm/options/1_24.d.ts.map +1 -1
  135. package/esm/options/1_24.js.map +1 -1
  136. package/esm/options/index.d.ts +1 -1
  137. package/esm/options/index.d.ts.map +1 -1
  138. package/esm/options/index.js +1 -1
  139. package/esm/options/index.js.map +1 -1
  140. package/esm/options/types.d.ts +45 -0
  141. package/esm/options/types.d.ts.map +1 -0
  142. package/esm/options/{common.js → types.js} +1 -1
  143. package/esm/options/types.js.map +1 -0
  144. package/package.json +24 -24
  145. package/scrcpy.LICENSE +203 -203
  146. package/scripts/fetch-server.cjs +28 -28
  147. package/src/adb/client.ts +292 -0
  148. package/src/{connection.ts → adb/connection.ts} +145 -148
  149. package/src/adb/index.ts +3 -0
  150. package/src/adb/options/1_16.ts +23 -0
  151. package/src/adb/options/1_22.ts +20 -0
  152. package/src/adb/options/index.ts +3 -0
  153. package/src/adb/options/types.ts +55 -0
  154. package/src/codec.ts +35 -35
  155. package/src/control/index.ts +7 -0
  156. package/src/control/inject-keycode.ts +59 -0
  157. package/src/control/inject-text.ts +12 -0
  158. package/src/control/inject-touch.ts +34 -0
  159. package/src/control/rotate-device.ts +10 -0
  160. package/src/control/serializer.ts +86 -0
  161. package/src/control/set-screen-power-mode.ts +17 -0
  162. package/src/control/type.ts +17 -0
  163. package/src/device-message/clipboard.ts +11 -0
  164. package/src/device-message/index.ts +3 -0
  165. package/src/device-message/stream.ts +19 -0
  166. package/src/device-message/type.ts +5 -0
  167. package/src/index.ts +5 -8
  168. package/src/options/1_16/codec-options.ts +48 -0
  169. package/src/options/1_16/index.ts +3 -330
  170. package/src/options/1_16/options.ts +294 -0
  171. package/src/options/1_16/sps.ts +300 -300
  172. package/src/options/1_18.ts +63 -61
  173. package/src/options/1_21.ts +44 -34
  174. package/src/options/1_22.ts +67 -78
  175. package/src/options/1_23.ts +7 -5
  176. package/src/options/1_24.ts +3 -2
  177. package/src/options/index.ts +7 -7
  178. package/src/options/{common.ts → types.ts} +85 -66
  179. package/tsconfig.build.json +3 -0
  180. package/tsconfig.build.tsbuildinfo +1 -0
  181. package/tsconfig.test.tsbuildinfo +1 -0
  182. package/esm/client.d.ts +0 -48
  183. package/esm/client.d.ts.map +0 -1
  184. package/esm/client.js +0 -341
  185. package/esm/client.js.map +0 -1
  186. package/esm/connection.d.ts +0 -37
  187. package/esm/connection.d.ts.map +0 -1
  188. package/esm/connection.js.map +0 -1
  189. package/esm/message.d.ts +0 -97
  190. package/esm/message.d.ts.map +0 -1
  191. package/esm/message.js +0 -97
  192. package/esm/message.js.map +0 -1
  193. package/esm/options/common.d.ts +0 -34
  194. package/esm/options/common.d.ts.map +0 -1
  195. package/esm/options/common.js.map +0 -1
  196. package/esm/push-server.d.ts +0 -6
  197. package/esm/push-server.d.ts.map +0 -1
  198. package/esm/push-server.js +0 -16
  199. package/esm/push-server.js.map +0 -1
  200. package/esm/utils.d.ts +0 -2
  201. package/esm/utils.d.ts.map +0 -1
  202. package/esm/utils.js +0 -6
  203. package/esm/utils.js.map +0 -1
  204. package/src/client.ts +0 -435
  205. package/src/decoder/index.ts +0 -3
  206. package/src/decoder/tinyh264/index.ts +0 -112
  207. package/src/decoder/tinyh264/types.d.ts +0 -137
  208. package/src/decoder/tinyh264/worker.ts +0 -2
  209. package/src/decoder/tinyh264/wrapper.ts +0 -89
  210. package/src/decoder/types.ts +0 -35
  211. package/src/decoder/web-codecs/index.ts +0 -99
  212. package/src/message.ts +0 -112
  213. package/src/push-server.ts +0 -24
  214. 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,148 +1,145 @@
1
- import type { Adb, AdbSocket } from "@yume-chan/adb";
2
- import type { Disposable } from "@yume-chan/event";
3
- import type { ValueOrPromise } from "@yume-chan/struct";
4
- import { delay } from "./utils.js";
5
-
6
- export interface ScrcpyClientConnectionOptions {
7
- control: boolean;
8
-
9
- /**
10
- * Write a byte on start to detect connection issues
11
- */
12
- sendDummyByte: boolean;
13
-
14
- /**
15
- * Send device name and size
16
- */
17
- sendDeviceMeta: boolean;
18
- }
19
-
20
- export abstract class ScrcpyClientConnection implements Disposable {
21
- protected device: Adb;
22
-
23
- protected options: ScrcpyClientConnectionOptions;
24
-
25
- public constructor(device: Adb, options: ScrcpyClientConnectionOptions) {
26
- this.device = device;
27
- this.options = options;
28
- }
29
-
30
- public initialize(): ValueOrPromise<void> { }
31
-
32
- public abstract getStreams(): ValueOrPromise<[videoSteam: AdbSocket, controlStream: AdbSocket | undefined]>;
33
-
34
- public dispose(): void { }
35
- }
36
-
37
- export class ScrcpyClientForwardConnection extends ScrcpyClientConnection {
38
- private async connect(): Promise<AdbSocket> {
39
- return await this.device.createSocket('localabstract:scrcpy');
40
- }
41
-
42
- private async connectAndRetry(): Promise<AdbSocket> {
43
- for (let i = 0; i < 100; i++) {
44
- try {
45
- return await this.connect();
46
- } catch (e) {
47
- await delay(100);
48
- }
49
- }
50
- throw new Error(`Can't connect to server after 100 retries`);
51
- }
52
-
53
- private async connectVideoStream(): Promise<AdbSocket> {
54
- const stream = await this.connectAndRetry();
55
- if (this.options.sendDummyByte) {
56
- const reader = stream.readable.getReader();
57
- const { done, value } = await reader.read();
58
- // server will write a `0` to signal connection success
59
- if (done || value.byteLength !== 1 || value[0] !== 0) {
60
- throw new Error('Unexpected response from server');
61
- }
62
- reader.releaseLock();
63
- }
64
- return stream;
65
- }
66
-
67
- public async getStreams(): Promise<[videoSteam: AdbSocket, controlStream: AdbSocket | undefined]> {
68
- const videoStream = await this.connectVideoStream();
69
-
70
- let controlStream: AdbSocket | undefined;
71
- if (this.options.control) {
72
- controlStream = await this.connectAndRetry();
73
- }
74
-
75
- // Server only writes device meta after control socket is connected (if enabled)
76
- if (this.options.sendDeviceMeta) {
77
- const reader = videoStream.readable.getReader();
78
- const { done, value } = await reader.read();
79
- // 64 bytes device name + 2 bytes video width + 2 bytes video height
80
- if (done || value.byteLength !== 64 + 2 + 2) {
81
- throw new Error('Unexpected response from server');
82
- }
83
- reader.releaseLock();
84
- }
85
-
86
- return [videoStream, controlStream];
87
- }
88
- }
89
-
90
- export class ScrcpyClientReverseConnection extends ScrcpyClientConnection {
91
- private streams!: ReadableStreamDefaultReader<AdbSocket>;
92
-
93
- private address!: string;
94
-
95
- public override async initialize(): Promise<void> {
96
- try {
97
- // try to unbind first
98
- await this.device.reverse.remove('localabstract:scrcpy');
99
- } catch {
100
- // ignore error
101
- }
102
-
103
- const queue = new TransformStream<AdbSocket>();
104
- this.streams = queue.readable.getReader();
105
- const writer = queue.writable.getWriter();
106
- this.address = await this.device.reverse.add(
107
- 'localabstract:scrcpy',
108
- 'tcp:27183',
109
- socket => {
110
- writer.write(socket);
111
- return true;
112
- },
113
- );
114
- }
115
-
116
- private async accept(): Promise<AdbSocket> {
117
- return (await this.streams.read()).value!;
118
- }
119
-
120
- public async getStreams(): Promise<[videoSteam: AdbSocket, controlStream: AdbSocket | undefined]> {
121
- const videoStream = await this.accept();
122
-
123
- let controlStream: AdbSocket | undefined;
124
- if (this.options.control) {
125
- controlStream = await this.accept();
126
- }
127
-
128
- // Server only writes device meta after control socket is connected (if enabled)
129
- if (this.options.sendDeviceMeta) {
130
- const reader = videoStream.readable.getReader();
131
- const { done, value } = await reader.read();
132
- // 64 bytes device name + 2 bytes video width + 2 bytes video height
133
- if (done || value.byteLength !== 64 + 2 + 2) {
134
- throw new Error('Unexpected response from server');
135
- }
136
- reader.releaseLock();
137
- }
138
-
139
- return [videoStream, controlStream];
140
- }
141
-
142
- public override dispose() {
143
- // Don't await this!
144
- // `reverse.remove`'s response will never arrive
145
- // before we read all pending data from `videoStream`
146
- this.device.reverse.remove(this.address);
147
- }
148
- }
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';
6
+
7
+ export interface AdbScrcpyConnectionOptions {
8
+ control: boolean;
9
+
10
+ /**
11
+ * Write a byte on start to detect connection issues
12
+ */
13
+ sendDummyByte: boolean;
14
+
15
+ /**
16
+ * Send device name and size
17
+ */
18
+ sendDeviceMeta: boolean;
19
+ }
20
+
21
+ export abstract class AdbScrcpyConnection implements Disposable {
22
+ protected adb: Adb;
23
+
24
+ protected options: AdbScrcpyConnectionOptions;
25
+
26
+ public constructor(adb: Adb, options: AdbScrcpyConnectionOptions) {
27
+ this.adb = adb;
28
+ this.options = options;
29
+ }
30
+
31
+ public initialize(): ValueOrPromise<void> { }
32
+
33
+ public abstract getStreams(): ValueOrPromise<[videoSteam: ReadableStream<Uint8Array>, controlStream: ReadableWritablePair<Uint8Array, Uint8Array> | undefined]>;
34
+
35
+ public dispose(): void { }
36
+ }
37
+
38
+ export class AdbScrcpyForwardConnection extends AdbScrcpyConnection {
39
+ private connect(): Promise<ReadableWritablePair<Uint8Array, Uint8Array>> {
40
+ return this.adb.createSocket('localabstract:scrcpy');
41
+ }
42
+
43
+ private async connectAndRetry(): Promise<ReadableWritablePair<Uint8Array, Uint8Array>> {
44
+ for (let i = 0; i < 100; i++) {
45
+ try {
46
+ return await this.connect();
47
+ } catch (e) {
48
+ await delay(100);
49
+ }
50
+ }
51
+ throw new Error(`Can't connect to server after 100 retries`);
52
+ }
53
+
54
+ private async connectVideoStream(): Promise<ReadableStream<Uint8Array>> {
55
+ const { readable: videoStream } = await this.connectAndRetry();
56
+ if (this.options.sendDummyByte) {
57
+ const reader = videoStream.getReader();
58
+ const { done, value } = await reader.read();
59
+ // server will write a `0` to signal connection success
60
+ if (done || value.byteLength !== 1 || value[0] !== 0) {
61
+ throw new Error('Unexpected response from server');
62
+ }
63
+ reader.releaseLock();
64
+ }
65
+ return videoStream;
66
+ }
67
+
68
+ public async getStreams(): Promise<[videoSteam: ReadableStream<Uint8Array>, controlStream: ReadableWritablePair<Uint8Array, Uint8Array> | undefined]> {
69
+ const videoStream = await this.connectVideoStream();
70
+
71
+ let controlStream: ReadableWritablePair<Uint8Array, Uint8Array> | undefined;
72
+ if (this.options.control) {
73
+ controlStream = await this.connectAndRetry();
74
+ }
75
+
76
+ // Server only writes device meta after control socket is connected (if enabled)
77
+ if (this.options.sendDeviceMeta) {
78
+ const reader = videoStream.getReader();
79
+ const { done, value } = await reader.read();
80
+ // 64 bytes device name + 2 bytes video width + 2 bytes video height
81
+ if (done || value.byteLength !== 64 + 2 + 2) {
82
+ throw new Error('Unexpected response from server');
83
+ }
84
+ reader.releaseLock();
85
+ }
86
+
87
+ return [videoStream, controlStream];
88
+ }
89
+ }
90
+
91
+ export class AdbScrcpyReverseConnection extends AdbScrcpyConnection {
92
+ private streams!: ReadableStreamDefaultReader<ReadableWritablePair<Uint8Array, Uint8Array>>;
93
+
94
+ private address!: string;
95
+
96
+ public override async initialize(): Promise<void> {
97
+ // try to unbind first, ignore errors
98
+ await this.adb.reverse.remove('localabstract:scrcpy').catch(() => { });
99
+
100
+ const queue = new TransformStream<ReadableWritablePair<Uint8Array, Uint8Array>>();
101
+ this.streams = queue.readable.getReader();
102
+ const writer = queue.writable.getWriter();
103
+ this.address = await this.adb.reverse.add(
104
+ 'localabstract:scrcpy',
105
+ 'tcp:27183',
106
+ socket => {
107
+ writer.write(socket);
108
+ return true;
109
+ },
110
+ );
111
+ }
112
+
113
+ private async accept(): Promise<ReadableWritablePair<Uint8Array, Uint8Array>> {
114
+ return (await this.streams.read()).value!;
115
+ }
116
+
117
+ public async getStreams(): Promise<[videoSteam: ReadableStream<Uint8Array>, controlStream: ReadableWritablePair<Uint8Array, Uint8Array> | undefined]> {
118
+ const { readable: videoStream } = await this.accept();
119
+
120
+ let controlStream: ReadableWritablePair<Uint8Array, Uint8Array> | undefined;
121
+ if (this.options.control) {
122
+ controlStream = await this.accept();
123
+ }
124
+
125
+ // Server only writes device meta after control socket is connected (if enabled)
126
+ if (this.options.sendDeviceMeta) {
127
+ const reader = videoStream.getReader();
128
+ const { done, value } = await reader.read();
129
+ // 64 bytes device name + 2 bytes video width + 2 bytes video height
130
+ if (done || value.byteLength !== 64 + 2 + 2) {
131
+ throw new Error('Unexpected response from server');
132
+ }
133
+ reader.releaseLock();
134
+ }
135
+
136
+ return [videoStream, controlStream];
137
+ }
138
+
139
+ public override dispose() {
140
+ // Don't await this!
141
+ // `reverse.remove`'s response will never arrive
142
+ // before we read all pending data from `videoStream`
143
+ this.adb.reverse.remove(this.address);
144
+ }
145
+ }
@@ -0,0 +1,3 @@
1
+ export * from './options/index.js';
2
+ export * from './client.js';
3
+ export * from './connection.js';
@@ -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,3 @@
1
+ export * from './1_16.js';
2
+ export * from './1_22.js';
3
+ export * from './types.js';