Skip to content

feat: add @device-stream/stream-only package#2

Merged
HeiCg merged 13 commits intomainfrom
HeiCg/stream-only-mode
Apr 8, 2026
Merged

feat: add @device-stream/stream-only package#2
HeiCg merged 13 commits intomainfrom
HeiCg/stream-only-mode

Conversation

@HeiCg
Copy link
Copy Markdown
Owner

@HeiCg HeiCg commented Apr 8, 2026

Summary

  • New @device-stream/stream-only package for headless video streaming from Android and iOS Simulator devices
  • HTTP + WebSocket server (StreamServer) with /devices endpoint and /stream/:serial WebSocket routing
  • AndroidStreamer with multi-client broadcast, SPS/PPS + keyframe caching for late-joining browsers
  • SimulatorStreamer for iOS Simulator MJPEG streaming
  • Callback-based startStreamWithCallback added to ScrcpyService for decoupled frame delivery
  • POC test app with WebCodecs-based H.264 decoder viewer

Test plan

  • Build passes (npm run build)
  • POC tested with physical Android device (Moto G34 5G) — video renders in browser via WebCodecs
  • Late-joining browsers receive cached config + keyframe immediately
  • Test with iOS Simulator (requires Xcode/simctl)

🤖 Generated with Claude Code

HeiCg and others added 13 commits April 8, 2026 10:56
Standalone package for streaming device screens to browsers via WebSocket,
without control APIs. Supports Android (scrcpy H.264) and iOS Simulator (MJPEG).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7-task plan covering package scaffolding, simulator streamer, ScrcpyService
callback refactor, android streamer, StreamServer, monorepo integration, and docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds startStreamWithCallback(), pipeCallbackStream(), stopCallbackStream(),
and isCallbackStreaming() to enable multi-client broadcast via a FrameCallback
instead of piping directly to a single WebSocket. Updates stopAll() to drain
both session maps. Existing startStream() is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix metadata polling race: use interval+timeout that rejects on timeout
  instead of silently resolving with fallback dimensions
- Fix ghost sessions: queue WS clients in StreamServer until startStream()
  determines the platform, instead of registering on both streamers
- Add missing codecName and fps fields to Android metadata messages
- Fix stopStream race: delete stream entry after await, not before

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Late-joining browsers received metadata but no video frames because
scrcpy only sends frames on screen changes. Cache SPS/PPS config and
last keyframe per session so new WebSocket clients get the current
screen immediately.

Replace JMuxer with WebCodecs API in the POC viewer for proper H.264
decoding — prepend SPS/PPS to keyframes as required by WebCodecs spec.

Add POC test app (poc-stream-only.ts + poc-viewer.html) for manual
testing with a connected Android device.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cast videoStream.getReader() in pipeCallbackStream to match the native
ReadableStreamDefaultReader type, same as pipeVideoStream already does.
Fixes TS2322 on Node 20 where @yume-chan/stream-extra types diverge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@HeiCg HeiCg merged commit 322897b into main Apr 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant