{"_id":"@9b9387/android-stream-scrcpy","_rev":"3-98f59a264300313674fcf7212e4323ae","name":"@9b9387/android-stream-scrcpy","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@9b9387/android-stream-scrcpy","version":"0.1.0","keywords":["scrcpy","android","adb","streaming","electron","nextjs","typescript"],"license":"MIT","_id":"@9b9387/android-stream-scrcpy@0.1.0","maintainers":[{"name":"9b9387","email":"9b9387@gmail.com"}],"homepage":"https://github.com/9b9387/android-stream/tree/main/node-lib#readme","bugs":{"url":"https://github.com/9b9387/android-stream/issues"},"dist":{"shasum":"c25aad91554974bcdeecce6d6c1afc92ec092766","tarball":"https://registry.npmjs.org/@9b9387/android-stream-scrcpy/-/android-stream-scrcpy-0.1.0.tgz","fileCount":68,"integrity":"sha512-KzoKE/KxampnPLICWdWPLCt/xbPRhTwxFhFhvBX6z1vMoB9Qbm86Fy/l71NLOLkDwU9D3+u2fCe21oUcwsysBw==","signatures":[{"sig":"MEYCIQCpHorlzYqCd6H1uRPkCQPzTwcNRKE835zpvbyVqXVe3QIhAM5f5T/whi0J4CnPI73Va+2Vb3/AZaWi77TDqPrPLomb","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":818298},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./protocol":{"types":"./dist/protocol/index.d.ts","import":"./dist/protocol/index.js"},"./snapshot":{"types":"./dist/snapshot/index.d.ts","import":"./dist/snapshot/index.js"},"./websocket":{"types":"./dist/websocket/index.d.ts","import":"./dist/websocket/index.js"},"./package.json":"./package.json"},"gitHead":"9ca56282887febbaf0ac65d7a7dabfa61afb1c1b","scripts":{"lint":"prettier --check .","test":"vitest run","build":"npm run clean && tsc","clean":"node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","format":"prettier --write .","prepack":"npm run build","typecheck":"tsc --noEmit","example:demo":"npm run build && node --loader ts-node/esm examples/demo.ts","example:server":"npm run build && node --loader ts-node/esm examples/server.ts","prepublishOnly":"npm run typecheck && npm run test && npm run lint && npm run build"},"_npmUser":{"name":"9b9387","email":"9b9387@gmail.com"},"repository":{"url":"git+ssh://git@github.com/9b9387/android-stream.git","type":"git","directory":"node-lib"},"_npmVersion":"11.12.1","description":"Versioned scrcpy protocol client for Node.js/Electron","directories":{},"_nodeVersion":"25.9.0","dependencies":{"@devicefarmer/adbkit":"^3.2.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"ws":"^8.20.1","vitest":"^4.1.6","ts-node":"^10.9.2","prettier":"^3.8.3","@types/ws":"^8.18.1","typescript":"^5.9.3","@types/node":"^20.11.0"},"peerDependencies":{"ws":"^8.16.0"},"peerDependenciesMeta":{"ws":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/android-stream-scrcpy_0.1.0_1779160275014_0.3555259843757752","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@9b9387/android-stream-scrcpy","version":"0.1.1","_id":"@9b9387/android-stream-scrcpy@0.1.1","maintainers":[{"name":"9b9387","email":"9b9387@gmail.com"}],"dist":{"shasum":"37862cbe068b51a65ad9af3fa65441f21088d454","tarball":"https://registry.npmjs.org/@9b9387/android-stream-scrcpy/-/android-stream-scrcpy-0.1.1.tgz","fileCount":67,"integrity":"sha512-SOCCD81YWLaFPAVY5VIWbVodHTrJg2XYYzWiYyLfrn4owsSqVoBBzDVMFpGitHkinEnysEDoSDg3dRvNrpL3UQ==","signatures":[{"sig":"MEYCIQCRUrPdsa/lv+P+4ddYbtRqCfaJlsLuwTqJN7NyUjkCcwIhAK4qp240lcCZJz9ELyV9ZaPIXEhPEnlsqJcQkHJf5v2L","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":814211},"type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./snapshot":{"types":"./dist/snapshot/index.d.ts","import":"./dist/snapshot/index.js"},"./websocket":{"types":"./dist/websocket/index.d.ts","import":"./dist/websocket/index.js"}},"gitHead":"a5e1dba14259b78b21290f3d9e85a2b2293f8005","scripts":{"lint":"prettier --check .","test":"vitest run","build":"npm run clean && tsc","clean":"node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","format":"prettier --write .","typecheck":"tsc --noEmit","example:demo":"npm run build && node --loader ts-node/esm examples/demo.ts","example:server":"npm run build && node --loader ts-node/esm examples/server.ts"},"_npmUser":{"name":"9b9387","email":"9b9387@gmail.com"},"_npmVersion":"11.12.1","description":"Versioned scrcpy protocol client for Node.js/Electron","directories":{},"_nodeVersion":"26.0.0","dependencies":{"@devicefarmer/adbkit":"^3.2.3"},"_hasShrinkwrap":false,"devDependencies":{"ws":"^8.20.1","vitest":"^4.1.6","ts-node":"^10.9.2","prettier":"^3.8.3","@types/ws":"^8.18.1","typescript":"^5.9.3","@types/node":"^20.11.0"},"peerDependencies":{"ws":"^8.16.0"},"peerDependenciesMeta":{"ws":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/android-stream-scrcpy_0.1.1_1779202113354_0.4492974493197397","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@9b9387/android-stream-scrcpy","version":"0.1.2","description":"Versioned scrcpy protocol client for Node.js/Electron","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"},"./protocol":{"import":"./dist/protocol/index.js","types":"./dist/protocol/index.d.ts"},"./websocket":{"import":"./dist/websocket/index.js","types":"./dist/websocket/index.d.ts"},"./snapshot":{"import":"./dist/snapshot/index.js","types":"./dist/snapshot/index.d.ts"},"./package.json":"./package.json"},"engines":{"node":">=20"},"scripts":{"clean":"node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","build":"npm run clean && tsc","typecheck":"tsc --noEmit","test":"vitest run","lint":"prettier --check .","format":"prettier --write .","prepack":"npm run build","prepublishOnly":"npm run typecheck && npm run test && npm run lint && npm run build","example:demo":"npm run build && node --loader ts-node/esm examples/demo.ts","example:server":"npm run build && node --loader ts-node/esm examples/server.ts"},"keywords":["scrcpy","android","adb","streaming","electron","nextjs","typescript"],"license":"MIT","repository":{"type":"git","url":"git+ssh://git@github.com/9b9387/android-stream.git","directory":"node-lib"},"bugs":{"url":"https://github.com/9b9387/android-stream/issues"},"homepage":"https://github.com/9b9387/android-stream/tree/main/node-lib#readme","publishConfig":{"access":"public"},"dependencies":{"@devicefarmer/adbkit":"^3.2.3"},"peerDependencies":{"ws":"^8.16.0"},"peerDependenciesMeta":{"ws":{"optional":true}},"devDependencies":{"@types/node":"^20.11.0","@types/ws":"^8.18.1","prettier":"^3.8.3","ts-node":"^10.9.2","typescript":"^5.9.3","vitest":"^4.1.6","ws":"^8.20.1"},"gitHead":"aa0d16e4ff880633e9fac4ae4d7a2373e3ebff3a","_id":"@9b9387/android-stream-scrcpy@0.1.2","_nodeVersion":"26.0.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-RKO8v+dDPGhK8dN9jXbiF6J8QaQMcgrA3XxwhYQjlhAZQ/Jjef4KwZgr+aw8DBkulvOzIkOrCDxZWZz/wq/RCA==","shasum":"dbf1cc59ae9bb001ed045109f6debff72eac836a","tarball":"https://registry.npmjs.org/@9b9387/android-stream-scrcpy/-/android-stream-scrcpy-0.1.2.tgz","fileCount":68,"unpackedSize":829368,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDYN5VA+7V3i1pFaWW7T7o70gthVJHHSsZagzr90btAvwIhAN/xOVGLSYDNF2CjUVpKDzHtN1EnUKSiS02nJB9th8E4"}]},"_npmUser":{"name":"9b9387","email":"9b9387@gmail.com"},"directories":{},"maintainers":[{"name":"9b9387","email":"9b9387@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/android-stream-scrcpy_0.1.2_1780643985518_0.6471514450112912"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-19T03:11:14.866Z","modified":"2026-06-05T07:19:45.893Z","0.1.0":"2026-05-19T03:11:15.183Z","0.1.1":"2026-05-19T14:48:33.520Z","0.1.2":"2026-06-05T07:19:45.771Z"},"description":"Versioned scrcpy protocol client for Node.js/Electron","maintainers":[{"name":"9b9387","email":"9b9387@gmail.com"}],"readme":"# @9b9387/android-stream-scrcpy\n\nTypeScript 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`).\n\nThe 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.\n\n> 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.\n\n## Project Structure\n\n```text\nnode-lib/\n  assets/\n    scrcpy-server-v4.0.jar\n  examples/\n    demo.ts\n    server.ts\n  src/\n    backend/\n      adb/\n        scrcpy-adb-client.ts\n      io/\n        buffered-stream-reader.ts\n      server/\n        options.ts\n        server-command.ts\n        socket-name.ts\n      scrcpy-backend.ts\n      index.ts\n    protocol/\n      core/\n        binary.ts\n        types.ts\n      registry.ts\n      v4_0/\n        codecs.ts\n        control-message.ts\n        control-message-types.ts\n        device-message.ts\n        frame-header.ts\n        server-options.ts\n        string-payload.ts\n        index.ts\n      index.ts\n    service/\n      packet-queue-subscriber.ts\n      scrcpy-stream-service.ts\n      types.ts\n      index.ts\n    snapshot/\n      ffmpeg-snapshot-cache.ts\n      types.ts\n      index.ts\n    websocket/\n      binary-packet.ts\n      control-json.ts\n      scrcpy-websocket-bridge.ts\n      types.ts\n      index.ts\n    index.ts\n```\n\n### Module Responsibilities\n\n- `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.\n- `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.\n- `src/service/`: public stream service. It manages state, caches decoder config/session snapshots, and exposes `for await...of` media packet subscriptions.\n- `src/snapshot/`: optional Node-side screenshot cache. It pipes H.264 packets into ffmpeg and stores the latest JPEG frame.\n- `src/websocket/`: optional `ws` bridge for browser clients. It is exported from the `./websocket` subpath and is not part of the root API.\n- `examples/`: runnable examples kept outside the library build.\n\n## Install\n\nInstall the package in your application:\n\n```bash\nnpm install @9b9387/android-stream-scrcpy\n```\n\nCore runtime dependency is `@devicefarmer/adbkit`. The library does not call the `adb` system command directly.\n\nIf your application uses the optional WebSocket bridge, install `ws` in the application:\n\n```bash\nnpm install ws\n```\n\nIf your application uses the optional screenshot cache, make sure `ffmpeg` is available on `PATH`, or pass `ffmpegPath` to `FfmpegSnapshotCache`.\n\n## Build From Source\n\n```bash\ncd node-lib\nnpm install\nnpm run build\n```\n\n## Package Entrypoints\n\n- `@9b9387/android-stream-scrcpy`: Node-only service/backend API.\n- `@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.\n- `@9b9387/android-stream-scrcpy/websocket`: optional Node-only `ws` bridge.\n- `@9b9387/android-stream-scrcpy/snapshot`: optional Node-only ffmpeg screenshot cache.\n\nThe package is ESM-only and requires Node.js 20 or newer.\n\n## Run Examples\n\nRun the console stream demo:\n\n```bash\nnpm run example:demo\n```\n\nRun the browser demo server:\n\n```bash\nnpm run example:server\n```\n\nThe server serves the existing demo page from the repository root:\n\n```text\n../web_demo/scrcpy.html\n```\n\nThen open:\n\n```text\nhttp://localhost:8000\n```\n\nYou can also request the page directly:\n\n```text\nhttp://localhost:8000/scrcpy.html\n```\n\nChoose a specific connected Android device by serial:\n\n```bash\nANDROID_SERIAL=<device-serial> npm run example:server\n```\n\n`ADB_SERIAL` is also supported for compatibility with existing local workflows.\n\nEnable the optional Node-side screenshot cache for the browser demo:\n\n```bash\nSNAPSHOT_ENABLED=1 SNAPSHOT_FPS=2 SNAPSHOT_JPEG_QUALITY=85 npm run example:server\n```\n\nThis requires `ffmpeg` on `PATH` by default. Set `FFMPEG_PATH=/path/to/ffmpeg`\nif the binary lives elsewhere. When enabled, the demo exposes:\n\n```text\nhttp://localhost:8000/screenshot.jpg\n```\n\nThe `scrcpy.html` page also shows a `截图` button that downloads the latest\ncached JPEG. The cache is produced from the scrcpy H.264 stream in Node; it does\nnot call ADB `screencap` and does not require a browser canvas.\n\n## Integrate The Core Library\n\n```typescript\nimport { ScrcpyStreamService } from \"@9b9387/android-stream-scrcpy\";\n\nconst service = new ScrcpyStreamService({\n  protocolVersion: \"4.0\",\n  maxSize: 1080,\n  video: true,\n  audio: true,\n  control: true,\n});\n\nconst meta = await service.start();\nconsole.log(`Connected to ${meta.deviceName}`);\n\nfor await (const packet of service.subscribe()) {\n  // packet.kind: \"video\" | \"audio\" | \"session\"\n  // packet.payload: Uint8Array\n  if (packet.kind === \"video\") {\n    // Feed H.264/H.265/AV1 bytes into your decoder.\n  }\n}\n```\n\nStop streaming when your application shuts down:\n\n```typescript\nservice.stop();\n```\n\n## Send Control Messages\n\n```typescript\nimport {\n  ControlMessageType,\n  KEY_ACTION_DOWN,\n  ScrcpyStreamService,\n} from \"@9b9387/android-stream-scrcpy\";\n\nconst service = new ScrcpyStreamService();\nawait service.start();\n\nservice.sendControlMessage({\n  type: ControlMessageType.INJECT_KEYCODE,\n  action: KEY_ACTION_DOWN,\n  keycode: 26,\n});\n```\n\nThe scrcpy 4.0 adapter includes key, text, touch, scroll, clipboard, UHID, app start, display power, camera, and display resize control messages.\n\n## Integrate The WebSocket Bridge\n\nThe WebSocket bridge is optional and imported from a subpath:\n\n```typescript\nimport { createServer } from \"node:http\";\nimport { ScrcpyStreamService } from \"@9b9387/android-stream-scrcpy\";\nimport { ScrcpyWebSocketBridge } from \"@9b9387/android-stream-scrcpy/websocket\";\n\nconst service = new ScrcpyStreamService({ protocolVersion: \"4.0\" });\nconst server = createServer();\nconst bridge = new ScrcpyWebSocketBridge(service, { server });\n\nawait service.start();\nserver.listen(8000);\n\nprocess.on(\"SIGINT\", () => {\n  bridge.close();\n  service.stop();\n  server.close();\n});\n```\n\nThe bridge sends an initial JSON `init` message, then binary media packets with a 16-byte header followed by the media payload.\n\n## Integrate The Screenshot Cache\n\nThe screenshot cache is optional and imported from the `./snapshot` subpath:\n\n```typescript\nimport { ScrcpyStreamService } from \"@9b9387/android-stream-scrcpy\";\nimport { FfmpegSnapshotCache } from \"@9b9387/android-stream-scrcpy/snapshot\";\n\nconst service = new ScrcpyStreamService({ videoCodec: \"h264\" });\nconst snapshots = new FfmpegSnapshotCache(service, {\n  enabled: true,\n  fps: 2,\n  quality: 85,\n  // Optional robustness knobs (defaults shown):\n  drainTimeoutMs: 5000, // tear down ffmpeg if stdin stalls this long\n  killTimeoutMs: 2000, // SIGTERM grace before SIGKILL on stop()\n  staleTimeoutMs: 10000, // emit \"stale\" when no new frame for this long (0 = off)\n  maxStdoutBytes: 16 * 1024 * 1024, // cap on the JPEG reassembly buffer\n});\n\nawait service.start();\nsnapshots.start(); // must be called after service.start()\n\n// Non-blocking read of the most recent frame (may be null before the first):\nconst latest = snapshots.latest();\n\n// Or wait up to N ms for the first/next frame instead of busy-polling 404s:\ntry {\n  const shot = await snapshots.waitForFresh(3000);\n  // shot.contentType === \"image/jpeg\"; shot.data is a JPEG Buffer\n} catch {\n  // no frame within the timeout — surface a 503 / \"warming up\" to the client\n}\n\n// Observe a stalled stream (device asleep, encoder paused, etc.):\nsnapshots.on(\"stale\", ({ ageMs }) => {\n  console.warn(`no new snapshot for ${ageMs}ms`);\n});\n```\n\nThe first implementation supports H.264 input and JPEG output. The configured\n`fps` limits how often ffmpeg emits JPEG frames; ffmpeg still receives the\ncontinuous H.264 stream so inter-frame decoding remains correct.\n\n### Lifecycle & resource safety\n\n- `FfmpegSnapshotCache` listens to the service state: when the service stops or\n  errors, the ffmpeg process is killed automatically, so it never lingers as an\n  orphan across reconnects. You should still call `snapshots.stop()` explicitly\n  when you tear a session down yourself.\n- `waitForFresh()` resolves immediately if a frame is cached, otherwise it\n  resolves on the next frame or rejects after the timeout — use it in HTTP\n  handlers so a single request never hangs and clients never busy-poll.\n- The WebSocket bridge applies backpressure: a slow client's droppable video\n  frames are dropped (config/keyframe/session packets are preserved) once its\n  outbound buffer exceeds `maxBufferedBytes` (8MiB default), preventing\n  unbounded memory growth. Tune via `new ScrcpyWebSocketBridge(service, { maxBufferedBytes })`.\n\nFor multi-device streaming and per-device screenshot HTTP routes, see\n[docs/multi-device-screenshot.md](./docs/multi-device-screenshot.md) (Web integration guide, 中文).\n\n## Next.js And Electron Usage\n\n### Next.js\n\nUse this package only from the server side of a self-hosted or long-lived Node.js runtime:\n\n```typescript\nexport const runtime = \"nodejs\";\n\nimport { ScrcpyStreamService } from \"@9b9387/android-stream-scrcpy\";\n```\n\nDo not import the root package from Client Components, browser bundles, Middleware,\nor Edge Runtime route handlers. The backend opens ADB sockets and long-lived media\nstreams, so a custom Node.js server or separate local daemon is usually a better\nfit than a short-lived serverless function.\n\n### Electron\n\nCreate and manage `ScrcpyStreamService` in the main process. Renderer processes\nshould communicate with the main process through IPC or connect to the optional\nWebSocket bridge.\n\nWhen packaging Electron apps, make sure `assets/scrcpy-server-v4.0.jar` is copied\nas a runtime resource. If your packager moves or packs assets into an archive,\npass an explicit `serverJarPath` to `ScrcpyStreamService`. If screenshot caching\nis enabled, also ship `ffmpeg` yourself or pass `ffmpegPath`.\n\n## Protocol Versioning\n\n`protocolVersion` defaults to `\"4.0\"`:\n\n```typescript\nconst service = new ScrcpyStreamService({ protocolVersion: \"4.0\" });\n```\n\nTo add a future scrcpy version:\n\n1. Add a new adapter under `src/protocol/`.\n2. Register it in `src/protocol/registry.ts`.\n3. Add the matching `scrcpy-server` jar to `assets/`.\n4. Add fixed-byte protocol tests for changed behavior.\n\nThe backend depends on the protocol interface, so frame parsing, control serialization, server options, and socket naming can evolve per version.\n\n## Development Commands\n\n```bash\nnpm run format\nnpm run lint\nnpm run typecheck\nnpm test\nnpm run build\nnpm pack --dry-run\n```\n\nEnd-to-end streaming requires a connected Android device. Unit tests cover protocol serialization/parsing, backend option normalization, subscriber queue behavior, and WebSocket packet encoding.\n\n## Publish\n\nBefore publishing, make sure you are logged in to the npm account that owns the\n`@9b9387` scope:\n\n```bash\nnpm whoami\n```\n\nRun the release checks and inspect the tarball contents:\n\n```bash\nnpm run typecheck\nnpm run test\nnpm run lint\nnpm run build\nnpm pack --dry-run\n```\n\nPublish the public scoped package:\n\n```bash\nnpm publish --access public\n```\n","readmeFilename":"README.md","homepage":"https://github.com/9b9387/android-stream/tree/main/node-lib#readme","keywords":["scrcpy","android","adb","streaming","electron","nextjs","typescript"],"repository":{"type":"git","url":"git+ssh://git@github.com/9b9387/android-stream.git","directory":"node-lib"},"bugs":{"url":"https://github.com/9b9387/android-stream/issues"},"license":"MIT"}