ios视频通话三⽅_iOS基于Socket.io即时通讯IM实
现,WebRTC实现视频通话
Socket.io-FLSocketIM-iOS
基于Socket.io iOS即时通讯客户端 iOS IM Client based on Socket.io
实现功能
⽂本发送
图⽚发送(从相册选取,或者拍摄)
短视频
语⾳发送
视频通话
其他⼀些效果(类似QQ底部tabBar,短视频拍摄等)
功能扩展中。。。。。
先看看实际效果
⽂字.gif
图⽚.gif
定位.gif
语⾳.gif
IMG_1227.PNG
使⽤技术
⼀、Socket.io
Socket.io是该项⽬实现即时通讯关键所在,⾮常强⼤;
Socket.io将Websocket和轮询 (Polling)机制以及其它的实时通信⽅式封装成了通⽤的接⼝,并且在服务端实现了这些实时机制的相应代码。
先上代码
1.创建Socket连接,通过单例管理类FLSocketManager实现
- (void)connectWithToken:(NSString *)token success:(void (^)())success fail:(void (^)())fail {
NSURL* url = [[NSURL alloc] initWithString:BaseUrl];
/**
log 是否打印⽇志
forceNew 这个参数设为NO从后台恢复到前台时总是重连,暂不清楚原因
forcePolling 是否强制使⽤轮询
reconnectAttempts 重连次数,-1表⽰⼀直重连
reconnectWait 重连间隔时间
connectParams 参数
forceWebsockets 是否强制使⽤websocket, 解释The reason it uses polling first is because some firewalls/proxies block websockets. So polling lets socket.io work behind those.
*/
SocketIOClient* socket;
if (!self.client) {
socket = [[SocketIOClient alloc] initWithSocketURL:url config:@{@"log": @NO, @"forceNew" : @YES, @"forcePolling": @NO, @"reconnectAttempts":@(-1), @"reconnectWait" : @4, @"connectParams": @{@"auth_token" : token}, @"forceWebsockets" : @NO}];
}
else {
socket = self.client;
}
// 连接超时时间设置为15秒
[socket connectWithTimeoutAfter:15 withHandler:^{
fail();
}];
// 监听⼀次连接成功
[socket once:@"connect" callback:^(NSArray * _Nonnull data, SocketAckEmitter * _Nonnull ack) {
success();
}];
_client = socket;
}
这个⽅法是在⽤户登录后调⽤,主要作⽤是初始化Socket连接,关于socket初始化相关参数请参照socket.io⽂档。
2.监听服务器向客户端发送的消息,通过单例管理类FLClientManager进⾏管理,然后让代理实现功能
// 收到消息
[socket on:@"chat" callback:^(NSArray * _Nonnull data, SocketAckEmitter * _Nonnull ack) {
if (pected == YES) {
[ack with:@[@"hello 我是应答"]];
}
FLMessageModel *message = [FLMessageModel yy_modelWithJSON:data.firstObject];
NSData *fileData = message.bodies.fileData;
if (fileData && fileData != NULL && fileData.length) {
NSString *fileName = message.bodies.fileName;
NSString *savePath = nil;
switch (pe) {
case FLMessageImage:
savePath = [[NSString getFielSavePath] stringByAppendingPathComponent:[NSString stringWithFormat:@"s_%@", fileName]];
break;
case FlMessageAudio:
savePath = [[NSString getAudioSavePath] stringByAppendingPathComponent:fileName];
break;
default:
savePath = [[NSString getFielSavePath] stringByAppendingPathComponent:fileName];
break;
}
message.bodies.fileData = nil;
[fileData saveToLocalPath:savePath];
}
id bodyStr = data.firstObject[@"bodies"];
if ([bodyStr isKindOfClass:[NSString class]]) {
FLMessageBody *body = [FLMessageBody yy_modelWithJSON:[bodyStr stringToJsonDictionary]];
message.bodies = body;
}
// 消息插⼊数据库
[[FLChatDBManager shareManager] addMessage:message];
/
/ 会话插⼊数据库或者更新会话
BOOL isChatting = [message.from isEqualToString:[FLClientManager shareManager].User];
[[FLChatDBManager shareManager] addOrUpdateConversationWithMessage:message isChatting:isChatting];
// 本地推送,收到消息添加红点,声⾳及震动提⽰
[FLLocalNotification pushLocalNotificationWithMessage:message];
// 代理处理
for (FLBridgeDelegateModel *model in self.delegateArray) {
iddelegate = model.delegate;
if (delegate && [delegate respondsToSelector:@selector(clientManager:didReceivedMessage:)]) {
if (message) {
[delegate clientManager:self didReceivedMessage:message];
}
}
}
}];
// 视频通话请求
[socket on:@"videoChat" callback:^(NSArray * _Nonnull data, SocketAckEmitter * _Nonnull ack) {
UIViewController *vc = [self getCurrentVC];
NSDictionary *dataDict = data.firstObject;
FLVideoChatViewController *videoVC = [[FLVideoChatViewController alloc] initWithFromUser:dataDict[@"from_user"] toUser:[FLClientManager shareManager].currentUserID type:FLVideoChatCallee];
< = dataDict[@"room"];
[vc presentViewController:videoVC animated:YES completion:nil];
FLLog(@"%@============", data);
}];
// ⽤户上线
[socket on:@"onLine" callback:^(NSArray * _Nonnull data, SocketAckEmitter * _Nonnull ack) {getsavefilename
for (FLBridgeDelegateModel *model in self.delegateArray) {
iddelegate = model.delegate;
if (delegate && [delegate respondsToSelector:@selector(clientManager:userOnline:)]) {
[delegate clientManager:self userOnline:[data.firstObject valueForKey:@"user"]];
}
}
}];
// ⽤户下线
[socket on:@"offLine" callback:^(NSArray * _Nonnull data, SocketAckEmitter * _Nonnull ack) {
for (FLBridgeDelegateModel *model in self.delegateArray) {
iddelegate = model.delegate;
if (delegate && [delegate respondsToSelector:@selector(clientManager:userOffline:)]) {
[delegate clientManager:self userOffline:[data.firstObject valueForKey:@"user"]];
}
}
}];
/
/ 连接状态改变
[socket on:@"statusChange" callback:^(NSArray * _Nonnull data, SocketAckEmitter * _Nonnull ack) {
FLLog(@"%ld========================状态改变", socket.status);
for (FLBridgeDelegateModel *model in self.delegateArray) {
iddelegate = model.delegate;
if (delegate && [delegate respondsToSelector:@selector(clientManager:didChangeStatus:)]) {
[delegate clientManager:self didChangeStatus:socket.status];
}
}
}];
-
(NSUUID * _Nonnull)on:(NSString * _Nonnull)event callback:(void (^ _Nonnull)(NSArray * _Nonnull, SocketAckEmitter *
_Nonnull))callback;
socket.io 提供的事件监听⽅法,这⾥监听的事件包括:
“chat” 接收到好友消息
“videoChat” 视频通话请求
“onLine” 有好友上线
“offLine” 有好友离线
“statusChange” socket.io内部提供的,连接状态改变
这部分代码,有个⽐较关键的需要说明⼀下,举个例⼦,在接收到“chat”事件后,数据库管理类需要将消息存放到数据库,会话列表需要更新UI,聊天列表需要显⽰该消息...也就是该事件需要多个对象响应。对于这种需求最先想到的就是使⽤通知的功能,毕竟可以实现⼀对多的消息传递嘛!后来⼜思考,
通过代理模式能否实现呢,通过制定协议代码质量更⾼?于是乎将代理存放在⼀个数组中,接收到事件后遍历数组中的代理去响应事件。 然⽽出现了⼀个问题,我们在⼀般使⽤代理模式中,代理都是⼀个weak修饰属性,代理释放该属性⾃动置nil,然⽽将代理放到数组中,代理被强引⽤,引⽤计数加1,数组不释放,代理永远⽆法释放。这该怎么解决呢,后来仿照⼀般的代理模式,创建⼀个桥接对象,代理数组⾥⾯存放桥接对象,然后桥接对象有⼀个weak修饰的属性指向真正的代理。桥接对象FLBridgeDelegateModel 如下:
#import
@interface FLBridgeDelegateModel : NSObject
@property (nonatomic, weak) id delegate;
- (instancetype)initWithDelegate:(id)delegate;
@end
添加代理:
- (void)addDelegate:(id)delegate {
BOOL isExist = NO;
for (FLBridgeDelegateModel *model in self.delegateArray) {
if ([delegate isEqual:model.delegate]) {
isExist = YES;
break;
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论