webRTC(十九):webrtc 实现web+android 端对端的纯文本聊天互通

效果

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

服务器的实现

请查看:https://huangxiaoguo.blog.csdn.net/article/details/104226077

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"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".ChatActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <EditText
            android:id="@+id/et_input"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <Button
            android:id="@+id/btn_send"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="发送" />
    </LinearLayout>

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <TextView
            android:id="@+id/tv_context"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </androidx.core.widget.NestedScrollView>
    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <TextView
            android:id="@+id/LogcatView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="top|start" />
    </androidx.core.widget.NestedScrollView>

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

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.example.webrtcdemo.signal.SignalClient;

import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.DataChannel;
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.RtpReceiver;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.VideoDecoderFactory;
import org.webrtc.VideoEncoderFactory;

import java.nio.ByteBuffer;
import java.util.LinkedList;

public class OnlyTextActivity extends AppCompatActivity implements SignalClient.OnSignalEventListener {
    private static final String TAG = "OnlyTextActivity--huang";
    private String serverAddr;
    private String roomName;

    private TextView tvContext;
    private EditText etInput;
    private Button btnSend;
    private TextView mLogcatView;

    /**
     * 状态机
     */
    private String mState = "init";
    //用于数据传输
    private PeerConnection mPeerConnection;
    private PeerConnectionFactory mPeerConnectionFactory;
    private DataChannel mDataChannel;

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

        SignalClient.getInstance().leaveRoom();

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        doLeave();
        PeerConnectionFactory.stopInternalTracingCapture();
        PeerConnectionFactory.shutdownInternalTracer();
        if (mPeerConnectionFactory != null)
            mPeerConnectionFactory.dispose();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_only_text);

        mPeerConnectionFactory = createPeerConnectionFactory(this);

        initData();
        initView();
        initListener();
    }

    private void initData() {
        /**
         * 服务器
         */
        serverAddr = getIntent().getStringExtra("ServerAddr");
        /**
         * 房间名称
         */
        roomName = getIntent().getStringExtra("RoomName");
        /**
         * 添加监听
         */
        SignalClient.getInstance().setSignalEventListener(this);

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

    private void initView() {
        mLogcatView = findViewById(R.id.LogcatView);
        tvContext = findViewById(R.id.tv_context);
        etInput = findViewById(R.id.et_input);
        btnSend = findViewById(R.id.btn_send);
    }

    private void initListener() {
        btnSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String message = etInput.getText().toString();
                if (TextUtils.isEmpty(message)){
                    return;
                }
                String text = "我: " + message;
                tvContext.setText(tvContext.getText().toString() + "\n" + text);
                sendDataChannelMessage(message);
                etInput.setText("");
            }
        });
        this.setWebRtcListener(new WebRtcListener() {
            @Override
            public void onReceiveDataChannelMessage(String message) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        tvContext.setText(tvContext.getText().toString() + "\n" + "对方: "+message);
                    }
                });
            }
        });
    }

    /**
     * 连接服务器成功
     */
    @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";

        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();
        }
    }

    /**
     * 创建 factory, pc是从factory里获得的
     *
     * @param context
     * @return
     */
    public PeerConnectionFactory createPeerConnectionFactory(Context context) {
        /**
         * 这里音视频解码必须添加,因为对比发现web端转发过来端sdp 即使不进行媒体传输,也是含有音视频信息,
         * 如果不添加,在setRemoteDescription时回奔溃,
         *
         * 注意如果单独进行,移动端的传输可以去掉
         * .setVideoEncoderFactory(encoderFactory)
         * .setVideoDecoderFactory(decoderFactory);
         *
         * */
        EglBase mRootEglBase = EglBase.create();
        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();
    }

    /**
     * 创建通道连接
     *
     * @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);

        /*
            DataChannel.Init 可配参数说明:
            ordered:是否保证顺序传输;
            maxRetransmitTimeMs:重传允许的最长时间;
            maxRetransmits:重传允许的最大次数;
             */
        DataChannel.Init init = new DataChannel.Init();
        init.ordered = true;
        mDataChannel = connection.createDataChannel("dataChannel", init);
        mDataChannel.registerObserver(mDataChannelObserver);

        return connection;
    }


    /**
     * PeerConnection的 观察者,监听回掉
     */
    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.name());

        }

        @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("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());
        }

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

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

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

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

        }
    };

    /**
     * DataChannel 观察者,监听回掉
     */
    private DataChannel.Observer mDataChannelObserver = new DataChannel.Observer() {
        @Override
        public void onBufferedAmountChange(long l) {

        }

        @Override
        public void onStateChange() {
            Log.d(TAG, "onDataChannel onStateChange:" + mDataChannel.state());
        }

        @Override
        public void onMessage(DataChannel.Buffer buffer) {
            Log.d(TAG, "onDataChannel onMessage : " + buffer);
            ByteBuffer data = buffer.data;
            byte[] bytes = new byte[data.capacity()];
            data.get(bytes);
            String msg = new String(bytes);
            //TODO  --------------------------------------------------------------------------------------------
            Log.e(TAG, "msg============> : " + msg);
            if (webRtcListener != null) {
                webRtcListener.onReceiveDataChannelMessage(msg);
            }
        }
    };

    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);
            }
        });
    }

    private WebRtcListener webRtcListener;

    public void setWebRtcListener(WebRtcListener webRtcListener) {
        this.webRtcListener = webRtcListener;
    }

    public interface WebRtcListener {
        void onReceiveDataChannelMessage(String message);
    }


    //---------------------------------------媒体协商--------------------------------------------

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

        //打开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 sdp) {
            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");
            SessionDescription sessionDescription = new SessionDescription(
                    SessionDescription.Type.OFFER,
                    description);

            Log.e(TAG,"description=====>"+description);

            mPeerConnection.setRemoteDescription(
                    new SimpleSdpObserver(),
                    sessionDescription);
            doAnswerCall();
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

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

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

        MediaConstraints sdpMediaConstraints = new MediaConstraints();
        //打开DTLS,不打开和浏览器之间不可以进行互通
        sdpMediaConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
        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);
    }

    /**
     * 接收远程回答
     *
     * @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();
        }
    }

    /**
     * 接收远程候选人
     *
     * @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 hangup() {
        logcatOnUI("Hangup Call, Wait ...");
        if (mPeerConnection == null) {
            return;
        }
        mPeerConnection.close();
        mPeerConnection = null;
        logcatOnUI("Hangup Done.");
    }

    /********************************************************************/
    /**
     * 发送消息
     *
     * @param message
     */
    public void sendDataChannelMessage(String message) {
        byte[] msg = message.getBytes();
        DataChannel.Buffer buffer = new DataChannel.Buffer(
                ByteBuffer.wrap(msg),
                false);
        mDataChannel.send(buffer);
    }
}

web端测试地址:https://www.huangxiaoguo.club/onlyText/index.html

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

这样web端和web端,web端和android端,android端和android端纯文本聊天互通就完成了

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

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

猜你喜欢

转载自blog.csdn.net/huangxiaoguo1/article/details/104230582