Skip to content

WIP: Add live device screen streaming to the Screen Capture window - #217

Closed
todi1856 wants to merge 5 commits into
masterfrom
livestream
Closed

todi1856 wants to merge 5 commits into
masterfrom
livestream

Conversation

@todi1856

@todi1856 todi1856 commented Sep 15, 2026 •

Copy link
Copy Markdown
Member

Type of change:

  • Bug fix
  • Improvement
  • Code refactor
  • Breaking change
  • Documentation update
  • Other (please describe)

Description

The main logcat window is not affected by this change, only screen capture window is affected

Video

You can now watch the device's screen live in the Screen Capture window, and click, scroll and type into it from the Editor.

Also, it's not accessible via Window->Analysis->Android Screen Capture

Capturing needs something on the device: External/UnityLogcatServer is a small Gradle project that builds a 17 KB jar, run through app_process as the shell user. It mirrors a display into an ImageReader, JPEG-encodes each frame and writes it over an abstract socket that adb forward exposes. Frames are only produced when the screen changes, so an idle device costs almost nothing.

Input travels back up the same socket: touch, scroll wheel, keys (through the device's own keyboard layout), and Ctrl/Cmd + A, C, V for select-all/copy/paste on the device. Back / Home / Overview buttons sit beside the image, which is the only way in on gesture-navigation devices.

The window was reorganised around it. Screenshots used to overwrite one file in Temp; they're now kept in Library/AndroidLogcat/Screenshots and listed down the left, with the live view as the first row — arrow keys to cycle, F2 rename, Del delete, right-click for Show In Explorer / Open / Save As, Reconnect on the Live row, Ctrl+Shift+S to capture. The window also opens from Window > Analysis > Android Screen Capture. Stream size, JPEG quality and frame rate cap are in Preferences > Analysis > Android Logcat Settings.

Improved screenshot window

image

Settings

image

Checklist for PR maker

  • Have you added a backport label? (new feature, no backport intended — confirm)
  • Have you updated the Changelog?
  • Have you added or updated the Documentation to your PR?

Testing status

  • Existing or new automation tests - what automation was added, changed
  • New tests are covering
    • start/stop live steam
    • frame delivery
    • touch, keys
    • scroll
    • restart
    • streaming against a device that was asleep
    • screenshot list, delete, rename and external-change tests.

Devices:

  • Pixel 2 (Android 10)
  • Pixel 9 Pro Fold (Android 16)
    • Folding/Unfolding while viewing
  • CPH1831 (Android 8.1).

Testing checklist

  • Built and run editor Locally or Yamato
  • Run on device Android/iOS (if applicable)
  • All items have tooltips?

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Harness Review

LGTM

The live-stream implementation and packaging integration are cohesive overall, but secondary-display input handling contains a concrete reflection-cache flaw.

Reviewed commit f641860

🤖 Helpful? 👍/👎

@todi1856

Copy link
Copy Markdown
Member Author

/review

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Harness Review

Needs changes

The live-stream and persistent screenshot UI introduce several reachable lifecycle and compatibility failures that can break streaming or the screenshot workflow.

Reviewed commit 4a9603d

🤖 Helpful? 👍/👎

Comment thread com.unity.mobile.android-logcat/Editor/AndroidLogcatCaptureScreenshot.cs Outdated
Comment thread com.unity.mobile.android-logcat/Editor/AndroidLogcatScreenshotList.cs Outdated
@todi1856

Copy link
Copy Markdown
Member Author

/review

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Harness Review

Needs changes

The live-streaming and screenshot-history additions are substantial, but normal stream termination and shared screenshot state can produce incorrect UI behavior.

Reviewed commit 422e6ee

🤖 Helpful? 👍/👎

Comment thread com.unity.mobile.android-logcat/Editor/AndroidLogcatLiveStream.cs
Comment thread com.unity.mobile.android-logcat/Editor/AndroidLogcatScreenshotList.cs Outdated
Comment thread com.unity.mobile.android-logcat/Documentation~/android-logcat-settings.md Outdated
@todi1856

Copy link
Copy Markdown
Member Author

/review

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Harness Review

Needs changes

The live-stream integration is generally coherent, but concurrent startup and pending-screenshot handling contain reachable lifecycle races, and the protocol documentation does not match the wire format.

Reviewed commit 094a5fa

🤖 Helpful? 👍/👎

Comment thread com.unity.mobile.android-logcat/Editor/AndroidLogcatLiveStream.cs
Comment thread External/UnityLogcatServer/README.md Outdated
@todi1856

Copy link
Copy Markdown
Member Author

/review

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Harness Review

LGTM

The live-stream and screenshot-list integration is broadly coherent, but filesystem changes made outside the window leave its new persisted screenshot view stale.

Reviewed commit 63e4ed3

🤖 Helpful? 👍/👎

@todi1856

Copy link
Copy Markdown
Member Author

/review

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Harness Review

Needs changes

The live-stream feature currently introduces an Editor compilation failure and a selection-state regression when capturing from the live view.

Reviewed commit b707a8b

🤖 Helpful? 👍/👎

Comment thread com.unity.mobile.android-logcat/Editor/AndroidLogcatLiveStream.cs
// The list draws itself and the splitter, and hands back what is left.
var imageRect = m_ScreenshotList.DoGUI(rc);

if (m_ScreenshotList.LiveSelected)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1

When Live is selected, Capture remains enabled. Capture completion updates SelectedImagePath, but m_LiveSelected remains true, so this branch continues rendering the stream and the newly captured screenshot is not displayed as documented. This is especially visible on a fresh project, where the empty list automatically selects Live. Clear the live selection/stop streaming when capture starts or select the completed screenshot afterward.

🤖 Helpful? 👍/👎

@todi1856 todi1856 changed the title WIP Livestream Add live device screen streaming to the Screen Capture window Sep 17, 2026
@todi1856 todi1856 changed the title Add live device screen streaming to the Screen Capture window WIP: Add live device screen streaming to the Screen Capture window Sep 17, 2026
@todi1856

Copy link
Copy Markdown
Member Author

/review

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Harness Review

Needs changes

The live-streaming and screenshot-history additions are substantial, but a few reachable platform and edge paths remain incorrect.

Reviewed commit 5544c4c

🤖 Helpful? 👍/👎

// belongs to the Layout Viewer as much as to this window.
else if (!m_ScreenshotList.DoPreviewGUI(imageRect))
{
var message = m_DeviceSelection.SelectedDevice == null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1

This no-device message is unreachable because OnGUI bypasses DoPreviewGUI whenever SelectedDevice is null. Consequently, disconnecting the last device hides the entire device-independent screenshot list, preventing users from viewing, exporting, renaming, or deleting saved captures. Continue rendering the screenshot list without a device and gate only capture/live-stream actions.

🤖 Helpful? 👍/👎

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@u-pr fixed in latest changes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed — by removing the device null check from OnGUI() and moving device guarding to Mode.Video and device-specific actions (like capture and live streaming), DoScreenshotGUI() now renders regardless of whether a device is connected. Saved screenshots remain accessible in the list, and the fallback message in DoScreenshotGUI is now reachable when no device is selected.

🤖 Helpful? 👍/👎

var process = new System.Diagnostics.Process();
var si = process.StartInfo;
si.WorkingDirectory = projectDirectory;
si.FileName = windows ? Path.Combine(projectDirectory, "gradlew.bat") : "sh";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1

On Windows this assigns a .bat file directly to ProcessStartInfo.FileName while UseShellExecute is false. That path uses CreateProcess, which cannot execute batch files directly, so the offered “Build the server jar” action fails with a Win32Exception. Launch the wrapper through cmd.exe /c on Windows.

🤖 Helpful? 👍/👎

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@u-pr from what I see, there's no exception it runs fine

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be that RunGradle hasn't actually been executed on Windows in your test? A few reasons why it might appear to run fine without showing an exception:

  1. The server jar already exists: If com.unity.mobile.android-logcat/External~/unity-logcat-server.jar is already present on disk, live streaming connects directly through ADB and RunGradle is never invoked.
  2. Platform: If tested on macOS or Linux, windows is false, so it executes sh gradlew ..., which works as expected.
  3. Handled in try/catch: In AndroidLogcatUtilities.cs, process.Start() is wrapped in a try/catch block that catches Exception and logs Debug.LogError($"Failed to run Gradle in '{projectDirectory}'.\n{ex.Message}") while returning false, rather than bubbling up as an unhandled exception.

When RunGradle actually runs on Windows, UseShellExecute = false (which is necessary to redirect standard I/O) causes .NET to invoke the Win32 CreateProcess API directly. CreateProcess only executes binaries and cannot launch .bat or .cmd files directly, throwing a Win32Exception (%1 is not a valid Win32 application / error 193) on process.Start().

To execute the batch wrapper on Windows with UseShellExecute = false, you can route it through cmd.exe /c:

si.FileName = windows ? "cmd.exe" : "sh";
si.Arguments = windows ? $"/c gradlew.bat {task}" : $"gradlew {task}";

(Since si.WorkingDirectory is already set to projectDirectory, cmd.exe will find gradlew.bat there.)

🤖 Helpful? 👍/👎

@todi1856

Copy link
Copy Markdown
Member Author

/review

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Harness Review

Needs changes

The live streaming and screenshot-management additions are broadly cohesive, but sidecar handling can silently overwrite unrelated user data.

Reviewed commit fc65cd1

🤖 Helpful? 👍/👎


try
{
File.Copy(from, to, true);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1

Save As can silently overwrite an unrelated JSON file. For example, saving a captured screenshot as settings.png in a folder already containing settings.json reaches this call with overwrite: true; the file picker only confirms overwriting the PNG. Move has the same issue because it deletes the destination sidecar first. Check for a destination-sidecar collision and prompt/refuse before modifying it.

🤖 Helpful? 👍/👎

todi1856 and others added 5 commits September 23, 2026 15:40
A small Java server, built with Gradle into a dex jar and pushed to the device,
that mirrors a display into an ImageReader, encodes each frame as JPEG and writes
it to an abstract unix socket. It also reads control messages back up the same
socket and injects touch, scroll, key and text events.

The jar is a build output and is not committed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The window now lists saved screenshots beside a Live row that mirrors the device.
Selecting Live starts the server on the device, shows its frames, and forwards
touch, scroll, keyboard and text back to it.

Along with it: a shared zoom and pan viewer for the live view and screenshot
previews, a details file written beside each capture, a stats column, device
rotation, and settings for the stream's size, quality and frame rate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Integration tests covering the stream end to end: frames arriving, injected
touch, scroll, keys and text taking effect on the device, streaming a device
that was asleep, and an unknown display id. Edit mode coverage for the details
file written beside a screenshot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The jar the live stream pushes to the device is a build output and is not
committed, so a clone has none. A build_server_jar job produces it and the pack
job now depends on it, which is also what puts it in the tarball the test jobs
install.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant