scrcpy Integration in a Tauri App — Android Screen Mirroring on Mac

HiyokoKit includes Android remote control via scrcpy. Launching and managing scrcpy from a Tauri app has specific challenges.
Here’s how I handle it.

What scrcpy is

scrcpy is an open-source tool that mirrors and controls an Android device screen over ADB. It’s the best free option for Android screen mirroring on Mac — fast, low latency, no app required on the device.

Launching scrcpy from Rust

use std::process::{Command, Child};

pub struct ScrcpyProcess {
    child: Option<Child>,
}

impl ScrcpyProcess {
    pub fn start(
        &mut self,
        device_serial: &str,
        max_size: u32,
        bit_rate: &str,
    ) -> Result<(), AppError> {
        let child = Command::new("scrcpy")
            .args([
                "--serial", device_serial,
                "--max-size", &max_size.to_string(),
                "--video-bit-rate", bit_rate,
                "--window-title", "Android Mirror",
                "--no-audio",
            ])
            .spawn()
            .map_err(|e| AppError::Scrcpy(e.to_string()))?;

        self.child = Some(child);
        Ok(())
    }

    pub fn stop(&mut self) {
        if let Some(mut child) = self.child.take() {
            child.kill().ok();
        }
    }

    pub fn is_running(&mut self) -> bool {
        if let Some(child) = &mut self.child {
            child.try_wait().map(|s| s.is_none()).unwrap_or(false)
        } else {
            false
        }
    }
}

Bundling scrcpy

scrcpy needs to be available on the user’s machine or bundled with your app. I bundle it in app resources as a universal binary:

{
  "bundle": {
    "resources": [
      "bin/scrcpy",
      "bin/adb"
    ]
  }
}

At runtime, get the resource path:

let scrcpy_path = app_handle
    .path()
    .resource_dir()
    .unwrap()
    .join("bin/scrcpy");

Detecting when scrcpy exits

scrcpy exits when the user closes the mirror window. Detect this to update your UI:

// Poll in background
tokio::spawn(async move {
    loop {
        tokio::time::sleep(Duration::from_secs(1)).await;

        let running = {
            let mut proc = scrcpy_state.lock().unwrap();
            proc.is_running()
        };

        if !running {
            app_handle.emit("scrcpy-stopped", ()).ok();
            break;
        }
    }
});

Multiple device support

scrcpy’s --serial flag selects a specific device when multiple are connected. Get the serial from adb devices and pass it explicitly:

async fn get_device_serial() -> Result<String, AppError> {
    let output = Command::new("adb")
        .args(["devices"])
        .output()
        .await?;

    let stdout = String::from_utf8_lossy(&output.stdout);
    stdout.lines()
        .skip(1)
        .find(|l| l.contains("device"))
        .and_then(|l| l.split_whitespace().next())
        .map(|s| s.to_string())
        .ok_or(AppError::Device("No device found".into()))
}

If this was useful, a ❤️ helps more than you’d think — thanks!

HiyokoKit (includes scrcpy-based Android remote control) → https://hiyokomtp.lemonsqueezy.com/checkout/buy/2c94dd0f-e28a-4a17-8efc-7bd93087d46d

X → @hiyoyok