WebRTC -ios

1.podfile 文件

source 'https://github.com/CocoaPods/Specs.git'
 platform :ios, '9.0'

target 'iosWebRTCDemo' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!
pod 'GoogleWebRTC','1.1.24595'

pod 'ReactiveObjC'
pod 'SocketRocket','0.5.1'
pod 'HGAlertViewController', '~> 1.0.1'

  target 'iosWebRTCDemoTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'iosWebRTCDemoUITests' do
    # Pods for testing
  end

end
 [[WebRTCHelper shareInstance] connectServer:@"172.27.104.54" port:@"9090/SocketServer"];
  1. WebRTCHelper.h
//
//  WebRTCHelper.h
//  WebRTC_new
//
//  Created by
//  Copyright © 2020年
//

#import 
#import 
#import 
#import 
#import 

//@import SocketIO;

typedef enum : NSUInteger {
    WebSocketConnectSuccess = 0,
    WebSocketConnectField,
    WebSocketConnectClosed,
} WebSocketConnectState;

@protocol WebRTCHelperDelegate;
@protocol WebRTCHelperFrindDelegate;

@interface WebRTCHelper : NSObject

/**
 * 单例
 */
+(instancetype)shareInstance;

/*注释*/
@property (nonatomic,weak) id delegate;
/*注释*/
@property (nonatomic,weak) id friendDelegate;

/**
 * 与服务器建立连接
 * @param server 服务器地址
 * @param port 端口号
 */
-(void)connectServer:(NSString *)server port:(NSString *)port;
/**
 * 切换摄像头
 */
- (void)swichCamera:(BOOL)_usingFrontCamera;
/**
 * 是否显示本地视频
 */
- (void)showLocaolCamera:(BOOL)_usingCamera;
/**
 * 退出房间
 */
-(void)exitRoom;

@end

@protocol WebRTCHelperDelegate 
@optional
/**
 * 获取到发送信令消息
 * @param webRTCHelper 本类
 * @param message 消息内容
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper receiveMessage:(NSString *)message;
/**
 * 获取本地的localVideoStream数据
 * @param webRTCHelper 本类
 * @param steam 视频流
 * @param userId 用户标识
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper setLocalStream:(RTCMediaStream *)steam userId:(NSString *)userId;
/**
 * 获取远程的remoteVideoStream数据
 * @param webRTCHelper 本类
 * @param stream 视频流
 * @param userId 用户标识
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper addRemoteStream:(RTCMediaStream *)stream userId:(NSString *)userId;
/**
 * 某个用户退出后,关闭用户的连接
 * @param webRTCHelper 本类
 * @param userId 用户标识
 */
- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper closeWithUserId:(NSString *)userId;

/**
 * 获取socket连接状态
 * @param webRTCHelper 本类
 * @param connectState 连接状态,分为
 WebSocketConnectSuccess 成功,
 WebSocketConnectField, 失败
 WebSocketConnectClosed 关闭
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper socketConnectState:(WebSocketConnectState)connectState;
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper capturerSession:(AVCaptureSession *)captureSession;
@end

@protocol WebRTCHelperFrindDelegate 
@optional
/**
 * 获取房间内所有的用户(除了自己)
 * @param friendList 用户列表
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper gotFriendList:(NSArray *)friendList;
/**
 * 获取新加入的用户信息
 * @param friendId 新用户的id
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper gotNewFriend:(NSString *)friendId;
/**
 * 获取离开房间用户的信息
 * @param friendId 离开用户的ID
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper removeFriend:(NSString *)friendId;
@end




3 .WebRTCHelper.m

//
//  WebRTCHelper.m
//  WebRTC_new
//
//  Created by
//  Copyright © 2020年
//

#import "WebRTCHelper.h"

#define kAPPID  @"1234567890abcdefg"
#define kDeviceUUID [[[UIDevice currentDevice] identifierForVendor] UUIDString]

//google提供的
static NSString *const RTCSTUNServerURL = @"stun:stun.l.google.com:19302";
//static NSString *const RTCSTUNServerURL2 = @"stun:23.21.150.121";
static NSString *const RTCSTUNServerURL2 =@"stun:global.stun.twilio.com:3478?transport=udp";


@interface WebRTCHelper()
{
    SRWebSocket *_socket;
    NSString *_server;
    
    RTCPeerConnectionFactory *_factory;
    RTCMediaStream *_localStream;
    
    NSString *_myId;
    NSMutableDictionary *_connectionDic; // 存放 所有用户 对应的链接集合
    NSMutableArray *_connectionIdArray; // 存放 房间人数的 id
    
    NSMutableArray *ICEServers;
  
    //是否显示我的视频流(默认为yes,显示;no为不显示)
    BOOL _usingCamera;
    
    RTCCameraVideoCapturer * _capture;
}

@end

@implementation WebRTCHelper

static WebRTCHelper * instance = nil;

+(instancetype)shareInstance{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[[self class] alloc] init];
        [instance initData];
    });
    return instance;
}

-(void)initData{
    _connectionDic = [NSMutableDictionary dictionary];
    _connectionIdArray = [NSMutableArray array];
    _usingCamera = YES;
}


#pragma mark -提供给外部的方法

/**
 * 与服务器进行连接
 */
- (void)connectServer:(NSString *)server port:(NSString *)port{
    _server = server;
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"ws://%@:%@",server,port]] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20];

    _socket = [[SRWebSocket alloc] initWithURLRequest:request];
    _socket.delegate = self;
    [_socket open];

    
}


/**
 *  退出房间
 */
- (void)exitRoom
{
    _localStream = nil;
    [_capture stopCapture];
    _capture = nil;
    NSArray *arr =_connectionIdArray.copy;
    [arr enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) {
      [self closePeerConnection:obj];

    }];
    [_socket close];
}

/**
 * 切换摄像头
 */
- (void)swichCamera:(BOOL)_usingFrontCamera{

    [self switchFrontBackCamera:_usingFrontCamera];
}

/**
 * 是否显示本地摄像头
 */
- (void)showLocaolCamera:(BOOL)_usingCamera{
    _usingCamera = _usingCamera;
    //如果为空,则创建点对点工厂
    if (!_factory)
    {
        //设置SSL传输
        [RTCPeerConnectionFactory initialize];
        [self create_factory];
    }
    //如果本地视频流为空
    if (!_localStream)
    {
        //创建本地流
        [self createLocalStream];
    }
//    [_capture stopCapture];
    //创建连接
    [self createPeerConnections];
    
    //添加
    [self addStreams];
    [self createOffers];
}

#pragma mark -内部方法
/**
 *  关闭peerConnection
 *
 *  @param connectionId <#connectionId description#>
 */
- (void)closePeerConnection:(NSString *)connectionId
{
    RTCPeerConnection *peerConnection = [_connectionDic objectForKey:connectionId];
    if (peerConnection)
    {
        [peerConnection close];
    }
    [_connectionIdArray removeObject:connectionId];
    [_connectionDic removeObjectForKey:connectionId];
    dispatch_async(dispatch_get_main_queue(), ^{
        if ([self->_delegate respondsToSelector:@selector(webRTCHelper:closeWithUserId:)])
        {
            [self->_delegate webRTCHelper:self closeWithUserId:connectionId];
        }
    });
}


/**
 *  创建点对点连接
 *
 *  @param connectionId connectionId description
 *
 *  @return <#return value description#>
 */
- (RTCPeerConnection *)createPeerConnection:(NSString *)connectionId
{
    //如果点对点工厂为空
    if (!_factory)
    {
        [self create_factory];
    }
    
    //得到ICEServer
    if (!ICEServers) {
        ICEServers = [NSMutableArray array];
        [ICEServers addObject:[self defaultSTUNServer]];
    }
    
    //用工厂来创建连接
    RTCConfiguration *configuration = [[RTCConfiguration alloc] init];
    configuration.iceServers = ICEServers;
    RTCPeerConnection *connection = [_factory peerConnectionWithConfiguration:configuration constraints:[self creatPeerConnectionConstraint] delegate:self];
    return connection;
}

- (RTCMediaConstraints *)creatPeerConnectionConstraint
{
    RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints:@{kRTCMediaConstraintsOfferToReceiveAudio:kRTCMediaConstraintsValueTrue,kRTCMediaConstraintsOfferToReceiveVideo:kRTCMediaConstraintsValueTrue} optionalConstraints:@{@"DtlsSrtpKeyAgreement":@"true"}];
    
    return constraints;
}

//初始化STUN Server (ICE Server)
- (RTCIceServer *)defaultSTUNServer{
    return [[RTCIceServer alloc] initWithURLStrings:@[RTCSTUNServerURL,RTCSTUNServerURL2]];
}


/**
 *  为所有连接添加流
 */
- (void)addStreams
{
    //给每一个点对点连接,都加上本地流
    NSDictionary *dic = [_connectionDic copy];
    [dic enumerateKeysAndObjectsUsingBlock:^(NSString *key, RTCPeerConnection *obj, BOOL * _Nonnull stop) {
        if (!self->_localStream)
        {
            [self createLocalStream];
        }
        [obj addStream:self->_localStream];
    }];
}
/**
 *  创建所有连接
 */
- (void)createPeerConnections
{
    //从我们的连接数组里快速遍历
    NSArray *arr = _connectionIdArray.copy;
    [arr enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
        //根据连接ID去初始化 RTCPeerConnection 连接对象
        RTCPeerConnection *connection = [self createPeerConnection:obj];
        
        //设置这个ID对应的 RTCPeerConnection对象
        [self->_connectionDic setObject:connection forKey:obj];
       
    }];
}


/**
 * 创建本地视频流
 */
-(void)createLocalStream{
    _localStream = [_factory mediaStreamWithStreamId:@"ARDAMS"];
    //音频
    RTCAudioTrack * audioTrack = [_factory audioTrackWithTrackId:@"ARDAMSa0"];
    [_localStream addAudioTrack:audioTrack];
    NSArray *captureDevices = [RTCCameraVideoCapturer captureDevices];
    AVCaptureDevicePosition position =  AVCaptureDevicePositionFront;
    AVCaptureDevice * device = captureDevices[0];
    for (AVCaptureDevice *obj in captureDevices) {
        if (obj.position == position) {
            device = obj;
            break;
        }
    }
    
    //检测摄像头权限
    AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    if(authStatus == AVAuthorizationStatusRestricted || authStatus == AVAuthorizationStatusDenied)
    {
        NSLog(@"相机访问受限");
        if ([_delegate respondsToSelector:@selector(webRTCHelper:setLocalStream:userId:)])
        {
            
            [_delegate webRTCHelper:self setLocalStream:nil userId:_myId];
        }
    }
    else
    {
        if (device)
        {
            
            RTCVideoSource *videoSource = [_factory videoSource];
              [videoSource adaptOutputFormatToWidth:640 height:480 fps:20];
         _capture = [[RTCCameraVideoCapturer alloc] initWithDelegate:videoSource];
//            AVCaptureDeviceFormat * format = [[RTCCameraVideoCapturer supportedFormatsForDevice:device] lastObject];
             AVCaptureDeviceFormat* format = device.activeFormat;

            RTCVideoTrack *videoTrack = [_factory videoTrackWithSource:videoSource trackId:@"ARDAMSv0"];
            __weak RTCCameraVideoCapturer *weakCapture = _capture;
            [_localStream addVideoTrack:videoTrack];
            if ([self->_delegate respondsToSelector:@selector(webRTCHelper:capturerSession:)])
            {
                [self->_delegate webRTCHelper:self capturerSession:weakCapture.captureSession];
            }
      
              [weakCapture stopCapture];
            [weakCapture startCaptureWithDevice:device format:format fps:20 completionHandler:^(NSError * error) {
                NSLog(@"11111111");
            }];
        }
        else
        {
            NSLog(@"该设备不能打开摄像头");
            if ([_delegate respondsToSelector:@selector(webRTCHelper:setLocalStream:userId:)])
            {
                [_delegate webRTCHelper:self setLocalStream:nil userId:_myId];
            }
        }
    }
}
// 切换摄像头
-(void)switchFrontBackCamera:(BOOL)usingFrontCamera
{
    if (_localStream == nil)
        return;
    
    NSArray *captureDevices = [RTCCameraVideoCapturer captureDevices];
    AVCaptureDevicePosition position = usingFrontCamera ? AVCaptureDevicePositionFront : AVCaptureDevicePositionBack;
    AVCaptureDevice * device = nil;
    for (AVCaptureDevice *obj in captureDevices) {
        if (obj.position == position) {
            device = obj;
            break;
        }
    }
    
//    AVCaptureDeviceFormat * format = [[RTCCameraVideoCapturer supportedFormatsForDevice:device] lastObject];
     AVCaptureDeviceFormat* format = device.activeFormat;
    [_capture stopCapture];
    [_capture startCaptureWithDevice:device format:format fps:15 completionHandler:^(NSError * _Nonnull error)
     {
         
     }];
}
/**
 *  视频的相关约束
 */
- (RTCMediaConstraints *)localVideoConstraints
{
    NSDictionary *mandatory = @{kRTCMediaConstraintsMaxWidth:@640,kRTCMediaConstraintsMinWidth:@640,kRTCMediaConstraintsMaxHeight:@480,kRTCMediaConstraintsMinHeight:@480,kRTCMediaConstraintsMinFrameRate:@15};
    
    RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory optionalConstraints:@{@"DtlsSrtpKeyAgreement":@"true"}];
    return constraints;
}

/**
 * 创建offer
 */
-(void)createOffer:(RTCPeerConnection *)peerConnection{
    if (peerConnection == nil) {
        peerConnection = [self createPeerConnection:nil];
    }
    
    [peerConnection offerForConstraints:[self offerOranswerConstraint] completionHandler:^(RTCSessionDescription * _Nullable sdp, NSError * _Nullable error) {
        if (error == nil) {
//             RTCSessionDescription *newSdp = [self descriptionForDescription:sdp preferredVideoCodec:@"H264"];
            __weak RTCPeerConnection * weakPeerConnction = peerConnection;
            [peerConnection setLocalDescription:sdp completionHandler:^(NSError * _Nullable error) {
                if (error == nil) {
                    [self setSessionDescriptionWithPeerConnection:weakPeerConnction];
                }
            }];
        }
    }];

}
/**
 *  为所有连接创建offer
 */
- (void)createOffers
{
    //给每一个点对点连接,都去创建offer
    NSDictionary *dic = [_connectionDic copy];
    [dic enumerateKeysAndObjectsUsingBlock:^(NSString *key, RTCPeerConnection *obj, BOOL * _Nonnull stop) {
        [self createOffer:obj];
    }];
}

/**
 *  设置offer/answer的约束d
 */
- (RTCMediaConstraints *)offerOranswerConstraint
{
    NSMutableDictionary * dic = [@{kRTCMediaConstraintsOfferToReceiveAudio:kRTCMediaConstraintsValueTrue,kRTCMediaConstraintsOfferToReceiveVideo:kRTCMediaConstraintsValueTrue} mutableCopy];
    [dic setObject:(_usingCamera ? kRTCMediaConstraintsValueTrue : kRTCMediaConstraintsValueFalse) forKey:kRTCMediaConstraintsOfferToReceiveVideo];
    RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints:dic optionalConstraints:@{@"DtlsSrtpKeyAgreement":@"true"}];
    return constraints;
}

// Called when setting a local or remote description.
//当一个远程或者本地的SDP被设置就会调用
- (void)setSessionDescriptionWithPeerConnection:(RTCPeerConnection *)peerConnection
{
    NSLog(@"%s",__func__);
    NSString *currentId = [self getKeyFromConnectionDic:peerConnection];
   
    if (currentId == nil)
    {
        NSLog(@"找不到用户");
        return;
    }
    
    
    //判断,当前连接状态为,收到了远程点发来的offer,这个是进入房间的时候,尚且没人,来人就调到这里
    if (peerConnection.signalingState == RTCSignalingStateHaveRemoteOffer)
    {
        //创建一个answer,会把自己的SDP信息返回出去
        [peerConnection answerForConstraints:[self offerOranswerConstraint] completionHandler:^(RTCSessionDescription * _Nullable sdp, NSError * _Nullable error) {
            __weak RTCPeerConnection *obj = peerConnection;
            [peerConnection setLocalDescription:sdp completionHandler:^(NSError * _Nullable error) {
                [self setSessionDescriptionWithPeerConnection:obj];
            }];
        }];
    }
    //判断连接状态为本地发送offer
    else if (peerConnection.signalingState == RTCSignalingStateHaveLocalOffer)
    {
        if (peerConnection.localDescription.type == RTCSdpTypeAnswer)
        {
            NSDictionary *dic = @{@"event": @"answer", @"data": @{@"sdp": peerConnection.localDescription.sdp}, @"sender": _myId, @"receiver":currentId};
            NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
            NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            [_socket send:jsonString];
            
        }
        //发送者,发送自己的offer
        else if(peerConnection.localDescription.type == RTCSdpTypeOffer)
        {
            NSDictionary *dic = @{@"event": @"offer", @"data": @{@"sdp": peerConnection.localDescription.sdp}, @"sender": _myId, @"receiver":currentId};
            NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
             NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            [_socket send:jsonString];
        }
    }
    else if (peerConnection.signalingState == RTCSignalingStateStable)
    {
        if (peerConnection.localDescription.type == RTCSdpTypeAnswer)
        {
            NSDictionary *dic = @{@"event": @"answer", @"data": @{@"sdp": peerConnection.localDescription.sdp}, @"sender": _myId, @"receiver":currentId};
            NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
             NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            [_socket send:jsonString];
        }
    }
    
}


#pragma mark RTCPeerConnectionDelegate
/**获取远程视频流*/
- (void)peerConnection:( RTCPeerConnection *)peerConnection didAddStream:( RTCMediaStream *)stream {
    NSString * userId = [self getKeyFromConnectionDic:peerConnection];
    dispatch_async(dispatch_get_main_queue(), ^{
        if ([self->_delegate respondsToSelector:@selector(webRTCHelper:addRemoteStream:userId:)]) {
            [self->_delegate webRTCHelper:self addRemoteStream:stream userId:userId];
        }
    });
}

/**RTCIceConnectionState 状态变化*/
- (void)peerConnection:(nonnull RTCPeerConnection *)peerConnection didChangeIceConnectionState:(RTCIceConnectionState)newState {
    NSLog(@"%s",__func__);
    NSLog(@"newState=====%ld",(long)newState);
    NSString * connectId = [self getKeyFromConnectionDic:peerConnection];
   
    if (newState == RTCIceConnectionStateDisconnected) {
//         [self createOffer:peerConnection];
        //断开connection的连接
//        dispatch_async(dispatch_get_main_queue(), ^{
//            if ([self->_delegate respondsToSelector:@selector(webRTCHelper:closeWithUserId:)]) {
//                [self->_delegate webRTCHelper:self closeWithUserId:connectId];
//            }
//            [self closePeerConnection:connectId];
//        });
    }
}
//[candidate.sdpMid isEqualToString:@"audio"]?@"audio":@"video"
/**获取到新的candidate*/
- (void)peerConnection:(RTCPeerConnection *)peerConnection didGenerateIceCandidate:(RTCIceCandidate *)candidate{
    NSLog(@"+++%s",__func__);
    
    NSString *currentId = [self getKeyFromConnectionDic: peerConnection];
    
    NSDictionary *dic = @{
                          @"event": @"_ice_candidate",
                          @"data":@{
                                  @"candidate":@{
                                          @"sdpMid":candidate.sdpMid ,
                                          @"sdpMLineIndex":[NSNumber numberWithInteger:candidate.sdpMLineIndex],
                                          @"candidate": candidate.sdp
                                          }
                                  
                                  } ,
                          @"sender":_myId ,
                          @"receiver":currentId
                          };
     NSLog(@"%d-------%@",candidate.sdpMLineIndex,candidate.sdpMid);
    NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
      NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    [_socket send:jsonString];
}

/**删除某个视频流*/
- (void)peerConnection:(nonnull RTCPeerConnection *)peerConnection didRemoveStream:(nonnull RTCMediaStream *)stream {
    NSLog(@"%s",__func__);
}

- (void)peerConnectionShouldNegotiate:(RTCPeerConnection *)peerConnection{
    NSLog(@"%s,line = %d object = %@",__FUNCTION__,__LINE__,peerConnection);
}

- (void)peerConnection:(nonnull RTCPeerConnection *)peerConnection didRemoveIceCandidates:(nonnull NSArray *)candidates {
    NSLog(@"%s,line = %d object = %@",__FUNCTION__,__LINE__,candidates);
}

- (void)peerConnection:(RTCPeerConnection *)peerConnection didChangeSignalingState:(RTCSignalingState)stateChanged{
     NSString *currentId = [self getKeyFromConnectionDic: peerConnection];
    
    NSLog(@"stateChanged = %ld=======currentId=====%@",(long)stateChanged,currentId);
}
- (void)peerConnection:(RTCPeerConnection *)peerConnection didChangeIceGatheringState:(RTCIceGatheringState)newState{
    NSLog(@"newState = %ld",newState);
}


#pragma mark -消息相关
-(void)peerConnection:(RTCPeerConnection *)peerConnection didOpenDataChannel:(RTCDataChannel *)dataChannel{
    
}

#pragma mark -视频分辨率代理
- (void)capturer:(nonnull RTCVideoCapturer *)capturer didCaptureVideoFrame:(nonnull RTCVideoFrame *)frame {
    
}
-(void)create_factory
{
    if(!_factory)
    {
        RTCInitializeSSL();
        RTCDefaultVideoEncoderFactory *encodeFac = [[RTCDefaultVideoEncoderFactory alloc]init];
        RTCDefaultVideoDecoderFactory *decodeFac = [[RTCDefaultVideoDecoderFactory  alloc]init];
        NSArray *arrCodecs  = [encodeFac supportedCodecs];
//        RTCVideoCodecInfo *info = arrCodecs[2];
//         RTCVideoCodecInfo *info1 = arrCodecs[2];
//        [encodeFac setPreferredCodec :info];
    
        _factory = [[RTCPeerConnectionFactory alloc]initWithEncoderFactory:encodeFac decoderFactory:decodeFac];
//        _factory = [[RTCPeerConnectionFactory alloc] init];
        }
   
}
#pragma mark --open
-(void)openWith:(NSDictionary*)dic{
    //得到data
    //得到所有的连接
    NSArray *connections = dic[@"remoteIds"];
    //加到连接数组中去
    [_connectionIdArray addObjectsFromArray:connections];
    
    //拿到给自己分配的ID
    _myId = dic[@"sender"];
    
    //如果为空,则创建点对点工厂
    if (!_factory)
    {
        //设置SSL传输
        [RTCPeerConnectionFactory initialize];
        [self create_factory];
        
    }
  
    //如果本地视频流为空
    if (!_localStream)
    {
        //创建本地流
        [self createLocalStream];
    }
    //创建连接
    [self createPeerConnections];
    
    //添加
    [self addStreams];
//    [self createOffers];
    
    //获取房间内所有用户的代理回调
    dispatch_async(dispatch_get_main_queue(), ^{
      
        if ([self->_friendDelegate respondsToSelector:@selector(webRTCHelper:gotFriendList:)]) {
            [self->_friendDelegate webRTCHelper:self gotFriendList:connections];
        }
    });
}

#pragma mark --_ice_candidate
-(void)_ice_candidateWith:(NSDictionary *)dic{
    NSDictionary *dataDic = dic[@"data"];
    NSString *socketId = dic[@"sender"];
    NSDictionary *candidateDic =dataDic[@"candidate"];
    NSString *sdpMid = candidateDic[@"sdpMid"];
    
    int sdpMLineIndex = [candidateDic[@"sdpMLineIndex"] intValue];
    NSString *sdp = candidateDic[@"candidate"];
    //生成远端网络地址对象
    RTCIceCandidate *candidate = [[RTCIceCandidate alloc] initWithSdp:sdp sdpMLineIndex:sdpMLineIndex sdpMid:sdpMid];
    //拿到当前对应的点对点连接
    RTCPeerConnection *peerConnection = [_connectionDic objectForKey:socketId];
    //添加到点对点连接中
    [peerConnection addIceCandidate:candidate];
}
#pragma mark ------join
-(void)joinWith:(NSDictionary *)dic{
    //拿到新人的ID
    NSString *socketId = dic[@"sender"];
    
    //再去创建一个连接
    RTCPeerConnection *peerConnection = [self createPeerConnection:socketId];
    if (!_localStream)
    {
        [self createLocalStream];
    }
    //把本地流加到连接中去
    [peerConnection addStream:_localStream];
    //连接ID新加一个
    [_connectionIdArray addObject:socketId];
    //并且设置到Dic中去
    [_connectionDic setObject:peerConnection forKey:socketId];

   [self createOffer:peerConnection];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        //设置新加入用户代理
        if ([self->_friendDelegate respondsToSelector:@selector(webRTCHelper:gotNewFriend:)]) {
            [self->_friendDelegate webRTCHelper:self gotNewFriend:socketId];
        }
    });
}

#pragma mark --remove
-(void)removeWith:(NSDictionary *)dic{
    //得到socketId,关闭这个peerConnection
    NSString *socketId = dic[@"sender"];
    [self closePeerConnection:socketId];
    
    //设置关闭某个用户聊天代理回调
    dispatch_async(dispatch_get_main_queue(), ^{
//        if ([self->_delegate respondsToSelector:@selector(webRTCHelper:closeWithUserId:)])
//        {
//            [self->_delegate webRTCHelper:self closeWithUserId:socketId];
//        }
        //设置退出房间用户代理回调
        if ([self->_friendDelegate respondsToSelector:@selector(webRTCHelper:removeFriend:)]) {
            [self->_friendDelegate webRTCHelper:self removeFriend:socketId];
        }
    });
}

#pragma mark --offer
-(void)offerWith:(NSDictionary *)dic{
    NSDictionary *dataDic = dic[@"data"];
    //拿到SDP
    NSString *sdp = dataDic[@"sdp"];
    NSString *socketId =  dic[@"sender"];
    
    //拿到这个点对点的连接
    RTCPeerConnection *peerConnection = [_connectionDic objectForKey:socketId];
    //根据类型和SDP 生成SDP描述对象
    RTCSessionDescription *remoteSdp = [[RTCSessionDescription alloc] initWithType:RTCSdpTypeOffer sdp:sdp];
//    RTCSessionDescription *newSdp = [self descriptionForDescription:remoteSdp preferredVideoCodec:@"H264"];
    //设置给这个点对点连接
    __weak RTCPeerConnection *weakPeerConnection = peerConnection;
    [peerConnection setRemoteDescription:remoteSdp completionHandler:^(NSError * _Nullable error) {
        [self setSessionDescriptionWithPeerConnection:weakPeerConnection];
    }];
    
    //设置当前角色状态为被呼叫,(被发offer)
    //        _role = RoleCallee;
}
#pragma mark --answer
-(void)answerWith:(NSDictionary *)dic{
    NSDictionary *dataDic = dic[@"data"];
    if([dataDic count]==0){
        return;
    }
    NSString *sdp = dataDic[@"sdp"];
    
    NSString *socketId = dic[@"sender"];
    RTCPeerConnection *peerConnection = [_connectionDic objectForKey:socketId];
    RTCSessionDescription *remoteSdp = [[RTCSessionDescription alloc] initWithType:RTCSdpTypeAnswer sdp:sdp];
//    RTCSessionDescription *newSdp = [self descriptionForDescription:remoteSdp preferredVideoCodec:@"H264"];
    __weak RTCPeerConnection * weakPeerConnection = peerConnection;
    [peerConnection setRemoteDescription:remoteSdp completionHandler:^(NSError * _Nullable error) {
//                    [self setSessionDescriptionWithPeerConnection:weakPeerConnection];
    }];
}
#pragma mark WebSocketDelegate
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message{
    NSLog(@"收到服务器消息:%@",message);
    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil];
    NSString *eventName = dic[@"event"];
    NSLog(@"====================eventName===============%@",eventName);
    
    //1.发送加入房间后的反馈
    if ([eventName isEqualToString:@"open"])
    {
        [self openWith:dic];
        
    }
    //4.接收到新加入的人发了ICE候选,(即经过ICEServer而获取到的地址)
    else if ([eventName isEqualToString:@"_ice_candidate"])
    {
        [self _ice_candidateWith:dic];
    }
    //2.其他新人加入房间的信息
    else if ([eventName isEqualToString:@"join"])
    {
        [self joinWith:dic];
        
    }
    //有人离开房间的事件
    else if ([eventName isEqualToString:@"remove"])
    {
        [self removeWith:dic];
        
    }
    //这个新加入的人发了个offer
    else if ([eventName isEqualToString:@"offer"])
    {
        [self offerWith:dic];
    }
    //回应offer
    else if ([eventName isEqualToString:@"answer"])
    {
        [self answerWith:dic];
    }
}

- (void)webSocketDidOpen:(SRWebSocket *)webSocket{
    NSLog(@"socket连接成功");
 
    dispatch_async(dispatch_get_main_queue(), ^{
        if ([self->_delegate respondsToSelector:@selector(webRTCHelper:socketConnectState:)]) {
            [self->_delegate webRTCHelper:self socketConnectState:WebSocketConnectSuccess];
        }
    });
}

- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error{
    NSLog(@"socket连接失败");
    dispatch_async(dispatch_get_main_queue(), ^{
        if ([self->_delegate respondsToSelector:@selector(webRTCHelper:socketConnectState:)]) {
            [self->_delegate webRTCHelper:self socketConnectState:WebSocketConnectSuccess];
        }
    });
}

- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean{
    NSLog(@"socket关闭。code = %ld,reason = %@",code,reason);
    
}

- (NSString *)getKeyFromConnectionDic:(RTCPeerConnection *)peerConnection
{
    //find socketid by pc
    static NSString *socketId;
    [_connectionDic enumerateKeysAndObjectsUsingBlock:^(NSString *key, RTCPeerConnection *obj, BOOL * _Nonnull stop) {
        if ([obj isEqual:peerConnection])
        {
            NSLog(@"%@",key);
            socketId = key;
        }
    }];
    return socketId;
}

- (RTCSessionDescription *)descriptionForDescription:(RTCSessionDescription *)description preferredVideoCodec:(NSString *)codec
{
    NSString *sdpString = description.sdp;
    NSString *lineSeparator = @"\n";
    NSString *mLineSeparator = @" ";
    // Copied from PeerConnectionClient.java.
    // TODO(tkchin): Move this to a shared C++ file.
    NSMutableArray *lines =
    [NSMutableArray arrayWithArray:
     [sdpString componentsSeparatedByString:lineSeparator]];
    NSInteger mLineIndex = -1;
    NSString *codecRtpMap = nil;
    // a=rtpmap: /
    // [/]
    NSString *pattern =
    [NSString stringWithFormat:@"^a=rtpmap:(\\d+) %@(/\\d+)+[\r]?$", codec];
    NSRegularExpression *regex =
    [NSRegularExpression regularExpressionWithPattern:pattern
                                              options:0
                                                error:nil];
    for (NSInteger i = 0; (i < lines.count) && (mLineIndex == -1 || !codecRtpMap);
         ++i) {
        NSString *line = lines[i];
        if ([line hasPrefix:@"m=video"]) {
            mLineIndex = i;
            continue;
        }
        NSTextCheckingResult *codecMatches =
        [regex firstMatchInString:line
                          options:0
                            range:NSMakeRange(0, line.length)];
        if (codecMatches) {
            codecRtpMap =
            [line substringWithRange:[codecMatches rangeAtIndex:1]];
            continue;
        }
    }
    if (mLineIndex == -1) {
        RTCLog(@"No m=video line, so can't prefer %@", codec);
        return description;
    }
    if (!codecRtpMap) {
        RTCLog(@"No rtpmap for %@", codec);
        return description;
    }
    NSArray *origMLineParts =
    [lines[mLineIndex] componentsSeparatedByString:mLineSeparator];
    if (origMLineParts.count > 3) {
        NSMutableArray *newMLineParts =
        [NSMutableArray arrayWithCapacity:origMLineParts.count];
        NSInteger origPartIndex = 0;
        // Format is: m=    ...
        [newMLineParts addObject:origMLineParts[origPartIndex++]];
        [newMLineParts addObject:origMLineParts[origPartIndex++]];
        [newMLineParts addObject:origMLineParts[origPartIndex++]];
        [newMLineParts addObject:codecRtpMap];
        for (; origPartIndex < origMLineParts.count; ++origPartIndex) {
            if (![codecRtpMap isEqualToString:origMLineParts[origPartIndex]]) {
                [newMLineParts addObject:origMLineParts[origPartIndex]];
            }
        }
        NSString *newMLine =
        [newMLineParts componentsJoinedByString:mLineSeparator];
        [lines replaceObjectAtIndex:mLineIndex
                         withObject:newMLine];
    } else {
        RTCLogWarning(@"Wrong SDP media description format: %@", lines[mLineIndex]);
    }
    NSString *mangledSdpString = [lines componentsJoinedByString:lineSeparator];
    return [[RTCSessionDescription alloc] initWithType:description.type
                                                   sdp:mangledSdpString];
}

@end
  1. ChatViewController.h
#import 

@interface ChatViewController : UIViewController

@end

  1. ChatViewController.m
//
//  ChatViewController.m
//  WebRTC_new
//
//  Created by
//  Copyright © 2020年
//

#import "ChatViewController.h"
#import 
#import "WebRTCHelper.h"
#import 

#define kWidth [UIScreen mainScreen].bounds.size.width
#define kHeight [UIScreen mainScreen].bounds.size.height

@interface ChatCell:UICollectionViewCell
/*注释*/
@property (nonatomic,strong)  RTCEAGLVideoView *videoView;
/*注释*/
@property (nonatomic,strong) RTCVideoTrack *track;
/*注释*/
@property (nonatomic,strong) CALayer *baseLayer;
@end

@implementation ChatCell


- (instancetype)init
{
    self = [super init];
    if (self) {
        self.videoView = [[RTCEAGLVideoView alloc] initWithFrame:CGRectMake(0, 0, (kWidth-40)/3, (kWidth-40)/3 + 50)];
        [self.contentView addSubview:self.videoView];
    }
    return self;
}
- (instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        self.contentView.layer.borderColor = [UIColor whiteColor].CGColor;
        self.contentView.layer.borderWidth = 1;
        self.videoView = [[RTCEAGLVideoView alloc] initWithFrame:CGRectMake(0, 0, (kWidth-40)/3, (kWidth-40)/3 + 50)];
    }
    return self;
}

- (void)setTrack:(RTCVideoTrack *)track{
    if (track != nil) {
        self.contentView.layer.mask = nil;
        [self.contentView addSubview:self.videoView];
        [track addRenderer:self.videoView];
    }else{
        self.contentView.layer.mask = nil;
        for (UIView * view in self.contentView.subviews) {
            [view removeFromSuperview];
        }
        [self setShaperLayer];
    }
}


-(void)setShaperLayer{
    //高亮状态下的imageView
    UIImageView * highlightImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
    highlightImageView.center = self.contentView.center;
    highlightImageView.image = [UIImage imageNamed:@"voice_ highlight"];
    //默认状态下的imageView
    UIImageView * defaultImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
    defaultImageView.image = [UIImage imageNamed:@"voice_default"];
    //先添加highlightImageView,在添加defaultImageview
    defaultImageView.center = self.contentView.center;
    [self.contentView addSubview:defaultImageView];
    [self.contentView addSubview:highlightImageView];
    self.baseLayer = nil;
    if (self.baseLayer == nil) {
        self.baseLayer = [CALayer layer];
        self.baseLayer.frame = highlightImageView.bounds;
    }
    
    //创建左边layer
    CAShapeLayer * leftLayer = [CAShapeLayer layer];
    leftLayer.fillColor = [UIColor greenColor].CGColor;
    leftLayer.position = CGPointMake(-25, 25);
    leftLayer.bounds = highlightImageView.bounds;
    leftLayer.path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 50,50)].CGPath;
    [self.baseLayer addSublayer:leftLayer];
    
    //左边动画
    CABasicAnimation * leftAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    leftAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(-25, 25)];
    leftAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(5, 25)];
    leftAnimation.duration = 1.0;
    leftAnimation.repeatCount = MAXFLOAT;
    [leftLayer addAnimation:leftAnimation forKey:@"noVoiceLeftAnimation"];
    

    //创建右边layer
    CAShapeLayer * rightLayer = [CAShapeLayer layer];
//    rightLayer.strokeColor = [UIColor greenColor].CGColor;
    rightLayer.bounds = highlightImageView.bounds;
    rightLayer.position = CGPointMake(75, 25);
    rightLayer.fillColor = [UIColor greenColor].CGColor;
    rightLayer.path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 50, 50)].CGPath;
    [self.baseLayer addSublayer:rightLayer];
    //动画
    CABasicAnimation * rightAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    rightAnimation.duration = 1.0;
    rightAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(75, 25)];
    rightAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(45, 25)];
    rightAnimation.repeatCount = MAXFLOAT;
    [rightLayer addAnimation:rightAnimation forKey:@"noVoiceRightAnimation"];
    
    
    highlightImageView.layer.mask = self.baseLayer;
    
    
}

@end


@interface ChatViewController ()
{
    RTCMediaStream * _localSteam;
    //判断是显示前摄像头还是显示后摄像头(yes为前摄像头。false为后摄像头)
    BOOL _usingFrontCamera;
    //是否显示我的视频流(默认为yes,显示;no为不显示)
    BOOL _usingCamera;
}
@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
/*保存远端视频流*/
@property (nonatomic,strong) NSMutableDictionary *videoTracks;
/*房间内其他用户*/
@property (nonatomic,strong) NSMutableArray *members;
//显示本地视频的view
@property (weak, nonatomic) IBOutlet RTCCameraPreviewView *localVideoView;

@end

@implementation ChatViewController

/*注释*/
- (NSMutableArray *)members
{
    if(!_members){
        _members = [NSMutableArray array];
    }
    return _members;
}

/*注释*/
- (NSMutableDictionary *)videoTracks
{
    if(!_videoTracks){
        _videoTracks = [NSMutableDictionary dictionary];
    }
    return _videoTracks;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    _usingFrontCamera = YES;
    _usingCamera = YES;
    NSLog(@"uid = %@",[[[UIDevice currentDevice] identifierForVendor] UUIDString]);
    [WebRTCHelper shareInstance].delegate = self;
    [WebRTCHelper shareInstance].friendDelegate = self;
    
    UICollectionViewFlowLayout * layout = [[UICollectionViewFlowLayout alloc] init];
    layout.minimumLineSpacing = 10;
    layout.minimumInteritemSpacing = 10;
    layout.itemSize = CGSizeMake((kWidth-40)/3, (kWidth-40)/3+50);
    self.collectionView.collectionViewLayout = layout;
    self.collectionView.delegate = self;
    self.collectionView.dataSource = self;
    [self.collectionView registerClass:[ChatCell class] forCellWithReuseIdentifier:@"chatCell"];
    
    [self connect];
    //设置屏幕常亮
    [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
    self.navigationItem.hidesBackButton = YES;
}
-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.enabled = YES;
    }
}
#pragma mark -按钮点击操作
/**
 * 关闭按钮
 */
- (IBAction)closeChatBtnClick:(UIButton *)sender {
    [[WebRTCHelper shareInstance] exitRoom];
    [self.navigationController popViewControllerAnimated:YES];
}

/**
 * 摄像头转换(前后摄像头)
 */
- (IBAction)swithVideoBtnClick:(UIButton *)sender {
    _usingFrontCamera = !_usingFrontCamera;
    [[WebRTCHelper shareInstance] swichCamera:_usingFrontCamera];

}

/**
 * 语音是否开启
 */
- (IBAction)swichAudioBtnClick:(UIButton *)sender {
    [sender setImage:[UIImage imageNamed:@"audioOn"] forState:UIControlStateNormal];
}
/**
 是否显示本地摄像头
 */
- (IBAction)swichLocaolCameraBtnClick:(UIButton *)sender {
    _usingCamera = !_usingCamera;
//    [sender setImage:[UIImage imageNamed:_usingCamera?@"videoOn":@"videoOff"] forState:UIControlStateNormal];
//    [[WebRTCHelper shareInstance] showLocaolCamera:_usingCamera];
}

/**
 * 连接服务器
 */
-(void)connect{
    [[WebRTCHelper shareInstance] connectServer:@"172.27.104.54" port:@"9090/SocketServer"];
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    NSLog(@"self.members=====%@", self.members);
    return self.members.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    ChatCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"chatCell" forIndexPath:indexPath];
    NSString * userId = [self.members objectAtIndex:indexPath.item];
    RTCVideoTrack * track = [self.videoTracks objectForKey:userId];
    NSLog(@"%@==6666==%@",track,userId);
    cell.track = track;
    return cell;
}

#pragma mark -WebRTCHelperFrindDelegate
- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper gotFriendList:(NSArray *)friendList{
    [self.members removeAllObjects];
    [self.members addObjectsFromArray:friendList];
    [_collectionView reloadData];
}

- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper gotNewFriend:(NSString *)friendId{
    [self.members addObject:friendId];
    [_collectionView reloadData];
}

- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper removeFriend:(NSString *)friendId{
    [self.members removeObject:friendId];
    [_collectionView reloadData];
    if (self.members.count == 0) {
//        [[WebRTCHelper shareInstance] exitRoom];
//        [self.navigationController popViewControllerAnimated:YES];
    }
}

#pragma mark -WebRTCHelperDelegate
- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper receiveMessage:(NSString *)message{
    NSLog(@"messaga = %@",message);
    
}

/**
 * 旧版本获取本地视频流的代理,在这个代理里面会获取到RTCVideoTrack类,然后添加到RTCEAGLVideoView类型的localVideoView上面
 */
- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper setLocalStream:(RTCMediaStream *)steam userId:(NSString *)userId{
    if (steam) {
        _localSteam = steam;
        RTCVideoTrack * track = [_localSteam.videoTracks lastObject];
        [track addRenderer:self.localVideoView];
    }
    
}
/**
 * 新版获取本地视频流的方法
 * @param captureSession RTCCameraPreviewView类的参数,通过设置这个,就可以达到显示本地视频的功能
 */
- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper capturerSession:(AVCaptureSession *)captureSession{
    self.localVideoView.captureSession = captureSession;
    
}

/**
 * 获取远端视频流的方法,主要是获取到RTCVideoTrack类型的数据,然后保存起来,在刷新列表的时候,添加到对应item里面的RTCEAGLVideoView类型的view上面
 */
- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper addRemoteStream:(RTCMediaStream *)stream userId:(NSString *)userId{
    RTCVideoTrack * track = [stream.videoTracks lastObject];
    NSLog(@"track====%@",track);
    if (track != nil) {
        [self.videoTracks setObject:track forKey:userId];
    }
        [self.collectionView reloadData];
}
- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper closeWithUserId:(NSString *)userId{
    [self.videoTracks removeObjectForKey:userId];
    if (self.videoTracks.count >= self.members.count) {
        [self.collectionView reloadData];
    }
}

- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper socketConnectState:(WebSocketConnectState)connectState{
    if (connectState == WebSocketConnectField) {
        HGAlertViewController * alert = [HGAlertViewController alertControllerWithTitle:@"提示" message:@"连接socket失败" preferredStyle:(UIAlertControllerStyleAlert)];
        alert.addAction(@"取消",^(UIAlertAction *alertAction){
            [[WebRTCHelper shareInstance] exitRoom];
            [self.navigationController popViewControllerAnimated:YES];
        }).addAction(@"确定",^(UIAlertAction *alertAction){
            [[WebRTCHelper shareInstance] exitRoom];
            [self.navigationController popViewControllerAnimated:YES];
        });
        [self presentViewController:alert animated:YES completion:nil];
    }
}
-(void)dealloc{
    NSLog(@"移除了");
    //取消设置屏幕常亮
    [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
}

@end

你可能感兴趣的:(WebRTC -ios)