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

> Explore the @escrcpy/wscrcpy WebSocket stack design. Discover its two-entry-point architecture for efficient TCP socket handling and a clean Vue component API for streaming.

- Repository: [viarotel-org/escrcpy](https://github.com/viarotel-org/escrcpy)
- Tags: architecture
- Published: 2026-09-10

---

**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`](https://github.com/viarotel-org/escrcpy/blob/main/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`](https://github.com/viarotel-org/escrcpy/blob/main/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`](https://github.com/viarotel-org/escrcpy/blob/main/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`](https://github.com/viarotel-org/escrcpy/blob/main/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:

```vue
<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:

```typescript
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:

```typescript
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`](https://github.com/viarotel-org/escrcpy/blob/main/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`](https://github.com/viarotel-org/escrcpy/blob/main/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`](https://github.com/viarotel-org/escrcpy/blob/main/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`](https://github.com/viarotel-org/escrcpy/blob/main/packages/wscrcpy/src/options.ts).