diff --git a/app/build.gradle b/app/build.gradle
index d87bc1d..ee0c636 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -100,6 +100,9 @@ dependencies {
/*https://github.com/JeremyLiao/LiveEventBus*/
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'
+
//MMKV
implementation 'com.tencent:mmkv-static:1.2.14'
//bugly
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a849a4d..80e372e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -8,9 +8,10 @@
-
-
-
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/ttstd/remoteservice/activity/WebRTCScreenCaptureActivity.java b/app/src/main/java/com/ttstd/remoteservice/activity/WebRTCScreenCaptureActivity.java
new file mode 100644
index 0000000..8576f42
--- /dev/null
+++ b/app/src/main/java/com/ttstd/remoteservice/activity/WebRTCScreenCaptureActivity.java
@@ -0,0 +1,620 @@
+package com.ttstd.remoteservice.activity;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.SurfaceTexture;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.projection.MediaProjection;
+import android.media.projection.MediaProjectionManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Surface;
+import android.view.TextureView;
+import android.widget.Button;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.ttstd.remoteservice.R;
+import com.ttstd.remoteservice.service.ScreenCaptureService2;
+
+import org.webrtc.CapturerObserver;
+import org.webrtc.DefaultVideoDecoderFactory;
+import org.webrtc.DefaultVideoEncoderFactory;
+import org.webrtc.EglBase;
+import org.webrtc.IceCandidate;
+import org.webrtc.MediaConstraints;
+import org.webrtc.MediaStream;
+import org.webrtc.PeerConnection;
+import org.webrtc.PeerConnectionFactory;
+import org.webrtc.SdpObserver;
+import org.webrtc.SessionDescription;
+import org.webrtc.SurfaceTextureHelper;
+import org.webrtc.SurfaceViewRenderer;
+import org.webrtc.VideoCapturer;
+import org.webrtc.VideoSource;
+import org.webrtc.VideoTrack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class WebRTCScreenCaptureActivity extends AppCompatActivity {
+ private static final String TAG = "WebRTCScreenCaptureActivity";
+ // 请求码
+ private static final int REQUEST_CODE_SCREEN_CAPTURE = 1001;
+ private static final int REQUEST_CODE_PERMISSIONS = 1002;
+
+ // WebRTC核心组件
+ private PeerConnectionFactory peerConnectionFactory;
+ private PeerConnection peerConnection;
+ private VideoSource videoSource;
+ private VideoTrack videoTrack;
+ private EglBase rootEglBase;
+
+ // 屏幕采集相关
+ private MediaProjectionManager mediaProjectionManager;
+ private MediaProjection mediaProjection;
+ private VirtualDisplay virtualDisplay;
+ private ScreenVideoCapturer screenVideoCapturer; // 自定义屏幕采集器
+
+ // 屏幕参数
+ private int screenWidth;
+ private int screenHeight;
+ private int screenDpi;
+
+ // UI控件
+ private Button btnStartPush;
+ private SurfaceViewRenderer localRender; // 本地预览(可选)
+
+ private TextureView textureView;
+
+ private VirtualDisplay mVirtualDisplay;
+
+ // 远端信令信息(示例:实际需从信令服务器获取)
+ private String remoteSdp = ""; // 替换为远端实际SDP
+ private List remoteIceCandidates = new ArrayList<>();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_webrtc_screen_capture);
+
+ // 初始化控件
+ btnStartPush = findViewById(R.id.btn_start_push);
+ localRender = findViewById(R.id.local_render);
+ textureView = findViewById(R.id.textureView);
+
+ textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
+ Log.e(TAG, "onSurfaceTextureAvailable: ");
+ // SurfaceTexture准备就绪,开始屏幕捕捉
+ setupVirtualDisplay();
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ Log.e(TAG, "onSurfaceTextureSizeChanged: ");
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ Log.e(TAG, "onSurfaceTextureDestroyed: ");
+ return false;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+// Log.e(TAG, "onSurfaceTextureUpdated: ");
+ }
+ });
+
+ // 初始化屏幕参数
+ screenWidth = getResources().getDisplayMetrics().widthPixels;
+ screenHeight = getResources().getDisplayMetrics().heightPixels;
+ screenDpi = getResources().getDisplayMetrics().densityDpi;
+
+ // 初始化MediaProjectionManager
+ mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
+
+ // 检查权限
+ btnStartPush.setOnClickListener(v -> checkPermissionsAndStart());
+
+ // 初始化WebRTC EGL环境
+ rootEglBase = EglBase.create();
+ localRender.init(rootEglBase.getEglBaseContext(), null);
+ localRender.setMirror(true);
+ localRender.setEnableHardwareScaler(true);
+ }
+
+ private static final String FOREGROUND_SERVICE_MEDIA_PROJECTION = "android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION";
+
+ /**
+ * 检查权限并启动采集+推流
+ */
+ private void checkPermissionsAndStart() {
+ List neededPermissions = new ArrayList<>();
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED) {
+ neededPermissions.add(Manifest.permission.INTERNET);
+ }
+// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
+// ContextCompat.checkSelfPermission(this, Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION) != PackageManager.PERMISSION_GRANTED) {
+// neededPermissions.add(Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION);
+// }
+
+ if (Build.VERSION.SDK_INT >= 33 &&
+ ContextCompat.checkSelfPermission(this, FOREGROUND_SERVICE_MEDIA_PROJECTION) != PackageManager.PERMISSION_GRANTED) {
+ neededPermissions.add(FOREGROUND_SERVICE_MEDIA_PROJECTION);
+ }
+
+ if (!neededPermissions.isEmpty()) {
+ ActivityCompat.requestPermissions(this, neededPermissions.toArray(new String[0]), REQUEST_CODE_PERMISSIONS);
+ } else {
+ // 启动前台服务
+ startForegroundService();
+ // 权限已满足,请求屏幕录制授权
+ startScreenCaptureAuthorization();
+ }
+ }
+
+ /**
+ * 发起屏幕录制授权请求
+ */
+ private void startScreenCaptureAuthorization() {
+ Intent captureIntent = mediaProjectionManager.createScreenCaptureIntent();
+ startActivityForResult(captureIntent, REQUEST_CODE_SCREEN_CAPTURE);
+ }
+
+ /**
+ * 初始化WebRTC核心组件
+ */
+ private void initWebRTC() {
+ // 1. 初始化PeerConnectionFactory
+ PeerConnectionFactory.InitializationOptions initOptions = PeerConnectionFactory.InitializationOptions.builder(this)
+ .setEnableInternalTracer(true)
+ .setFieldTrials("WebRTC-H264HighProfile/Enabled/") // 可选:启用H264
+ .createInitializationOptions();
+ PeerConnectionFactory.initialize(initOptions);
+
+ // 2. 创建PeerConnectionFactory实例
+ PeerConnectionFactory.Options factoryOptions = new PeerConnectionFactory.Options();
+ peerConnectionFactory = PeerConnectionFactory.builder()
+ .setOptions(factoryOptions)
+ .setVideoEncoderFactory(new DefaultVideoEncoderFactory(
+ EglBase.create().getEglBaseContext(), true, true))
+ .setVideoDecoderFactory(new DefaultVideoDecoderFactory(
+ EglBase.create().getEglBaseContext()))
+ .createPeerConnectionFactory();
+
+ // 3. 创建自定义屏幕采集器
+ screenVideoCapturer = new ScreenVideoCapturer();
+
+ // 4. 创建VideoSource和VideoTrack
+ SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("ScreenCaptureThread", rootEglBase.getEglBaseContext());
+ videoSource = peerConnectionFactory.createVideoSource(screenVideoCapturer.isScreencast());
+ screenVideoCapturer.initialize(surfaceTextureHelper, this, videoSource.getCapturerObserver());
+ screenVideoCapturer.startCapture(screenWidth, screenHeight, 30); // 30fps
+
+ // 5. 创建视频轨道并绑定到本地渲染
+ videoTrack = peerConnectionFactory.createVideoTrack("screen_video_track", videoSource);
+ videoTrack.addSink(localRender);
+
+ // 6. 创建PeerConnection(需替换为实际的ICE服务器配置)
+ List iceServers = getIceServers();
+
+ PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);
+ // 配置 ICE 传输策略(可选,根据网络环境调整)
+ rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED; // 通常禁用 TCP 候选,使用 UDP
+ rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE; // 推荐启用 BUNDLE 以节省资源
+ rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY; // 持续收集 ICE 候选
+ peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, new PeerConnection.Observer() {
+ @Override
+ public void onSignalingChange(PeerConnection.SignalingState signalingState) {
+ Log.d(TAG, "PeerConnection onSignalingChange: " + signalingState);
+ }
+
+ @Override
+ public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
+ Log.d(TAG, "PeerConnection onIceConnectionChange: " + iceConnectionState);
+ if (iceConnectionState == PeerConnection.IceConnectionState.CONNECTED) {
+ runOnUiThread(() -> Toast.makeText(WebRTCScreenCaptureActivity.this, "WebRTC连接成功", Toast.LENGTH_SHORT).show());
+ }
+ }
+
+ @Override
+ public void onIceConnectionReceivingChange(boolean b) {
+ Log.d(TAG, "PeerConnection onIceConnectionReceivingChange: " + b);
+ }
+
+ @Override
+ public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
+ Log.d(TAG, "PeerConnection onIceGatheringChange: " + iceGatheringState);
+ // ICE候选收集完成后,可将本地SDP发送给远端
+ if (iceGatheringState == PeerConnection.IceGatheringState.COMPLETE) {
+ Log.d(TAG, "ICE候选收集完成,可发送本地SDP给远端");
+ }
+ }
+
+ @Override
+ public void onIceCandidate(IceCandidate iceCandidate) {
+ Log.d(TAG, "PeerConnection onIceCandidate: " + iceCandidate);
+ // 将ICE候选发送给远端(实际需通过信令服务器)
+ sendIceCandidateToRemote(iceCandidate);
+ }
+
+ @Override
+ public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
+ Log.d(TAG, "PeerConnection onIceCandidatesRemoved");
+ }
+
+ @Override
+ public void onAddStream(MediaStream mediaStream) {
+ Log.d(TAG, "PeerConnection onAddStream: 收到远端流");
+ }
+
+ @Override
+ public void onRemoveStream(MediaStream mediaStream) {
+ Log.d(TAG, "PeerConnection onRemoveStream");
+ }
+
+ @Override
+ public void onDataChannel(org.webrtc.DataChannel dataChannel) {
+ Log.d(TAG, "PeerConnection onDataChannel");
+ }
+
+ @Override
+ public void onRenegotiationNeeded() {
+ Log.d(TAG, "PeerConnection onRenegotiationNeeded");
+ }
+
+ @Override
+ public void onAddTrack(org.webrtc.RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
+ Log.d(TAG, "PeerConnection onAddTrack");
+ }
+ });
+
+ // 7. 创建MediaStream并添加视频轨道
+ MediaStream mediaStream = peerConnectionFactory.createLocalMediaStream("screen_stream");
+ mediaStream.addTrack(videoTrack);
+ peerConnection.addStream(mediaStream);
+
+ // 8. 创建Offer(发起端)
+ createOffer();
+ }
+
+ /**
+ * 创建WebRTC Offer
+ */
+ private void createOffer() {
+ MediaConstraints constraints = new MediaConstraints();
+ constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "false")); // 只推流,不接收视频
+ constraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "false")); // 不接收音频
+
+ peerConnection.createOffer(new SdpObserver() {
+ @Override
+ public void onCreateSuccess(SessionDescription sessionDescription) {
+ // 设置本地SDP
+ peerConnection.setLocalDescription(new SdpObserver() {
+ @Override
+ public void onCreateSuccess(SessionDescription sessionDescription) {
+ }
+
+ @Override
+ public void onSetSuccess() {
+ // 本地SDP设置成功,通过信令服务器发送给远端
+ Log.d(TAG, "本地Offer创建成功: " + sessionDescription.description);
+ sendSdpToRemote(sessionDescription);
+ // 将本地SDP发送给远端(实际需通过信令服务器)
+ remoteSdp = sessionDescription.description; // 示例:实际需发送到远端
+ }
+
+ @Override
+ public void onCreateFailure(String s) {
+ Log.e(TAG, "创建Offer失败: " + s);
+ }
+
+ @Override
+ public void onSetFailure(String s) {
+ Log.e(TAG, "设置本地SDP失败: " + s);
+ }
+ }, sessionDescription);
+ }
+
+ @Override
+ public void onSetSuccess() {
+ }
+
+ @Override
+ public void onCreateFailure(String s) {
+ Log.e(TAG, "创建Offer失败: " + s);
+ }
+
+ @Override
+ public void onSetFailure(String s) {
+ }
+ }, constraints);
+ }
+
+ private List getIceServers() {
+ List iceServers = new ArrayList<>();
+
+ // 添加用户指定的 STUN 服务器
+ PeerConnection.IceServer stunServer = PeerConnection.IceServer.builder("stun:47.242.112.133:3478")
+ .setUsername("tt")
+ .setPassword("tongtong")
+ .createIceServer();
+ iceServers.add(stunServer);
+
+ // 强烈建议添加备用的公共 STUN 服务器
+ PeerConnection.IceServer publicStunServer = PeerConnection.IceServer.builder("stun:stun.l.google.com:19302")
+ .createIceServer();
+ iceServers.add(publicStunServer);
+
+ // 如果您的部署包含 TURN 服务器(用于可靠的中继转发),也应在此添加
+ // PeerConnection.IceServer turnServer = PeerConnection.IceServer.builder("turn:your_turn_server:3478")
+ // .setUsername("your_username")
+ // .setPassword("your_password")
+ // .createIceServer();
+ // iceServers.add(turnServer);
+
+ return iceServers;
+ }
+
+ private void sendSdpToRemote(SessionDescription sdp) {
+ // TODO: 实现信令发送逻辑,例如通过WebSocket发送SDP字符串
+ }
+
+ /**
+ * 自定义屏幕视频采集器(核心:对接MediaProjection和WebRTC)
+ */
+ private class ScreenVideoCapturer implements VideoCapturer {
+ private CapturerObserver capturerObserver;
+ private SurfaceTextureHelper surfaceTextureHelper;
+ private Surface surface;
+
+ @Override
+ public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context context, CapturerObserver capturerObserver) {
+ this.surfaceTextureHelper = surfaceTextureHelper;
+ this.capturerObserver = capturerObserver;
+ this.surface = new Surface(surfaceTextureHelper.getSurfaceTexture());
+
+ // 创建虚拟显示器,将屏幕内容输出到WebRTC的Surface
+ createVirtualDisplay();
+ }
+
+ @Override
+ public void startCapture(int width, int height, int framerate) {
+ capturerObserver.onCapturerStarted(true);
+ Log.d(TAG, "Screen capturer started: " + width + "x" + height + " " + framerate + "fps");
+ }
+
+ @Override
+ public void stopCapture() throws InterruptedException {
+ capturerObserver.onCapturerStopped();
+ releaseVirtualDisplay();
+ }
+
+ @Override
+ public void changeCaptureFormat(int width, int height, int framerate) {
+ // 适配分辨率变化
+ releaseVirtualDisplay();
+ screenWidth = width;
+ screenHeight = height;
+ createVirtualDisplay();
+ }
+
+ @Override
+ public void dispose() {
+ surfaceTextureHelper.dispose();
+ releaseVirtualDisplay();
+ stopForegroundService();
+ }
+
+ @Override
+ public boolean isScreencast() {
+ return true; // 标记为屏幕采集(非摄像头)
+ }
+
+ private void createVirtualDisplay() {
+ if (mediaProjection == null || surface == null) {
+ return;
+ }
+ virtualDisplay = mediaProjection.createVirtualDisplay(
+ "WebRTC-ScreenCapture",
+ screenWidth,
+ screenHeight,
+ screenDpi,
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
+ surface,
+ null,
+ null
+ );
+ }
+
+ private void releaseVirtualDisplay() {
+ if (virtualDisplay != null) {
+ virtualDisplay.release();
+ virtualDisplay = null;
+ }
+ }
+ }
+
+ /**
+ * 启动前台服务(Android 10+ 必须)
+ */
+ private void startForegroundService() {
+ Intent serviceIntent = new Intent(this, ScreenCaptureService2.class);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ startForegroundService(serviceIntent);
+ } else {
+ startService(serviceIntent);
+ }
+ }
+
+ /**
+ * 停止前台服务
+ */
+ private void stopForegroundService() {
+ stopService(new Intent(this, ScreenCaptureService2.class));
+ }
+
+ /**
+ * 释放虚拟显示器
+ */
+ private void releaseVirtualDisplay() {
+ if (virtualDisplay != null) {
+ virtualDisplay.release();
+ virtualDisplay = null;
+ }
+ }
+
+ /**
+ * 发送ICE候选到远端(示例:实际需通过WebSocket/HTTP信令服务器)
+ */
+ private void sendIceCandidateToRemote(IceCandidate iceCandidate) {
+ // 这里仅做日志打印,实际需替换为信令发送逻辑
+ Log.d(TAG, "Send ICE Candidate to Remote: " + iceCandidate.sdpMid + " " + iceCandidate.sdpMLineIndex + " " + iceCandidate.sdp);
+ // 示例:将iceCandidate转为JSON发送到远端服务器
+ }
+
+ /**
+ * 处理权限请求结果
+ */
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (requestCode == REQUEST_CODE_PERMISSIONS) {
+ boolean allGranted = true;
+ for (int result : grantResults) {
+ if (result != PackageManager.PERMISSION_GRANTED) {
+ allGranted = false;
+ break;
+ }
+ }
+ if (allGranted) {
+ startScreenCaptureAuthorization();
+ } else {
+ Toast.makeText(this, "权限申请失败,无法启动推流", Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+ /**
+ * 处理屏幕录制授权结果
+ */
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == REQUEST_CODE_SCREEN_CAPTURE) {
+ if (resultCode == RESULT_OK && data != null) {
+
+ // 授权成功,获取MediaProjection
+ mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
+ mediaProjection.registerCallback(new MediaProjection.Callback() {
+ @Override
+ public void onStop() {
+ super.onStop();
+ Toast.makeText(WebRTCScreenCaptureActivity.this, "屏幕采集已停止", Toast.LENGTH_SHORT).show();
+ releaseResources();
+ }
+ }, null);
+
+ setupVirtualDisplay();
+
+ // 初始化WebRTC并开始推流
+ initWebRTC();
+
+ // 更新按钮状态
+ btnStartPush.setText("停止推流");
+ btnStartPush.setOnClickListener(v -> releaseResources());
+ } else {
+ Toast.makeText(this, "屏幕录制授权被拒绝", Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+ private void setupVirtualDisplay() {
+ if (mediaProjection == null) return;
+ // 获取屏幕尺寸和密度
+ Log.e(TAG, "setupVirtualDisplay: ");
+
+ // 从TextureView的SurfaceTexture创建Surface
+ Surface surface = new Surface(textureView.getSurfaceTexture());
+ // 创建虚拟显示器,将屏幕内容投射到Surface
+ mVirtualDisplay = mediaProjection.createVirtualDisplay(
+ "ScreenCapture",
+ screenWidth, screenHeight, screenDpi,
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
+ surface, null, null);
+ }
+
+ /**
+ * 释放所有资源
+ */
+ private void releaseResources() {
+ // 停止WebRTC
+ if (peerConnection != null) {
+ peerConnection.close();
+ peerConnection = null;
+ }
+ if (videoTrack != null) {
+ videoTrack.dispose();
+ videoTrack = null;
+ }
+ if (videoSource != null) {
+ videoSource.dispose();
+ videoSource = null;
+ }
+ if (peerConnectionFactory != null) {
+ peerConnectionFactory.dispose();
+ peerConnectionFactory = null;
+ }
+
+ // 停止屏幕采集
+ if (screenVideoCapturer != null) {
+ try {
+ screenVideoCapturer.stopCapture();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ screenVideoCapturer.dispose();
+ screenVideoCapturer = null;
+ }
+
+ // 释放MediaProjection
+ if (mediaProjection != null) {
+ mediaProjection.stop();
+ mediaProjection = null;
+ }
+
+ // 停止前台服务
+ stopForegroundService();
+
+ // 释放渲染
+ if (localRender != null) {
+ localRender.release();
+ }
+// if (rootEglBase != null) {
+// rootEglBase.release();
+// }
+
+ // 重置按钮
+ btnStartPush.setText("开始推流");
+ btnStartPush.setOnClickListener(v -> checkPermissionsAndStart());
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ releaseResources();
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/remoteservice/activity/main/MainActivity.java b/app/src/main/java/com/ttstd/remoteservice/activity/main/MainActivity.java
index e4b2611..a505c2e 100644
--- a/app/src/main/java/com/ttstd/remoteservice/activity/main/MainActivity.java
+++ b/app/src/main/java/com/ttstd/remoteservice/activity/main/MainActivity.java
@@ -12,13 +12,14 @@ import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.ttstd.remoteservice.R;
+import com.ttstd.remoteservice.activity.WebRTCScreenCaptureActivity;
import com.ttstd.remoteservice.base.mvvm.BaseMvvmActivity;
import com.ttstd.remoteservice.databinding.ActivityMainBinding;
import com.ttstd.remoteservice.service.ControlService;
import com.ttstd.remoteservice.service.ScreenCaptureService;
public class MainActivity extends BaseMvvmActivity {
- private static final String TAG ="MainActivity";
+ private static final String TAG = "MainActivity";
private static final int REQUEST_CODE_SCREEN_CAPTURE = 7897;
private MediaProjectionManager mMediaProjectionManager;
@@ -64,7 +65,7 @@ public class MainActivity extends BaseMvvmActivity= Build.VERSION_CODES.O) {
startForegroundService(screenServiceIntent);
- }else {
+ } else {
startService(screenServiceIntent);
}
@@ -77,7 +78,7 @@ public class MainActivity extends BaseMvvmActivity= 33) {
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
@@ -85,8 +86,10 @@ public class MainActivity extends BaseMvvmActivity= 31) {
// Android 12+ 必须指定前台服务类型
- startForeground(NOTIFICATION_ID, notification, android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
+ startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
} else {
// 低版本直接启动
startForeground(NOTIFICATION_ID, notification);
diff --git a/app/src/main/java/com/ttstd/remoteservice/service/ScreenCaptureService2.java b/app/src/main/java/com/ttstd/remoteservice/service/ScreenCaptureService2.java
new file mode 100644
index 0000000..ef354e8
--- /dev/null
+++ b/app/src/main/java/com/ttstd/remoteservice/service/ScreenCaptureService2.java
@@ -0,0 +1,67 @@
+package com.ttstd.remoteservice.service;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.ServiceInfo;
+import android.os.Build;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.core.app.NotificationCompat;
+
+public class ScreenCaptureService2 extends Service {
+ private static final String TAG = "ScreenCaptureService2";
+
+ private static final String CHANNEL_ID = "ScreenCaptureChannel";
+ private static final int NOTIFICATION_ID = 1001;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.e(TAG, "onCreate: ");
+ createNotificationChannel();
+ if (Build.VERSION.SDK_INT >= 31) {
+ // Android 12+ 必须指定前台服务类型
+ startForeground(NOTIFICATION_ID, createNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
+ } else {
+ // 低版本直接启动
+ startForeground(NOTIFICATION_ID, createNotification());
+ }
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ return START_STICKY; // 服务被杀死后尝试重启
+ }
+
+ private void createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(
+ CHANNEL_ID,
+ "屏幕采集服务",
+ NotificationManager.IMPORTANCE_LOW
+ );
+ NotificationManager manager = getSystemService(NotificationManager.class);
+ manager.createNotificationChannel(channel);
+ }
+ }
+
+ private Notification createNotification() {
+ return new NotificationCompat.Builder(this, CHANNEL_ID)
+ .setContentTitle("屏幕采集中")
+ .setContentText("正在将屏幕内容推流到WebRTC")
+ .setSmallIcon(android.R.drawable.ic_media_play)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_webrtc_screen_capture.xml b/app/src/main/res/layout/activity_webrtc_screen_capture.xml
new file mode 100644
index 0000000..d371f2d
--- /dev/null
+++ b/app/src/main/res/layout/activity_webrtc_screen_capture.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file