aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/invalid/lena/scrcpy/Session.java
diff options
context:
space:
mode:
authorLena <lena@omega>2026-07-01 00:00:00 +0000
committerLena <lena@omega>2026-07-01 00:00:00 +0000
commitff7acf898b48359275a5b09b82ed926a945233f8 (patch)
tree5e45b04be22cb1531f075ec35eb6f91518e09165 /app/src/main/java/invalid/lena/scrcpy/Session.java
parentbddc306de0cebde44f99b3a9c4ddba130946bef3 (diff)
downloadscrcpy-android-ff7acf898b48359275a5b09b82ed926a945233f8.tar.gz
app: fix session lifecycle, teardown, and opus playback
Opus playback never worked: feed() called dequeueInputBuffer() on a codec in async-callback mode, which always throws, so every packet was silently dropped. Feed input through the async callback with a bounded pending queue instead. Make bring-up transactional: roll back partially opened streams and the server shell on failure, join reader threads on stop, and close the owning ADB streams before releasing sinks. Replace the openAbstract watchdog thread with a real timeout in the vendored AdbConnection.open(), which also removes the half-open stream from the lookup table on failure. Harden the activity: release owned Surfaces, gate callbacks on a destroyed flag and a session generation, serialize reconnect, and handle target replacement via singleTask + onNewIntent. Propagate device-list write failures instead of swallowing them, bound the clipboard payload and the video pending queue against hostile peers, and discard incomplete recordings instead of keeping corrupt files. Use the mediaPlayback foreground-service type; Android 15 stops dataSync services after six hours. Drop the unused ACCESS_NETWORK_STATE permission.
Diffstat (limited to 'app/src/main/java/invalid/lena/scrcpy/Session.java')
-rw-r--r--app/src/main/java/invalid/lena/scrcpy/Session.java104
1 files changed, 63 insertions, 41 deletions
diff --git a/app/src/main/java/invalid/lena/scrcpy/Session.java b/app/src/main/java/invalid/lena/scrcpy/Session.java
index 774fdc1..947930b 100644
--- a/app/src/main/java/invalid/lena/scrcpy/Session.java
+++ b/app/src/main/java/invalid/lena/scrcpy/Session.java
@@ -238,48 +238,67 @@ public final class Session {
}
Log.i("adb connect ok");
- Server srv = new Server(ctx, adb);
- Server.Streams s = srv.bringUp();
-
- ControlStream cs = new ControlStream(s.controlIn, s.controlOut);
- Controller ctrl = new Controller(ctx, cs::send);
- cs.setInboundSink(ctrl);
-
- AudioSink ak = new AudioSink();
- AudioStream as = new AudioStream(s.audioIn, ak);
-
- VideoSink vk = new VideoSink(surface);
- final Controller ctrlRef = ctrl; // capture for SizeListener
- AtomicBoolean reported = new AtomicBoolean();
- VideoStream vs = new VideoStream(s.videoIn, vk, (w, h) -> {
- ctrlRef.setTargetSize(w, h);
- if (reported.compareAndSet(false, true) && listener != null) {
- listener.onConnected(w, h);
- }
- });
- vs.setOnEnd(this::onVideoEnded);
- MuxRecorder rec = new MuxRecorder();
- vs.setRecorder(rec);
+ Server srv = null;
+ ControlStream cs = null;
+ Controller ctrl = null;
+ AudioSink ak = null;
+ AudioStream as = null;
+ VideoSink vk = null;
+ VideoStream vs = null;
+ MuxRecorder rec = null;
+ boolean installed = false;
+ try {
+ srv = new Server(ctx, adb);
+ Server.Streams s = srv.bringUp();
+
+ cs = new ControlStream(s.controlIn, s.controlOut, this::onVideoEnded);
+ ctrl = new Controller(ctx, cs::send);
+ cs.setInboundSink(ctrl);
+
+ ak = new AudioSink(this::onVideoEnded);
+ as = new AudioStream(s.audioIn, ak);
+
+ vk = new VideoSink(surface, this::onVideoEnded);
+ Controller ctrlRef = ctrl;
+ AtomicBoolean reported = new AtomicBoolean();
+ vs = new VideoStream(s.videoIn, vk,
+ (w, h) -> reportConnected(ctrlRef, reported, w, h));
+ vs.setOnEnd(this::onVideoEnded);
+ rec = new MuxRecorder();
+ vs.setRecorder(rec);
+
+ // Start locals before publishing them. stop() either tears down
+ // a previously installed generation or marks this generation for
+ // rollback; it can never release objects that bringUp then starts.
+ cs.start();
+ as.start();
+ vs.start();
- synchronized (this) {
- if (stopped) {
- tearDownLocals(srv, cs, ctrl, ak, as, vk, vs, rec);
- throw new IOException("session: stopped during bring-up");
+ synchronized (this) {
+ if (stopped) throw new IOException("session: stopped during bring-up");
+ server = srv;
+ controlStream = cs;
+ controller = ctrl;
+ audioSink = ak;
+ audioStream = as;
+ videoSink = vk;
+ videoStream = vs;
+ recorder = rec;
+ if (pendingViewW > 0) ctrl.setViewSize(pendingViewW, pendingViewH);
+ installed = true;
}
- server = srv;
- controlStream = cs;
- controller = ctrl;
- audioSink = ak;
- audioStream = as;
- videoSink = vk;
- videoStream = vs;
- recorder = rec;
- if (pendingViewW > 0) ctrl.setViewSize(pendingViewW, pendingViewH);
+ } finally {
+ if (!installed) tearDownLocals(srv, cs, ctrl, ak, as, vk, vs, rec);
}
+ }
- cs.start();
- as.start();
- vs.start();
+ private synchronized void reportConnected(Controller ctrl, AtomicBoolean reported,
+ int w, int h) {
+ if (stopped) return;
+ ctrl.setTargetSize(w, h);
+ if (reported.compareAndSet(false, true) && listener != null) {
+ listener.onConnected(w, h);
+ }
}
// Fired on the video-reader thread when its read loop exits (EOF,
@@ -308,12 +327,15 @@ public final class Session {
AudioSink ak, AudioStream as,
VideoSink vk, VideoStream vs, MuxRecorder rec) {
if (rec != null) rec.close();
+ // Closing the owning ADB streams first unblocks readers. Join them
+ // before releasing their sinks so no callback can recreate resources
+ // after teardown.
+ if (srv != null) srv.close();
if (vs != null) vs.stop();
- if (vk != null) vk.release();
if (as != null) as.stop();
- if (ak != null) ak.release();
if (cs != null) cs.stop();
+ if (vk != null) vk.release();
+ if (ak != null) ak.release();
if (ctrl != null) ctrl.release();
- if (srv != null) srv.close();
}
}