From 11173eb04cebcb14365d9e0891da023f5a9172cd Mon Sep 17 00:00:00 2001 From: TongTongStudio Date: Sun, 26 Jul 2026 21:40:51 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E6=A0=BC=E5=BC=8F=E5=B9=B6=E4=BF=AE=E5=A4=8DWebSocket?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ScreenCaptureService.java | 131 ++++++++++++------ 1 file changed, 86 insertions(+), 45 deletions(-) diff --git a/WebRTCControlled/app/src/main/java/com/ttstd/controlled/service/ScreenCaptureService.java b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/service/ScreenCaptureService.java index f5eed56..58262ba 100644 --- a/WebRTCControlled/app/src/main/java/com/ttstd/controlled/service/ScreenCaptureService.java +++ b/WebRTCControlled/app/src/main/java/com/ttstd/controlled/service/ScreenCaptureService.java @@ -10,43 +10,40 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ServiceInfo; import android.media.projection.MediaProjection; -import android.media.projection.MediaProjectionManager; import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; -import android.text.TextUtils; -import android.widget.Toast; import android.util.DisplayMetrics; import android.util.Log; import android.view.WindowManager; +import android.widget.Toast; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.ttstd.control.Action; +import com.ttstd.control.ControlMessage; import com.ttstd.controlled.R; +import com.ttstd.controlled.accessibility.KeyboardAccessibilityService; import com.ttstd.controlled.activity.connection.ConnectionRequestActivity; import com.ttstd.controlled.activity.main.MainActivity; -import com.ttstd.controlled.accessibility.KeyboardAccessibilityService; import com.ttstd.controlled.input.AccessibilityInputUtils; import com.ttstd.controlled.input.InputCommandHandler; import com.ttstd.controlled.input.InputExecutor; import com.ttstd.controlled.input.RootShellInputUtils; import com.ttstd.controlled.input.ShellInputUtils; import com.ttstd.controlled.input.SystemInputUtils; -import com.ttstd.controlled.utils.SignatureUtils; -import com.ttstd.controlled.utils.InputSettings; import com.ttstd.controlled.signaling.SignalMessage; -import com.ttstd.controlled.utils.AuthSettings; import com.ttstd.controlled.signaling.WebSocketClient; -import com.ttstd.controlled.webrtc.WebRtcClient; +import com.ttstd.controlled.utils.AuthSettings; +import com.ttstd.controlled.utils.InputSettings; +import com.ttstd.controlled.utils.SignatureUtils; import com.ttstd.controlled.webrtc.SelfCodecEncoder; -import com.ttstd.control.Action; -import com.ttstd.control.ControlMessage; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; +import com.ttstd.controlled.webrtc.WebRtcClient; import org.webrtc.EglBase; import org.webrtc.IceCandidate; @@ -63,7 +60,9 @@ public class ScreenCaptureService extends Service { public static final String EXTRA_SERVER_URL = "server_url"; public static final String EXTRA_DEVICE_ID = "device_id"; - /** 静态授权结果,解决部分 Android 10 设备跨进程/Intent 传递 Intent 时 Token 失效的问题 */ + /** + * 静态授权结果,解决部分 Android 10 设备跨进程/Intent 传递 Intent 时 Token 失效的问题 + */ public static int sResultCode = Activity.RESULT_CANCELED; public static Intent sResultData = null; @@ -74,34 +73,52 @@ public class ScreenCaptureService extends Service { private EglBase eglBase; private final Gson gson = new Gson(); - /** 标记服务是否已经初始化,避免 onStartCommand 多次调用导致重复创建 */ + /** + * 标记服务是否已经初始化,避免 onStartCommand 多次调用导致重复创建 + */ private boolean isInitialized = false; - /** 静态实例引用,供弹窗 Activity 在无需绑定的情况下回调本服务。 */ + /** + * 静态实例引用,供弹窗 Activity 在无需绑定的情况下回调本服务。 + */ private static ScreenCaptureService instance; public static ScreenCaptureService getInstance() { return instance; } - /** 本设备 ID(注册到信令服务器用),保存为字段以便弹窗回调使用。 */ + /** + * 本设备 ID(注册到信令服务器用),保存为字段以便弹窗回调使用。 + */ private String deviceId; - /** 当前待处理的连接请求(OFFER)信息,供用户在弹窗中确认或拒绝。 */ + /** + * 当前待处理的连接请求(OFFER)信息,供用户在弹窗中确认或拒绝。 + */ private String pendingControllerId; private String pendingControllerName; private String pendingOfferSdp; - /** 当前正在控制本设备的控制端名称(用户名或设备 ID),用于断开提示。 */ + /** + * 当前正在控制本设备的控制端名称(用户名或设备 ID),用于断开提示。 + */ private String controllingName; - /** 设备真实屏幕分辨率,用于分辨率切换时按宽高比计算采集尺寸。 */ + /** + * 设备真实屏幕分辨率,用于分辨率切换时按宽高比计算采集尺寸。 + */ private int realScreenWidth; private int realScreenHeight; - /** 当前实际采集分辨率与帧率(运行时可被本地/远端请求切换)。 */ + /** + * 当前实际采集分辨率与帧率(运行时可被本地/远端请求切换)。 + */ private int currentCaptureWidth; private int currentCaptureHeight; private int currentCaptureFps; - /** 主线程 Handler,用于从 WebRTC 回调线程切回主线程执行 UI 提示。 */ + /** + * 主线程 Handler,用于从 WebRTC 回调线程切回主线程执行 UI 提示。 + */ private final Handler mainHandler = new Handler(Looper.getMainLooper()); - /** 标记服务是否正在关闭,避免重复停止/重复弹窗。 */ + /** + * 标记服务是否正在关闭,避免重复停止/重复弹窗。 + */ private boolean isShuttingDown = false; // ---- 屏幕串流"自编码"模式 ---- @@ -190,7 +207,8 @@ public class ScreenCaptureService extends Service { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { try { refreshRate = wm.getDefaultDisplay().getRefreshRate(); - } catch (Exception ignored) {} + } catch (Exception ignored) { + } } int fps = Math.round(refreshRate); // 屏幕控制 30fps 已足够跟手,并显著降低 H264 硬编延迟(影响“跟手”体验)。 @@ -301,23 +319,30 @@ public class ScreenCaptureService extends Service { /** * 切换屏幕采集分辨率(本地 UI 调用)。 + * * @param longEdge 目标长边像素;<=0 表示使用设备原生分辨率(按屏幕宽高比缩放)。 */ public void changeResolutionByLongEdge(int longEdge) { requestResolutionChange(longEdge, 0, 0, false); } - /** 返回当前采集分辨率(宽/高/帧率)。 */ + /** + * 返回当前采集分辨率(宽/高/帧率)。 + */ public int[] getCurrentCaptureResolution() { - return new int[]{ currentCaptureWidth, currentCaptureHeight, currentCaptureFps }; + return new int[]{currentCaptureWidth, currentCaptureHeight, currentCaptureFps}; } - /** 返回设备原生分辨率的长边(像素),供 UI 生成“原始画质”选项。 */ + /** + * 返回设备原生分辨率的长边(像素),供 UI 生成“原始画质”选项。 + */ public int getNativeLongEdge() { return Math.max(realScreenWidth, realScreenHeight); } - /** 控制端经 DataChannel 发来的 SET_RESOLUTION 指令入口。 */ + /** + * 控制端经 DataChannel 发来的 SET_RESOLUTION 指令入口。 + */ private void onRemoteResolutionRequested(int width, int height, int fps) { requestResolutionChange(width, height, fps, true); } @@ -330,9 +355,9 @@ public class ScreenCaptureService extends Service { * - 否则使用精确 width x height。 * 最终宽高会偶数对齐(部分 H264 编码器要求),并触发一次关键帧刷新远端画面。 * - * @param width 目标宽度/长边(像素) - * @param height 目标高度(像素,<=0 时按宽高比计算) - * @param fps 目标帧率(<=0 时沿用当前帧率) + * @param width 目标宽度/长边(像素) + * @param height 目标高度(像素,<=0 时按宽高比计算) + * @param fps 目标帧率(<=0 时沿用当前帧率) * @param fromRemote 是否由远端控制端触发(仅用于 UI 提示区分) */ public void requestResolutionChange(int width, int height, int fps, boolean fromRemote) { @@ -368,7 +393,9 @@ public class ScreenCaptureService extends Service { } } - /** 根据指令参数解析出最终采集尺寸(保留屏幕宽高比、偶数对齐)。 */ + /** + * 根据指令参数解析出最终采集尺寸(保留屏幕宽高比、偶数对齐)。 + */ private int[] resolveCaptureSize(int width, int height) { int w, h; if (width <= 0) { @@ -388,12 +415,14 @@ public class ScreenCaptureService extends Service { if (h % 2 != 0) h--; if (w <= 0) w = 2; if (h <= 0) h = 2; - return new int[]{ w, h }; + return new int[]{w, h}; } // ---- 屏幕串流"自编码"模式 ---- - /** 控制端经 SET_STREAM_MODE 请求切换串流模式。 */ + /** + * 控制端经 SET_STREAM_MODE 请求切换串流模式。 + */ private void onStreamModeRequested(int mode) { setStreamMode(mode); } @@ -402,7 +431,7 @@ public class ScreenCaptureService extends Service { * 切换屏幕串流模式: * - {@link SelfCodecEncoder#STREAM_MODE_WEBRTC}:使用 WebRTC 默认媒体轨道(ScreenCapturerAndroid)。 * - {@link SelfCodecEncoder#STREAM_MODE_SELF_CODEC}:暂停 WebRTC 采集,改用自建 MediaCodec 编码, - * 经 DataChannel 的 video_channel 透传裸 H.264 码流,由控制端自建解码器渲染。 + * 经 DataChannel 的 video_channel 透传裸 H.264 码流,由控制端自建解码器渲染。 */ public void setStreamMode(int mode) { if (mode == currentStreamMode) { @@ -429,12 +458,16 @@ public class ScreenCaptureService extends Service { reportStreamMode(mode); } - /** 返回当前串流模式。 */ + /** + * 返回当前串流模式。 + */ public int getCurrentStreamMode() { return currentStreamMode; } - /** 启动自编码通道。返回 false 表示启动失败(调用方应回退 WebRTC 模式)。 */ + /** + * 启动自编码通道。返回 false 表示启动失败(调用方应回退 WebRTC 模式)。 + */ private boolean startSelfCodecEncoder() { if (selfEncoder != null) { Log.w(TAG, "self codec encoder already running"); @@ -490,7 +523,9 @@ public class ScreenCaptureService extends Service { } } - /** 经 control DataChannel 向控制端回报当前生效的串流模式。 */ + /** + * 经 control DataChannel 向控制端回报当前生效的串流模式。 + */ private void reportStreamMode(int mode) { if (webRtcClient == null) return; try { @@ -508,9 +543,14 @@ public class ScreenCaptureService extends Service { * 状态回调。由已 bind 的 MainActivity 注册,用于把连接状态同步到界面。 */ public interface ServiceStateListener { - /** 信令服务器断开(WebSocket 断开)。 */ + /** + * 信令服务器断开(WebSocket 断开)。 + */ void onSignalDisconnected(); - /** 远程控制端断开,参数为控制端名称(用户名或设备 ID)。 */ + + /** + * 远程控制端断开,参数为控制端名称(用户名或设备 ID)。 + */ void onControllerDisconnected(String name); } @@ -586,10 +626,8 @@ public class ScreenCaptureService extends Service { @Override public void onError(String error) { - Log.e(TAG, "WebSocket error: " + error); - if(TextUtils.isEmpty(error)){ - mainHandler.post(() -> onSignalServerDisconnected()); - } + Log.e(TAG, "WebSocket onError: " + error); + mainHandler.post(() -> onSignalServerDisconnected()); } @Override @@ -730,7 +768,9 @@ public class ScreenCaptureService extends Service { return "未知的鉴权类型: " + authType; } - /** 鉴权失败时,向控制端回送带原因的拒绝消息并清理待处理状态。 */ + /** + * 鉴权失败时,向控制端回送带原因的拒绝消息并清理待处理状态。 + */ private void sendAuthRejected(String reason) { if (pendingControllerId == null || wsClient == null) { return; @@ -860,6 +900,7 @@ public class ScreenCaptureService extends Service { }; private int heartbeatCount = 0; + private void updateNotificationHeartbeat() { String dots = ""; for (int i = 0; i < (heartbeatCount % 3) + 1; i++) dots += ".";