@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.
- package/LICENSE +21 -0
- package/README.md +351 -0
- package/assets/scrcpy-server-v4.0.jar +0 -0
- package/dist/backend/adb/scrcpy-adb-client.d.ts +13 -0
- package/dist/backend/adb/scrcpy-adb-client.js +49 -0
- package/dist/backend/index.d.ts +6 -0
- package/dist/backend/index.js +6 -0
- package/dist/backend/io/buffered-stream-reader.d.ts +10 -0
- package/dist/backend/io/buffered-stream-reader.js +40 -0
- package/dist/backend/scrcpy-backend.d.ts +42 -0
- package/dist/backend/scrcpy-backend.js +250 -0
- package/dist/backend/server/options.d.ts +26 -0
- package/dist/backend/server/options.js +31 -0
- package/dist/backend/server/server-command.d.ts +3 -0
- package/dist/backend/server/server-command.js +24 -0
- package/dist/backend/server/socket-name.d.ts +2 -0
- package/dist/backend/server/socket-name.js +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/protocol/core/binary.d.ts +5 -0
- package/dist/protocol/core/binary.js +23 -0
- package/dist/protocol/core/types.d.ts +15 -0
- package/dist/protocol/core/types.js +1 -0
- package/dist/protocol/index.d.ts +3 -0
- package/dist/protocol/index.js +3 -0
- package/dist/protocol/registry.d.ts +3 -0
- package/dist/protocol/registry.js +16 -0
- package/dist/protocol/v4_0/codecs.d.ts +14 -0
- package/dist/protocol/v4_0/codecs.js +22 -0
- package/dist/protocol/v4_0/control-message-types.d.ts +115 -0
- package/dist/protocol/v4_0/control-message-types.js +47 -0
- package/dist/protocol/v4_0/control-message.d.ts +2 -0
- package/dist/protocol/v4_0/control-message.js +149 -0
- package/dist/protocol/v4_0/device-message.d.ts +17 -0
- package/dist/protocol/v4_0/device-message.js +33 -0
- package/dist/protocol/v4_0/frame-header.d.ts +19 -0
- package/dist/protocol/v4_0/frame-header.js +29 -0
- package/dist/protocol/v4_0/index.d.ts +9 -0
- package/dist/protocol/v4_0/index.js +21 -0
- package/dist/protocol/v4_0/server-options.d.ts +19 -0
- package/dist/protocol/v4_0/server-options.js +38 -0
- package/dist/protocol/v4_0/string-payload.d.ts +2 -0
- package/dist/protocol/v4_0/string-payload.js +23 -0
- package/dist/service/index.d.ts +3 -0
- package/dist/service/index.js +3 -0
- package/dist/service/packet-queue-subscriber.d.ts +11 -0
- package/dist/service/packet-queue-subscriber.js +56 -0
- package/dist/service/scrcpy-stream-service.d.ts +26 -0
- package/dist/service/scrcpy-stream-service.js +178 -0
- package/dist/service/types.d.ts +25 -0
- package/dist/service/types.js +14 -0
- package/dist/snapshot/ffmpeg-snapshot-cache.d.ts +34 -0
- package/dist/snapshot/ffmpeg-snapshot-cache.js +195 -0
- package/dist/snapshot/index.d.ts +2 -0
- package/dist/snapshot/index.js +2 -0
- package/dist/snapshot/types.d.ts +15 -0
- package/dist/snapshot/types.js +1 -0
- package/dist/websocket/binary-packet.d.ts +2 -0
- package/dist/websocket/binary-packet.js +33 -0
- package/dist/websocket/control-json.d.ts +2 -0
- package/dist/websocket/control-json.js +129 -0
- package/dist/websocket/index.d.ts +4 -0
- package/dist/websocket/index.js +4 -0
- package/dist/websocket/scrcpy-websocket-bridge.d.ts +10 -0
- package/dist/websocket/scrcpy-websocket-bridge.js +75 -0
- package/dist/websocket/types.d.ts +9 -0
- package/dist/websocket/types.js +4 -0
- package/package.json +90 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 9b9387
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# @9b9387/android-stream-scrcpy
|
|
2
|
+
|
|
3
|
+
TypeScript scrcpy client library for Node.js and Electron. It starts `scrcpy-server` through `@devicefarmer/adbkit`, reads the video/audio/control sockets, and exposes media packets as Web Standards binary types (`Uint8Array`).
|
|
4
|
+
|
|
5
|
+
The bundled protocol implementation targets scrcpy `4.0`. Protocol code is versioned so future versions, such as `4.1`, can be added side by side without rewriting the backend or service layers.
|
|
6
|
+
|
|
7
|
+
> Runtime note: the root package is Node-only. Use it from a Node.js process, an Electron main process, a Next.js Node runtime route/server, or another long-lived backend process. Do not import the root package from browser code, Next.js Client Components, or Edge Runtime handlers.
|
|
8
|
+
|
|
9
|
+
## Project Structure
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
node-lib/
|
|
13
|
+
assets/
|
|
14
|
+
scrcpy-server-v4.0.jar
|
|
15
|
+
examples/
|
|
16
|
+
demo.ts
|
|
17
|
+
server.ts
|
|
18
|
+
src/
|
|
19
|
+
backend/
|
|
20
|
+
adb/
|
|
21
|
+
scrcpy-adb-client.ts
|
|
22
|
+
io/
|
|
23
|
+
buffered-stream-reader.ts
|
|
24
|
+
server/
|
|
25
|
+
options.ts
|
|
26
|
+
server-command.ts
|
|
27
|
+
socket-name.ts
|
|
28
|
+
scrcpy-backend.ts
|
|
29
|
+
index.ts
|
|
30
|
+
protocol/
|
|
31
|
+
core/
|
|
32
|
+
binary.ts
|
|
33
|
+
types.ts
|
|
34
|
+
registry.ts
|
|
35
|
+
v4_0/
|
|
36
|
+
codecs.ts
|
|
37
|
+
control-message.ts
|
|
38
|
+
control-message-types.ts
|
|
39
|
+
device-message.ts
|
|
40
|
+
frame-header.ts
|
|
41
|
+
server-options.ts
|
|
42
|
+
string-payload.ts
|
|
43
|
+
index.ts
|
|
44
|
+
index.ts
|
|
45
|
+
service/
|
|
46
|
+
packet-queue-subscriber.ts
|
|
47
|
+
scrcpy-stream-service.ts
|
|
48
|
+
types.ts
|
|
49
|
+
index.ts
|
|
50
|
+
snapshot/
|
|
51
|
+
ffmpeg-snapshot-cache.ts
|
|
52
|
+
types.ts
|
|
53
|
+
index.ts
|
|
54
|
+
websocket/
|
|
55
|
+
binary-packet.ts
|
|
56
|
+
control-json.ts
|
|
57
|
+
scrcpy-websocket-bridge.ts
|
|
58
|
+
types.ts
|
|
59
|
+
index.ts
|
|
60
|
+
index.ts
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Module Responsibilities
|
|
64
|
+
|
|
65
|
+
- `src/protocol/`: scrcpy wire protocol implementation. `core/` contains shared binary helpers and protocol interfaces; `registry.ts` selects a protocol adapter by version; `v4_0/` contains scrcpy 4.0 frame, control, device, codec, and server-option logic.
|
|
66
|
+
- `src/backend/`: adbkit-only device integration. `adb/` wraps adbkit operations, `io/` contains socket reading utilities, `server/` builds and normalizes scrcpy server launch details, and `scrcpy-backend.ts` orchestrates the streaming connection.
|
|
67
|
+
- `src/service/`: public stream service. It manages state, caches decoder config/session snapshots, and exposes `for await...of` media packet subscriptions.
|
|
68
|
+
- `src/snapshot/`: optional Node-side screenshot cache. It pipes H.264 packets into ffmpeg and stores the latest JPEG frame.
|
|
69
|
+
- `src/websocket/`: optional `ws` bridge for browser clients. It is exported from the `./websocket` subpath and is not part of the root API.
|
|
70
|
+
- `examples/`: runnable examples kept outside the library build.
|
|
71
|
+
|
|
72
|
+
## Install
|
|
73
|
+
|
|
74
|
+
Install the package in your application:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install @9b9387/android-stream-scrcpy
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Core runtime dependency is `@devicefarmer/adbkit`. The library does not call the `adb` system command directly.
|
|
81
|
+
|
|
82
|
+
If your application uses the optional WebSocket bridge, install `ws` in the application:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npm install ws
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If your application uses the optional screenshot cache, make sure `ffmpeg` is available on `PATH`, or pass `ffmpegPath` to `FfmpegSnapshotCache`.
|
|
89
|
+
|
|
90
|
+
## Build From Source
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
cd node-lib
|
|
94
|
+
npm install
|
|
95
|
+
npm run build
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Package Entrypoints
|
|
99
|
+
|
|
100
|
+
- `@9b9387/android-stream-scrcpy`: Node-only service/backend API.
|
|
101
|
+
- `@9b9387/android-stream-scrcpy/protocol`: protocol adapters and types. This is the safest entrypoint for code that only needs scrcpy protocol constants, codecs, and message types.
|
|
102
|
+
- `@9b9387/android-stream-scrcpy/websocket`: optional Node-only `ws` bridge.
|
|
103
|
+
- `@9b9387/android-stream-scrcpy/snapshot`: optional Node-only ffmpeg screenshot cache.
|
|
104
|
+
|
|
105
|
+
The package is ESM-only and requires Node.js 20 or newer.
|
|
106
|
+
|
|
107
|
+
## Run Examples
|
|
108
|
+
|
|
109
|
+
Run the console stream demo:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm run example:demo
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Run the browser demo server:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npm run example:server
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The server serves the existing demo page from the repository root:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
../web_demo/scrcpy.html
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Then open:
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
http://localhost:8000
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
You can also request the page directly:
|
|
134
|
+
|
|
135
|
+
```text
|
|
136
|
+
http://localhost:8000/scrcpy.html
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Choose a specific connected Android device by serial:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
ANDROID_SERIAL=<device-serial> npm run example:server
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`ADB_SERIAL` is also supported for compatibility with existing local workflows.
|
|
146
|
+
|
|
147
|
+
Enable the optional Node-side screenshot cache for the browser demo:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
SNAPSHOT_ENABLED=1 SNAPSHOT_FPS=2 SNAPSHOT_JPEG_QUALITY=85 npm run example:server
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
This requires `ffmpeg` on `PATH` by default. Set `FFMPEG_PATH=/path/to/ffmpeg`
|
|
154
|
+
if the binary lives elsewhere. When enabled, the demo exposes:
|
|
155
|
+
|
|
156
|
+
```text
|
|
157
|
+
http://localhost:8000/screenshot.jpg
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The `scrcpy.html` page also shows a `截图` button that downloads the latest
|
|
161
|
+
cached JPEG. The cache is produced from the scrcpy H.264 stream in Node; it does
|
|
162
|
+
not call ADB `screencap` and does not require a browser canvas.
|
|
163
|
+
|
|
164
|
+
## Integrate The Core Library
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { ScrcpyStreamService } from "@9b9387/android-stream-scrcpy";
|
|
168
|
+
|
|
169
|
+
const service = new ScrcpyStreamService({
|
|
170
|
+
protocolVersion: "4.0",
|
|
171
|
+
maxSize: 1080,
|
|
172
|
+
video: true,
|
|
173
|
+
audio: true,
|
|
174
|
+
control: true,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const meta = await service.start();
|
|
178
|
+
console.log(`Connected to ${meta.deviceName}`);
|
|
179
|
+
|
|
180
|
+
for await (const packet of service.subscribe()) {
|
|
181
|
+
// packet.kind: "video" | "audio" | "session"
|
|
182
|
+
// packet.payload: Uint8Array
|
|
183
|
+
if (packet.kind === "video") {
|
|
184
|
+
// Feed H.264/H.265/AV1 bytes into your decoder.
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Stop streaming when your application shuts down:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
service.stop();
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Send Control Messages
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import {
|
|
199
|
+
ControlMessageType,
|
|
200
|
+
KEY_ACTION_DOWN,
|
|
201
|
+
ScrcpyStreamService,
|
|
202
|
+
} from "@9b9387/android-stream-scrcpy";
|
|
203
|
+
|
|
204
|
+
const service = new ScrcpyStreamService();
|
|
205
|
+
await service.start();
|
|
206
|
+
|
|
207
|
+
service.sendControlMessage({
|
|
208
|
+
type: ControlMessageType.INJECT_KEYCODE,
|
|
209
|
+
action: KEY_ACTION_DOWN,
|
|
210
|
+
keycode: 26,
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The scrcpy 4.0 adapter includes key, text, touch, scroll, clipboard, UHID, app start, display power, camera, and display resize control messages.
|
|
215
|
+
|
|
216
|
+
## Integrate The WebSocket Bridge
|
|
217
|
+
|
|
218
|
+
The WebSocket bridge is optional and imported from a subpath:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { createServer } from "node:http";
|
|
222
|
+
import { ScrcpyStreamService } from "@9b9387/android-stream-scrcpy";
|
|
223
|
+
import { ScrcpyWebSocketBridge } from "@9b9387/android-stream-scrcpy/websocket";
|
|
224
|
+
|
|
225
|
+
const service = new ScrcpyStreamService({ protocolVersion: "4.0" });
|
|
226
|
+
const server = createServer();
|
|
227
|
+
const bridge = new ScrcpyWebSocketBridge(service, { server });
|
|
228
|
+
|
|
229
|
+
await service.start();
|
|
230
|
+
server.listen(8000);
|
|
231
|
+
|
|
232
|
+
process.on("SIGINT", () => {
|
|
233
|
+
bridge.close();
|
|
234
|
+
service.stop();
|
|
235
|
+
server.close();
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The bridge sends an initial JSON `init` message, then binary media packets with a 16-byte header followed by the media payload.
|
|
240
|
+
|
|
241
|
+
## Integrate The Screenshot Cache
|
|
242
|
+
|
|
243
|
+
The screenshot cache is optional and imported from the `./snapshot` subpath:
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { ScrcpyStreamService } from "@9b9387/android-stream-scrcpy";
|
|
247
|
+
import { FfmpegSnapshotCache } from "@9b9387/android-stream-scrcpy/snapshot";
|
|
248
|
+
|
|
249
|
+
const service = new ScrcpyStreamService({ videoCodec: "h264" });
|
|
250
|
+
const snapshots = new FfmpegSnapshotCache(service, {
|
|
251
|
+
enabled: true,
|
|
252
|
+
fps: 2,
|
|
253
|
+
quality: 85,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
await service.start();
|
|
257
|
+
snapshots.start();
|
|
258
|
+
|
|
259
|
+
const latest = snapshots.latest();
|
|
260
|
+
if (latest) {
|
|
261
|
+
// latest.contentType === "image/jpeg"
|
|
262
|
+
// latest.data is a Buffer containing JPEG bytes.
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The first implementation supports H.264 input and JPEG output. The configured
|
|
267
|
+
`fps` limits how often ffmpeg emits JPEG frames; ffmpeg still receives the
|
|
268
|
+
continuous H.264 stream so inter-frame decoding remains correct.
|
|
269
|
+
|
|
270
|
+
## Next.js And Electron Usage
|
|
271
|
+
|
|
272
|
+
### Next.js
|
|
273
|
+
|
|
274
|
+
Use this package only from the server side of a self-hosted or long-lived Node.js runtime:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
export const runtime = "nodejs";
|
|
278
|
+
|
|
279
|
+
import { ScrcpyStreamService } from "@9b9387/android-stream-scrcpy";
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Do not import the root package from Client Components, browser bundles, Middleware,
|
|
283
|
+
or Edge Runtime route handlers. The backend opens ADB sockets and long-lived media
|
|
284
|
+
streams, so a custom Node.js server or separate local daemon is usually a better
|
|
285
|
+
fit than a short-lived serverless function.
|
|
286
|
+
|
|
287
|
+
### Electron
|
|
288
|
+
|
|
289
|
+
Create and manage `ScrcpyStreamService` in the main process. Renderer processes
|
|
290
|
+
should communicate with the main process through IPC or connect to the optional
|
|
291
|
+
WebSocket bridge.
|
|
292
|
+
|
|
293
|
+
When packaging Electron apps, make sure `assets/scrcpy-server-v4.0.jar` is copied
|
|
294
|
+
as a runtime resource. If your packager moves or packs assets into an archive,
|
|
295
|
+
pass an explicit `serverJarPath` to `ScrcpyStreamService`. If screenshot caching
|
|
296
|
+
is enabled, also ship `ffmpeg` yourself or pass `ffmpegPath`.
|
|
297
|
+
|
|
298
|
+
## Protocol Versioning
|
|
299
|
+
|
|
300
|
+
`protocolVersion` defaults to `"4.0"`:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
const service = new ScrcpyStreamService({ protocolVersion: "4.0" });
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
To add a future scrcpy version:
|
|
307
|
+
|
|
308
|
+
1. Add a new adapter under `src/protocol/`.
|
|
309
|
+
2. Register it in `src/protocol/registry.ts`.
|
|
310
|
+
3. Add the matching `scrcpy-server` jar to `assets/`.
|
|
311
|
+
4. Add fixed-byte protocol tests for changed behavior.
|
|
312
|
+
|
|
313
|
+
The backend depends on the protocol interface, so frame parsing, control serialization, server options, and socket naming can evolve per version.
|
|
314
|
+
|
|
315
|
+
## Development Commands
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
npm run format
|
|
319
|
+
npm run lint
|
|
320
|
+
npm run typecheck
|
|
321
|
+
npm test
|
|
322
|
+
npm run build
|
|
323
|
+
npm pack --dry-run
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
End-to-end streaming requires a connected Android device. Unit tests cover protocol serialization/parsing, backend option normalization, subscriber queue behavior, and WebSocket packet encoding.
|
|
327
|
+
|
|
328
|
+
## Publish
|
|
329
|
+
|
|
330
|
+
Before publishing, make sure you are logged in to the npm account that owns the
|
|
331
|
+
`@9b9387` scope:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
npm whoami
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Run the release checks and inspect the tarball contents:
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
npm run typecheck
|
|
341
|
+
npm run test
|
|
342
|
+
npm run lint
|
|
343
|
+
npm run build
|
|
344
|
+
npm pack --dry-run
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Publish the public scoped package:
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
npm publish --access public
|
|
351
|
+
```
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as net from "node:net";
|
|
2
|
+
export interface SelectedAdbDevice {
|
|
3
|
+
serial: string;
|
|
4
|
+
device: any;
|
|
5
|
+
}
|
|
6
|
+
export declare class ScrcpyAdbClient {
|
|
7
|
+
private adb;
|
|
8
|
+
selectDevice(deviceSerial?: string): Promise<SelectedAdbDevice>;
|
|
9
|
+
pushServerJar(device: any, localPath: string, remotePath: string, timeoutMs: number): Promise<void>;
|
|
10
|
+
startServer(device: any, command: string): Promise<any>;
|
|
11
|
+
openLocal(device: any, socketName: string): Promise<net.Socket>;
|
|
12
|
+
private waitForTransfer;
|
|
13
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import adbkit from "@devicefarmer/adbkit";
|
|
2
|
+
const Adb = adbkit.default || adbkit;
|
|
3
|
+
export class ScrcpyAdbClient {
|
|
4
|
+
adb = Adb.createClient();
|
|
5
|
+
async selectDevice(deviceSerial) {
|
|
6
|
+
const devices = await this.adb.listDevices();
|
|
7
|
+
if (devices.length === 0)
|
|
8
|
+
throw new Error("no ADB device found");
|
|
9
|
+
const serial = deviceSerial || devices[0].id;
|
|
10
|
+
return {
|
|
11
|
+
serial,
|
|
12
|
+
device: this.adb.getDevice(serial),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
async pushServerJar(device, localPath, remotePath, timeoutMs) {
|
|
16
|
+
const transfer = await device.push(localPath, remotePath);
|
|
17
|
+
await this.waitForTransfer(transfer, timeoutMs);
|
|
18
|
+
}
|
|
19
|
+
async startServer(device, command) {
|
|
20
|
+
return device.shell(command);
|
|
21
|
+
}
|
|
22
|
+
async openLocal(device, socketName) {
|
|
23
|
+
return device.openLocal(`localabstract:${socketName}`);
|
|
24
|
+
}
|
|
25
|
+
async waitForTransfer(transfer, timeoutMs) {
|
|
26
|
+
await new Promise((resolve, reject) => {
|
|
27
|
+
const timeout = setTimeout(() => {
|
|
28
|
+
cleanup();
|
|
29
|
+
reject(new Error(`server push timeout after ${timeoutMs}ms`));
|
|
30
|
+
}, timeoutMs);
|
|
31
|
+
const onEnd = () => {
|
|
32
|
+
clearTimeout(timeout);
|
|
33
|
+
cleanup();
|
|
34
|
+
resolve();
|
|
35
|
+
};
|
|
36
|
+
const onError = (e) => {
|
|
37
|
+
clearTimeout(timeout);
|
|
38
|
+
cleanup();
|
|
39
|
+
reject(e);
|
|
40
|
+
};
|
|
41
|
+
const cleanup = () => {
|
|
42
|
+
transfer.removeListener("end", onEnd);
|
|
43
|
+
transfer.removeListener("error", onError);
|
|
44
|
+
};
|
|
45
|
+
transfer.once("end", onEnd);
|
|
46
|
+
transfer.once("error", onError);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as net from "node:net";
|
|
2
|
+
export declare class BufferedStreamReader {
|
|
3
|
+
private buffer;
|
|
4
|
+
private waiters;
|
|
5
|
+
private ended;
|
|
6
|
+
constructor(stream: net.Socket);
|
|
7
|
+
readExact(n: number): Promise<Uint8Array>;
|
|
8
|
+
private rejectAll;
|
|
9
|
+
private checkWaiters;
|
|
10
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { concatBytes } from "../../protocol/core/binary.js";
|
|
2
|
+
export class BufferedStreamReader {
|
|
3
|
+
buffer = new Uint8Array(0);
|
|
4
|
+
waiters = [];
|
|
5
|
+
ended = false;
|
|
6
|
+
constructor(stream) {
|
|
7
|
+
stream.on("data", (chunk) => {
|
|
8
|
+
this.buffer = concatBytes([this.buffer, chunk]);
|
|
9
|
+
this.checkWaiters();
|
|
10
|
+
});
|
|
11
|
+
stream.on("error", (err) => {
|
|
12
|
+
this.rejectAll(err);
|
|
13
|
+
});
|
|
14
|
+
stream.on("end", () => {
|
|
15
|
+
this.ended = true;
|
|
16
|
+
this.rejectAll(new Error("stream ended"));
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
readExact(n) {
|
|
20
|
+
if (this.ended)
|
|
21
|
+
return Promise.reject(new Error("stream already ended"));
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
this.waiters.push({ n, resolve, reject });
|
|
24
|
+
this.checkWaiters();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
rejectAll(err) {
|
|
28
|
+
this.waiters.forEach((waiter) => waiter.reject(err));
|
|
29
|
+
this.waiters = [];
|
|
30
|
+
}
|
|
31
|
+
checkWaiters() {
|
|
32
|
+
while (this.waiters.length > 0 &&
|
|
33
|
+
this.buffer.byteLength >= this.waiters[0].n) {
|
|
34
|
+
const waiter = this.waiters.shift();
|
|
35
|
+
const chunk = this.buffer.subarray(0, waiter.n);
|
|
36
|
+
this.buffer = this.buffer.subarray(waiter.n);
|
|
37
|
+
waiter.resolve(chunk);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { AudioCodec, ControlMessage, VideoCodec } from "../protocol/index.js";
|
|
3
|
+
import { ScrcpyAdbClient } from "./adb/scrcpy-adb-client.js";
|
|
4
|
+
import { ScrcpyBackendOptions } from "./server/options.js";
|
|
5
|
+
export interface StreamMeta {
|
|
6
|
+
deviceName: string;
|
|
7
|
+
videoCodec?: VideoCodec;
|
|
8
|
+
audioCodec?: AudioCodec;
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class ScrcpyBackend extends EventEmitter {
|
|
13
|
+
private readonly options;
|
|
14
|
+
private readonly adbClient;
|
|
15
|
+
private device;
|
|
16
|
+
private serverStream;
|
|
17
|
+
private videoSocket;
|
|
18
|
+
private audioSocket;
|
|
19
|
+
private controlSocket;
|
|
20
|
+
private videoReader;
|
|
21
|
+
private audioReader;
|
|
22
|
+
private controlReader;
|
|
23
|
+
private running;
|
|
24
|
+
private meta;
|
|
25
|
+
constructor(options?: ScrcpyBackendOptions, adbClient?: ScrcpyAdbClient);
|
|
26
|
+
start(): Promise<StreamMeta>;
|
|
27
|
+
stop(): void;
|
|
28
|
+
sendControlMessage(msg: ControlMessage): void;
|
|
29
|
+
private spawnServer;
|
|
30
|
+
private waitForServerStartProbe;
|
|
31
|
+
private connectSockets;
|
|
32
|
+
private connectSocket;
|
|
33
|
+
private setSocket;
|
|
34
|
+
private handshake;
|
|
35
|
+
private socketReadExact;
|
|
36
|
+
private startWorkerLoops;
|
|
37
|
+
private workerLoop;
|
|
38
|
+
private handleVideoHeader;
|
|
39
|
+
private handleAudioHeader;
|
|
40
|
+
private controlWorkerLoop;
|
|
41
|
+
private cleanup;
|
|
42
|
+
}
|