Architectural Notes for @escrcpy/wscrcpy: Scrcpy WebSocket Stack Design

The @escrcpy/wscrcpy package implements a strict two-entry-point architecture that isolates raw TCP socket handling in the Electron main process while exposing a clean Vue component API for renderer-side streaming.

The @escrcpy/wscrcpy module powers the core streaming functionality of Escrcpy, an open-source Electron application for Android screen mirroring. This self-contained Vue and TypeScript package encapsulates the complete Scrcpy-over-WebSocket protocol stack, deliberately separating UI concerns from low-level transport logic to provide a contract-driven API for frontend developers.

Core Architectural Layers

The architecture organizes functionality into distinct layers that communicate through well-defined boundaries.

Renderer API Layer

The renderer-facing entry point exports UI components and composables from the package root at packages/wscrcpy/. The primary <Wscrcpy/> component located in packages/wscrcpy/src/component/Wscrcpy.vue handles video stream rendering, user input capture, and control command forwarding via IPC. Developers access connection management through composables like useWscrcpyConnection, which abstracts session lifecycle management while maintaining type safety through the public contracts defined in packages/wscrcpy/shared/types.ts.

Main-Process Bridge Layer

The main-process entry point (@escrcpy/wscrcpy/main) provides the ByteBridge factory implemented in packages/wscrcpy/service/bridge/. This thin layer creates a TCP-to-MessagePort pump for each Scrcpy channel, forwarding raw bytes between the Electron main process and renderer without interpreting protocol semantics. The bridge intentionally avoids processing logic, serving solely as a transport abstraction that presents a message-based API to the renderer while managing raw socket I/O in the privileged main process.

Protocol Stack Implementation

The full Scrcpy protocol implementation resides in packages/wscrcpy/src/stack/, processing incoming byte streams to decode video frames via WebCodecs, handle audio streams, and manage control and clipboard channels. This layer emits high-level events that the renderer consumes, translating binary Scrcpy protocols into JavaScript-friendly interfaces while maintaining cross-platform compatibility across Windows, macOS, and Linux.

Runtime Router and Session Management

The runtime router in packages/wscrcpy/src/core/runtime.ts binds each window's channels to isolated protocol stack instances, ensuring that every renderer window owns a dedicated WscrcpySession. When a window destroys, its session terminates automatically, preventing stray streams from consuming resources.

Critical Design Decisions

Several architectural constraints ensure stability and maintainability across the Electron process boundary.

Two-Entry-Point Model – The package strictly separates exports: the renderer entry provides components and hooks, while @escrcpy/wscrcpy/main provides the bridge factory. Importing the root entry from the main process or the main entry from the renderer is prohibited to prevent circular dependencies and context violations.

Thin Bridge Abstraction – The service/bridge/ implementation maintains minimal responsibility, forwarding raw bytes without protocol interpretation. All Scrcpy semantics live exclusively in the stack layer, allowing the transport mechanism to change without affecting protocol logic.

Audio Opt-In Behavior – Audio remains disabled by default. When both audio and control channels are enabled, the createScrcpyOptions function in packages/wscrcpy/src/options.ts automatically sets clipboardAutosync to false to prevent controller instability. Users must explicitly enable audio support, which functions identically across all platforms through WebCodecs and Web Audio APIs.

Forward Tunnel Mode – The default configuration uses forward tunnel mode (tunnelForward: true), which tags streams by ADB protocol local-id to eliminate race conditions that frequently occur on Windows when using reverse mode.

Implementation Examples

Renderer Component Usage

The following Vue component demonstrates standard integration using the renderer entry point and options builder:

<template>
  <Wscrcpy :device-id="deviceId" :options="scrcpyOpts" />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { createScrcpyOptions } from '@escrcpy/wscrcpy/src/options'
import Wscrcpy from '@escrcpy/wscrcpy'

const deviceId = ref('emulator-5554')
const scrcpyOpts = createScrcpyOptions({
  audio: true,
  clipboardAutosync: false,
  tunnelForward: true,
})
</script>

Custom Connection Hook

For manual session management, import the useWscrcpyConnection composable and DeviceTarget type from the shared contracts:

import { ref } from 'vue'
import { useWscrcpyConnection } from '@escrcpy/wscrcpy'
import { DeviceTarget } from '@escrcpy/wscrcpy/shared/types'

export function useCustomWscrcpy(target: DeviceTarget) {
  const { connect, disconnect, status } = useWscrcpyConnection()
  const sessionId = ref<string | null>(null)

  async function start() {
    const opts = { audio: false, clipboardAutosync: true }
    sessionId.value = await connect(target, opts)
  }

  async function stop() {
    if (sessionId.value) {
      await disconnect(sessionId.value)
      sessionId.value = null
    }
  }

  return { start, stop, status, sessionId }
}

Main Process Bridge Initialization

In the Electron main thread, create transport bridges using the main-process entry:

import { createByteBridge } from '@escrcpy/wscrcpy/service/bridge'
import { SCRCPY_CHANNELS } from '@escrcpy/wscrcpy/shared/channels'

export function launchWscrcpyBridge(deviceSerial: string) {
  return createByteBridge(deviceSerial, SCRCPY_CHANNELS)
}

Summary

  • The @escrcpy/wscrcpy architecture enforces strict separation between renderer UI and main-process socket handling through distinct entry points.
  • The ByteBridge in packages/wscrcpy/service/bridge/ provides a thin, message-based transport layer without protocol semantics.
  • Protocol implementation in packages/wscrcpy/src/stack/ handles video, audio, control, and clipboard channels using WebCodecs for cross-platform decoding.
  • The runtime router in packages/wscrcpy/src/core/runtime.ts guarantees isolated sessions per window with automatic cleanup on destruction.
  • Public TypeScript contracts in packages/wscrcpy/shared/types.ts define stable APIs including the DeviceTarget union type.
  • Audio requires explicit opt-in and automatically disables clipboard autosync to prevent controller instability.

Frequently Asked Questions

How does @escrcpy/wscrcpy handle process separation in Electron?

The package exports two distinct entry points: the default export for renderer processes containing Vue components and composables, and @escrcpy/wscrcpy/main for the main process containing the ByteBridge factory. This prevents importing main-process modules in the renderer or vice versa, eliminating Electron context isolation violations and circular dependencies.

Where is the Scrcpy protocol actually implemented?

The complete protocol logic resides in packages/wscrcpy/src/stack/, which processes raw byte streams into decoded video frames and high-level events. The bridge layer (packages/wscrcpy/service/bridge/) only forwards bytes without interpreting Scrcpy semantics, ensuring the transport mechanism remains independent from protocol implementation details.

Why is clipboard autosync disabled when audio is enabled?

According to the source in packages/wscrcpy/src/options.ts, enabling both audio and control channels simultaneously can cause controller instability in the Scrcpy protocol stack. The createScrcpyOptions function therefore sets clipboardAutosync: false by default when audio is enabled, requiring users to explicitly accept this trade-off or disable audio to maintain clipboard synchronization.

What is the difference between forward and reverse tunnel modes?

Forward tunnel mode (tunnelForward: true) tags streams using the ADB protocol's local-id, preventing race conditions that occur when multiple connections attempt reverse tunneling on Windows. The @escrcpy/wscrcpy architecture defaults to forward mode for stability, though the implementation supports both modes through the options configuration in packages/wscrcpy/src/options.ts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →