手机上自己如何做网站,企业门户网站管理制度,wordpress 顶部公告,建设电子元器件网站webrtc Android入门非常的简单不要被那些博客给带乱了#xff0c;我看了几篇这方面的博客都是给你零散的代码更本就不能实现通话#xff0c;学这个要先从全局流程再到详细步骤来学习。
简单介绍下实现webrtc通话总体流程并且给出全部代码#xff0c;复制粘贴即可 本文福利我看了几篇这方面的博客都是给你零散的代码更本就不能实现通话学这个要先从全局流程再到详细步骤来学习。
简单介绍下实现webrtc通话总体流程并且给出全部代码复制粘贴即可 本文福利 免费领取C音视频学习资料包学习路线大纲、技术视频/代码内容包括音视频开发面试题FFmpeg webRTC rtmp hls rtsp ffplay 编解码推拉流srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓ 1、用户A和用户B实现通信双方间建立链接最重要的是双方获取彼此的sdp信息和ice信息。 sdp就是一段文本描述里面包含了当前本地设备所支持的一些信息比如设备是否支持h264编码传输协议是什么。 ice也是一段文本是配合p2p打洞服务器stun/turn让双方知道各自的公网ip和端口从而实现端对端通信。 对于实现一个简单入门通信案例来说不要太去深入的理解每个名词的具体意思只要能总的明白是做什么就行了等入门后再去深入理解每个知识点。 简单理解就是sdp是设备描述文本ice就是ip端口描述文本
2、sdp,ice的创建和使用 用户A,B都需要设置setLocationDescription和setRemoteDescription。 呼叫者调用createOffer创建sdp,被呼叫者调用createAnswer创建sdp ice信息是在创建peerConnection后会自动从stun/turn服务器请求回调。
下面来具体实现
xml布局
?xml version1.0 encodingutf-8?
androidx.constraintlayout.widget.ConstraintLayout xmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:apphttp://schemas.android.com/apk/res-autoxmlns:toolshttp://schemas.android.com/toolsandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:background#000tools:context.ui.SimpleMeetingActivity!--播放远端视频--org.webrtc.SurfaceViewRendererandroid:layout_marginBottom200dpandroid:idid/ivRemoteRenderandroid:layout_widthmatch_parentandroid:layout_heightmatch_parent/!--播放本地视频--org.webrtc.SurfaceViewRendererandroid:idid/ivLocalRenderapp:layout_constraintTop_toTopOfparentapp:layout_constraintRight_toRightOfparentandroid:layout_width150dpandroid:layout_height200dp/Viewandroid:idid/ivJoinandroid:layout_marginBottom20dpapp:layout_constraintRight_toRightOfparentapp:layout_constraintLeft_toLeftOfparentapp:layout_constraintBottom_toBottomOfparentandroid:background#4CAF50android:layout_width100dpandroid:layout_height100dp//androidx.constraintlayout.widget.ConstraintLayout
1.引入webrtc Android 和websocket
implementation org.webrtc:google-webrtc:1.0.32006
//引入socket依赖
implementation org.java-websocket:Java-WebSocket:1.4.0
2.全局初始化
//在初次使用PeerConnectionFactory之前必须调用静态方法initialize()对其进行全局的初始化与资源加载PeerConnectionFactory.InitializationOptions initializationOptions PeerConnectionFactory.InitializationOptions.builder(this).setEnableInternalTracer(true)// 启用内部追踪器用来记录一些相关数据.createInitializationOptions();PeerConnectionFactory.initialize(initializationOptions);
3.创建PeerConnectionFactory //-----------创建视频编码和解码器VideoEncoderFactory encoderFactory new DefaultVideoEncoderFactory(mRootEGL.getEglBaseContext(), true, true);VideoDecoderFactory decoderFactory new DefaultVideoDecoderFactory(mRootEGL.getEglBaseContext());//-----------创建PeerConnectionFactoryAudioDeviceModule adm JavaAudioDeviceModule.builder(this).createAudioDeviceModule();//音频配置当前JAVA实现还有nativePeerConnectionFactory.Options options new PeerConnectionFactory.Options();//options.disableEncryption : true表示不用数据加密//options.disableNetworkMonitor : true表示禁用网络监视器factory PeerConnectionFactory.builder().setOptions(options)//设置网络配置,使用默认.setAudioDeviceModule(adm)//设置音频采集和播放使用的配置,当前使用java中的audioTrack 和audioRecord.setVideoEncoderFactory(encoderFactory).setVideoDecoderFactory(decoderFactory).createPeerConnectionFactory();
4.创建声音源
//配置音频约束MediaConstraints audioConstraints new MediaConstraints();//回声消除audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair(googEchoCancellation, true));//自动增益audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair(googAutoGainControl, true));//高音过滤audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair(googHighpassFilter, true));//噪音处理audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair(googNoiseSuppression, true));audioSource factory.createAudioSource(audioConstraints);
//创建音频轨道localAudioTrack factory.createAudioTrack(102, audioSource);
5.创建视频源
//ScreenCapturerAndroid 录屏 FileVideoCapturer文件//从相机里获取视频流CameraEnumerator enumerator new Camera2Enumerator(this);String[] deviceNames enumerator.getDeviceNames();//遍历所有摄像头找到前置摄像头for (String deviceName : deviceNames) {if (enumerator.isFrontFacing(deviceName)) {videoCapturer enumerator.createCapturer(deviceName, null);}}assert videoCapturer ! null;videoSource factory.createVideoSource(videoCapturer.isScreencast());//创建视频轨道videoTrack factory.createVideoTrack(103, videoSource);
6.播放本地视频 //因为使用的是opengl 渲染surfaceTextureHelper SurfaceTextureHelper.create(capture-thread, mRootEGL.getEglBaseContext());videoCapturer.initialize(surfaceTextureHelper, this, videoSource.getCapturerObserver());//开始录制videoCapturer.startCapture(720,//宽1080,//高25//fps 帧率);//播放本地视频localRender.init(mRootEGL.getEglBaseContext(), null);//SCALE_ASPECT_FILL 自动适应屏幕比例 画面存在被裁剪的可能//SCALE_ASPECT_FIT 自动适应画面比例屏幕上可能存在黑边//SCALE_ASPECT_BALANCED 视频尺寸非等比缩放。保证视频内容全部显示且填满视窗。localRender.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);localRender.setMirror(true);//启用镜像videoTrack.addSink(localRender);//最后播放
7.初始化远端render
//初始化远端render 因为使用opengl渲染所以 必须在在主线程初始化remoteRender.init(mRootEGL.getEglBaseContext(), null);remoteRender.setMirror(true);//启用镜像remoteRender.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
8.安装ice服务器
//在ubunto服务器上安装coturn软件即可实现ice服务器
sudo apt-get install libssl-dev
sudo apt-get install libevent-dev
git clone https://github.com/coturn/coturn
cd coturn
./configure
make
sudo make install
//启动服务器 wzp:123456 表示用户名密码也可用通过配置文件来设置
sudo nohup turnserver -L 0.0.0.0 -a -u wzp:123456 -v -f -r nort.text
//查看端口号,默认3478端口,
sudo lsof -i:3478
9.创建peerConntion
//ice服务器列表
ListPeerConnection.IceServer iceServers new ArrayList();
//添加一个turn服务器,turn服务器主要用户下面的stun服务器打洞失败的时候使用这个turn服务器转发数据流可以添加多个
iceServers.add(PeerConnection.IceServer.builder(turn:**.**.**.**3478)//这是你服务器的地址.setUsername(wzp)//用户名.setPassword(123456)//密码.createIceServer());//添加一个stun服务器
iceServers.add(PeerConnection.IceServer.builder(stun:**.**.**.**:3478).createIceServer());peerConnection factory.createPeerConnection(iceServers, new PeerConnection.Observer() {Overridepublic void onSignalingChange(PeerConnection.SignalingState signalingState) {}Overridepublic void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {//ICE 连接状态变化后回调}Overridepublic void onIceConnectionReceivingChange(boolean b) {}Overridepublic void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {}Overridepublic void onIceCandidate(IceCandidate iceCandidate) {//自动请求stun/turn服务器后回调这个方法//发送Ice信息给对端用户 ,下面的代码只是用于发送信息给远端用户我使用的是websocket自己可以用其他方式实现。最后结尾我会给出服务器端的代码。JSONObject sendObj new JSONObject();try {sendObj.put(cmd, cmd_ice);sendObj.put(uid, uid);sendObj.put(remoteUid, remoteUid);sendObj.put(roomId,roomId);JSONObject msgObj new JSONObject();msgObj.put(sdpMid, iceCandidate.sdpMid);msgObj.put(sdpMLineIndex, iceCandidate.sdpMLineIndex);msgObj.put(sdp, iceCandidate.sdp);sendObj.put(msg, msgObj);socket.send(sendObj.toString());} catch (JSONException e) {throw new RuntimeException(e);}}Overridepublic void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {}Overridepublic void onAddStream(MediaStream mediaStream) {//收到远端数据流信息mediaStream.videoTracks.get(0).addSink(remoteRender);mediaStream.audioTracks.get(0).setEnabled(true);}Overridepublic void onRemoveStream(MediaStream mediaStream) {}Overridepublic void onDataChannel(DataChannel dataChannel) {}Overridepublic void onRenegotiationNeeded() {}Overridepublic void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {}});// ListString mediaStreamLabels Collections.singletonList(ARDAMS);
// peerConnection.addTrack(videoTrack,mediaStreamLabels);
// peerConnection.addTrack(localAudioTrack,mediaStreamLabels);//将本地流添加到peerConnection远端的onAddStream回调将接受该数据流MediaStream stream factory.createLocalMediaStream(110);stream.addTrack(videoTrack);stream.addTrack(localAudioTrack);peerConnection.addStream(stream);
10.建立websocket链接只是用于演示效果所以这里只是简单的实现。重要的是如何简单的去理解流程 URI uri null;//信令服务器地址try {uri new URI(ws://192.168.2.134:8090);} catch (Exception e) {}socket new WebSocketClient(uri) {Overridepublic void onOpen(ServerHandshake handshakedata) {if (isDestroyed()) {return;}Log.e(tag,链接socket成功);}Overridepublic void onMessage(String message) {if (isDestroyed()) {return;}try {JSONObject msgObj new JSONObject(message);String cmd msgObj.getString(cmd);Log.e(tag,收到消息: message);if (cmd.equals(cmd_new_peer)) {//有新人加入房间handleNewPeer(msgObj);return;}if (cmd.equals(cmd_offer)) {//收到offer请求handleOffer(msgObj);return;}if (cmd.equals(cmd_answer)) {//收到answer请求handleAnswer(msgObj);return;}if (cmd.equals(cmd_ice)) {//收到ice信息handleIce(msgObj);}} catch (JSONException e) {throw new RuntimeException(e);}}Overridepublic void onClose(int code, String reason, boolean remote) {if (isDestroyed()) {return;}}Overridepublic void onError(Exception ex) {if (isDestroyed()) {return;}Log.e(tag,socket错误 ex.toString());}};socket.connect();
11.发起通话者创建offer并且设置本地setLocalDescription,最后发送给被呼叫者
private void handleNewPeer(JSONObject msgObj) {/*新人加入房间,创建offer,发起通话*/try {//被呼叫者的uidremoteUid msgObj.getString(uid);MediaConstraints constraints new MediaConstraints();constraints.mandatory.add(new MediaConstraints.KeyValuePair(OfferToReceiveAudio, true));constraints.mandatory.add(new MediaConstraints.KeyValuePair(OfferToReceiveVideo, true));peerConnection.createOffer(new SdpObserver() {private SessionDescription localSdp;Overridepublic void onCreateSuccess(SessionDescription sessionDescription) {//创建offer成功即成功localSdp sessionDescription;peerConnection.setLocalDescription(this, sessionDescription);}Overridepublic void onSetSuccess() {//setLocalDescription 成功后回调JSONObject sendObj new JSONObject();try {sendObj.put(cmd, cmd_offer);sendObj.put(uid, uid);sendObj.put(remoteUid, remoteUid);sendObj.put(roomId,roomId);sendObj.put(msg, localSdp.description);socket.send(sendObj.toString());} catch (JSONException e) {throw new RuntimeException(e);}}Overridepublic void onCreateFailure(String s) {}Overridepublic void onSetFailure(String s) {}}, constraints);} catch (JSONException e) {throw new RuntimeException(e);}}
12.被呼叫者收到offer后设置setRemoteDescription 创建answer并设置setLocalDescription,最后发送给呼叫者 private void handleOffer(JSONObject msgObj) {//收到offer, 当前身份是被呼叫者try {//发起通话者的uidremoteUid msgObj.getString(uid);String sdpDescription msgObj.getString(msg);SessionDescription sdp new SessionDescription(SessionDescription.Type.OFFER, sdpDescription);peerConnection.setRemoteDescription(new SdpObserver() {private boolean isCreateAnswer;private String sdpDescription;Overridepublic void onCreateSuccess(SessionDescription sessionDescription) {//createAnswer 创建成功时候回调Log.e(tag,创建answer成功);sdpDescription sessionDescription.description;peerConnection.setLocalDescription(this, sessionDescription);}Overridepublic void onSetSuccess() {//setRemoteDescription setLocalDescription 成功时候的回调if (!isCreateAnswer) {Log.e(tag,onSetSuccess1);//还未创建answer 说明是setRemoteDescription回调isCreateAnswer true;MediaConstraints constraints new MediaConstraints();constraints.mandatory.add(new MediaConstraints.KeyValuePair(OfferToReceiveAudio, true));constraints.mandatory.add(new MediaConstraints.KeyValuePair(OfferToReceiveVideo, true));peerConnection.createAnswer(this, constraints);} else {Log.e(tag,onSetSuccess2);//是setLocalDescription回调JSONObject sendObj new JSONObject();try {sendObj.put(cmd, cmd_answer);sendObj.put(uid, uid);sendObj.put(remoteUid, remoteUid);sendObj.put(roomId,roomId);sendObj.put(msg, sdpDescription);socket.send(sendObj.toString());} catch (JSONException e) {throw new RuntimeException(e);}}}Overridepublic void onCreateFailure(String s) {}Overridepublic void onSetFailure(String s) {}}, sdp);} catch (JSONException e) {e.printStackTrace();throw new RuntimeException(e);}}
13.发起通话者收到answer后设置setRemoteDescription
private void handleAnswer(JSONObject msgObj) {//收到answer,当前是发起通话者try {String sdpDescription msgObj.getString(msg);SessionDescription sdp new SessionDescription(SessionDescription.Type.ANSWER, sdpDescription);peerConnection.setRemoteDescription(new SdpObserver() {Overridepublic void onCreateSuccess(SessionDescription sessionDescription) {}Overridepublic void onSetSuccess() {// setRemoteDescription 设置成功}Overridepublic void onCreateFailure(String s) {}Overridepublic void onSetFailure(String s) {}}, sdp);} catch (JSONException e) {throw new RuntimeException(e);}}
14,各自收到对方发来的ice信息时候addIceCandidate
private void handleIce(JSONObject msgObj) {//收到对方的ice信息try {Log.e(tag,设置ICE信息);JSONObject iceObj msgObj.getJSONObject(msg);IceCandidate iceCandidate new IceCandidate(iceObj.getString(sdpMid),iceObj.getInt(sdpMLineIndex),iceObj.getString(sdp));peerConnection.addIceCandidate(iceCandidate);} catch (JSONException e) {Log.e(tag,ice设置失败: e.getMessage());throw new RuntimeException(e);}}
15.销毁
Overrideprotected void onDestroy() {if (socket ! null socket.isOpen()) {socket.close();}socket null;if(peerConnection ! null){peerConnection.close();peerConnection null;}if(videoSource ! null){videoSource.dispose();videoSource null;}if (audioSource ! null) {audioSource.dispose();audioSource null;}if(videoCapturer ! null){try {videoCapturer.stopCapture();} catch (InterruptedException e) {throw new RuntimeException(e);}videoCapturer.dispose();videoCapturer null;}if(surfaceTextureHelper ! null){surfaceTextureHelper.dispose();surfaceTextureHelper null;}localRender.release();remoteRender.release();if(factory ! null){factory.dispose();factory null;}if(mRootEGL ! null){mRootEGL.release();mRootEGL null;}super.onDestroy();}
最后给出java全部源码
package com.example.rtcmy.ui;import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.BindingConversion;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ObservableField;import android.database.Observable;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;import com.example.rtcmy.R;import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
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.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RendererCommon;
import org.webrtc.RtpReceiver;
import org.webrtc.ScreenCapturerAndroid;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoDecoder;
import org.webrtc.VideoDecoderFactory;
import org.webrtc.VideoEncoderFactory;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
import org.webrtc.audio.AudioDeviceModule;
import org.webrtc.audio.JavaAudioDeviceModule;import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;public class SimpleMeetingActivity extends AppCompatActivity {private WebSocketClient socket;private String roomId 1;//房间号private String cmd_join_room cmd_join_room;//加入房间命令private String cmd_new_peer cmd_new_peer;//有新人加入房间private String cmd_offer cmd_offer;private String cmd_answer cmd_answer;private String cmd_ice cmd_ice;private SurfaceViewRenderer remoteRender;private SurfaceViewRenderer localRender;private TextView ivStatus;private View ivJoin;private EglBase mRootEGL;private PeerConnectionFactory factory;private VideoCapturer videoCapturer;private AudioSource audioSource;private AudioTrack localAudioTrack;private VideoSource videoSource;private VideoTrack videoTrack;private ListPeerConnection.IceServer iceServers new ArrayList();private SurfaceTextureHelper surfaceTextureHelper;private PeerConnection peerConnection;private String remoteUid;private String uid UUID.randomUUID().toString();private String tag simpleWebrtc;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_simple_metting);ivStatus findViewById(R.id.ivStatus);ivJoin findViewById(R.id.ivJoin);remoteRender findViewById(R.id.ivRemoteRender);localRender findViewById(R.id.ivLocalRender);//开启日志iceServers.add(PeerConnection.IceServer.builder(turn:123.60.151.37:3478).setUsername(wzp).setPassword(123456).createIceServer());iceServers.add(PeerConnection.IceServer.builder(stun:123.60.151.37:3478).createIceServer());mRootEGL EglBase.create();//在初次使用PeerConnectionFactory之前必须调用静态方法initialize()对其进行全局的初始化与资源加载PeerConnectionFactory.InitializationOptions initializationOptions PeerConnectionFactory.InitializationOptions.builder(this).setEnableInternalTracer(true)// 启用内部追踪器用来记录一些相关数据.createInitializationOptions();PeerConnectionFactory.initialize(initializationOptions);//-----------创建视频编码和解码器VideoEncoderFactory encoderFactory new DefaultVideoEncoderFactory(mRootEGL.getEglBaseContext(),true, true);VideoDecoderFactory decoderFactory new DefaultVideoDecoderFactory(mRootEGL.getEglBaseContext());//-----------创建PeerConnectionFactoryAudioDeviceModule adm JavaAudioDeviceModule.builder(this).createAudioDeviceModule();//音频配置当前JAVA实现还有nativePeerConnectionFactory.Options options new PeerConnectionFactory.Options();//options.disableEncryption : true表示不用数据加密//options.disableNetworkMonitor : true表示禁用网络监视器factory PeerConnectionFactory.builder().setOptions(options)//设置网络配置,使用默认.setAudioDeviceModule(adm)//设置音频采集和播放使用的配置,当前使用java中的audioTrack 和audioRecord.setVideoEncoderFactory(encoderFactory).setVideoDecoderFactory(decoderFactory).createPeerConnectionFactory();//-----------创建声音源//配置音频约束MediaConstraints audioConstraints new MediaConstraints();//回声消除audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair(googEchoCancellation, true));//自动增益audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair(googAutoGainControl, true));//高音过滤audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair(googHighpassFilter, true));//噪音处理audioConstraints.mandatory.add(new MediaConstraints.KeyValuePair(googNoiseSuppression, true));audioSource factory.createAudioSource(audioConstraints);localAudioTrack factory.createAudioTrack(102, audioSource);//----------创建视频源//ScreenCapturerAndroid 录屏 FileVideoCapturer文件//从相机里获取视频流CameraEnumerator enumerator new Camera2Enumerator(this);String[] deviceNames enumerator.getDeviceNames();//遍历所有摄像头找到前置摄像头for (String deviceName : deviceNames) {if (enumerator.isFrontFacing(deviceName)) {videoCapturer enumerator.createCapturer(deviceName, null);}}assert videoCapturer ! null;videoSource factory.createVideoSource(videoCapturer.isScreencast());//创建视频轨道videoTrack factory.createVideoTrack(103, videoSource);//因为使用的是opengl 渲染surfaceTextureHelper SurfaceTextureHelper.create(capture-thread, mRootEGL.getEglBaseContext());videoCapturer.initialize(surfaceTextureHelper, this, videoSource.getCapturerObserver());//开始录制videoCapturer.startCapture(720,//宽1080,//高25//fps 帧率);//播放本地视频localRender.init(mRootEGL.getEglBaseContext(), null);//SCALE_ASPECT_FILL 自动适应屏幕比例 画面存在被裁剪的可能//SCALE_ASPECT_FIT 自动适应画面比例屏幕上可能存在黑边//SCALE_ASPECT_BALANCED 视频尺寸非等比缩放。保证视频内容全部显示且填满视窗。localRender.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);localRender.setMirror(true);//启用镜像videoTrack.addSink(localRender);//最后播放//初始化远端render 因为使用opengl渲染所以 必须在在主线程初始化remoteRender.init(mRootEGL.getEglBaseContext(), null);remoteRender.setMirror(true);//启用镜像remoteRender.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);//创建peerConnectionpeerConnection factory.createPeerConnection(iceServers, new PeerConnection.Observer() {Overridepublic void onSignalingChange(PeerConnection.SignalingState signalingState) {}Overridepublic void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {//ICE 连接状态变化后回调}Overridepublic void onIceConnectionReceivingChange(boolean b) {}Overridepublic void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {}Overridepublic void onIceCandidate(IceCandidate iceCandidate) {//自动请求stun/turn服务器后回调这个方法//发送Ice信息给对端用户JSONObject sendObj new JSONObject();try {sendObj.put(cmd, cmd_ice);sendObj.put(uid, uid);sendObj.put(remoteUid, remoteUid);sendObj.put(roomId,roomId);JSONObject msgObj new JSONObject();msgObj.put(sdpMid, iceCandidate.sdpMid);msgObj.put(sdpMLineIndex, iceCandidate.sdpMLineIndex);msgObj.put(sdp, iceCandidate.sdp);sendObj.put(msg, msgObj);socket.send(sendObj.toString());} catch (JSONException e) {throw new RuntimeException(e);}}Overridepublic void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {}Overridepublic void onAddStream(MediaStream mediaStream) {//收到远端数据流信息mediaStream.videoTracks.get(0).addSink(remoteRender);mediaStream.audioTracks.get(0).setEnabled(true);}Overridepublic void onRemoveStream(MediaStream mediaStream) {}Overridepublic void onDataChannel(DataChannel dataChannel) {}Overridepublic void onRenegotiationNeeded() {}Overridepublic void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {}});// ListString mediaStreamLabels Collections.singletonList(ARDAMS);
// peerConnection.addTrack(videoTrack,mediaStreamLabels);
// peerConnection.addTrack(localAudioTrack,mediaStreamLabels);//将本地流添加到peerConnection远端的onAddStream回调将接受该数据流MediaStream stream factory.createLocalMediaStream(110);stream.addTrack(videoTrack);stream.addTrack(localAudioTrack);peerConnection.addStream(stream);createSocket();ivJoin.setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View view) {if (!socket.isOpen()) {return;}ivStatus.setText(正在加入房间...);JSONObject sendObj new JSONObject();try {sendObj.put(cmd, cmd_join_room);sendObj.put(uid, uid);sendObj.put(roomId, roomId);socket.send(sendObj.toString());} catch (JSONException e) {throw new RuntimeException(e);}}});}private void createSocket() {if (socket ! null) {socket.close();}URI uri null;//信令服务器地址try {uri new URI(ws://192.168.2.134:8090);} catch (Exception e) {}socket new WebSocketClient(uri) {Overridepublic void onOpen(ServerHandshake handshakedata) {if (isDestroyed()) {return;}Log.e(tag,链接socket成功);}Overridepublic void onMessage(String message) {if (isDestroyed()) {return;}try {JSONObject msgObj new JSONObject(message);String cmd msgObj.getString(cmd);Log.e(tag,收到消息: message);if (cmd.equals(cmd_new_peer)) {//有新人加入房间handleNewPeer(msgObj);return;}if (cmd.equals(cmd_offer)) {//收到offer请求handleOffer(msgObj);return;}if (cmd.equals(cmd_answer)) {//收到answer请求handleAnswer(msgObj);return;}if (cmd.equals(cmd_ice)) {//收到ice信息handleIce(msgObj);}} catch (JSONException e) {throw new RuntimeException(e);}}Overridepublic void onClose(int code, String reason, boolean remote) {if (isDestroyed()) {return;}}Overridepublic void onError(Exception ex) {if (isDestroyed()) {return;}Log.e(tag,socket错误 ex.toString());}};socket.connect();}private void handleIce(JSONObject msgObj) {//收到对方的ice信息try {Log.e(tag,设置ICE信息);JSONObject iceObj msgObj.getJSONObject(msg);IceCandidate iceCandidate new IceCandidate(iceObj.getString(sdpMid),iceObj.getInt(sdpMLineIndex),iceObj.getString(sdp));peerConnection.addIceCandidate(iceCandidate);} catch (JSONException e) {Log.e(tag,ice设置失败: e.getMessage());throw new RuntimeException(e);}}private void handleAnswer(JSONObject msgObj) {//收到answer,当前是发起通话者try {String sdpDescription msgObj.getString(msg);SessionDescription sdp new SessionDescription(SessionDescription.Type.ANSWER, sdpDescription);peerConnection.setRemoteDescription(new SdpObserver() {Overridepublic void onCreateSuccess(SessionDescription sessionDescription) {}Overridepublic void onSetSuccess() {// setRemoteDescription 设置成功}Overridepublic void onCreateFailure(String s) {}Overridepublic void onSetFailure(String s) {}}, sdp);} catch (JSONException e) {throw new RuntimeException(e);}}private void handleOffer(JSONObject msgObj) {//收到offer, 当前身份是被呼叫者try {//发起通话者的uidremoteUid msgObj.getString(uid);String sdpDescription msgObj.getString(msg);SessionDescription sdp new SessionDescription(SessionDescription.Type.OFFER, sdpDescription);peerConnection.setRemoteDescription(new SdpObserver() {private boolean isCreateAnswer;private String sdpDescription;Overridepublic void onCreateSuccess(SessionDescription sessionDescription) {//createAnswer 创建成功时候回调Log.e(tag,创建answer成功);sdpDescription sessionDescription.description;peerConnection.setLocalDescription(this, sessionDescription);}Overridepublic void onSetSuccess() {//setRemoteDescription setLocalDescription 成功时候的回调if (!isCreateAnswer) {Log.e(tag,onSetSuccess1);//还未创建answer 说明是setRemoteDescription回调isCreateAnswer true;MediaConstraints constraints new MediaConstraints();constraints.mandatory.add(new MediaConstraints.KeyValuePair(OfferToReceiveAudio, true));constraints.mandatory.add(new MediaConstraints.KeyValuePair(OfferToReceiveVideo, true));peerConnection.createAnswer(this, constraints);} else {Log.e(tag,onSetSuccess2);//是setLocalDescription回调JSONObject sendObj new JSONObject();try {sendObj.put(cmd, cmd_answer);sendObj.put(uid, uid);sendObj.put(remoteUid, remoteUid);sendObj.put(roomId,roomId);sendObj.put(msg, sdpDescription);socket.send(sendObj.toString());} catch (JSONException e) {throw new RuntimeException(e);}}}Overridepublic void onCreateFailure(String s) {}Overridepublic void onSetFailure(String s) {}}, sdp);} catch (JSONException e) {e.printStackTrace();throw new RuntimeException(e);}}private void handleNewPeer(JSONObject msgObj) {/*新人加入房间,创建offer,发起通话*/try {//被呼叫者的uidremoteUid msgObj.getString(uid);MediaConstraints constraints new MediaConstraints();constraints.mandatory.add(new MediaConstraints.KeyValuePair(OfferToReceiveAudio, true));constraints.mandatory.add(new MediaConstraints.KeyValuePair(OfferToReceiveVideo, true));peerConnection.createOffer(new SdpObserver() {private SessionDescription localSdp;Overridepublic void onCreateSuccess(SessionDescription sessionDescription) {//创建offer成功即成功localSdp sessionDescription;peerConnection.setLocalDescription(this, sessionDescription);}Overridepublic void onSetSuccess() {//setLocalDescription 成功后回调JSONObject sendObj new JSONObject();try {sendObj.put(cmd, cmd_offer);sendObj.put(uid, uid);sendObj.put(remoteUid, remoteUid);sendObj.put(roomId,roomId);sendObj.put(msg, localSdp.description);socket.send(sendObj.toString());} catch (JSONException e) {throw new RuntimeException(e);}}Overridepublic void onCreateFailure(String s) {}Overridepublic void onSetFailure(String s) {}}, constraints);} catch (JSONException e) {throw new RuntimeException(e);}}Overrideprotected void onDestroy() {if (socket ! null socket.isOpen()) {socket.close();}socket null;if(peerConnection ! null){peerConnection.close();peerConnection null;}if(videoSource ! null){videoSource.dispose();videoSource null;}if (audioSource ! null) {audioSource.dispose();audioSource null;}if(videoCapturer ! null){try {videoCapturer.stopCapture();} catch (InterruptedException e) {throw new RuntimeException(e);}videoCapturer.dispose();videoCapturer null;}if(surfaceTextureHelper ! null){surfaceTextureHelper.dispose();surfaceTextureHelper null;}localRender.release();remoteRender.release();if(factory ! null){factory.dispose();factory null;}if(mRootEGL ! null){mRootEGL.release();mRootEGL null;}super.onDestroy();}
}
服务器端node js全部源码
//简单信令服务器 npm install nodejs-websocket
let ws require(nodejs-websocket)
class Client {constructor(uid,conn,roomId) {this.uid uid;this.conn conn;this.conn.uid this.uid;this.roomId roomId;this.conn.roomId roomId;}
}let roomMaps new Mapfunction handleJoinRoom(receiveObj, conn) {//有人加入房间let uid receiveObj.uid;let roomId receiveObj.roomId;let room roomMaps.get(roomId);let client new Client(uid,conn,roomId)if(!room){//创建房间room new Map}if(room.get(uid)){//已经在房间了console.log(已经在房间里了)return}room.set(uid,client)roomMaps.set(roomId,room);console.log(加入房间了);if(room.size 1){let clients Array.from(room.keys())clients.forEach(remoteUid {if(remoteUid ! uid){//通知房间其他人有新人加入let sendObj {cmd: cmd_new_peer,uid: uid,remoteUid}let remoteClient room.get(remoteUid)remoteClient.conn.sendText(JSON.stringify(sendObj))console.log(新人发送成功);}})}}function handleOffer(receiveObj) {//转发offerlet remoteUid receiveObj.remoteUidlet roomId receiveObj.roomIdlet room roomMaps.get(roomId)let client room.get(remoteUid)client.conn.sendText(JSON.stringify(receiveObj))}function handleAnswer(receiveObj) {//转发answer;let remoteUid receiveObj.remoteUidlet roomId receiveObj.roomIdlet room roomMaps.get(roomId)let client room.get(remoteUid)client.conn.sendText(JSON.stringify(receiveObj))
}function handleIce(receiveObj) {//转发icelet remoteUid receiveObj.remoteUidlet roomId receiveObj.roomIdlet room roomMaps.get(roomId)let client room.get(remoteUid)client.conn.sendText(JSON.stringify(receiveObj))
}function handleClose(conn) {let uid conn.uidlet roomId conn.roomIdlet room roomMaps.get(roomId)room.delete(uid)
}ws.createServer(function (conn) {//有客服端链接conn.on(text,function (str) {//收到消息let receiveObj JSON.parse(str);console.log(str)switch (receiveObj.cmd) {case cmd_join_room://有人加入handleJoinRoom(receiveObj,conn)breakcase cmd_offer://转发offerhandleOffer(receiveObj)breakcase cmd_answer:handleAnswer(receiveObj);breakcase cmd_ice:handleIce(receiveObj);break}})conn.on(close,function (code,reason) {console.log(链接关闭)handleClose(conn);});conn.on(error,function (err){console.log(err);})}).listen(8090) 本文福利 免费领取C音视频学习资料包学习路线大纲、技术视频/代码内容包括音视频开发面试题FFmpeg webRTC rtmp hls rtsp ffplay 编解码推拉流srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓