webRTC(十六):webrtc 实现android端对端的视频互通

效果

在这里插入图片描述
在这里插入图片描述

服务器的实现

请查看:webRTC(六):webrtc信令服务器实现

web端实现

请查看:webRTC(十):webrtc 实现web端对端视频

android端实现

  • 引入第三方库

	implementation 'org.webrtc:google-webrtc:1.0.30039'
	
    implementation('io.socket:socket.io-client:1.0.0') {
        exclude group: 'org.json', module: 'json'
    }
    implementation 'com.github.huangxiaoguo1:hxgpermissions:1.0.2'
android {

    ...
    //处理打包出错
    lintOptions {
        checkReleaseBuilds false
        // Or, if you prefer, you can continue to check for errors in release builds,
        // but continue the build even when errors are found:
        abortOnError false
    }
    //处理编译报错
    compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }
    ...
}
  • 申请权限
	<uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />
    <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  • 动态权限处理
 	@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        HxgPermissionHelper.requestPermissionsResult(this, requestCode, permissions);
    }
 //   ------------------------------------
	HxgPermissionHelper.with(this)
                .requestCode(REQUESE_CODE)
                .requestPermission(Manifest.permission.CAMERA,
                        Manifest.permission.RECORD_AUDIO)
                .request();

 //   ------------------------------------
	@HxgPermissionSuccess(requestCode = REQUESE_CODE)
    private void success() {

    }

    @HxgPermissionFail(requestCode = REQUESE_CODE)
    private void fail() {
        Toast.makeText(this, "失败了", Toast.LENGTH_SHORT).show();
    }
  • 信令处理
import org.json.JSONObject;

import java.net.URISyntaxException;

import io.socket.client.IO;
import io.socket.client.Socket;
import io.socket.emitter.Emitter;
import okhttp3.OkHttpClient;

public class SignalClient {

    private static final String TAG = "SignalClient";

    private static SignalClient mInstance;
    private OnSignalEventListener mOnSignalEventListener;

    private Socket mSocket;
    private String mRoomName;
    private IO.Options opts;

    public interface OnSignalEventListener {
        void onConnected();

        void onConnecting();

        void onDisconnected();

        void onUserJoined(String roomName, String userID);

        void onUserLeaved(String roomName, String userID);

        void onRemoteUserJoined(String roomName);

        void onRemoteUserLeaved(String roomName, String userID);

        void onRoomFull(String roomName, String userID);

        void onMessage(JSONObject message);
    }

    public static SignalClient getInstance() {
        synchronized (SignalClient.class) {
            if (mInstance == null) {
                mInstance = new SignalClient();
            }
        }
        return mInstance;
    }

    public void setSignalEventListener(final OnSignalEventListener listener) {
        mOnSignalEventListener = listener;
    }

//    private OkHttpClient okHttpClient = new OkHttpClient.Builder()
//            .hostnameVerifier(new TrustAllCerts.TrustAllHostnameVerifier())
//            .sslSocketFactory(TrustAllCerts.createSSLSocketFactory())
//            .build();
    /**
     * 加入房间
     *
     * @param url
     * @param roomName
     */
    public void joinRoom(String url, String roomName) {
        Log.i(TAG, "joinRoom: " + url + ", " + roomName);
        try {
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            //cloud_huang.pem证书
            HTTPSCerUtils.setCertificate(UIUtils.getContext(), builder, R.raw.cloud_huang);
            OkHttpClient okHttpClient = builder.build();


            IO.setDefaultOkHttpWebSocketFactory(okHttpClient);
            IO.setDefaultOkHttpCallFactory(okHttpClient);
            opts = new IO.Options();
            opts.callFactory = okHttpClient;
            opts.webSocketFactory = okHttpClient;
            mSocket = IO.socket(url,opts);
            mSocket.connect();

        } catch (URISyntaxException e) {
            e.printStackTrace();
            return;
        }
        //mUserId = userId;
        mRoomName = roomName;
        listenSignalEvents();

        mSocket.emit("join", mRoomName);
    }

    public void leaveRoom() {

        Log.i(TAG, "leaveRoom: " + mRoomName);
        if (mSocket == null) {
            return;
        }

        mSocket.emit("leave", mRoomName);
        mSocket.close();
        mSocket = null;
    }

    public void sendMessage(JSONObject message) {
        Log.i(TAG, "broadcast: " + message);
        if (mSocket == null) {
            return;
        }
        mSocket.emit("message", mRoomName, message);
    }

    //侦听从服务器收到的消息
    private void listenSignalEvents() {

        if (mSocket == null) {
            return;
        }

        mSocket.on(Socket.EVENT_CONNECT_ERROR, new Emitter.Listener() {
            @Override
            public void call(Object... args) {

                Log.e(TAG, "onConnectError: " + args.getClass().getClasses());
            }
        });

        mSocket.on(Socket.EVENT_ERROR, new Emitter.Listener() {
            @Override
            public void call(Object... args) {

                Log.e(TAG, "onError: " + args);
            }
        });

        mSocket.on(Socket.EVENT_CONNECT, new Emitter.Listener() {
            @Override
            public void call(Object... args) {
                String sessionId = mSocket.id();
                Log.i(TAG, "onConnected");
                if (mOnSignalEventListener != null) {
                    mOnSignalEventListener.onConnected();
                }
            }
        });

        mSocket.on(Socket.EVENT_CONNECTING, new Emitter.Listener() {
            @Override
            public void call(Object... args) {
                Log.i(TAG, "onConnecting");
                if (mOnSignalEventListener != null) {
                    mOnSignalEventListener.onConnecting();
                }
            }
        });

        mSocket.on(Socket.EVENT_DISCONNECT, new Emitter.Listener() {
            @Override
            public void call(Object... args) {
                Log.i(TAG, "onDisconnected");
                if (mOnSignalEventListener != null) {
                    mOnSignalEventListener.onDisconnected();
                }
            }
        });

        mSocket.on("joined", new Emitter.Listener() {
            @Override
            public void call(Object... args) {
                String roomName = (String) args[0];
                String userId = (String) args[1];
                if (/*!mUserId.equals(userId) &&*/ mOnSignalEventListener != null) {
                    //mOnSignalEventListener.onRemoteUserJoined(userId);
                    mOnSignalEventListener.onUserJoined(roomName, userId);
                }
                //Log.i(TAG, "onRemoteUserJoined: " + userId);
                Log.i(TAG, "onUserJoined, room:" + roomName + "uid:" + userId);
            }
        });

        mSocket.on("leaved", new Emitter.Listener() {
            @Override
            public void call(Object... args) {
                String roomName = (String) args[0];
                String userId = (String) args[1];
                if (/*!mUserId.equals(userId) &&*/ mOnSignalEventListener != null) {
                    //mOnSignalEventListener.onRemoteUserLeft(userId);
                    mOnSignalEventListener.onUserLeaved(roomName, userId);
                }
                Log.i(TAG, "onUserLeaved, room:" + roomName + "uid:" + userId);
            }
        });

        mSocket.on("otherjoin", new Emitter.Listener() {

            @Override
            public void call(Object... args) {
                String roomName = (String) args[0];
                String userId = (String) args[1];
                if (mOnSignalEventListener != null) {
                    mOnSignalEventListener.onRemoteUserJoined(roomName);
                }
                Log.i(TAG, "onRemoteUserJoined, room:" + roomName + "uid:" + userId);
            }
        });

        mSocket.on("bye", new Emitter.Listener() {
            @Override
            public void call(Object... args) {
                String roomName = (String) args[0];
                String userId = (String) args[1];
                if (mOnSignalEventListener != null) {
                    mOnSignalEventListener.onRemoteUserLeaved(roomName, userId);
                }
                Log.i(TAG, "onRemoteUserLeaved, room:" + roomName + "uid:" + userId);

            }
        });

        mSocket.on("full", new Emitter.Listener() {
            @Override
            public void call(Object... args) {

                //释放资源
                mSocket.disconnect();
                mSocket.close();
                mSocket = null;

                String roomName = (String) args[0];
                String userId = (String) args[1];

                if (mOnSignalEventListener != null) {
                    mOnSignalEventListener.onRoomFull(roomName, userId);
                }

                Log.i(TAG, "onRoomFull, room:" + roomName + "uid:" + userId);

            }
        });

        mSocket.on("message", new Emitter.Listener() {
            @Override
            public void call(Object... args) {
                try {
                    String roomName = (String) args[0];
                    String userId = (String) args[1];
                    JSONObject msg = (JSONObject) args[2];
                    if (mOnSignalEventListener != null) {
                        mOnSignalEventListener.onMessage(msg);
                    }
                    Log.i(TAG, "onMessage, room:" + roomName +",userId:"+userId+ ",data:" + msg);
                }catch (Exception e){
                    e.printStackTrace();
                }

            }
        });
    }
}

  • https 信任证书
    //只信任指定证书(传入raw资源ID)
    public static void setCertificate(Context context, OkHttpClient.Builder okHttpClientBuilder, int cerResID) {
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            InputStream inputStream = context.getResources().openRawResource(cerResID);
            Certificate ca = certificateFactory.generateCertificate(inputStream);
 
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null, null);
            keyStore.setCertificateEntry("ca", ca);
 
            inputStream.close();
 
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(keyStore);
 
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
            okHttpClientBuilder.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) tmf.getTrustManagers()[0]);
            okHttpClientBuilder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • 布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent" >
	
	<org.webrtc.SurfaceViewRenderer
	    android:id="@+id/RemoteSurfaceView"
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    android:layout_gravity="center" />
	
	<org.webrtc.SurfaceViewRenderer
	    android:id="@+id/LocalSurfaceView"
	    android:layout_width="120dp"
	    android:layout_height="160dp"
	    android:layout_gravity="top|end"
	    android:layout_margin="16dp"/>
	
	<TextView
	    android:id="@+id/LogcatView"
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    android:layout_margin="5dp"
	    android:textColor="@android:color/white"
	    android:layout_gravity="top|start" />

</FrameLayout>
  • 视频交互实现(代码内有详细注释)

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.example.webrtcdemo.signal.SignalClient;

import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.Camera1Enumerator;
import org.webrtc.Camera2Enumerator;
import org.webrtc.CameraEnumerator;
import org.webrtc.DataChannel;
import org.webrtc.DefaultVideoDecoderFactory;
import org.webrtc.DefaultVideoEncoderFactory;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
import org.webrtc.Logging;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.MediaStreamTrack;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RendererCommon;
import org.webrtc.RtpReceiver;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoDecoderFactory;
import org.webrtc.VideoEncoderFactory;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class CallActivity extends AppCompatActivity implements SignalClient.OnSignalEventListener {

    private String serverAddr;
    private String roomName;
    private TextView mLogcatView;
    private SurfaceViewRenderer mLocalSurfaceView;
    private SurfaceViewRenderer mRemoteSurfaceView;
    private static final String TAG = "CallActivity----huang";

    private static final int VIDEO_RESOLUTION_WIDTH = 1280;
    private static final int VIDEO_RESOLUTION_HEIGHT = 720;
    private static final int VIDEO_FPS = 30;

    /**
     * 状态机
     */
    private String mState = "init";

    public static final String VIDEO_TRACK_ID = "ARDAMSv0";//"ARDAMSv0";
    public static final String AUDIO_TRACK_ID = "ARDAMSa0";//"ARDAMSa0";

    //用于数据传输
    private PeerConnection mPeerConnection;
    private PeerConnectionFactory mPeerConnectionFactory;

    //OpenGL ES
    private EglBase mRootEglBase;
    //纹理渲染
    private SurfaceTextureHelper mSurfaceTextureHelper;

    private VideoTrack mVideoTrack;
    private AudioTrack mAudioTrack;

    private VideoCapturer mVideoCapturer;

    public void doLeave() {
        logcatOnUI("Leave room, Wait ...");
        hangup();

        SignalClient.getInstance().leaveRoom();

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        doLeave();
        mLocalSurfaceView.release();
        mRemoteSurfaceView.release();
        mVideoCapturer.dispose();
        mSurfaceTextureHelper.dispose();
        PeerConnectionFactory.stopInternalTracingCapture();
        PeerConnectionFactory.shutdownInternalTracer();
        mPeerConnectionFactory.dispose();
    }

    @Override
    protected void onResume() {
        super.onResume();
        mVideoCapturer.startCapture(VIDEO_RESOLUTION_WIDTH, VIDEO_RESOLUTION_HEIGHT, VIDEO_FPS);
    }

    @Override
    protected void onPause() {
        super.onPause();
        try {
            mVideoCapturer.stopCapture();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_call);
        mRootEglBase = EglBase.create();
        initData();
        initView();


        //创建 factory, pc是从factory里获得的
        mPeerConnectionFactory = createPeerConnectionFactory(this);
        // NOTE: this _must_ happen while PeerConnectionFactory is alive!
        Logging.enableLogToDebugOutput(Logging.Severity.LS_VERBOSE);

        /**
         * 捕捉视频
         */
        mVideoCapturer = createVideoCapturer();

        /**
         * 纹理渲染
         */
        mSurfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", mRootEglBase.getEglBaseContext());

        /**
         * 创建视频源,isScreencast:是否是投屏
         */
        VideoSource videoSource = mPeerConnectionFactory.createVideoSource(false);
        //传递视频信息
        mVideoCapturer.initialize(mSurfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver());
        mVideoTrack = mPeerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, videoSource);
        mVideoTrack.setEnabled(true);
        mVideoTrack.addSink(mLocalSurfaceView);


        /**
         * 创建音屏源
         */
        MediaConstraints mediaConstraints = new MediaConstraints();


        // 添加所有现有的音频过滤器,以避免回声
        //回声消除
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation", "true"));
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation2", "true"));
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googDAEchoCancellation", "true"));

        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googTypingNoiseDetection", "true"));
        //自动增益
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl", "true"));
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl2", "true"));
        //噪音处理
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseSuppression", "true"));
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseSuppression2", "true"));
        //高音过滤
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAudioMirroring", "false"));
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googHighpassFilter", "true"));


        AudioSource audioSource = mPeerConnectionFactory.createAudioSource(mediaConstraints);
        mAudioTrack = mPeerConnectionFactory.createAudioTrack(AUDIO_TRACK_ID, audioSource);
        mAudioTrack.setEnabled(true);

        /**
         * 添加监听
         */
        SignalClient.getInstance().setSignalEventListener(this);

        /**
         * 加入房间
         */
        SignalClient.getInstance().joinRoom(serverAddr, roomName);
    }


    private void initData() {
        /**
         * 服务器
         */
        serverAddr = getIntent().getStringExtra("ServerAddr");
        /**
         * 房间名称
         */
        roomName = getIntent().getStringExtra("RoomName");

    }

    private void initView() {
        mLogcatView = findViewById(R.id.LogcatView);
        mLocalSurfaceView = findViewById(R.id.LocalSurfaceView);
        mRemoteSurfaceView = findViewById(R.id.RemoteSurfaceView);

        /**
         * 初始化,并传入openGL上下文
         */
        mLocalSurfaceView.init(mRootEglBase.getEglBaseContext(), null);
        /**
         * 缩放类型,RendererCommon.ScalingType.SCALE_ASPECT_FILL:按照比例填充
         */
        mLocalSurfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
        /**
         * 反转摄像头画面
         */
        mLocalSurfaceView.setMirror(true);
        /**
         * 缩放时是否使用硬件
         */
        mLocalSurfaceView.setEnableHardwareScaler(false /* enabled */);

        mRemoteSurfaceView.init(mRootEglBase.getEglBaseContext(), null);
        mRemoteSurfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
        mRemoteSurfaceView.setMirror(true);
        mRemoteSurfaceView.setEnableHardwareScaler(true /* enabled */);
        /**
         * 当两个view叠加时,谁在上面
         */
        mLocalSurfaceView.setZOrderMediaOverlay(true);
    }

    /**
     * 创建 factory, pc是从factory里获得的
     *
     * @param context
     * @return
     */
    public PeerConnectionFactory createPeerConnectionFactory(Context context) {
        final VideoEncoderFactory encoderFactory;
        final VideoDecoderFactory decoderFactory;
        /**
         * 编码
         */
        encoderFactory = new DefaultVideoEncoderFactory(
                mRootEglBase.getEglBaseContext(),
                false /* enableIntelVp8Encoder */,
                true);//android H264 才能硬件加速
        /**
         *解码
         */
        decoderFactory = new DefaultVideoDecoderFactory(mRootEglBase.getEglBaseContext());

        PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(context)
                .setEnableInternalTracer(true)//打开日志,线上关闭
                .createInitializationOptions());

        PeerConnectionFactory.Builder builder = PeerConnectionFactory.builder()
                .setVideoEncoderFactory(encoderFactory)
                .setVideoDecoderFactory(decoderFactory);
        builder.setOptions(null);

        return builder.createPeerConnectionFactory();
    }

    /**
     * 捕捉视频
     * 使用Camera2
     * Read more about Camera2 here
     * https://developer.android.com/reference/android/hardware/camera2/package-summary.html
     **/
    private VideoCapturer createVideoCapturer() {
        if (Camera2Enumerator.isSupported(this)) {
            return createCameraCapturer(new Camera2Enumerator(this));
        } else {
            return createCameraCapturer(new Camera1Enumerator(true));
        }
    }

    /**
     * 捕捉视频
     *
     * @param enumerator
     * @return
     */
    private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
        final String[] deviceNames = enumerator.getDeviceNames();

        // 使用前置摄像头
        Log.d(TAG, "使用前置摄像头.");
        for (String deviceName : deviceNames) {
            if (enumerator.isFrontFacing(deviceName)) {
                Logging.d(TAG, "创建前置摄像头捕获程序.");
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }

        // 使用后置摄像头
        Log.d(TAG, "使用后置摄像头.");
        for (String deviceName : deviceNames) {
            if (!enumerator.isFrontFacing(deviceName)) {
                Logging.d(TAG, "创建后置摄像头捕获程序.");
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }
        return null;
    }

    /**
     * 连接服务器成功
     */
    @Override
    public void onConnected() {
        logcatOnUI("连接服务器成功");
    }

    /**
     * 正在连接服务器
     */
    @Override
    public void onConnecting() {
        logcatOnUI("正在连接服务器");
    }

    /**
     * 断开连接
     */
    @Override
    public void onDisconnected() {
        logcatOnUI("断开连接");
    }

    /**
     * 本地用户加入房间
     *
     * @param roomName
     * @param userID
     */
    @Override
    public void onUserJoined(String roomName, String userID) {
        logcatOnUI("本地用户加入房间");
        mState = "joined";
        //这里应该创建PeerConnection
        if (mPeerConnection == null) {
            mPeerConnection = createPeerConnection();
        }
    }

    /**
     * 本地用户离开房间
     *
     * @param roomName
     * @param userID
     */
    @Override
    public void onUserLeaved(String roomName, String userID) {
        logcatOnUI("本地用户离开房间");
        mState = "leaved";
    }

    /**
     * 远端客户加入房间
     *
     * @param roomName
     */
    @Override
    public void onRemoteUserJoined(String roomName) {
        logcatOnUI("远端客户加入房间, room: " + roomName);

        if (mState.equals("joined_unbind")) {
            if (mPeerConnection == null) {
                mPeerConnection = createPeerConnection();
            }
        }

        mState = "joined_conn";
        //调用call, 进行媒体协商
        doStartCall();
    }

    /**
     * 远端客户离开房间
     *
     * @param roomName
     * @param userID
     */
    @Override
    public void onRemoteUserLeaved(String roomName, String userID) {
        logcatOnUI("远端客户离开房间, room: " + roomName + "uid:" + userID);
        mState = "joined_unbind";

        if (mPeerConnection != null) {
            mPeerConnection.close();
            mPeerConnection = null;
        }
    }

    /**
     * 房间满员
     *
     * @param roomName
     * @param userID
     */
    @Override
    public void onRoomFull(String roomName, String userID) {
        logcatOnUI("房间满员, room: " + roomName + "uid:" + userID);
        mState = "leaved";

        if (mLocalSurfaceView != null) {
            mLocalSurfaceView.release();
            mLocalSurfaceView = null;
        }

        if (mRemoteSurfaceView != null) {
            mRemoteSurfaceView.release();
            mRemoteSurfaceView = null;
        }

        if (mVideoCapturer != null) {
            mVideoCapturer.dispose();
            mVideoCapturer = null;
        }

        if (mSurfaceTextureHelper != null) {
            mSurfaceTextureHelper.dispose();
            mSurfaceTextureHelper = null;

        }

        PeerConnectionFactory.stopInternalTracingCapture();
        PeerConnectionFactory.shutdownInternalTracer();

        if (mPeerConnectionFactory != null) {
            mPeerConnectionFactory.dispose();
            mPeerConnectionFactory = null;
        }

        finish();
    }

    /**
     * 消息通道
     *
     * @param message
     */
    @Override
    public void onMessage(JSONObject message) {
        Log.i(TAG, "onMessage: " + message);

        try {
            String type = message.getString("type");
            if (type.equals("offer")) {
                onRemoteOfferReceived(message);
            } else if (type.equals("answer")) {
                onRemoteAnswerReceived(message);
            } else if (type.equals("candidate")) {
                onRemoteCandidateReceived(message);
            } else {
                Log.w(TAG, "the type is invalid: " + type);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    /**
     * 创建通道连接
     *
     * @return
     */
    public PeerConnection createPeerConnection() {
        Log.i(TAG, "创建 PeerConnection ...");

        LinkedList<PeerConnection.IceServer> iceServers = new LinkedList<PeerConnection.IceServer>();

        PeerConnection.IceServer ice_server =
                PeerConnection.IceServer.builder("turn:www.huangxiaoguo.club:3478")
                        .setPassword("123456")
                        .setUsername("huang")
                        .createIceServer();

        iceServers.add(ice_server);

        PeerConnection.RTCConfiguration rtcConfig = new PeerConnection.RTCConfiguration(iceServers);
        // TCP 候选者仅在连接到支持的服务器时有用
        // candidates are only useful when connecting to a server that supports
        // ICE-TCP.
        rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED;
        //rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
        //rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
        rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY;
        // Use ECDSA encryption.
        //rtcConfig.keyType = PeerConnection.KeyType.ECDSA;
        // Enable DTLS for normal calls and disable for loopback calls.
        rtcConfig.enableDtlsSrtp = true;
        //rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
        PeerConnection connection =
                mPeerConnectionFactory.createPeerConnection(rtcConfig,
                        mPeerConnectionObserver);
        if (connection == null) {
            Log.e(TAG, "Failed to createPeerConnection !");
            return null;
        }

        List<String> mediaStreamLabels = Collections.singletonList("ARDAMS");
        connection.addTrack(mVideoTrack, mediaStreamLabels);
        connection.addTrack(mAudioTrack, mediaStreamLabels);

        return connection;
    }

    private PeerConnection.Observer mPeerConnectionObserver = new PeerConnection.Observer() {
        @Override
        public void onSignalingChange(PeerConnection.SignalingState signalingState) {
            Log.i(TAG, "onSignalingChange: " + signalingState);
        }

        @Override
        public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
            Log.i(TAG, "onIceConnectionChange: " + iceConnectionState);
        }

        @Override
        public void onIceConnectionReceivingChange(boolean b) {
            Log.i(TAG, "onIceConnectionChange: " + b);
        }

        @Override
        public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
            Log.i(TAG, "onIceGatheringChange: " + iceGatheringState);
        }

        @Override
        public void onIceCandidate(IceCandidate iceCandidate) {
            Log.i(TAG, "onIceCandidate: " + iceCandidate);

            try {
                JSONObject message = new JSONObject();
                //message.put("userId", RTCSignalClient.getInstance().getUserId());
                message.put("type", "candidate");
                message.put("label", iceCandidate.sdpMLineIndex);
                message.put("id", iceCandidate.sdpMid);
                message.put("candidate", iceCandidate.sdp);
                SignalClient.getInstance().sendMessage(message);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
            for (int i = 0; i < iceCandidates.length; i++) {
                Log.i(TAG, "onIceCandidatesRemoved: " + iceCandidates[i]);
            }
            mPeerConnection.removeIceCandidates(iceCandidates);
        }

        @Override
        public void onAddStream(MediaStream mediaStream) {
            Log.i(TAG, "onAddStream: " + mediaStream.videoTracks.size());
            try {
                /**
                 * 这里设置false,可以静音功能
                 */
//                mediaStream.audioTracks.get(0).setEnabled(false);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onRemoveStream(MediaStream mediaStream) {
            Log.i(TAG, "onRemoveStream");
        }

        @Override
        public void onDataChannel(DataChannel dataChannel) {
            Log.i(TAG, "onDataChannel");
        }

        @Override
        public void onRenegotiationNeeded() {
            Log.i(TAG, "onRenegotiationNeeded");
        }

        @Override
        public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {

            MediaStreamTrack track = rtpReceiver.track();
            if (track instanceof VideoTrack) {
                Log.i(TAG, "onAddVideoTrack");
                VideoTrack remoteVideoTrack = (VideoTrack) track;
                remoteVideoTrack.setEnabled(true);
                remoteVideoTrack.addSink(mRemoteSurfaceView);
            }
        }
    };

    /**
     * 开始进行媒体协商
     */
    public void doStartCall() {
        logcatOnUI("Start Call, Wait ...");
        if (mPeerConnection == null) {
            mPeerConnection = createPeerConnection();
        }
        MediaConstraints mediaConstraints = new MediaConstraints();


        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
        mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
        //打开DTLS,不打开和浏览器之间不可以进行互通
        mediaConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
        mPeerConnection.createOffer(new SimpleSdpObserver() {
            @Override
            public void onCreateSuccess(SessionDescription sessionDescription) {
                Log.i(TAG, "Create local offer success: \n" + sessionDescription.description);
                mPeerConnection.setLocalDescription(new SimpleSdpObserver(), sessionDescription);
                JSONObject message = new JSONObject();
                try {
                    message.put("type", "offer");
                    message.put("sdp", sessionDescription.description);
                    SignalClient.getInstance().sendMessage(message);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }, mediaConstraints);

    }

    public static class SimpleSdpObserver implements SdpObserver {
        @Override
        public void onCreateSuccess(SessionDescription sessionDescription) {
            Log.i(TAG, "SdpObserver: onCreateSuccess !");
        }

        @Override
        public void onSetSuccess() {
            Log.i(TAG, "SdpObserver: onSetSuccess");
        }

        @Override
        public void onCreateFailure(String msg) {
            Log.e(TAG, "SdpObserver onCreateFailure: " + msg);
        }

        @Override
        public void onSetFailure(String msg) {

            Log.e(TAG, "SdpObserver onSetFailure: " + msg);
        }
    }

    /**
     * 与远端进行媒体协商
     *
     * @param message
     */
    private void onRemoteOfferReceived(JSONObject message) {
        logcatOnUI("Receive Remote Call ...");

        if (mPeerConnection == null) {
            mPeerConnection = createPeerConnection();
        }

        try {
            String description = message.getString("sdp");
            mPeerConnection.setRemoteDescription(
                    new SimpleSdpObserver(),
                    new SessionDescription(
                            SessionDescription.Type.OFFER,
                            description));
            doAnswerCall();
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    /**
     * 创建回答
     */
    public void doAnswerCall() {
        logcatOnUI("Answer Call, Wait ...");

        if (mPeerConnection == null) {
            mPeerConnection = createPeerConnection();
        }

        MediaConstraints sdpMediaConstraints = new MediaConstraints();
        Log.i(TAG, "Create answer ...");
        mPeerConnection.createAnswer(new SimpleSdpObserver() {
            @Override
            public void onCreateSuccess(SessionDescription sessionDescription) {
                Log.i(TAG, "Create answer success !");
                mPeerConnection.setLocalDescription(new SimpleSdpObserver(),
                        sessionDescription);

                JSONObject message = new JSONObject();
                try {
                    message.put("type", "answer");
                    message.put("sdp", sessionDescription.description);
                    SignalClient.getInstance().sendMessage(message);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }, sdpMediaConstraints);
        updateCallState(false);
    }


    private void updateCallState(boolean idle) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (idle) {
                    mRemoteSurfaceView.setVisibility(View.GONE);
                } else {
                    mRemoteSurfaceView.setVisibility(View.VISIBLE);
                }
            }
        });
    }

    /**
     * 接收远程回答
     *
     * @param message
     */
    private void onRemoteAnswerReceived(JSONObject message) {
        logcatOnUI("Receive Remote Answer ...");
        try {
            String description = message.getString("sdp");
            mPeerConnection.setRemoteDescription(
                    new SimpleSdpObserver(),
                    new SessionDescription(
                            SessionDescription.Type.ANSWER,
                            description));
        } catch (JSONException e) {
            e.printStackTrace();
        }
        updateCallState(false);
    }

    /**
     * 接收远程候选人
     *
     * @param message
     */
    private void onRemoteCandidateReceived(JSONObject message) {
        logcatOnUI("Receive Remote Candidate ...");
        try {
            IceCandidate remoteIceCandidate =
                    new IceCandidate(message.getString("id"),
                            message.getInt("label"),
                            message.getString("candidate"));

            mPeerConnection.addIceCandidate(remoteIceCandidate);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    /**
     * 接收远程挂起事件
     */
    private void onRemoteHangup() {
        logcatOnUI("Receive Remote Hangup Event ...");
        hangup();
    }

    /**
     * 挂断
     */
    private void hangup() {
        logcatOnUI("Hangup Call, Wait ...");
        if (mPeerConnection == null) {
            return;
        }
        mPeerConnection.close();
        mPeerConnection = null;
        logcatOnUI("Hangup Done.");
        updateCallState(true);
    }

    private void logcatOnUI(String msg) {
        Log.i(TAG, msg);
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                String output = mLogcatView.getText() + "\n" + msg;
                mLogcatView.setText(output);
            }
        });
    }
}

web端测试地址:https://www.huangxiaoguo.club/textchat/room.html

服务器地址:https://www.huangxiaoguo.club
房间号:555555 (建议,因为web端写死为555555了)

这样web端和web端,web端和android端,android端和android端视频互通就完成了

源码地址:https://gitee.com/huangxiaoguo/webRTCdemo

发布了316 篇原创文章 · 获赞 660 · 访问量 122万+

猜你喜欢

转载自blog.csdn.net/huangxiaoguo1/article/details/104230444
今日推荐