权限:
<!-- 相机权限 --> <uses-featureandroid:name="android.hardware.camera"android:required="false" /> <uses-permission android:name="android.permission.CAMERA" /><!-- 录音权限(包括麦克风权限) --> <uses-permission android:name="android.permission.RECORD_AUDIO" />
layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><TextureViewandroid:id="@+id/textureView"android:layout_width="match_parent"android:layout_height="0dp"app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toTopOf="@id/switchCameraButton"/><Buttonandroid:id="@+id/switchCameraButton"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="翻转摄像头"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"android:layout_marginBottom="16dp"/></androidx.constraintlayout.widget.ConstraintLayout>
Activity:
package com.xmkjsoft.video_call;import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.view.Surface;
import android.view.TextureView;
import android.widget.Button;
import android.widget.Toast;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.webrtc.MediaStream;import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;public class MainActivity extends AppCompatActivity {private MyWebSocketClient webSocketClient;private static final int PERMISSION_REQUEST_CAMERA = 1;private static final int PERMISSION_REQUEST_RECORD_AUDIO = 2;private static final int REQUEST_CAMERA_PERMISSION = 200;private TextureView textureView;private CameraDevice cameraDevice;private CaptureRequest.Builder captureRequestBuilder;private CameraCaptureSession cameraCaptureSession;private String cameraId;private MediaRecorder mediaRecorder;// 储存视频的文件路径private static final String VIDEO_FILE_PATH = Environment.getExternalStorageDirectory().getPath() + "/video.mp4";// 默认无参数构造函数public MainActivity() {}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 检查并请求相机权限if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CAMERA},PERMISSION_REQUEST_CAMERA);}// 检查并请求录音权限(包括麦克风权限)if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)!= PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.RECORD_AUDIO},PERMISSION_REQUEST_RECORD_AUDIO);}// 设置按钮点击事件监听器Button switchCameraButton = findViewById(R.id.switchCameraButton);switchCameraButton.setOnClickListener(v -> {if (cameraDevice != null) {closeCamera();// 切换摄像头cameraId = (cameraId.equals("0")) ? "1" : "0"; // 更新 cameraIdopenCamera(); // 重新打开摄像头}});// 获取本地视频流textureView = findViewById(R.id.textureView);textureView.setSurfaceTextureListener(textureListener);// 初始化 MediaRecordermediaRecorder = new MediaRecorder();mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);mediaRecorder.setOutputFile(VIDEO_FILE_PATH);connectWebSocket();}private void connectWebSocket() {try {webSocketClient = new MyWebSocketClient("ws://192.168.28.218/ws/1233");webSocketClient.connect();} catch (URISyntaxException e) {e.printStackTrace();}}// 内部类,用于处理 WebSocket 连接状态和消息private class MyWebSocketClient extends WebSocketClient {public MyWebSocketClient(String serverUri) throws URISyntaxException {super(new java.net.URI(serverUri));}@Overridepublic void onOpen(ServerHandshake handshakedata) {// WebSocket 连接已打开System.out.println("WebSocket 连接已打开");}@Overridepublic void onMessage(String message) {// 收到文本消息System.out.println("收到文本消息:" + message);}@Overridepublic void onClose(int code, String reason, boolean remote) {// WebSocket 连接已关闭System.out.println("WebSocket 连接已关闭,code:" + code + ", reason:" + reason + ", remote:" + remote);}@Overridepublic void onError(Exception ex) {// WebSocket 连接出错System.out.println("WebSocket 连接出错:" + ex.getMessage());}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == PERMISSION_REQUEST_CAMERA) {if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {// 相机权限已授予System.out.println("相机权限已授予");} else {// 相机权限被拒绝System.out.println("相机权限被拒绝");}} else if (requestCode == PERMISSION_REQUEST_RECORD_AUDIO) {if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {// 录音权限已授予System.out.println("录音权限已授予");} else {// 录音权限被拒绝System.out.println("录音权限被拒绝");}}}private TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {openCamera();}@Overridepublic void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {}@Overridepublic boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {return false;}@Overridepublic void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {}};// 打开相机private void openCamera() {CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);try {// 检查 cameraId 是否为空if (cameraId == null) {// 如果为空,选择默认摄像头cameraId = manager.getCameraIdList()[0];}if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);return;}manager.openCamera(cameraId, stateCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}}private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {cameraDevice = camera;createCameraPreview();}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {cameraDevice.close();}@Overridepublic void onError(@NonNull CameraDevice camera, int i) {cameraDevice.close();cameraDevice = null;}};private void createCameraPreview() {try {SurfaceTexture texture = textureView.getSurfaceTexture();texture.setDefaultBufferSize(1920, 1080);Surface surface = new Surface(texture);captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);captureRequestBuilder.addTarget(surface);cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {if (cameraDevice == null) {return;}MainActivity.this.cameraCaptureSession = cameraCaptureSession;try {// 开始预览MainActivity.this.cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);// 添加 MediaRecorder 的 Surfaceif (mediaRecorder != null) {captureRequestBuilder.addTarget(mediaRecorder.getSurface());// 开始录制视频mediaRecorder.start();}} catch (CameraAccessException | IllegalStateException e) {e.printStackTrace();}}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {Toast.makeText(MainActivity.this, "Failed", Toast.LENGTH_SHORT).show();}}, null);} catch (CameraAccessException e) {e.printStackTrace();}}// 在 onStop() 方法中停止录制视频@Overrideprotected void onStop() {super.onStop();stopRecording();}private void stopRecording() {if (mediaRecorder != null) {try {mediaRecorder.stop();mediaRecorder.reset();mediaRecorder.release();} catch (RuntimeException e) {e.printStackTrace();}}}// 关闭相机private void closeCamera() {if (cameraCaptureSession != null) {cameraCaptureSession.close();cameraCaptureSession = null;}if (cameraDevice != null) {cameraDevice.close();cameraDevice = null;}}
}
运行效果: