@9b9387/android-stream-scrcpy
v0.1.2
Published
Versioned scrcpy protocol client for Node.js/Electron
Downloads
80
Maintainers
Readme
@9b9387/android-stream-scrcpy
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).
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.
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.
Project Structure
node-lib/
assets/
scrcpy-server-v4.0.jar
examples/
demo.ts
server.ts
src/
backend/
adb/
scrcpy-adb-client.ts
io/
buffered-stream-reader.ts
server/
options.ts
server-command.ts
socket-name.ts
scrcpy-backend.ts
index.ts
protocol/
core/
binary.ts
types.ts
registry.ts
v4_0/
codecs.ts
control-message.ts
control-message-types.ts
device-message.ts
frame-header.ts
server-options.ts
string-payload.ts
index.ts
index.ts
service/
packet-queue-subscriber.ts
scrcpy-stream-service.ts
types.ts
index.ts
snapshot/
ffmpeg-snapshot-cache.ts
types.ts
index.ts
websocket/
binary-packet.ts
control-json.ts
scrcpy-websocket-bridge.ts
types.ts
index.ts
index.tsModule Responsibilities
src/protocol/: scrcpy wire protocol implementation.core/contains shared binary helpers and protocol interfaces;registry.tsselects a protocol adapter by version;v4_0/contains scrcpy 4.0 frame, control, device, codec, and server-option logic.src/backend/: adbkit-only device integration.adb/wraps adbkit operations,io/contains socket reading utilities,server/builds and normalizes scrcpy server launch details, andscrcpy-backend.tsorchestrates the streaming connection.src/service/: public stream service. It manages state, caches decoder config/session snapshots, and exposesfor await...ofmedia packet subscriptions.src/snapshot/: optional Node-side screenshot cache. It pipes H.264 packets into ffmpeg and stores the latest JPEG frame.src/websocket/: optionalwsbridge for browser clients. It is exported from the./websocketsubpath and is not part of the root API.examples/: runnable examples kept outside the library build.
Install
Install the package in your application:
npm install @9b9387/android-stream-scrcpyCore runtime dependency is @devicefarmer/adbkit. The library does not call the adb system command directly.
If your application uses the optional WebSocket bridge, install ws in the application:
npm install wsIf your application uses the optional screenshot cache, make sure ffmpeg is available on PATH, or pass ffmpegPath to FfmpegSnapshotCache.
Build From Source
cd node-lib
npm install
npm run buildPackage Entrypoints
@9b9387/android-stream-scrcpy: Node-only service/backend API.@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.@9b9387/android-stream-scrcpy/websocket: optional Node-onlywsbridge.@9b9387/android-stream-scrcpy/snapshot: optional Node-only ffmpeg screenshot cache.
The package is ESM-only and requires Node.js 20 or newer.
Run Examples
Run the console stream demo:
npm run example:demoRun the browser demo server:
npm run example:serverThe server serves the existing demo page from the repository root:
../web_demo/scrcpy.htmlThen open:
http://localhost:8000You can also request the page directly:
http://localhost:8000/scrcpy.htmlChoose a specific connected Android device by serial:
ANDROID_SERIAL=<device-serial> npm run example:serverADB_SERIAL is also supported for compatibility with existing local workflows.
Enable the optional Node-side screenshot cache for the browser demo:
SNAPSHOT_ENABLED=1 SNAPSHOT_FPS=2 SNAPSHOT_JPEG_QUALITY=85 npm run example:serverThis requires ffmpeg on PATH by default. Set FFMPEG_PATH=/path/to/ffmpeg
if the binary lives elsewhere. When enabled, the demo exposes:
http://localhost:8000/screenshot.jpgThe scrcpy.html page also shows a 截图 button that downloads the latest
cached JPEG. The cache is produced from the scrcpy H.264 stream in Node; it does
not call ADB screencap and does not require a browser canvas.
Integrate The Core Library
import { ScrcpyStreamService } from "@9b9387/android-stream-scrcpy";
const service = new ScrcpyStreamService({
protocolVersion: "4.0",
maxSize: 1080,
video: true,
audio: true,
control: true,
});
const meta = await service.start();
console.log(`Connected to ${meta.deviceName}`);
for await (const packet of service.subscribe()) {
// packet.kind: "video" | "audio" | "session"
// packet.payload: Uint8Array
if (packet.kind === "video") {
// Feed H.264/H.265/AV1 bytes into your decoder.
}
}Stop streaming when your application shuts down:
service.stop();Send Control Messages
import {
ControlMessageType,
KEY_ACTION_DOWN,
ScrcpyStreamService,
} from "@9b9387/android-stream-scrcpy";
const service = new ScrcpyStreamService();
await service.start();
service.sendControlMessage({
type: ControlMessageType.INJECT_KEYCODE,
action: KEY_ACTION_DOWN,
keycode: 26,
});The scrcpy 4.0 adapter includes key, text, touch, scroll, clipboard, UHID, app start, display power, camera, and display resize control messages.
Integrate The WebSocket Bridge
The WebSocket bridge is optional and imported from a subpath:
import { createServer } from "node:http";
import { ScrcpyStreamService } from "@9b9387/android-stream-scrcpy";
import { ScrcpyWebSocketBridge } from "@9b9387/android-stream-scrcpy/websocket";
const service = new ScrcpyStreamService({ protocolVersion: "4.0" });
const server = createServer();
const bridge = new ScrcpyWebSocketBridge(service, { server });
await service.start();
server.listen(8000);
process.on("SIGINT", () => {
bridge.close();
service.stop();
server.close();
});The bridge sends an initial JSON init message, then binary media packets with a 16-byte header followed by the media payload.
Integrate The Screenshot Cache
The screenshot cache is optional and imported from the ./snapshot subpath:
import { ScrcpyStreamService } from "@9b9387/android-stream-scrcpy";
import { FfmpegSnapshotCache } from "@9b9387/android-stream-scrcpy/snapshot";
const service = new ScrcpyStreamService({ videoCodec: "h264" });
const snapshots = new FfmpegSnapshotCache(service, {
enabled: true,
fps: 2,
quality: 85,
// Optional robustness knobs (defaults shown):
drainTimeoutMs: 5000, // tear down ffmpeg if stdin stalls this long
killTimeoutMs: 2000, // SIGTERM grace before SIGKILL on stop()
staleTimeoutMs: 10000, // emit "stale" when no new frame for this long (0 = off)
maxStdoutBytes: 16 * 1024 * 1024, // cap on the JPEG reassembly buffer
});
await service.start();
snapshots.start(); // must be called after service.start()
// Non-blocking read of the most recent frame (may be null before the first):
const latest = snapshots.latest();
// Or wait up to N ms for the first/next frame instead of busy-polling 404s:
try {
const shot = await snapshots.waitForFresh(3000);
// shot.contentType === "image/jpeg"; shot.data is a JPEG Buffer
} catch {
// no frame within the timeout — surface a 503 / "warming up" to the client
}
// Observe a stalled stream (device asleep, encoder paused, etc.):
snapshots.on("stale", ({ ageMs }) => {
console.warn(`no new snapshot for ${ageMs}ms`);
});The first implementation supports H.264 input and JPEG output. The configured
fps limits how often ffmpeg emits JPEG frames; ffmpeg still receives the
continuous H.264 stream so inter-frame decoding remains correct.
Lifecycle & resource safety
FfmpegSnapshotCachelistens to the service state: when the service stops or errors, the ffmpeg process is killed automatically, so it never lingers as an orphan across reconnects. You should still callsnapshots.stop()explicitly when you tear a session down yourself.waitForFresh()resolves immediately if a frame is cached, otherwise it resolves on the next frame or rejects after the timeout — use it in HTTP handlers so a single request never hangs and clients never busy-poll.- The WebSocket bridge applies backpressure: a slow client's droppable video
frames are dropped (config/keyframe/session packets are preserved) once its
outbound buffer exceeds
maxBufferedBytes(8MiB default), preventing unbounded memory growth. Tune vianew ScrcpyWebSocketBridge(service, { maxBufferedBytes }).
For multi-device streaming and per-device screenshot HTTP routes, see docs/multi-device-screenshot.md (Web integration guide, 中文).
Next.js And Electron Usage
Next.js
Use this package only from the server side of a self-hosted or long-lived Node.js runtime:
export const runtime = "nodejs";
import { ScrcpyStreamService } from "@9b9387/android-stream-scrcpy";Do not import the root package from Client Components, browser bundles, Middleware, or Edge Runtime route handlers. The backend opens ADB sockets and long-lived media streams, so a custom Node.js server or separate local daemon is usually a better fit than a short-lived serverless function.
Electron
Create and manage ScrcpyStreamService in the main process. Renderer processes
should communicate with the main process through IPC or connect to the optional
WebSocket bridge.
When packaging Electron apps, make sure assets/scrcpy-server-v4.0.jar is copied
as a runtime resource. If your packager moves or packs assets into an archive,
pass an explicit serverJarPath to ScrcpyStreamService. If screenshot caching
is enabled, also ship ffmpeg yourself or pass ffmpegPath.
Protocol Versioning
protocolVersion defaults to "4.0":
const service = new ScrcpyStreamService({ protocolVersion: "4.0" });To add a future scrcpy version:
- Add a new adapter under
src/protocol/. - Register it in
src/protocol/registry.ts. - Add the matching
scrcpy-serverjar toassets/. - Add fixed-byte protocol tests for changed behavior.
The backend depends on the protocol interface, so frame parsing, control serialization, server options, and socket naming can evolve per version.
Development Commands
npm run format
npm run lint
npm run typecheck
npm test
npm run build
npm pack --dry-runEnd-to-end streaming requires a connected Android device. Unit tests cover protocol serialization/parsing, backend option normalization, subscriber queue behavior, and WebSocket packet encoding.
Publish
Before publishing, make sure you are logged in to the npm account that owns the
@9b9387 scope:
npm whoamiRun the release checks and inspect the tarball contents:
npm run typecheck
npm run test
npm run lint
npm run build
npm pack --dry-runPublish the public scoped package:
npm publish --access public