build: 更新项目至studio panda
This commit is contained in:
@@ -29,6 +29,10 @@ android {
|
||||
enabled true
|
||||
}
|
||||
|
||||
viewBinding {
|
||||
enabled true
|
||||
}
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments = [AROUTER_MODULE_NAME: project.getName()]
|
||||
@@ -101,7 +105,7 @@ dependencies {
|
||||
implementation 'com.jeremyliao:live-event-bus-x:1.7.3'
|
||||
|
||||
implementation 'org.webrtc:google-webrtc:1.0.32006'
|
||||
implementation 'org.java-websocket:Java-WebSocket:1.5.3'
|
||||
// implementation 'org.java-websocket:Java-WebSocket:1.5.3'
|
||||
|
||||
//MMKV
|
||||
implementation 'com.tencent:mmkv-static:1.2.14'
|
||||
|
||||
@@ -14,7 +14,9 @@ import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -23,7 +25,11 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.ttstd.remoteservice.R;
|
||||
import com.ttstd.remoteservice.network.WebSocketCallback;
|
||||
import com.ttstd.remoteservice.network.WebSocketManager;
|
||||
import com.ttstd.remoteservice.service.ScreenCaptureService2;
|
||||
|
||||
import org.webrtc.CapturerObserver;
|
||||
@@ -46,7 +52,9 @@ import org.webrtc.VideoTrack;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class WebRTCScreenCaptureActivity extends AppCompatActivity {
|
||||
import okio.ByteString;
|
||||
|
||||
public class WebRTCScreenCaptureActivity extends AppCompatActivity implements WebSocketCallback {
|
||||
private static final String TAG = "WebRTCScreenCaptureActivity";
|
||||
// 请求码
|
||||
private static final int REQUEST_CODE_SCREEN_CAPTURE = 1001;
|
||||
@@ -71,7 +79,8 @@ public class WebRTCScreenCaptureActivity extends AppCompatActivity {
|
||||
private int screenDpi;
|
||||
|
||||
// UI控件
|
||||
private Button btnStartPush;
|
||||
private TextView tvStatus;
|
||||
private Button btConnect, btnStartPush;
|
||||
private SurfaceViewRenderer localRender; // 本地预览(可选)
|
||||
|
||||
private TextureView textureView;
|
||||
@@ -82,16 +91,27 @@ public class WebRTCScreenCaptureActivity extends AppCompatActivity {
|
||||
private String remoteSdp = ""; // 替换为远端实际SDP
|
||||
private List<IceCandidate> remoteIceCandidates = new ArrayList<>();
|
||||
|
||||
private WebSocketManager webSocketManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_webrtc_screen_capture);
|
||||
|
||||
// 初始化控件
|
||||
tvStatus = findViewById(R.id.tv_status);
|
||||
btConnect = findViewById(R.id.bt_connect);
|
||||
btnStartPush = findViewById(R.id.btn_start_push);
|
||||
localRender = findViewById(R.id.local_render);
|
||||
textureView = findViewById(R.id.textureView);
|
||||
|
||||
btConnect.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
initWebSocket();
|
||||
}
|
||||
});
|
||||
|
||||
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
|
||||
@@ -173,6 +193,15 @@ public class WebRTCScreenCaptureActivity extends AppCompatActivity {
|
||||
startActivityForResult(captureIntent, REQUEST_CODE_SCREEN_CAPTURE);
|
||||
}
|
||||
|
||||
private void initWebSocket() {
|
||||
Log.e(TAG, "initWebSocket: ");
|
||||
// 初始化 WebSocket 管理器
|
||||
webSocketManager = WebSocketManager.getInstance();
|
||||
// String wsUrl = "ws://175.178.213.60:2310/signaling/981964879";
|
||||
String wsUrl = "ws://192.168.100.111:2310/signaling/981964879";
|
||||
webSocketManager.init(wsUrl, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化WebRTC核心组件
|
||||
*/
|
||||
@@ -348,8 +377,8 @@ public class WebRTCScreenCaptureActivity extends AppCompatActivity {
|
||||
|
||||
// 添加用户指定的 STUN 服务器
|
||||
PeerConnection.IceServer stunServer = PeerConnection.IceServer.builder("stun:47.242.112.133:3478")
|
||||
.setUsername("tt")
|
||||
.setPassword("tongtong")
|
||||
.setUsername("tt")
|
||||
.setPassword("tongtong")
|
||||
.createIceServer();
|
||||
iceServers.add(stunServer);
|
||||
|
||||
@@ -368,8 +397,139 @@ public class WebRTCScreenCaptureActivity extends AppCompatActivity {
|
||||
return iceServers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送SDP给远端信令服务器
|
||||
*/
|
||||
private void sendSdpToRemote(SessionDescription sdp) {
|
||||
// TODO: 实现信令发送逻辑,例如通过WebSocket发送SDP字符串
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty("msg_type", 1);
|
||||
|
||||
JsonObject sdpJson = new JsonObject();
|
||||
sdpJson.addProperty("type", sdp.type.canonicalForm()); // "offer" 或 "answer"
|
||||
sdpJson.addProperty("sdp", sdp.description);
|
||||
// TODO: 2026/3/11 之后改为唯一标识码
|
||||
sdpJson.addProperty("fromUser", "981964879"); // 标识发送方
|
||||
sdpJson.addProperty("timestamp", System.currentTimeMillis());
|
||||
|
||||
jsonObject.add("content", sdpJson);
|
||||
String json = jsonObject.toString();
|
||||
|
||||
Log.d(TAG, "发送SDP到信令服务器: ");
|
||||
Log.e(TAG, "sendSdpToRemote: SDP内容: " + json);
|
||||
|
||||
webSocketManager.sendMessage(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送ICE Candidate给远端信令服务器
|
||||
*/
|
||||
private void sendIceCandidateToRemote(IceCandidate iceCandidate) {
|
||||
Log.d(TAG, "Send ICE Candidate to Remote: " + iceCandidate.sdpMid + " " + iceCandidate.sdpMLineIndex + " " + iceCandidate.sdp);
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty("msg_type", 2);
|
||||
|
||||
JsonObject iceJson = new JsonObject();
|
||||
iceJson.addProperty("sdpMid", iceCandidate.sdpMid);
|
||||
iceJson.addProperty("sdpMLineIndex", iceCandidate.sdpMLineIndex);
|
||||
iceJson.addProperty("sdp", iceCandidate.sdp);
|
||||
|
||||
// TODO: 2026/3/11 之后改为唯一标识码
|
||||
iceJson.addProperty("fromUser", "981964879");
|
||||
iceJson.addProperty("timestamp", System.currentTimeMillis());
|
||||
|
||||
jsonObject.add("content", iceJson);
|
||||
|
||||
String json = jsonObject.toString();
|
||||
|
||||
Log.d(TAG, "发送ICE Candidate到信令服务器: ");
|
||||
Log.e(TAG, "sendIceCandidateToRemote: ICE内容: " + json);
|
||||
webSocketManager.sendMessage(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理信令服务器响应(接收远端的Answer SDP)
|
||||
*/
|
||||
private void handleSignalingResponse(String response) {
|
||||
JsonObject jsonResponse = JsonParser.parseString(response).getAsJsonObject();
|
||||
;
|
||||
|
||||
// 检查是否包含远端的Answer SDP
|
||||
if (jsonResponse.has("type") && "answer".equals(jsonResponse.get("type").getAsString())) {
|
||||
String remoteSdp = jsonResponse.get("sdp").getAsString();
|
||||
SessionDescription remoteSessionDescription =
|
||||
new SessionDescription(SessionDescription.Type.ANSWER, remoteSdp);
|
||||
|
||||
// 设置远端SDP
|
||||
peerConnection.setRemoteDescription(new SimpleSdpObserver(), remoteSessionDescription);
|
||||
Log.d(TAG, "已设置远端Answer SDP");
|
||||
}
|
||||
|
||||
// 检查是否包含远端的ICE Candidate
|
||||
if (jsonResponse.has("candidate")) {
|
||||
String sdpMid = jsonResponse.get("sdpMid").getAsString();
|
||||
int sdpMLineIndex = jsonResponse.get("sdpMLineIndex").getAsInt();
|
||||
String candidate = jsonResponse.get("candidate").getAsString();
|
||||
|
||||
IceCandidate remoteIceCandidate = new IceCandidate(sdpMid, sdpMLineIndex, candidate);
|
||||
peerConnection.addIceCandidate(remoteIceCandidate);
|
||||
Log.d(TAG, "已添加远端ICE Candidate");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected() {
|
||||
Log.e(TAG, "onConnected: ");
|
||||
tvStatus.setText("websocket已连接");
|
||||
btConnect.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected(String reason) {
|
||||
Log.e(TAG, "onDisconnected: " + reason);
|
||||
tvStatus.setText("websocket已断开:" + reason);
|
||||
btConnect.setEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
Log.e(TAG, "onMessage: " + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(ByteString bytes) {
|
||||
Log.e(TAG, "onMessage: ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
Log.e(TAG, "onError: " + error);
|
||||
tvStatus.setText("websocket错误:" + error);
|
||||
btConnect.setEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化的SdpObserver实现
|
||||
*/
|
||||
private static class SimpleSdpObserver implements SdpObserver {
|
||||
@Override
|
||||
public void onCreateSuccess(SessionDescription sessionDescription) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetSuccess() {
|
||||
Log.d(TAG, "SDP设置成功");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateFailure(String error) {
|
||||
Log.e(TAG, "创建SDP失败: " + error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetFailure(String error) {
|
||||
Log.e(TAG, "设置SDP失败: " + error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -476,15 +636,6 @@ public class WebRTCScreenCaptureActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送ICE候选到远端(示例:实际需通过WebSocket/HTTP信令服务器)
|
||||
*/
|
||||
private void sendIceCandidateToRemote(IceCandidate iceCandidate) {
|
||||
// 这里仅做日志打印,实际需替换为信令发送逻辑
|
||||
Log.d(TAG, "Send ICE Candidate to Remote: " + iceCandidate.sdpMid + " " + iceCandidate.sdpMLineIndex + " " + iceCandidate.sdp);
|
||||
// 示例:将iceCandidate转为JSON发送到远端服务器
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理权限请求结果
|
||||
*/
|
||||
@@ -606,6 +757,10 @@ public class WebRTCScreenCaptureActivity extends AppCompatActivity {
|
||||
// rootEglBase.release();
|
||||
// }
|
||||
|
||||
if (webSocketManager != null) {
|
||||
webSocketManager.disconnect();
|
||||
}
|
||||
|
||||
// 重置按钮
|
||||
btnStartPush.setText("开始推流");
|
||||
btnStartPush.setOnClickListener(v -> checkPermissionsAndStart());
|
||||
|
||||
@@ -24,7 +24,6 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
||||
private static final int REQUEST_CODE_SCREEN_CAPTURE = 7897;
|
||||
private MediaProjectionManager mMediaProjectionManager;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean setNightMode() {
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.ttstd.remoteservice.config;
|
||||
|
||||
/**
|
||||
* WebSocket 配置类
|
||||
* 用于配置WebSocket连接的各种参数
|
||||
*/
|
||||
public class WebSocketConfig {
|
||||
public String wsUrl; // WebSocket服务器地址
|
||||
public static final long heartbeatInterval = 30000; // 心跳间隔,默认10秒
|
||||
public static final long reconnectInterval = 5000; // 重连间隔,默认5秒
|
||||
public static final int maxReconnectAttempts = 5; // 最大重连次数
|
||||
public static final boolean autoReconnect = true; // 是否自动重连
|
||||
public static final boolean needHeartbeat = true; // 是否需要心跳检测
|
||||
public static final long connectTimeout = 10; // 连接超时时间(秒)
|
||||
public static final long readTimeout = 10; // 读取超时时间(秒)
|
||||
public static final long writeTimeout = 10; // 写入超时时间(秒)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.ttstd.remoteservice.network;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
/**
|
||||
* WebSocket 回调接口
|
||||
* 所有回调都在主线程执行,方便更新 UI
|
||||
*/
|
||||
public interface WebSocketCallback {
|
||||
/**
|
||||
* 连接成功
|
||||
*/
|
||||
void onConnected();
|
||||
|
||||
/**
|
||||
* 连接断开
|
||||
*
|
||||
* @param reason 断开原因
|
||||
*/
|
||||
void onDisconnected(String reason);
|
||||
|
||||
/**
|
||||
* 收到文本消息
|
||||
*
|
||||
* @param message 文本内容
|
||||
*/
|
||||
void onMessage(String message);
|
||||
|
||||
/**
|
||||
* 收到二进制消息
|
||||
*
|
||||
* @param bytes 二进制数据
|
||||
*/
|
||||
void onMessage(ByteString bytes);
|
||||
|
||||
/**
|
||||
* 连接发生错误
|
||||
*
|
||||
* @param error 错误信息
|
||||
*/
|
||||
void onError(String error);
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
package com.ttstd.remoteservice.network;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.ttstd.remoteservice.config.WebSocketConfig;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
import okio.ByteString;
|
||||
|
||||
/**
|
||||
* WebSocket 管理类(单例模式)
|
||||
* 功能:自动重连、心跳检测、连接状态管理
|
||||
*/
|
||||
public class WebSocketManager {
|
||||
private static final String TAG = "WebSocketManager";
|
||||
private static volatile WebSocketManager instance;
|
||||
|
||||
private OkHttpClient client;
|
||||
private WebSocket webSocket;
|
||||
private WebSocketCallback callback;
|
||||
private String wsUrl;
|
||||
|
||||
// 连接状态
|
||||
private boolean isConnected = false;
|
||||
private boolean shouldReconnect = true;
|
||||
|
||||
private Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||
private Runnable reconnectRunnable;
|
||||
private Runnable heartbeatRunnable;
|
||||
|
||||
// 重连相关
|
||||
private int reconnectAttempts = 0;
|
||||
|
||||
// 消息队列(线程安全)
|
||||
private ConcurrentLinkedQueue<String> messageQueue = new ConcurrentLinkedQueue<>();
|
||||
// 连接状态锁
|
||||
private final Object connectionLock = new Object();
|
||||
|
||||
private WebSocketManager() {
|
||||
// 私有构造函数
|
||||
}
|
||||
|
||||
public static WebSocketManager getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (WebSocketManager.class) {
|
||||
if (instance == null) {
|
||||
instance = new WebSocketManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 WebSocket 连接
|
||||
*/
|
||||
public void init(String url, WebSocketCallback callback) {
|
||||
this.wsUrl = url;
|
||||
this.callback = callback;
|
||||
|
||||
client = new OkHttpClient.Builder()
|
||||
.connectTimeout(WebSocketConfig.connectTimeout, TimeUnit.SECONDS)
|
||||
.readTimeout(WebSocketConfig.readTimeout, TimeUnit.SECONDS)
|
||||
.writeTimeout(WebSocketConfig.writeTimeout, TimeUnit.SECONDS)
|
||||
.pingInterval(10, TimeUnit.SECONDS) // 设置心跳
|
||||
.retryOnConnectionFailure(true)
|
||||
.build();
|
||||
|
||||
connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 建立 WebSocket 连接
|
||||
*/
|
||||
private void connect() {
|
||||
if (wsUrl == null) {
|
||||
Log.e(TAG, "WebSocket URL 不能为 null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(wsUrl)) {
|
||||
Log.e(TAG, "WebSocketConfig or URL is null");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Request request = new Request.Builder()
|
||||
.url(wsUrl)
|
||||
.build();
|
||||
|
||||
webSocket = client.newWebSocket(request, new InnerWebSocketListener());
|
||||
Log.d(TAG, "Connecting to WebSocket: " + wsUrl);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Connect failed: " + e.getMessage());
|
||||
notifyError("连接失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送文本消息
|
||||
*/
|
||||
public boolean sendMessage(String message) {
|
||||
if (webSocket != null && isConnected) {
|
||||
boolean sent = webSocket.send(message);
|
||||
if (sent) {
|
||||
Log.d(TAG, "Message sent: " + message);
|
||||
} else {
|
||||
// 发送失败,加入队列
|
||||
messageQueue.offer(message);
|
||||
Log.w(TAG, "消息发送失败,已加入队列: " + message);
|
||||
}
|
||||
return sent;
|
||||
} else {
|
||||
// 未连接,加入队列等待重发
|
||||
messageQueue.offer(message);
|
||||
Log.e(TAG, "WebSocket not connected, cannot send message");
|
||||
notifyError("WebSocket未连接,无法发送消息");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送二进制消息
|
||||
*/
|
||||
public boolean sendMessage(ByteString bytes) {
|
||||
if (webSocket != null && isConnected) {
|
||||
return webSocket.send(bytes);
|
||||
} else {
|
||||
Log.e(TAG, "WebSocket not connected, cannot send binary message");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭 WebSocket 连接
|
||||
*/
|
||||
public void disconnect() {
|
||||
Log.d(TAG, "Disconnecting WebSocket");
|
||||
|
||||
shouldReconnect = false;
|
||||
|
||||
// 停止重连和心跳
|
||||
stopReconnect();
|
||||
stopHeartbeat();
|
||||
|
||||
if (webSocket != null) {
|
||||
webSocket.close(1000, "正常关闭");
|
||||
webSocket = null;
|
||||
}
|
||||
|
||||
isConnected = false;
|
||||
Log.d(TAG, "WebSocket 连接已关闭");
|
||||
reconnectAttempts = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接状态
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return isConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始心跳检测
|
||||
*/
|
||||
private void startHeartbeat() {
|
||||
stopHeartbeat();
|
||||
|
||||
heartbeatRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isConnected && webSocket != null) {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty("msg_type", 0);
|
||||
jsonObject.addProperty("content", "ping");
|
||||
// 发送心跳消息(可以是空消息或特定协议)
|
||||
boolean sent = webSocket.send(jsonObject.toString());
|
||||
Log.d(TAG, "发送心跳包");
|
||||
if (sent) {
|
||||
// 继续下一次心跳
|
||||
mainHandler.postDelayed(this, WebSocketConfig.heartbeatInterval);
|
||||
} else {
|
||||
// 发送失败,尝试重连
|
||||
handleDisconnect("Heartbeat failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
mainHandler.postDelayed(heartbeatRunnable, WebSocketConfig.heartbeatInterval);
|
||||
Log.d(TAG, "Heartbeat started");
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理断开连接
|
||||
*/
|
||||
private void handleDisconnect(String reason) {
|
||||
Log.d(TAG, "Handle disconnect: " + reason);
|
||||
|
||||
isConnected = false;
|
||||
stopHeartbeat();
|
||||
|
||||
notifyDisconnected(reason);
|
||||
|
||||
// 自动重连逻辑
|
||||
if (WebSocketConfig.autoReconnect && reconnectAttempts < WebSocketConfig.maxReconnectAttempts) {
|
||||
scheduleReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安排重连
|
||||
*/
|
||||
private void scheduleReconnect() {
|
||||
if (!shouldReconnect) return;
|
||||
|
||||
reconnectAttempts++;
|
||||
Log.d(TAG, "Scheduling reconnect attempt " + reconnectAttempts + " after " + WebSocketConfig.reconnectInterval + "ms");
|
||||
|
||||
reconnectRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(TAG, "尝试重连 WebSocket");
|
||||
connect();
|
||||
}
|
||||
};
|
||||
mainHandler.postDelayed(reconnectRunnable, WebSocketConfig.heartbeatInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止重连
|
||||
*/
|
||||
private void stopReconnect() {
|
||||
if (heartbeatRunnable != null) {
|
||||
mainHandler.removeCallbacks(heartbeatRunnable);
|
||||
heartbeatRunnable = null;
|
||||
}
|
||||
reconnectAttempts = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止心跳检测
|
||||
*/
|
||||
private void stopHeartbeat() {
|
||||
if (heartbeatRunnable != null) {
|
||||
mainHandler.removeCallbacks(heartbeatRunnable);
|
||||
heartbeatRunnable = null;
|
||||
}
|
||||
reconnectAttempts = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部WebSocket监听器
|
||||
*/
|
||||
private class InnerWebSocketListener extends WebSocketListener {
|
||||
@Override
|
||||
public void onOpen(@NonNull WebSocket webSocket, @NonNull Response response) {
|
||||
Log.d(TAG, "WebSocket connected successfully");
|
||||
|
||||
isConnected = true;
|
||||
reconnectAttempts = 0; // 重置重连计数器
|
||||
|
||||
// 连接成功,发送队列中的积压消息
|
||||
processMessageQueue();
|
||||
|
||||
// 在主线程通知连接成功
|
||||
mainHandler.post(() -> {
|
||||
if (callback != null) {
|
||||
callback.onConnected();
|
||||
}
|
||||
});
|
||||
|
||||
// 开始心跳检测
|
||||
startHeartbeat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) {
|
||||
Log.d(TAG, "Received text message: " + text);
|
||||
|
||||
// 在主线程通知消息接收
|
||||
mainHandler.post(() -> {
|
||||
if (callback != null) {
|
||||
callback.onMessage(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(@NonNull WebSocket webSocket, @NonNull ByteString bytes) {
|
||||
Log.d(TAG, "Received binary message, size: " + bytes.size());
|
||||
|
||||
// 在主线程通知消息接收
|
||||
mainHandler.post(() -> {
|
||||
if (callback != null) {
|
||||
callback.onMessage(bytes);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosing(@NonNull WebSocket webSocket, int code, @NonNull String reason) {
|
||||
Log.d(TAG, "WebSocket closing: " + code + " - " + reason);
|
||||
webSocket.close(1000, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(@NonNull WebSocket webSocket, int code, @NonNull String reason) {
|
||||
Log.d(TAG, "WebSocket closed: " + code + " - " + reason);
|
||||
handleDisconnect("Connection closed: " + reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull WebSocket webSocket, @NonNull Throwable t, Response response) {
|
||||
Log.e(TAG, "WebSocket failure: " + t.getMessage());
|
||||
handleDisconnect("Connection failure: " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理消息队列(发送所有积压消息)
|
||||
*/
|
||||
private void processMessageQueue() {
|
||||
new Thread(() -> {
|
||||
synchronized (connectionLock) {
|
||||
while (!messageQueue.isEmpty()) {
|
||||
String message = messageQueue.poll();
|
||||
if (message != null && isConnected && webSocket != null) {
|
||||
boolean sent = webSocket.send(message);
|
||||
if (sent) {
|
||||
Log.d(TAG, "队列消息发送成功: " + message);
|
||||
} else {
|
||||
// 发送失败,重新放回队列头部
|
||||
messageQueue.offer(message);
|
||||
Log.e(TAG, "队列消息发送失败,已重新排队: " + message);
|
||||
break; // 暂停发送,避免连续失败
|
||||
}
|
||||
|
||||
// 添加短暂延迟,避免洪水攻击服务器
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取队列大小(用于监控)
|
||||
*/
|
||||
public int getQueueSize() {
|
||||
return messageQueue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空消息队列
|
||||
*/
|
||||
public void clearQueue() {
|
||||
messageQueue.clear();
|
||||
Log.d(TAG, "消息队列已清空");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知错误(主线程)
|
||||
*/
|
||||
private void notifyError(String error) {
|
||||
mainHandler.post(() -> {
|
||||
if (callback != null) {
|
||||
callback.onError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知断开连接(主线程)
|
||||
*/
|
||||
private void notifyDisconnected(String reason) {
|
||||
mainHandler.post(() -> {
|
||||
if (callback != null) {
|
||||
callback.onDisconnected(reason);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理资源
|
||||
*/
|
||||
public void release() {
|
||||
disconnect();
|
||||
if (client != null) {
|
||||
client.dispatcher().executorService().shutdown();
|
||||
}
|
||||
instance = null;
|
||||
Log.d(TAG, "WebSocketManager released");
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,41 @@
|
||||
package com.ttstd.remoteservice.utils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
public class SystemUtils {
|
||||
|
||||
/**
|
||||
* 获取设备序列号
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@SuppressLint("MissingPermission")
|
||||
public static String getSerial() {
|
||||
String serial = "unknow";
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {//9.0+
|
||||
serial = Build.getSerial();
|
||||
} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {//8.0+
|
||||
serial = Build.SERIAL;
|
||||
} else {//8.0-
|
||||
Class<?> c = Class.forName("android.os.SystemProperties");
|
||||
Method get = c.getMethod("get", String.class);
|
||||
serial = (String) get.invoke(c, "ro.serialno");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("e", "读取设备序列号异常:" + e.toString());
|
||||
}
|
||||
return serial;
|
||||
}
|
||||
|
||||
public static boolean isMainProcessName(Context cxt, int pid) {
|
||||
String packageName = cxt.getPackageName();
|
||||
ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
|
||||
@@ -1,26 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context=".activity.WebRTCScreenCaptureActivity">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_start_push"
|
||||
<data>
|
||||
|
||||
</data>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="开始推流" />
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- WebRTC本地预览(显示采集的屏幕内容) -->
|
||||
<org.webrtc.SurfaceViewRenderer
|
||||
android:id="@+id/local_render"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_weight="1" />
|
||||
<TextView
|
||||
android:id="@+id/tv_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="等待连接..."
|
||||
android:textColor="#000000"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextureView
|
||||
android:id="@+id/textureView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_weight="1" />
|
||||
<Button
|
||||
android:id="@+id/bt_connect"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="连接websocket"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</LinearLayout>
|
||||
<Button
|
||||
android:id="@+id/btn_start_push"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="开始推流" />
|
||||
|
||||
<!-- WebRTC本地预览(显示采集的屏幕内容) -->
|
||||
<org.webrtc.SurfaceViewRenderer
|
||||
android:id="@+id/local_render"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextureView
|
||||
android:id="@+id/textureView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
||||
Reference in New Issue
Block a user