scrcpy_video_view

CI License: BSD-3-Clause

A Flutter plugin that renders a low-latency Android screen stream inside a macOS app. It reads H.264 packets directly from the scrcpy server, decodes them with VideoToolbox, and publishes the latest frame through a Flutter texture.

Android MediaCodec
      │ H.264 over a scrcpy socket forwarded by adb
      ▼
Dart packet reader
      │ Annex-B H.264 access units
      ▼
macOS VideoToolbox
      │ CVPixelBuffer
      ▼
Flutter Texture

Demo

https://github.com/user-attachments/assets/d54d2d16-a5a7-448f-9a86-d61e0487e3f2

Platform support

Platform Status
macOS 10.15+ Supported
Windows, Linux, iOS, Android, web Not implemented

Video is currently H.264-only and view-only. Audio and device control are not implemented.

Requirements

  • Flutter with macOS desktop support
  • adb available on PATH, or supplied with adbPath
  • scrcpy available on PATH, or supplied with scrcpyPath
  • An Android device with USB debugging enabled and authorized
  • The macOS App Sandbox disabled in the host application

Verify the local setup before running the example:

adb devices -l
scrcpy --version

The plugin uses the installed scrcpy version and matching server binary. It does not enforce a version allowlist. Because scrcpy's client/server protocol is internal, a future release may require a compatibility update.

Installation

dependencies::
...
    scrcpy_video_view: ^0.0.1

macOS entitlements

Set com.apple.security.app-sandbox to false in both macos/Runner/DebugProfile.entitlements and macos/Runner/Release.entitlements:

<key>com.apple.security.app-sandbox</key>
<false/>

This is required because the plugin launches local adb processes, pushes the scrcpy server to the device, and connects to an adb-forwarded localhost socket.

Usage

Create one controller for the lifetime of the video view:

final controller = ScrcpyVideoController();

final devices = await controller.discoverDevices();
if (devices.isNotEmpty) {
  await controller.start(devices.first.serial);
}

Render its texture:

ScrcpyVideo(
  controller: controller,
  fit: BoxFit.contain,
  placeholder: const Center(child: Text('No video')),
)

Observe state, dimensions, errors, and diagnostic logs through the controller:

controller.addListener(() {
  debugPrint('state: ${controller.state}');
  debugPrint('size: ${controller.videoWidth} x ${controller.videoHeight}');
  debugPrint('error: ${controller.error}');
});

Stop explicitly when appropriate and dispose the controller with its owning widget:

await controller.stop();
controller.dispose();

The native macOS implementation also records the active adb forward and server process so it can remove them if the application terminates before Dart cleanup runs.

Configuration

Executable paths and encoding parameters can be customized:

final controller = ScrcpyVideoController(
  configuration: const ScrcpyVideoConfiguration(
    adbPath: '/path/to/adb',
    scrcpyPath: '/path/to/scrcpy',
    serverPath: '/path/to/scrcpy-server',
    maxSize: 1920,
    maxFps: 60,
    videoBitRate: 8 * 1000 * 1000,
  ),
);

If serverPath is omitted, the plugin checks SCRCPY_SERVER_PATH and common Homebrew installation locations.

Handling errors

Failures use ScrcpyVideoException with a machine-readable ScrcpyVideoErrorCode, so applications can present targeted recovery steps:

try {
  await controller.start(serial);
} on ScrcpyVideoException catch (error) {
  switch (error.code) {
    case ScrcpyVideoErrorCode.adbNotFound:
      showSetupMessage('Install Android platform tools.');
    case ScrcpyVideoErrorCode.scrcpyNotFound:
      showSetupMessage('Install scrcpy or configure scrcpyPath.');
    case ScrcpyVideoErrorCode.noAuthorizedDevices:
      showSetupMessage('Connect and authorize an Android device.');
    case ScrcpyVideoErrorCode.unsupportedScrcpyProtocol:
      showSetupMessage('Report your scrcpy version and controller logs.');
    default:
      showSetupMessage(error.message);
  }
}

Available codes also distinguish adb command failures, missing server binaries, connection failures, decoder failures, unexpected stream closure, unsupported platforms, and uncategorized failures. The original low-level failure is available through error.cause when one exists.

Example

The example includes device discovery, start/stop controls, logs, and the reusable video widget:

cd example
flutter run -d macos

Reporting compatibility problems

Open a bug report with the following information:

  • scrcpy version
  • Flutter version
  • macOS and Android versions
  • Device model
  • Complete controller.logs output

Please remove device serial numbers or other sensitive values before posting logs publicly.

Contributing

Contributions are welcome. Read CONTRIBUTING.md and the Code of Conduct before opening a pull request. For vulnerabilities, follow SECURITY.md instead of opening a public issue.

License and attribution

scrcpy_video_view is available under the BSD 3-Clause License.

This project is an independent integration built around Genymobile/scrcpy. It is not affiliated with or endorsed by the scrcpy project. scrcpy and its server are not bundled in this package.

Libraries

scrcpy_video_view
Low-latency Android screen streaming for Flutter on macOS.
scrcpy_video_view_method_channel
scrcpy_video_view_platform_interface