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 <Foundation/Foundation.h>
#import <WebRTC/WebRTC.h>
#import <SocketRocket/SocketRocket.h>
#import <WebRTC/RTCMacros.h>
#import <WebRTC/RTCVideoCodec.h>

//@import SocketIO;

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

@protocol WebRTCHelperDelegate;
@protocol WebRTCHelperFrindDelegate;

@interface WebRTCHelper : NSObject

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

/*注釋*/
@property (nonatomic,weak) id<WebRTCHelperDelegate> delegate;
/*注釋*/
@property (nonatomic,weak) id<WebRTCHelperFrindDelegate> friendDelegate;

/**
 * 與服務(wù)器建立連接
 * @param server 服務(wù)器地址
 * @param port 端口號(hào)
 */
-(void)connectServer:(NSString *)server port:(NSString *)port;
/**
 * 切換攝像頭
 */
- (void)swichCamera:(BOOL)_usingFrontCamera;
/**
 * 是否顯示本地視頻
 */
- (void)showLocaolCamera:(BOOL)_usingCamera;
/**
 * 退出房間
 */
-(void)exitRoom;

@end

@protocol WebRTCHelperDelegate <NSObject>
@optional
/**
 * 獲取到發(fā)送信令消息
 * @param webRTCHelper 本類(lèi)
 * @param message 消息內(nèi)容
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper receiveMessage:(NSString *)message;
/**
 * 獲取本地的localVideoStream數(shù)據(jù)
 * @param webRTCHelper 本類(lèi)
 * @param steam 視頻流
 * @param userId 用戶(hù)標(biāo)識(shí)
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper setLocalStream:(RTCMediaStream *)steam userId:(NSString *)userId;
/**
 * 獲取遠(yuǎn)程的remoteVideoStream數(shù)據(jù)
 * @param webRTCHelper 本類(lèi)
 * @param stream 視頻流
 * @param userId 用戶(hù)標(biāo)識(shí)
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper addRemoteStream:(RTCMediaStream *)stream userId:(NSString *)userId;
/**
 * 某個(gè)用戶(hù)退出后苫拍,關(guān)閉用戶(hù)的連接
 * @param webRTCHelper 本類(lèi)
 * @param userId 用戶(hù)標(biāo)識(shí)
 */
- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper closeWithUserId:(NSString *)userId;

/**
 * 獲取socket連接狀態(tài)
 * @param webRTCHelper 本類(lèi)
 * @param connectState 連接狀態(tài),分為
 WebSocketConnectSuccess 成功,
 WebSocketConnectField, 失敗
 WebSocketConnectClosed 關(guān)閉
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper socketConnectState:(WebSocketConnectState)connectState;
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper capturerSession:(AVCaptureSession *)captureSession;
@end

@protocol WebRTCHelperFrindDelegate <NSObject>
@optional
/**
 * 獲取房間內(nèi)所有的用戶(hù)(除了自己)
 * @param friendList 用戶(hù)列表
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper gotFriendList:(NSArray *)friendList;
/**
 * 獲取新加入的用戶(hù)信息
 * @param friendId 新用戶(hù)的id
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper gotNewFriend:(NSString *)friendId;
/**
 * 獲取離開(kāi)房間用戶(hù)的信息
 * @param friendId 離開(kāi)用戶(hù)的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()<RTCPeerConnectionDelegate,RTCVideoCapturerDelegate,SRWebSocketDelegate>
{
    SRWebSocket *_socket;
    NSString *_server;
    
    RTCPeerConnectionFactory *_factory;
    RTCMediaStream *_localStream;
    
    NSString *_myId;
    NSMutableDictionary *_connectionDic; // 存放 所有用戶(hù) 對(duì)應(yīng)的鏈接集合
    NSMutableArray *_connectionIdArray; // 存放 房間人數(shù)的 id
    
    NSMutableArray *ICEServers;
  
    //是否顯示我的視頻流(默認(rèn)為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 -提供給外部的方法

/**
 * 與服務(wù)器進(jìn)行連接
 */
- (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];
}

/**
 * 是否顯示本地?cái)z像頭
 */
- (void)showLocaolCamera:(BOOL)_usingCamera{
    _usingCamera = _usingCamera;
    //如果為空班眯,則創(chuàng)建點(diǎn)對(duì)點(diǎn)工廠
    if (!_factory)
    {
        //設(shè)置SSL傳輸
        [RTCPeerConnectionFactory initialize];
        [self create_factory];
    }
    //如果本地視頻流為空
    if (!_localStream)
    {
        //創(chuàng)建本地流
        [self createLocalStream];
    }
//    [_capture stopCapture];
    //創(chuàng)建連接
    [self createPeerConnections];
    
    //添加
    [self addStreams];
    [self createOffers];
}

#pragma mark -內(nèi)部方法
/**
 *  關(guān)閉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];
        }
    });
}


/**
 *  創(chuàng)建點(diǎn)對(duì)點(diǎn)連接
 *
 *  @param connectionId connectionId description
 *
 *  @return <#return value description#>
 */
- (RTCPeerConnection *)createPeerConnection:(NSString *)connectionId
{
    //如果點(diǎn)對(duì)點(diǎn)工廠為空
    if (!_factory)
    {
        [self create_factory];
    }
    
    //得到ICEServer
    if (!ICEServers) {
        ICEServers = [NSMutableArray array];
        [ICEServers addObject:[self defaultSTUNServer]];
    }
    
    //用工廠來(lái)創(chuàng)建連接
    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
{
    //給每一個(gè)點(diǎn)對(duì)點(diǎn)連接,都加上本地流
    NSDictionary *dic = [_connectionDic copy];
    [dic enumerateKeysAndObjectsUsingBlock:^(NSString *key, RTCPeerConnection *obj, BOOL * _Nonnull stop) {
        if (!self->_localStream)
        {
            [self createLocalStream];
        }
        [obj addStream:self->_localStream];
    }];
}
/**
 *  創(chuàng)建所有連接
 */
- (void)createPeerConnections
{
    //從我們的連接數(shù)組里快速遍歷
    NSArray *arr = _connectionIdArray.copy;
    [arr enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
        //根據(jù)連接ID去初始化 RTCPeerConnection 連接對(duì)象
        RTCPeerConnection *connection = [self createPeerConnection:obj];
        
        //設(shè)置這個(gè)ID對(duì)應(yīng)的 RTCPeerConnection對(duì)象
        [self->_connectionDic setObject:connection forKey:obj];
       
    }];
}


/**
 * 創(chuàng)建本地視頻流
 */
-(void)createLocalStream{
    _localStream = [_factory mediaStreamWithStreamId:@"ARDAMS"];
    //音頻
    RTCAudioTrack * audioTrack = [_factory audioTrackWithTrackId:@"ARDAMSa0"];
    [_localStream addAudioTrack:audioTrack];
    NSArray<AVCaptureDevice *> *captureDevices = [RTCCameraVideoCapturer captureDevices];
    AVCaptureDevicePosition position =  AVCaptureDevicePositionFront;
    AVCaptureDevice * device = captureDevices[0];
    for (AVCaptureDevice *obj in captureDevices) {
        if (obj.position == position) {
            device = obj;
            break;
        }
    }
    
    //檢測(cè)攝像頭權(quán)限
    AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    if(authStatus == AVAuthorizationStatusRestricted || authStatus == AVAuthorizationStatusDenied)
    {
        NSLog(@"相機(jī)訪問(wèn)受限");
        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(@"該設(shè)備不能打開(kāi)攝像頭");
            if ([_delegate respondsToSelector:@selector(webRTCHelper:setLocalStream:userId:)])
            {
                [_delegate webRTCHelper:self setLocalStream:nil userId:_myId];
            }
        }
    }
}
// 切換攝像頭
-(void)switchFrontBackCamera:(BOOL)usingFrontCamera
{
    if (_localStream == nil)
        return;
    
    NSArray<AVCaptureDevice *> *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)
     {
         
     }];
}
/**
 *  視頻的相關(guān)約束
 */
- (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;
}

/**
 * 創(chuàng)建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];
                }
            }];
        }
    }];

}
/**
 *  為所有連接創(chuàng)建offer
 */
- (void)createOffers
{
    //給每一個(gè)點(diǎn)對(duì)點(diǎn)連接,都去創(chuàng)建offer
    NSDictionary *dic = [_connectionDic copy];
    [dic enumerateKeysAndObjectsUsingBlock:^(NSString *key, RTCPeerConnection *obj, BOOL * _Nonnull stop) {
        [self createOffer:obj];
    }];
}

/**
 *  設(shè)置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.
//當(dāng)一個(gè)遠(yuǎn)程或者本地的SDP被設(shè)置就會(huì)調(diào)用
- (void)setSessionDescriptionWithPeerConnection:(RTCPeerConnection *)peerConnection
{
    NSLog(@"%s",__func__);
    NSString *currentId = [self getKeyFromConnectionDic:peerConnection];
   
    if (currentId == nil)
    {
        NSLog(@"找不到用戶(hù)");
        return;
    }
    
    
    //判斷蓖柔,當(dāng)前連接狀態(tài)為,收到了遠(yuǎn)程點(diǎn)發(fā)來(lái)的offer风纠,這個(gè)是進(jìn)入房間的時(shí)候况鸣,尚且沒(méi)人,來(lái)人就調(diào)到這里
    if (peerConnection.signalingState == RTCSignalingStateHaveRemoteOffer)
    {
        //創(chuàng)建一個(gè)answer,會(huì)把自己的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];
            }];
        }];
    }
    //判斷連接狀態(tài)為本地發(fā)送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];
            
        }
        //發(fā)送者,發(fā)送自己的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
/**獲取遠(yuǎn)程視頻流*/
- (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 狀態(tài)變化*/
- (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];
        //斷開(kāi)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];
}

/**刪除某個(gè)視頻流*/
- (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<RTCIceCandidate *> *)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 -消息相關(guān)
-(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"];
    //加到連接數(shù)組中去
    [_connectionIdArray addObjectsFromArray:connections];
    
    //拿到給自己分配的ID
    _myId = dic[@"sender"];
    
    //如果為空议忽,則創(chuàng)建點(diǎn)對(duì)點(diǎn)工廠
    if (!_factory)
    {
        //設(shè)置SSL傳輸
        [RTCPeerConnectionFactory initialize];
        [self create_factory];
        
    }
  
    //如果本地視頻流為空
    if (!_localStream)
    {
        //創(chuàng)建本地流
        [self createLocalStream];
    }
    //創(chuàng)建連接
    [self createPeerConnections];
    
    //添加
    [self addStreams];
//    [self createOffers];
    
    //獲取房間內(nèi)所有用戶(hù)的代理回調(diào)
    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"];
    //生成遠(yuǎn)端網(wǎng)絡(luò)地址對(duì)象
    RTCIceCandidate *candidate = [[RTCIceCandidate alloc] initWithSdp:sdp sdpMLineIndex:sdpMLineIndex sdpMid:sdpMid];
    //拿到當(dāng)前對(duì)應(yīng)的點(diǎn)對(duì)點(diǎn)連接
    RTCPeerConnection *peerConnection = [_connectionDic objectForKey:socketId];
    //添加到點(diǎn)對(duì)點(diǎn)連接中
    [peerConnection addIceCandidate:candidate];
}
#pragma mark ------join
-(void)joinWith:(NSDictionary *)dic{
    //拿到新人的ID
    NSString *socketId = dic[@"sender"];
    
    //再去創(chuàng)建一個(gè)連接
    RTCPeerConnection *peerConnection = [self createPeerConnection:socketId];
    if (!_localStream)
    {
        [self createLocalStream];
    }
    //把本地流加到連接中去
    [peerConnection addStream:_localStream];
    //連接ID新加一個(gè)
    [_connectionIdArray addObject:socketId];
    //并且設(shè)置到Dic中去
    [_connectionDic setObject:peerConnection forKey:socketId];

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

#pragma mark --remove
-(void)removeWith:(NSDictionary *)dic{
    //得到socketId懒闷,關(guān)閉這個(gè)peerConnection
    NSString *socketId = dic[@"sender"];
    [self closePeerConnection:socketId];
    
    //設(shè)置關(guān)閉某個(gè)用戶(hù)聊天代理回調(diào)
    dispatch_async(dispatch_get_main_queue(), ^{
//        if ([self->_delegate respondsToSelector:@selector(webRTCHelper:closeWithUserId:)])
//        {
//            [self->_delegate webRTCHelper:self closeWithUserId:socketId];
//        }
        //設(shè)置退出房間用戶(hù)代理回調(diào)
        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"];
    
    //拿到這個(gè)點(diǎn)對(duì)點(diǎn)的連接
    RTCPeerConnection *peerConnection = [_connectionDic objectForKey:socketId];
    //根據(jù)類(lèi)型和SDP 生成SDP描述對(duì)象
    RTCSessionDescription *remoteSdp = [[RTCSessionDescription alloc] initWithType:RTCSdpTypeOffer sdp:sdp];
//    RTCSessionDescription *newSdp = [self descriptionForDescription:remoteSdp preferredVideoCodec:@"H264"];
    //設(shè)置給這個(gè)點(diǎn)對(duì)點(diǎn)連接
    __weak RTCPeerConnection *weakPeerConnection = peerConnection;
    [peerConnection setRemoteDescription:remoteSdp completionHandler:^(NSError * _Nullable error) {
        [self setSessionDescriptionWithPeerConnection:weakPeerConnection];
    }];
    
    //設(shè)置當(dāng)前角色狀態(tài)為被呼叫,(被發(fā)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(@"收到服務(wù)器消息:%@",message);
    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil];
    NSString *eventName = dic[@"event"];
    NSLog(@"====================eventName===============%@",eventName);
    
    //1.發(fā)送加入房間后的反饋
    if ([eventName isEqualToString:@"open"])
    {
        [self openWith:dic];
        
    }
    //4.接收到新加入的人發(fā)了ICE候選栈幸,(即經(jīng)過(guò)ICEServer而獲取到的地址)
    else if ([eventName isEqualToString:@"_ice_candidate"])
    {
        [self _ice_candidateWith:dic];
    }
    //2.其他新人加入房間的信息
    else if ([eventName isEqualToString:@"join"])
    {
        [self joinWith:dic];
        
    }
    //有人離開(kāi)房間的事件
    else if ([eventName isEqualToString:@"remove"])
    {
        [self removeWith:dic];
        
    }
    //這個(gè)新加入的人發(fā)了個(gè)offer
    else if ([eventName isEqualToString:@"offer"])
    {
        [self offerWith:dic];
    }
    //回應(yīng)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關(guān)閉愤估。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:<payload type> <encoding name>/<clock rate>
    // [/<encoding parameters>]
    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=<media> <port> <proto> <fmt> ...
        [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 <UIKit/UIKit.h>

@interface ChatViewController : UIViewController

@end

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

#import "ChatViewController.h"
#import <WebRTC/WebRTC.h>
#import "WebRTCHelper.h"
#import <HGAlertViewController/HGAlertViewController.h>

#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{
    //高亮狀態(tài)下的imageView
    UIImageView * highlightImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
    highlightImageView.center = self.contentView.center;
    highlightImageView.image = [UIImage imageNamed:@"voice_ highlight"];
    //默認(rèn)狀態(tài)下的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;
    }
    
    //創(chuàng)建左邊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];
    
    //左邊動(dòng)畫(huà)
    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"];
    

    //創(chuàng)建右邊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];
    //動(dòng)畫(huà)
    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 ()<UICollectionViewDelegate,UICollectionViewDataSource,WebRTCHelperDelegate,WebRTCHelperFrindDelegate,UIGestureRecognizerDelegate>
{
    RTCMediaStream * _localSteam;
    //判斷是顯示前攝像頭還是顯示后攝像頭(yes為前攝像頭。false為后攝像頭)
    BOOL _usingFrontCamera;
    //是否顯示我的視頻流(默認(rèn)為yes速址,顯示玩焰;no為不顯示)
    BOOL _usingCamera;
}
@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
/*保存遠(yuǎn)端視頻流*/
@property (nonatomic,strong) NSMutableDictionary *videoTracks;
/*房間內(nèi)其他用戶(hù)*/
@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];
    //設(shè)置屏幕常亮
    [[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 -按鈕點(diǎn)擊操作
/**
 * 關(guān)閉按鈕
 */
- (IBAction)closeChatBtnClick:(UIButton *)sender {
    [[WebRTCHelper shareInstance] exitRoom];
    [self.navigationController popViewControllerAnimated:YES];
}

/**
 * 攝像頭轉(zhuǎn)換(前后攝像頭)
 */
- (IBAction)swithVideoBtnClick:(UIButton *)sender {
    _usingFrontCamera = !_usingFrontCamera;
    [[WebRTCHelper shareInstance] swichCamera:_usingFrontCamera];

}

/**
 * 語(yǔ)音是否開(kāi)啟
 */
- (IBAction)swichAudioBtnClick:(UIButton *)sender {
    [sender setImage:[UIImage imageNamed:@"audioOn"] forState:UIControlStateNormal];
}
/**
 是否顯示本地?cái)z像頭
 */
- (IBAction)swichLocaolCameraBtnClick:(UIButton *)sender {
    _usingCamera = !_usingCamera;
//    [sender setImage:[UIImage imageNamed:_usingCamera?@"videoOn":@"videoOff"] forState:UIControlStateNormal];
//    [[WebRTCHelper shareInstance] showLocaolCamera:_usingCamera];
}

/**
 * 連接服務(wù)器
 */
-(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);
    
}

/**
 * 舊版本獲取本地視頻流的代理,在這個(gè)代理里面會(huì)獲取到RTCVideoTrack類(lèi)芍锚,然后添加到RTCEAGLVideoView類(lèi)型的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類(lèi)的參數(shù)昔园,通過(guò)設(shè)置這個(gè),就可以達(dá)到顯示本地視頻的功能
 */
- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper capturerSession:(AVCaptureSession *)captureSession{
    self.localVideoView.captureSession = captureSession;
    
}

/**
 * 獲取遠(yuǎn)端視頻流的方法并炮,主要是獲取到RTCVideoTrack類(lèi)型的數(shù)據(jù)默刚,然后保存起來(lái),在刷新列表的時(shí)候逃魄,添加到對(duì)應(yīng)item里面的RTCEAGLVideoView類(lèi)型的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(@"移除了");
    //取消設(shè)置屏幕常亮
    [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
}

@end

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荤西,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌邪锌,老刑警劉巖勉躺,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異觅丰,居然都是意外死亡饵溅,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)妇萄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蜕企,“玉大人,你說(shuō)我怎么就攤上這事冠句√桥猓” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵轩端,是天一觀的道長(zhǎng)放典。 經(jīng)常有香客問(wèn)我,道長(zhǎng)基茵,這世上最難降的妖魔是什么奋构? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮拱层,結(jié)果婚禮上弥臼,老公的妹妹穿的比我還像新娘。我一直安慰自己根灯,他們只是感情好径缅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著烙肺,像睡著了一般纳猪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上桃笙,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天氏堤,我揣著相機(jī)與錄音,去河邊找鬼搏明。 笑死鼠锈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的星著。 我是一名探鬼主播购笆,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼虚循!你這毒婦竟也來(lái)了同欠?” 一聲冷哼從身側(cè)響起为黎,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎行您,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體剪廉,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡娃循,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了斗蒋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捌斧。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖泉沾,靈堂內(nèi)的尸體忽然破棺而出捞蚂,到底是詐尸還是另有隱情,我是刑警寧澤跷究,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布姓迅,位于F島的核電站,受9級(jí)特大地震影響俊马,放射性物質(zhì)發(fā)生泄漏丁存。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一柴我、第九天 我趴在偏房一處隱蔽的房頂上張望解寝。 院中可真熱鬧,春花似錦艘儒、人聲如沸聋伦。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)觉增。三九已至,卻和暖如春翻斟,著一層夾襖步出監(jiān)牢的瞬間抑片,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工杨赤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留敞斋,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓疾牲,卻偏偏與公主長(zhǎng)得像植捎,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子阳柔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354