# How the Scrcpy Protocol Is Implemented in @escrcpy/wscrcpy: A Deep Dive

> Explore the scrcpy protocol implementation in @escrcpy/wscrcpy. Discover how ByteBridge and a renderer-process stack enable video, audio, and control frame decoding via WebCodecs and Web Audio.

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

---

**The `@escrcpy/wscrcpy` package bridges the native scrcpy protocol to the Electron-Vue frontend by splitting implementation between a main-process ByteBridge that pumps TCP streams through MessagePort and a renderer-process protocol stack that decodes video, audio, and control frames via WebCodecs and Web Audio.**

The `@escrcpy/wscrcpy` package serves as the core protocol layer in the [viarotel-org/escrcpy](https://github.com/viarotel-org/escrcpy) repository, translating binary scrcpy streams from Android devices into web-compatible formats. This implementation adapts the original scrcpy protocol—which relies on ADB-forwarded TCP connections for video, audio, control, and clipboard synchronization—into a browser-friendly architecture using Electron's multi-process model and modern web APIs.

## Architecture Overview

The implementation strictly separates concerns between Electron's main process and renderer process to handle the binary nature of the scrcpy protocol safely.

### Main Process Responsibilities

The main process manages raw socket connections to Android devices. It uses the `@yume-chan/*` library suite to establish ADB connections and negotiate scrcpy streams. The **ByteBridge** factory located in `packages/wscrcpy/service/bridge/` creates a thin pump that forwards raw byte chunks from TCP sockets into MessagePort messages destined for the renderer. This isolates risky binary operations from the browser context.

### Renderer Process Responsibilities

The renderer consumes MessagePort streams and handles all protocol decoding. Located in `packages/wscrcpy/src/stack/`, the protocol stack parses scrcpy binary frames for separate channels including `scrcpy:video`, `scrcpy:audio`, `scrcpy:control`, and `scrcpy:clipboard`. Video and audio decoding leverage the **WebCodecs API** and **Web Audio** respectively, while input events serialize back into the scrcpy binary format for transmission.

## Main Process Implementation: ByteBridge and TCP Transport

The main-process implementation centers on the **ByteBridge** pattern found in `packages/wscrcpy/service/bridge/`. This module exports a factory function that instantiates bridge instances for each device connection.

The ByteBridge performs three critical functions:

- Opens a TCP socket to the Android device via ADB forward
- Translates the continuous binary scrcpy stream into discrete MessagePort messages
- Forwards these messages to the specific renderer process that owns the session

The implementation ensures that only one session per window owns the device connection, enforced by the `WscrcpySession` class in [`packages/wscrcpy/service/session.ts`](https://github.com/viarotel-org/escrcpy/blob/main/packages/wscrcpy/service/session.ts).

## Renderer Process Protocol Stack

The renderer side implements the full scrcpy protocol stack in `packages/wscrcpy/src/stack/`. This directory contains specialized handlers for each scrcpy channel:

- **Video Handler**: Parses H.264 NAL units from the `scrcpy:video` channel and feeds them into WebCodecs `VideoDecoder` instances
- **Audio Handler**: Processes Opus or AAC frames from `scrcpy:audio` through the Web Audio API
- **Control Handler**: Manages the `scrcpy:control` channel for injecting touch events, key presses, and scroll gestures
- **Clipboard Handler**: Handles asynchronous clipboard synchronization via `scrcpy:clipboard` when `clipboardAutosync` is enabled

Each handler re-assembles binary frames from the MessagePort chunks and publishes typed events that the Vue application consumes.

## Runtime Routing and Session Management

The [`packages/wscrcpy/src/core/runtime.ts`](https://github.com/viarotel-org/escrcpy/blob/main/packages/wscrcpy/src/core/runtime.ts) file serves as the central router for all wscrcpy channels. It receives MessagePort messages from the main process and dispatches them to the appropriate stack layer based on the channel identifier.

Session ownership is strictly managed by the `WscrcpySession` class in [`packages/wscrcpy/service/session.ts`](https://github.com/viarotel-org/escrcpy/blob/main/packages/wscrcpy/service/session.ts). This class:

1. Initializes the ByteBridge connection
2. Maintains the lifecycle of the scrcpy session
3. Ensures proper cleanup of streams and processes when the session terminates
4. Prevents multiple simultaneous connections to the same device from a single window

Type definitions across the package are centralized in [`packages/wscrcpy/shared/types.ts`](https://github.com/viarotel-org/escrcpy/blob/main/packages/wscrcpy/shared/types.ts), which exports interfaces like `DeviceTarget` and channel name constants used throughout the stack.

## Configuration and Options

The [`packages/wscrcpy/src/options.ts`](https://github.com/viarotel-org/escrcpy/blob/main/packages/wscrcpy/src/options.ts) file builds scrcpy command-line arguments and injects sensible defaults for the Electron environment. Key configurations include:

- `maxSize`: Limits the video resolution to prevent memory issues in the renderer
- `bitRate`: Controls the H.264 encoding bitrate for network efficiency  
- `tunnelForward`: Determines whether to use ADB reverse tunneling or forward connections
- `clipboardAutosync`: Enables automatic clipboard synchronization between desktop and device

These options default to keeping audio opt-in and avoiding clipboard-induced control stalls, adapting the standard scrcpy behavior for GUI applications.

## Practical Implementation Examples

### Using the Wscrcpy Vue Component

The simplest integration uses the high-level `Wscrcpy` component, which encapsulates the entire protocol stack:

```vue
<!-- src/views/mirror/Screen.vue -->
<template>
  <Wscrcpy
    :device="deviceId"
    :options="scrcpyOptions"
    @ready="onReady"
    @error="onError"
  />
</template>

<script setup>
import { ref } from 'vue'
import Wscrcpy from '@escrcpy/wscrcpy'

const deviceId = ref('emulator-5554')
const scrcpyOptions = { 
  maxSize: 720, 
  bitRate: 2_000_000,
  tunnelForward: true 
}

const onReady = () => console.log('scrcpy protocol connected')
const onError = err => console.error('scrcpy protocol error', err)
</script>

```

### Accessing Streams via Composables

For fine-grained control over the scrcpy protocol streams, use the `useWscrcpyConnection` composable:

```typescript
// src/hooks/useDeviceMirror.ts
import { useWscrcpyConnection } from '@escrcpy/wscrcpy'

const { video, audio, control, clipboard } = useWscrcpyConnection({
  device: 'emulator-5554',
  options: { 
    maxSize: 1080,
    clipboardAutosync: true 
  }
})

video.onFrame((frame: VideoFrame) => {
  // frame is a decoded VideoFrame from WebCodecs
  console.log('scrcpy video frame received', frame)
})

control.sendTouch({ x: 100, y: 200, pressure: 1.0 })

```

### Programmatic Session Control

Direct access to the scrcpy protocol lifecycle is available through the `WscrcpySession` API:

```typescript
import { WscrcpySession } from '@escrcpy/wscrcpy/service'

const session = new WscrcpySession('emulator-5554')

await session.start({
  maxSize: 720,
  bitRate: 8000000
})

// Session runs with full protocol stack active
console.log('ByteBridge connected, protocol stack initialized')

await session.stop()
// Cleans up MessagePort, TCP socket, and decoders

```

## Summary

The `@escrcpy/wscrcpy` implementation of the scrcpy protocol follows a strict separation between main and renderer processes:

- **ByteBridge** in `service/bridge/` handles raw TCP to MessagePort translation in the main process
- **Protocol Stack** in `src/stack/` decodes video, audio, control, and clipboard channels in the renderer
- **Runtime Router** in [`src/core/runtime.ts`](https://github.com/viarotel-org/escrcpy/blob/main/src/core/runtime.ts) dispatches MessagePort messages to appropriate handlers
- **Session Management** in [`service/session.ts`](https://github.com/viarotel-org/escrcpy/blob/main/service/session.ts) enforces single-ownership lifecycle control
- **Options Builder** in [`src/options.ts`](https://github.com/viarotel-org/escrcpy/blob/main/src/options.ts) adapts scrcpy CLI arguments for safe GUI defaults

This architecture allows the Escrcpy application to leverage the full performance of the scrcpy protocol while maintaining the security and compatibility constraints of a Chromium-based environment.

## Frequently Asked Questions

### What is ByteBridge in @escrcpy/wscrcpy?

**ByteBridge is a factory-created transport layer in `packages/wscrcpy/service/bridge/` that connects raw TCP sockets from ADB-forwarded scrcpy streams to Electron's MessagePort API.** It runs exclusively in the main process and forwards binary chunks from the Android device to the renderer process without parsing the protocol, ensuring that risky socket operations remain outside the browser sandbox.

### How does @escrcpy/wscrcpy handle video decoding?

**Video frames are decoded using the WebCodecs API in the renderer process.** The `scrcpy:video` channel handler in `packages/wscrcpy/src/stack/` receives H.264 NAL units via MessagePort, assembles complete frames, and feeds them into `VideoDecoder` instances. This approach provides hardware-accelerated decoding directly in the browser window without requiring native video players.

### What is the difference between the main process and renderer process implementation?

**The main process manages network transport and binary sockets, while the renderer process handles protocol parsing and media decoding.** The main process uses `@yume-chan` libraries to establish ADB connections and ByteBridge to pump bytes, whereas the renderer implements the scrcpy protocol stack including `scrcpy:video`, `scrcpy:audio`, `scrcpy:control`, and `scrcpy:clipboard` handlers that translate binary frames into JavaScript events and WebCodecs frames.

### How are scrcpy command-line options configured in the package?

**Options are centralized in [`packages/wscrcpy/src/options.ts`](https://github.com/viarotel-org/escrcpy/blob/main/packages/wscrcpy/src/options.ts), which constructs scrcpy arguments and sets Electron-safe defaults.** The options builder handles parameters like `maxSize`, `bitRate`, `tunnelForward`, and `clipboardAutosync`, automatically applying values that prevent common issues such as audio synchronization stalls or excessive memory usage in renderer processes.