iOS入门学什么?—— 先写个简单的Demo,比如做个视频直播吧(WebRTC)上

aaa.jpeg

惯例 ,WebRTC是什么####

就是 Web browsers with Real-Time Communications 啊
它是Google开源的啊,不要钱啊!

为什么要用WebRTC?####

就是老板叫看一下的啊
它是Google开源的啊,不要钱啊!

就是这样,不要在意太多细节 —— 百里潋長

目标####

在内网里使用iOS实现视频通话


有了目标就很容易知道需要些什么

服务器######

WebRTC使用libjingle进行穿透,p2p传输数据,连接过程为
尝试直连……
尝试使用Stun Service进行穿透……
尝试使用Turn Service进行中转……
这里只进行内网视频通话,就不需要准备Stun以及Turn服务器了。但即便如此,在交换通信的元数据(信令)的时候依然需要一个服务器,并且WebRTC并没有统一实现 —— 我们还需要简单地实现一个信令服务器来支持这个Demo。
我使用Java搭建信令服务器,WebSocket是一个用来做WebRTC信令的好方式,今后加上Web一起视频也是轻松加愉快

iOS######
  1. Safari显然还没有支持WebRTC的视频通话,只能使用代码来实现,在这里使用libjingle_peerconnection库(传送门)来实现WebRTC通话
  2. iOS上使用WebSocket有一个非常不错的轮子SocketRocket(传送门)
  3. 其他常用库AFNetworkingReactiveCocoaMantleSVProgressHUDMasonry

我们不生产代码,我们只是Github的搬运工 —— 农夫三拳,有点疼……


从信令服务开始####

JavaEE 7中出了JSR-356:Java API for WebSocket规范。我们需要使用JDK 7 以上版本,以Tomcat作为Web容器需要Tomcat 7.0.47 以上版本即可。
Java WebSocket只是跑起来不可谓不简单,我们新建一个Java Web项目名字就随便叫做VideoMeeting好了。再随便创建一个SocketAction类来完成信令转发,只需要简单地标注@ServerEndpoint (访问地址)@OnOpen (新建连接)@OnClose (断开连接)@OnMessage (接受到消息) 这几个注解就可以完成工作

package com.hsq.videomeeting.socketservice;

import java.lang.reflect.Method;

import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/socket/{userId}")
public class SocketAction {
   private Session session;
   private int userId;

   @OnOpen
   public void onOpen(Session session, @PathParam(value="userId")int userId) {
       //注意 @ServerEndpoint 标注时声明了可以传入一个userId字段,此处再次声明并接收,作为用户及Session唯一标识
       this.userId = userId;
       this.session = session;
       //这是另一个随便写的处理类,将Session保存,必要时通过userid可以查找到并处理一些逻辑,此处省略,在OnMessage处有说明省了哪些逻辑的
       SocketService.addAction(this);
       System.out.println("打开连接:"+userId);
   }
   
   @OnClose
   public void onClose(CloseReason c) {
       SocketService.removeAction(this);
       System.out.println("关闭连接:"+userId);
   }
   
   @OnError
   public void onError(Throwable t) {
       
   }
   
   @SuppressWarnings("unchecked")
   @OnMessage
   public void onMessage(String message) {
       String decodeString = null;
       try {
           decodeString = new String(decodeBase64(message),"utf-8");
       } catch (Exception e) {
           e.printStackTrace();
       }
       System.out.println("消息 "+decodeString);
       //此处省略一点代码哟~
       //我传递的message是一个经过base64编码的JSON串,此处需要:
       //1、解析收到的message串,获取发送者id,接受者id以及消息主体内容
       //2、如果内容是offer/anwser/Candidate,则通过接受者id查找到其Session,使用send转发出去(offer什么的在后面客户端解释)
       //3、还有其他诸如接听、挂断、拒绝、正在通话中等等就自己脑洞啦
   }
   
   public boolean send(String jsonMessage){
       try {
           String base64JSON = encodeBase64(jsonMessage.getBytes("utf-8"));
           session.getBasicRemote().sendText(base64JSON);
           return true;
       } catch (Exception e) {
           e.printStackTrace();
           return false;
       }
   }
   
   /*** 
    * 编码
    */  
   @SuppressWarnings({ "rawtypes", "unchecked" })
   public static String encodeBase64(byte[] input) throws Exception {
       Class clazz=Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64");
       Method mainMethod= clazz.getMethod("encode", byte[].class);
       mainMethod.setAccessible(true);
        Object retObj=mainMethod.invoke(null, new Object[]{input});
        return (String)retObj;
   }  
   /*** 
    * 解码
    */  
   @SuppressWarnings({ "rawtypes", "unchecked" })
   public static byte[] decodeBase64(String input) throws Exception {
       Class clazz=Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64");
       Method mainMethod= clazz.getMethod("decode", String.class);
       mainMethod.setAccessible(true);
        Object retObj=mainMethod.invoke(null, input);
        return (byte[])retObj;
   }

   public Session getSession() {
       return session;
   }

   public int getUserId() {
       return userId;
   }

   public void setUserId(int userId) {
       this.userId = userId;
   }

   @Override
   public int hashCode() {
       final int prime = 31;
       int result = 1;
       result = prime * result + userId;
       return result;
   }
   
   @Override
   public boolean equals(Object obj) {
       if (this == obj)
           return true;
       if (obj == null)
           return false;
       if (getClass() != obj.getClass())
           return false;
       SocketAction other = (SocketAction) obj;
       if (userId != other.userId)
           return false;
       return true;
   }
}

我们把它发布到Tomcat中运行起来,在iOS中尝试使用SocketRocket进行连接

NSString * const WEBSOCKET_ADDRESS = @"ws://192.168.168.41:9999/VideoMeeting/socket/";
NSString *urlStr = [NSString stringWithFormat:@"%@%d", WEBSOCKET_ADDRESS,[User user].userId];
SRWebSocket  *socket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:urlStr]];
socket.delegate = self;
[socket open];

这时就能在SRWebSocket的Delegate回调函数中获取到消息

////Delegate  -  WebSocket正常创建连接
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
    NSLog(@"连接到信令服务器啦");
}
////Delegate  -  接受消息的函数
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(NSString *)message {
    NSLog(@"接受到消息啦:%@",message);
}

有了信令服务器,不过还有关于用户体系和好友关系链之类的接口,不过不是视频必须并且与WebRTC无关,就不啰嗦了,接下来就开始着手敲iOS端的代码了啊喂

推荐阅读更多精彩内容