Repository navigation
fix: release adb and scrcpy processes on stop and hangs #86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -263,7 +263,11 @@ class _EmulatorGrpcSession implements EmulatorGrpcSession { | |
| final path = _recordingPath; | ||
| _recordingPath = null; | ||
| if (path == null) return null; | ||
| await _emu(['screenrecord', 'stop']); | ||
| try { | ||
| await _emu(['screenrecord', 'stop']); | ||
| } catch (_) { | ||
| // A hung console: convert whatever was written. | ||
| } | ||
|
Comment on lines
+266
to
+270
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not report a recording as saved when stop fails. If the Preserve an error or unconfirmed state when stop fails. Do not delete or report the recording as saved until the recorder has stopped.
|
||
| final converted = await runFfmpeg(_exec, [ | ||
| '-y', | ||
| '-loglevel', | ||
|
|
@@ -282,9 +286,12 @@ class _EmulatorGrpcSession implements EmulatorGrpcSession { | |
| return path; | ||
| } | ||
|
|
||
| /// `adb emu` waits on the emulator console, which a hung emulator never | ||
| /// answers. | ||
| Future<CommandResult> _emu(List<String> args) => _exec.run( | ||
| adbPath, | ||
| arguments: ['-s', serial, 'emu', ...args], | ||
| timeout: const Duration(seconds: 15), | ||
| priority: CommandPriority.interactive, | ||
| ); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -187,24 +187,49 @@ class _ScrcpySession implements ScrcpySession { | |
| _emit(const SessionConnecting()); | ||
| try { | ||
| await _startServer(); | ||
| await _connect(); | ||
| if (!_stopped) await _connect(); | ||
| } catch (e) { | ||
| // Emit before stop(): it closes the status stream. | ||
| _emit(SessionFailed('$e')); | ||
| await stop(); | ||
| } finally { | ||
| // stop() during the start only released what existed by then. | ||
| if (_stopped) await _release(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| } | ||
|
|
||
| /// Polls `sys.boot_completed` with short commands, so a booting device | ||
| /// does not hold a [CommandExec] slot for minutes and [stop] ends the | ||
| /// wait. | ||
| Future<void> _waitForBoot() async { | ||
| final deadline = DateTime.now().add(_bootTimeout); | ||
| while (!_stopped) { | ||
| final boot = await _adb([ | ||
| 'shell', | ||
| 'getprop', | ||
| 'sys.boot_completed', | ||
| ], timeout: _bootPollTimeout); | ||
| if (!boot.success) { | ||
| throw StateError( | ||
| 'device did not finish booting: ${boot.stderr.trim()}', | ||
| ); | ||
| } | ||
| if (boot.stdout.trim() == '1') return; | ||
| if (DateTime.now().isAfter(deadline)) { | ||
| throw StateError( | ||
| 'device did not finish booting within ' | ||
| '${_bootTimeout.inMinutes} min', | ||
| ); | ||
| } | ||
| await Future<void>.delayed(_bootPollInterval); | ||
| } | ||
| } | ||
|
|
||
| Future<void> _startServer() async { | ||
| // adb lists an emulator while Android is still booting; the server needs | ||
| // system services (its cleanup thread crashes on a null ActivityManager). | ||
| final boot = await _adb([ | ||
| 'shell', | ||
| 'while [ "\$(getprop sys.boot_completed)" != 1 ]; do sleep 1; done', | ||
| ], timeout: _bootTimeout); | ||
| if (!boot.success) { | ||
| throw StateError('device did not finish booting: ${boot.stderr.trim()}'); | ||
| } | ||
| await _waitForBoot(); | ||
| if (_stopped) return; | ||
| final push = await _adb(['push', install.serverPath, _remoteJar]); | ||
| if (!push.success) { | ||
| throw StateError('adb push failed: ${push.stderr.trim()}'); | ||
|
|
@@ -221,6 +246,7 @@ class _ScrcpySession implements ScrcpySession { | |
| if (!forward.success || _forwardPort == null) { | ||
| throw StateError('adb forward failed: ${forward.stderr.trim()}'); | ||
| } | ||
| if (_stopped) return; | ||
|
|
||
| _server = await Process.start(adbPath, [ | ||
| '-s', | ||
|
|
@@ -280,6 +306,7 @@ class _ScrcpySession implements ScrcpySession { | |
| _controlSocket = | ||
| await Socket.connect(InternetAddress.loopbackIPv4, _forwardPort!) | ||
| ..setOption(SocketOption.tcpNoDelay, true); | ||
| if (_stopped) return; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not restore If the video worker reports
|
||
| unawaited(_controlSocket!.drain<void>()); | ||
| if (turnScreenOff) _controlSocket!.add(ScrcpyProtocol.displayPower(false)); | ||
| _emitLive(); | ||
|
|
@@ -395,19 +422,38 @@ class _ScrcpySession implements ScrcpySession { | |
| await stopRecording(); | ||
| _stopped = true; | ||
| _keyFrameTimer?.cancel(); | ||
| await _release(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| await _frames.close(); | ||
| await _statusController.close(); | ||
| } | ||
|
|
||
| /// Ends the worker, sockets, server and forward that exist so far; safe | ||
| /// to call again for ones a still-running [start] created after [stop]. | ||
| Future<void> _release() async { | ||
| _video?.stop(); | ||
| _video = null; | ||
| _controlSocket?.destroy(); | ||
| _controlSocket = null; | ||
| _server?.kill(); | ||
| if (_forwardPort case final port?) { | ||
| await _adb(['forward', '--remove', 'tcp:$port']); | ||
| _server = null; | ||
| final port = _forwardPort; | ||
| _forwardPort = null; | ||
| if (port != null) { | ||
| try { | ||
| await _adb(['forward', '--remove', 'tcp:$port']); | ||
| } catch (_) {} | ||
| } | ||
| await _frames.close(); | ||
| await _statusController.close(); | ||
| } | ||
|
|
||
| /// A cold boot can take minutes after adb first sees the emulator. | ||
| static const _bootTimeout = Duration(minutes: 3); | ||
|
|
||
| /// One `getprop` while waiting for the boot. | ||
| static const _bootPollTimeout = Duration(seconds: 10); | ||
|
|
||
| /// Pause between boot checks. | ||
| static const _bootPollInterval = Duration(seconds: 1); | ||
|
|
||
| Future<CommandResult> _adb( | ||
| List<String> args, { | ||
| Duration timeout = const Duration(seconds: 30), | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add the required
Fixedsection.The
1.4.1fix bullets appear directly below the version heading. Add### Fixedbefore them. As per coding guidelines, “Use the existing Keep-a-Changelog sections (Added/Changed/Fixed).”Proposed changelog change
Committable suggestion
Source: Coding guidelines