基于webRTC技术,实现1v1视频通话

WebRTC,即Web Real-Time Communication,web实时通信技术。简单地说就是在web浏览器里面引入实时通信,包括音视频通话等。
本技术需要使用到socket,作为信令服务器,作为俩个浏览器信令交互的工具。
移动端是基于webview做的H5页面。

  • 效果图
    移动端 link 移动端页面:
图1 发起通话
图2 接听
图3 通话
图4 通话结束

移动端 link Web端页面:

图5 web端发起通话
图6 移动端接听
图7 移动端通话
图8 web端通话

- Java后台

socketIo.java(包含即时聊天)

package com.linksaint.web.socket.service;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import net.sf.json.JSONObject;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.ContextLoader;

import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.BroadcastOperations;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketConfig;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIONamespace;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.listener.ConnectListener;
import com.corundumstudio.socketio.listener.DataListener;
import com.corundumstudio.socketio.listener.DisconnectListener;
import com.linksaint.system.po.SysUser;
import com.linksaint.system.service.SysMaxIdService;
import com.linksaint.system.service.SysUserService;
import com.linksaint.util.DateUtils;
import com.linksaint.web.fileUpload.FileUploadModel;
import com.linksaint.web.fileUpload.FileUploadService;
import com.linksaint.web.socket.listener.CharteventListener;
import com.linksaint.web.socket.listener.ChatConnectListenner;
import com.linksaint.web.socket.po.ChatConnect;
import com.linksaint.web.socket.po.ChatInfo;
import com.linksaint.web.socket.po.ClientInfo;
import com.linksaint.web.socket.po.Room_user_rel;
import com.linksaint.web.socket.po.UnreadNum;
/*
 * netty-socketio工具类
 * 启动、添加客户端
 * 消息推送
 * 关闭服务
 */
public class Socketio {
	@Autowired
	private ClientInfoService clientInfoService;
	@Autowired
	private ChatInfoService chatInfoService;
	@Autowired
	private ChatRoomService chatRoomService;
	@Autowired
	private SysUserService sysUserService;
	@Autowired
	private Room_user_relService room_user_relService;
	@Autowired
	private SysMaxIdService sysMaxIdService;
	@Autowired
	private FileUploadService fileUploadService;
	@Autowired
	private UnreadNumService unreadNumService;

	static SocketIOServer socketIOServer;
	private String serverIp="";
	public static ConcurrentMap<String, SocketIOClient> socketIOClientMap = new ConcurrentHashMap<>();//存储socket客户端session
	/*
	 * 添加客户端
	 */
	public void startSocketio() throws InterruptedException, IOException {
		clientInfoService = (ClientInfoService) ContextLoader.getCurrentWebApplicationContext().getBean("clientInfoService");
		chatInfoService = (ChatInfoService) ContextLoader.getCurrentWebApplicationContext().getBean("chatInfoService");
		sysUserService = (SysUserService) ContextLoader.getCurrentWebApplicationContext().getBean("sysUserService");
		room_user_relService = (Room_user_relService) ContextLoader.getCurrentWebApplicationContext().getBean("room_user_relService");
		sysMaxIdService = (SysMaxIdService) ContextLoader.getCurrentWebApplicationContext().getBean("sysMaxIdService");
		chatRoomService = (ChatRoomService) ContextLoader.getCurrentWebApplicationContext().getBean("chatRoomService");
		fileUploadService = (FileUploadService) ContextLoader.getCurrentWebApplicationContext().getBean("fileUploadService");
		unreadNumService = (UnreadNumService) ContextLoader.getCurrentWebApplicationContext().getBean("unreadNumService");
		// 配置
		Properties prop = new Properties();   
		InputStream inputStream = this.getClass().getResourceAsStream("/nettySocket.properties");  
	    prop.load(inputStream);    
	    serverIp=prop.getProperty("server.ip");
		inputStream.close();
		Configuration conf = new Configuration();
		conf.setHostname(serverIp);
		// 指定端口号
		conf.setPort(9092);
		// 设置最大的WebSocket帧内容长度限制
		conf.setMaxFramePayloadLength(1024 * 1024);
		// 设置最大HTTP内容长度限制
		conf.setMaxHttpContentLength(1024 * 1024);
		clientInfoService.deleteAllClient();//清空client
		socketIOServer = new SocketIOServer(conf);
		socketIOServer.start();
		System.out.println("socketIo服务器已经建立,等待客户端连接...");
		 
		/**
		 * 添加连接监听事件,监听是否与客户端连接到服务器
		 */
		socketIOServer.addConnectListener(new ConnectListener() {
			@Override
			public void onConnect(SocketIOClient client) {
		        Collection<SocketIOClient> clients = socketIOServer.getAllClients();
		        System.out.println("客户端:" +client.getSessionId().toString() + "连接,已连接+"+clients.size());
			}
		});

		/**
		 * 添加连接监听事件,监听是否与客户端连接到服务器
		 */
		socketIOServer.addDisconnectListener(new DisconnectListener() {
			@Override
			public void onDisconnect(SocketIOClient client) {
				// 判断是否有客户端连接
				System.out.println("客户端:" + client.getSessionId() + "断开连接");
					// 根据客户端sessionID获取用户与client缓存中的信息
					ClientInfo cInfo = clientInfoService.getClientInfoBycId(client.getSessionId().toString());
					if (null != cInfo) {
//						cInfo.setConnected((short) 0);
//						clientInfoService.updateClient(cInfo);
						clientInfoService.deleteClientByUserId(cInfo.getUserId());
					}
			}
		});
		
		
		 final SocketIONamespace chatnamespace = socketIOServer.addNamespace("/chat");
		//握手请求(绑定userId和roomId,更新客户端client状态)
		 	chatnamespace.addEventListener("helloevent", ChatConnect.class, new ChatConnectListenner() {
					@Override
					public void onData(final SocketIOClient client, ChatConnect data, AckRequest ackRequest) {
						// 握手
						String s = data.getType() == 0 ? "移动端" : "web端";
						System.out.println("握手接收到" + s + "客户端" + data.getUserId() + "握手请求");
						long userId = data.getUserId();
						int type = data.getType();
						long roomId = data.getRoomId();
						ClientInfo clientInfo =  clientInfoService.getClientInfoByUserId(userId, type);
						if (clientInfo != null) {
							clientInfo.setClientid(client.getSessionId().toString());
							clientInfo.setConnected((short) 1);
							clientInfo.setMostsignbits(client.getSessionId().getMostSignificantBits());
							clientInfo.setLeastsignbits(client.getSessionId().getLeastSignificantBits());
							clientInfo.setLastconnecteddate(DateUtils.getNowDate());
							clientInfo.setUserId(userId);
							clientInfo.setType(type);
							clientInfo.setRoomId(roomId);
							clientInfoService.updateClient(clientInfo);
						} else {
							ClientInfo info = new ClientInfo();
							info.setClientid(client.getSessionId().toString());
							info.setConnected((short) 1);
							info.setMostsignbits(client.getSessionId().getMostSignificantBits());
							info.setLeastsignbits(client.getSessionId().getLeastSignificantBits());
							info.setLastconnecteddate(DateUtils.getNowDate());
							info.setUserId(userId);
							info.setType(type);
							info.setRoomId(roomId);
							clientInfoService.addClient(info);
						}

					}
				});
		
		 	chatnamespace.addEventListener("communication",  ChatInfo.class, new DataListener<ChatInfo>(){
					@Override
					public void onData(SocketIOClient client,ChatInfo chatinfo, AckRequest ackSender)
							throws Exception {
						// 发送chatnamespace聊天室在线人员(不包括自己)
						List<ClientInfo> cons = clientInfoService.getClientInfosByRoomIdAndUserId(chatinfo.getRoomid(), chatinfo.getSourceUserId());
						Collection<SocketIOClient> clients = chatnamespace.getAllClients();
						for (ClientInfo info : cons) {
							for (SocketIOClient c : clients) {
								if (c.getSessionId().toString().equals(info.getClientid())&&null!=c&&c.isChannelOpen()) {
									c.sendEvent("link",chatinfo.getRoomid());
								}
							}
						}
					}
				});	
			
			
		   
			// 私信(私聊&群聊)
			 chatnamespace.addEventListener("message", ChatInfo.class, new DataListener<ChatInfo>() {
				@Override
				public void onData(SocketIOClient client, ChatInfo data, AckRequest ackSender) throws Exception {
					long changeId = data.getSourceUserId();
					SysUser user = sysUserService.GetUserById(data.getSourceUserId());
					// 向客户端发送消息
					System.out.println("私聊房间号:" + data.getRoomid() + "--" + user.getUserName() + ":" + data.getMessage());
					// 发送chatnamespace聊天室在线人员(不包括自己)
					List<ClientInfo> cons = clientInfoService.getClientInfosByRoomIdAndUserId(data.getRoomid(), changeId);
					long chatid = sysMaxIdService.selectSysMaxId("chatMessage");
					data.setMessageId(chatid);
					data.setMessage(data.getMessage());
					data.setState(0);// 初始状态未发送
					data.setSenddate(DateUtils.getNowDate());
					if (data.getType() == 1) {// 用户发送的图片
						FileUploadModel file = fileUploadService.selectById(Long.parseLong(data.getMessage()));
						if (null != file && null != file.getFilePath()) {
							data.setMessage(file.getFilePath());
						}
					} else if (data.getType() == 3 && data.getTargetUserId() > 0) {// 系统广播【人员加入群聊】
						SysUser tar_user = sysUserService.GetUserById(data.getTargetUserId());
						data.setMessage(user.getUserName() + "邀请" + tar_user.getUserName() + "加入群聊!");
					} else if (data.getType() == 4) {// 系统广播【人员退出群聊】
						data.setMessage(user.getUserName() + "退出了群聊!");
					} else if (data.getType() == 5) {// 系统广播【人员被移出群聊】
						data.setType(4);
						changeId = 0;
						data.setMessage(user.getUserName() + "被移出群聊!");
					}else if (data.getType() == 6) {// 用户发送的视频
						FileUploadModel file = fileUploadService.selectById(Long.parseLong(data.getMessage()));
						if (null != file && null != file.getFilePath()) {
							data.setMessage(file.getFilePath());
						}
					}else if (data.getType() == 7) {// 用户发送的语音
						FileUploadModel file = fileUploadService.selectById(Long.parseLong(data.getMessage()));
						if (null != file && null != file.getFilePath()) {
							String paths=Socketio.class.getClassLoader().getResource("").getPath();
							paths=paths.substring(1,paths.length());
							long seconds=getAmrDuration(new File(paths+"../"+file.getFilePath()));
							double d=seconds/(double)1000;
							long s=(long) Math.ceil(d);
							data.setDuration(s);
							data.setMessage(file.getFilePath());
						}
						//包括自己(录音需要获取时长)
						cons = clientInfoService.getClientInfosByRoomId(data.getRoomid());;
					}else if (data.getType() == 8) {// 用户发送的音视频通话,发起语音插入数据库,挂断时修改时长或者通话取消
						cons = clientInfoService.getClientInfosByRoomId(data.getRoomid());
						data.setMessage("音视频通话");
						//发起通话的人状态改为通话中
						ClientInfo cInfo = clientInfoService.getClientInfoBycId(client.getSessionId().toString());
						if (null != cInfo) {
							cInfo.setConnected((short) 2);//通话中
							clientInfoService.updateClient(cInfo);
						}
					}
					
					
					if (data.getType() == 9) {
						// 用户挂断的音视频通话
						cons = clientInfoService.getClientInfosByRoomId(data.getRoomid());
						ChatInfo ch=null;
						if(null!=data.getId()&&data.getId()>0){
							 ch=chatInfoService.selectChatInfo(data.getId());
							if(data.getFun()==0){
								ch.setMessage("0");//拨打取消  --发送者:取消,接受者:未接听
							}else if(data.getFun()==1){
								ch.setMessage("1");//对方已拒绝
							}else{
								String sendTime=ch.getSenddate();
								String time=dateDiff(sendTime,DateUtils.getNowDate());
								ch.setMessage("通话时长:"+time);//通话结束
							}
							chatInfoService.updateChatInfo(ch);
						}
						ch.setType(9);
						Collection<SocketIOClient> clients = chatnamespace.getAllClients();
						for (ClientInfo info : cons) {
							for (SocketIOClient c : clients) {
								if (c.getSessionId().toString().equals(info.getClientid())&&null!=c&&c.isChannelOpen()) {
									UnreadNum u = unreadNumService.selectByuserIdAndroomId(info.getUserId(), data.getRoomid());
									if (null != u && u.getUnreadnum() > -1) {
										data.setHotnum((int) u.getUnreadnum());
									} else {
										data.setHotnum(0);
									}
									c.sendEvent("message", ch);
									//设置状态为在线
									ClientInfo cInfo = clientInfoService.getClientInfoBycId(c.getSessionId().toString());
									if (null != cInfo) {
										cInfo.setConnected((short) 1);//在线
										clientInfoService.updateClient(cInfo);
									}

								}
							}
						}
					}else{
						// 获得消息所在房间
						List<Room_user_rel> rels = room_user_relService.getRelInfosByRoomId(data.getRoomid());
							chatRoomService.updateNowtime(DateUtils.getNowDate(), data.getRoomid());//更新房间最后活跃时间
							long id = chatInfoService.insertChatInfo(data);
								data.setId(id);
						SysUser s = sysUserService.getUserInfoById(data.getSourceUserId());
						if (null != s) {
							if (s.getFilePath() != null && s.getFilePath().length() > 0) {
								data.setHeadimg(s.getFilePath());
							}
							data.setUserName(s.getUserName());
						}
						for (Room_user_rel rel : rels) {
							if (data.getSourceUserId() != rel.getUserid()) {
								// 设置未读数量+1
								unreadNumService.addByuserIdAndroomId(rel.getUserid(), data.getRoomid(), 1);
							}
							if (rel.getTargetid() > 0) {// 单聊聊天记录
								if (data.getSourceUserId() != rel.getTargetid()) {
									unreadNumService.addByuserIdAndroomId(rel.getTargetid(), data.getRoomid(), 1);
								}
								// 激活聊天室
								if (rel.getSourcestate() == 1) {
									room_user_relService.updateByRoomIdAndUserId(data.getRoomid(), 0);
								} else {
									room_user_relService.updateByRoomIdAndtargetId(data.getRoomid(), 0);
								}
							}
						}
						
						Collection<SocketIOClient> clients = chatnamespace.getAllClients();
						
						for (ClientInfo info : cons) {
							for (SocketIOClient c : clients) {
								if (c.getSessionId().toString().equals(info.getClientid())&&null!=c&&c.isChannelOpen()) {
									UnreadNum u = unreadNumService.selectByuserIdAndroomId(info.getUserId(), data.getRoomid());
									if (null != u && u.getUnreadnum() > -1) {
										data.setHotnum((int) u.getUnreadnum());
									} else {
										data.setHotnum(0);
									}
									c.sendEvent("message", data);
	
								}
							}
						}
					
					}
					
				}
			});
			 
			 // 讨论joinRoom
			 final SocketIONamespace groupnamespace = socketIOServer.addNamespace("/group");
			//握手请求(绑定userId和roomId,更新客户端client状态)
			 	   groupnamespace.addEventListener("helloevent", ChatConnect.class, new ChatConnectListenner() {
						@Override
						public void onData(final SocketIOClient client, ChatConnect data, AckRequest ackRequest) {
							 Collection<SocketIOClient> clients = socketIOServer.getAllClients();
							// 握手
							String s = data.getType() == 0 ? "移动端" : "web端";
							System.out.println("握手接收到" + s + "客户端" + data.getUserId() + "握手请求");
							long userId = data.getUserId();
							int type = data.getType();
							long roomId = data.getRoomId();
							client.joinRoom(String.valueOf(roomId));//客户端加入房间
						}
					});
			
			// 讨论
			 groupnamespace.addEventListener("chat", ChatInfo.class, new CharteventListener() {
					@Override
					public void onData(SocketIOClient client, ChatInfo data, AckRequest ackSender) throws Exception {
						SysUser user = sysUserService.GetUserById(data.getSourceUserId());
						System.out.println("群聊房间号:" + data.getRoomid() + "--" + user.getUserName() + ":" + data.getMessage());
						if (data.getType() != -1) {
							// 向客户端发送消息
							long chatid = sysMaxIdService.selectSysMaxId("chatMessage");
							data.setMessageId(chatid);
							data.setMessage(data.getMessage());
							data.setState(0);// 初始状态未发送
							data.setSenddate(DateUtils.getNowDate());
							// 获得消息所在房间
								data.setTargetUserId(0);
								long id = chatInfoService.insertChatInfo(data);
								data.setId(id);

							Collection<SocketIOClient> clients = socketIOServer.getAllClients();
							// 发送给聊天室在线人员(包括自己)
							BroadcastOperations broadcast=groupnamespace.getRoomOperations(String.valueOf(data.getRoomid()));
							broadcast.sendEvent("chat", data);
						} else {
							data.setUserName(user.getUserName());
							Collection<SocketIOClient> clients = socketIOServer.getAllClients();
							BroadcastOperations broadcast=groupnamespace.getRoomOperations(String.valueOf(data.getRoomid()));
							broadcast.sendEvent("chat", data);
						}
					}
				});

				// 各客户端同步群聊标记重点
			 	groupnamespace.addEventListener("sign", ChatInfo.class, new CharteventListener() {
					@Override
					public void onData(SocketIOClient client, ChatInfo data, AckRequest ackSender) throws Exception {
						SysUser user = sysUserService.GetUserById(data.getSourceUserId());
						Collection<SocketIOClient> clients = socketIOServer.getAllClients();
						// 发送给聊天室在线人员该条记录的标记次数(包括自己)
						BroadcastOperations broadcast=groupnamespace.getRoomOperations(String.valueOf(data.getRoomid()));
						broadcast.sendEvent("sign", data);
					}
				});
			 	
			 	
			 	 // 直播弹幕joinRoom
				 final SocketIONamespace livenamespace = socketIOServer.addNamespace("/live");
					//握手请求(绑定userId和roomId,更新客户端client状态)
				 		livenamespace.addEventListener("helloevent", ChatConnect.class, new ChatConnectListenner() {
								@Override
								public void onData(final SocketIOClient client, ChatConnect data, AckRequest ackRequest) {
									 Collection<SocketIOClient> clients = socketIOServer.getAllClients();
									// 握手
									String s = data.getType() == 0 ? "移动端" : "web端";
									System.out.println("握手接收到" + s + "客户端" + data.getUserId() + "握手请求");
									long userId = data.getUserId();
									int type = data.getType();
									long roomId = data.getRoomId();
									client.joinRoom(String.valueOf(roomId));//客户端加入房间
								}
							});
			 	
			 	
				 		// 直播弹幕
				 		livenamespace.addEventListener("chat", ChatInfo.class, new CharteventListener() {
								@Override
								public void onData(SocketIOClient client, ChatInfo data, AckRequest ackSender) throws Exception {
									SysUser user = sysUserService.GetUserById(data.getSourceUserId());
									if (data.getType() != -1) {
										// 向客户端发送消息
										long chatid = sysMaxIdService.selectSysMaxId("chatMessage");
										data.setMessageId(chatid);
										data.setMessage(data.getMessage());
										data.setState(0);// 初始状态未发送
										data.setSenddate(DateUtils.getNowDate());
										long startTs = System.currentTimeMillis(); // 当前时间戳
										data.setId(startTs);
										// 发送给聊天室在线人员(包括自己)
										BroadcastOperations broadcast=livenamespace.getRoomOperations(String.valueOf(data.getRoomid()));
										broadcast.sendEvent("chat", data);
									} else {
										data.setUserName(user.getUserName());
										// 发送给聊天室在线人员(包括自己)
										BroadcastOperations broadcast=livenamespace.getRoomOperations(String.valueOf(data.getRoomid()));
										broadcast.sendEvent("chat", data);
										
									}
								}
							});
				 		
				 		
				 		 // 视频播放joinRoom
						 final SocketIONamespace videonamespace = socketIOServer.addNamespace("/video");
							//握手请求(绑定userId和roomId,更新客户端client状态)
						 	videonamespace.addEventListener("helloevent", ChatConnect.class, new ChatConnectListenner() {
										@Override
										public void onData(final SocketIOClient client, ChatConnect data, AckRequest ackRequest) {
											 Collection<SocketIOClient> clients = socketIOServer.getAllClients();
											// 握手
											String s = data.getType() == 0 ? "移动端" : "web端";
											System.out.println("握手接收到" + s + "客户端" + data.getUserId() + "握手请求");
											long userId = data.getUserId();
											int type = data.getType();
											long roomId = data.getRoomId();
											client.joinRoom(String.valueOf(roomId));//客户端加入房间
										}
									});
					 	
					 	
						 		//视频播放
						 		videonamespace.addEventListener("chat", ChatInfo.class, new CharteventListener() {
										@Override
										public void onData(SocketIOClient client, ChatInfo data, AckRequest ackSender) throws Exception {
											SysUser user = sysUserService.GetUserById(data.getSourceUserId());
											if (data.getType() != -1) {
												// 向客户端发送消息
												long chatid = sysMaxIdService.selectSysMaxId("chatMessage");
												data.setMessageId(chatid);
												data.setMessage(data.getMessage());
												data.setState(0);// 初始状态未发送
												data.setSenddate(DateUtils.getNowDate());
												// 获得消息所在房间
												data.setTargetUserId(0);
												long id = chatInfoService.insertChatInfo(data);
												data.setId(id);
												// 发送给聊天室在线人员(包括自己)
												BroadcastOperations broadcast=videonamespace.getRoomOperations(String.valueOf(data.getRoomid()));
												broadcast.sendEvent("chat", data);
											} else {
												data.setUserName(user.getUserName());
												// 发送给聊天室在线人员(包括自己)
												BroadcastOperations broadcast=videonamespace.getRoomOperations(String.valueOf(data.getRoomid()));
												broadcast.sendEvent("chat", data);
												
											}
										}
									});
						 		
						 		
						 		 // 音视频joinRoom
								 final SocketIONamespace webRtcnamespace = socketIOServer.addNamespace("/webRTC");
								 
								 webRtcnamespace.addEventListener("join", String.class, new DataListener<String>() {
										@Override
										public void onData(final SocketIOClient client, String roomId, AckRequest ackRequest) {
											Collection<SocketIOClient> c=webRtcnamespace.getRoomOperations(roomId).getClients();
											if(c.size()==0){
												client.joinRoom(String.valueOf(roomId));//客户端加入房间
												client.sendEvent("joined", roomId, client.getSessionId());
											}else if(c.size()==1){
												client.joinRoom(String.valueOf(roomId));//客户端加入房间
												client.sendEvent("otherjoin", roomId, client.getSessionId());
												webRtcnamespace.getRoomOperations(roomId).sendEvent("ready", roomId);
											}else{
												client.sendEvent("full", roomId);
											}
											
										}
											
							 });
								
							 	
								 //视频通话
								 	webRtcnamespace.addEventListener("message", String.class, new DataListener<String>() {
								 		@Override
											public void onData(SocketIOClient client, String str, AckRequest ackSender) throws Exception {
								 				JSONObject json = JSONObject.fromObject(str);  
								 				String roomid=json.getString("roomId");
								 				String data=json.getString("data");
												Collection<SocketIOClient> clients=webRtcnamespace.getRoomOperations(roomid).getClients();
												for (SocketIOClient c : clients) {
													if(c.getSessionId()!=client.getSessionId()){
														c.sendEvent("message",data);
													}
													
												}
												
											}
										});	
								 	
						 		
									 webRtcnamespace.addEventListener("leave", String.class, new DataListener<String>(){
											@Override
											public void onData(SocketIOClient client,String roomId, AckRequest ackSender)
													throws Exception {
												client.leaveRoom(roomId);
												
											}
										});		
									 
								 	
								 webRtcnamespace.addEventListener("ipaddr", String.class, new DataListener<String>() {
											@Override
											public void onData(final SocketIOClient client, String data, AckRequest ackRequest) {
												client.sendEvent("ipaddr", client.getRemoteAddress());
											}
								 });	
								 
								 
								 
								 //课堂互动
								 final SocketIONamespace classnamespace = socketIOServer.addNamespace("/classWebRTC");
								 
								 	classnamespace.addEventListener("helloevent", ChatConnect.class, new ChatConnectListenner() {
										@Override
										public void onData(final SocketIOClient client, ChatConnect data, AckRequest ackRequest) {
											System.out.println("课堂互动建立连接.");
											long userId = data.getUserId();
											socketIOClientMap.put(String.valueOf(userId), client);
										}
									});
								 
							   
								// 视频通话呼叫
								 classnamespace.addEventListener("call", ChatInfo.class, new DataListener<ChatInfo>() {
									@Override
									public void onData(SocketIOClient client, ChatInfo data, AckRequest ackSender) throws Exception {
										long sendId = data.getSourceUserId();//呼叫人
										SocketIOClient senderSocket=socketIOClientMap.get(String.valueOf(sendId));//获取呼叫人socket,并返回结果
										SysUser sender = sysUserService.GetUserById(sendId);
										
										long receiveId = data.getTargetUserId();//接听人
										SocketIOClient receiverSocket=socketIOClientMap.get(String.valueOf(receiveId));//获取呼叫人socket,并返回结果
										SysUser receiver = sysUserService.GetUserById(receiveId);
										
										// 向客户端发送消息
										System.out.println("课堂互动呼叫:--呼叫人:" + sender.getUserName() + "-接听人:" + receiver.getUserName());
										// 发送classnamespace聊天室在线人员(不包括自己)
										if (data.getType() == 1) {// 用户发送的课堂互动通话,发起语音,挂断时修改时长或者通话取消
											data.setMessage("课堂互动转接");
											//发起通话的人状态改为通话中
											ClientInfo cInfo = clientInfoService.getClientInfoByUserId(sender.getUserId(),0);
											if (null != cInfo) {
												cInfo.setConnected((short) 2);//通话中
												clientInfoService.updateClient(cInfo);
											}
											// 获得呼叫人信息
											SysUser s = sysUserService.getUserInfoById(data.getSourceUserId());
											if (null != s) {
												if (s.getFilePath() != null && s.getFilePath().length() > 0) {
													data.setHeadimg(s.getFilePath());
												}
												data.setUserName(s.getUserName());
											}
											receiverSocket.sendEvent("message", data);											
											
										}else if (data.getType() == 2) {
											// 课堂互动挂断
												if(data.getFun()==0){
													//拨打取消  --发送者:取消,接受者:未接听
													data.setMessage("已取消");
												}else if(data.getFun()==1){
													//对方已拒绝
													data.setMessage("已拒绝");
												}else{
													//通话结束
													data.setMessage("互动结束");
												}
												ClientInfo receiverClient = clientInfoService.getClientInfoByUserId(receiver.getUserId(),0);
												if (null != receiverClient) {
													receiverClient.setConnected((short) 1);//在线
													clientInfoService.updateClient(receiverClient);
												}
												ClientInfo senderClient = clientInfoService.getClientInfoByUserId(sender.getUserId(),0);
												if (null != senderClient) {
													senderClient.setConnected((short) 1);//在线
													clientInfoService.updateClient(senderClient);
												}
												if (null!=receiverSocket &&receiverSocket.isChannelOpen()) {
													receiverSocket.sendEvent("call", data);
												}
											 
										}
										
									}
								});
								 
								 
								 //视频通话接听
									classnamespace.addEventListener("communication",  ChatInfo.class, new DataListener<ChatInfo>(){
										@Override
										public void onData(SocketIOClient client,ChatInfo data, AckRequest ackSender)
												throws Exception {
											long sendId = data.getSourceUserId();//接听人
											SocketIOClient senderSocket=socketIOClientMap.get(String.valueOf(sendId));//获取呼叫人socket,并返回结果
											SysUser sender = sysUserService.GetUserById(sendId);
											
											long receiveId = data.getTargetUserId();//呼叫人
											SocketIOClient receiverSocket=socketIOClientMap.get(String.valueOf(receiveId));//获取呼叫人socket,并返回结果
											SysUser receiver = sysUserService.GetUserById(receiveId);
											//给呼叫人反馈接听
											receiverSocket.sendEvent("link", data.getRoomid());		
											// 向客户端发送消息
											System.out.println("互动接听:--接听人:" + sender.getUserName() + "-呼叫人:" + receiver.getUserName());
										}
									});	
								 
								 
								 classnamespace.addEventListener("join", String.class, new DataListener<String>() {
										@Override
										public void onData(final SocketIOClient client, String roomId, AckRequest ackRequest) {
											Collection<SocketIOClient> c=classnamespace.getRoomOperations(roomId).getClients();
											if(c.size()==0){
												client.joinRoom(String.valueOf(roomId));//客户端加入房间
												client.sendEvent("joined", roomId, client.getSessionId());
											}else if(c.size()==1){
												client.joinRoom(String.valueOf(roomId));//客户端加入房间
												client.sendEvent("otherjoin", roomId, client.getSessionId());
												classnamespace.getRoomOperations(roomId).sendEvent("ready", roomId);
											}else{
												client.sendEvent("full", roomId);
											}
											
										}
											
							 });
								
							 	
								 //视频通话
								 	classnamespace.addEventListener("message", String.class, new DataListener<String>() {
								 		@Override
											public void onData(SocketIOClient client, String str, AckRequest ackSender) throws Exception {
								 				JSONObject json = JSONObject.fromObject(str);  
								 				String roomid=json.getString("roomId");
								 				String data=json.getString("data");
												Collection<SocketIOClient> clients=classnamespace.getRoomOperations(roomid).getClients();
												for (SocketIOClient c : clients) {
													if(c.getSessionId()!=client.getSessionId()){
														c.sendEvent("message",data);
													}
													
												}
												
											}
										});	
								 	
						 		
								 	classnamespace.addEventListener("leave", String.class, new DataListener<String>(){
											@Override
											public void onData(SocketIOClient client,String roomId, AckRequest ackSender)
													throws Exception {
												client.leaveRoom(roomId);
												
											}
										});		
								 
								 
					
						 
						 

		// 设置超时时间
		Thread.sleep(Integer.MAX_VALUE);

		socketIOServer.stop();
	}
	


	/*
	 * 启动服务
	 */
	public void startServer() {
		if (socketIOServer == null) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						startSocketio();
					} catch (InterruptedException  | IOException e) {
						e.printStackTrace();
					}
				}
			}).start();
		}
	}

	/*
	 * 停止服务
	 */
	public void stopSocketio() {
		if (socketIOServer != null) {
			socketIOServer.stop();
			socketIOServer = null;
		}
	}
	
	public static long getAmrDuration(File file) throws IOException {
        long duration = -1;
        int[] packedSize = { 12, 13, 15, 17, 19, 20, 26, 31, 5, 0, 0, 0, 0, 0, 0, 0 };
        RandomAccessFile randomAccessFile = null;
        try {
            randomAccessFile = new RandomAccessFile(file, "rw");
            long length = file.length();//文件的长度
            int pos = 6;//设置初始位置
            int frameCount = 0;//初始帧数
            int packedPos = -1;
            byte[] datas = new byte[1];//初始数据值
            while (pos <= length) {
                randomAccessFile.seek(pos);
                if (randomAccessFile.read(datas, 0, 1) != 1) {
                    duration = length > 0 ? ((length - 6) / 650) : 0;
                    break;
                }
                packedPos = (datas[0] >> 3) & 0x0F;
                pos += packedSize[packedPos] + 1;
                frameCount++;
            }
            duration += frameCount * 20;//帧数*20
        } finally {
            if (randomAccessFile != null) {
                randomAccessFile.close();
            }
        }
       
        return duration;
    }
	
	public String dateDiff(String startTime, String endTime) throws ParseException {
		//按照传入的格式生成一个simpledateformate对象
			String time="";
			SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
			long nd = 1000*24*60*60;//一天的毫秒数
			long nh = 1000*60*60;//一小时的毫秒数
			long nm = 1000*60;//一分钟的毫秒数
			long ns = 1000;//一秒钟的毫秒数long diff;try {
			//获得两个时间的毫秒时间差异
			long diff;
			diff = sd.parse(endTime).getTime() - sd.parse(startTime).getTime();
			long hour = diff%nd/nh;//计算差多少小时
			long min = diff%nd%nh/nm;//计算差多少分钟
			long sec = diff%nd%nh%nm/ns;//计算差多少秒
			System.out.println("时间相差:"+hour+"小时"+min+"分钟"+sec+"秒");
			if(hour>0){
				time=hour+":"+min+":"+sec;
			}else if(min>0){
				time=min+":"+sec;
			}else{
				time=sec+"s";
			}
			return time;
	}

}

video.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>互动</title>
<%@include file="../../head.jsp"%>
<link rel="stylesheet" href="../js/toastr.css">
<script>
</script>
<script src="videojs/adapter-latest.js"></script>
<script type="text/javascript" src="../js/socketIp.js"></script>
<script src="../js/socket.io.js"></script>
<script type="text/javascript" src="../js/toastr.js"></script>
<script type="text/javascript" src="../js/toastr.setting.js"></script>
<style>
	html, body {
		margin:0;
		padding:0;
	}

	.container {
		height:100%;width:100%;
		/*width: 100%;*/
		/*border: 1px solid green;*/
		position:fixed;
	}
	#touch {
		width: 150px;
		height: 150px;
		position: absolute;
		left: 90%;
		top: 10%;
		margin-left: -90px;
		margin-top: -74px;
		z-index: 999;
	}
			
	#localvideo {
		cursor: move;
	}
			
	.call{
		position: fixed;
		top: 0;
		right: 0;
		bottom: 0; 
		left: 0; 
		z-index: 998; 
		background-color: rgba(0, 0, 0, 0.9);
		text-align: center;
	}
</style>
</head>
<body>
	<div id="sender" class="call" style="display: none;">
			<img id="callimg" src="img/dept.png" style="width: 100px;height: 100px;margin-top:60%;border-radius: 10px;"  onerror="imgheaderror(this);" />
			<p id="callname" style="color: #F0F0F0;font-size: 25px;width: 100%;margin-top: 25px;"></p>
			<p style="color: #F0F0F0;font-size: 15px;width: 100%;margin-top: 5px;">&nbsp;&nbsp;拨打中...</p>
			<img onclick="back(0)" src="img/guaduan.png" style="width: 60px;height: 60px;position: absolute;bottom: 50px;left: 45%;cursor: pointer;" />
		</div>
		
		<div id="communication" class="mui-content" style="display: none;">
			<div class="container" onclick="toggle()" >
				<video id="remotevideo" autoplay playsinline style="height: 100%;background-color: rgba(0, 0, 0, 0.9);" ></video>
			</div>
		</div>
		<div id="closeButton" style="width: 100%;height: 15%;position: absolute;bottom: 0px;background: rgba(6,5,10,0.7);display: none;" >
			<img onclick="back(2)" src="img/guaduan.png" style="width: 60px;height: 60px;position: absolute;bottom: 15%;left: 42%;" />
		</div>
		<audio autoplay="autoplay" id="auto" src=""></audio>
		<audio id="call"  controls autoplay loop src="" style="display: none;"></audio>
	<script type="text/javascript" charset="utf-8">
	var room;
	var sendId;
	var configuration = null;
	var socket;
	let pc =null;
	var msgid;
	var userId=0;
	var targetUserId=0;
	
	var localVideo = document.querySelector('video#localvideo');
	var remoteVideo = document.querySelector('video#remotevideo');
	
	var localStream = null;
	var remoteStream = null;
	var roomid=0;
	var state = 'init';
	var flag=0;
	var constraints = {
		 video: false,
		 audio: true
		};
	
	
	$(function() {
		userId=window.localStorage.getItem('userId');
		roomid=window.localStorage.getItem('roomId');
		targetUserId=window.localStorage.getItem('targetUserId');
		 //获取互动学生信息
		  $.ajax({
			url : '../appPath/appPath!ajaxUserInfo',
			data : {userId:targetUserId},
			type : 'get',
			dataType : 'json',
			success : function(data) {
				$("#callname").text(data.personName)
				if(data.imgSrc!=undefined){
					$("#callimg").attr("src",data.imgSrc)
				}
			}
		});
		
// 		socket = io.connect("http://192.168.88.56:9092/classWebRTC",{ 'reconnect': true });
		socket = io.connect(socketip+"/classWebRTC",{ 'reconnect': true });
		socket.on('connect', function() {
			// 发送握手请求
			var jsonObject = {
				userId: userId
			};
			this.emit('helloevent', jsonObject);
		});
		$("#call").attr("src", "videojs/call.mp3");
		
		socket.on('call', function(data) {
			toastr.warning(data.message);
			closeView();
		})
		socket.on('link', function(data) {
			createLink(data)
		})
		var isInitiator;
		$("#sender").show();
			
	});		
	
	
		function back(fun){
			//通知对方已关闭
			var jsonObject = {
				'sourceUserId' : userId,
				'targetUserId' : targetUserId,
				'roomid' : roomid,
				'type':2,
				'fun':fun// 0:拨打取消;1:对方已拒绝;2:通话结束
			}
			if(fun==0){
				toastr.warning("互动取消!");
			}else if(fun==2){
				toastr.warning("通话结束!");
			}
		 	socket.emit('call', jsonObject);
			closeView();
		}
		
		function closeView(){
			$("#auto").attr("src", "videojs/callend.mp3");
			 leave();
// 			  closePeerConnection();
// 			  closeLocalMedia();
			  window.opener.reset();
			  setTimeout(function(){
				window.close();
			},1000);
			
		}
		
		function toggle(){
			$("#closeButton").toggle();
		}
		
				
		 function createLink(data){
			 	$("#call").remove();
				connSignalServer();//获取本地视频流
				if(data==roomid){
					setInterval(function(){
						$("#sender").hide();
						$("#communication").show();
					},1000);
				}
			 }
		
		 function imgheaderror(img){
				img.src="img/bayi.png";
				img.onerror=null;   
			}
		
		</script>
		<script src="videojs/client.js"></script>
</body>
</html>

client.js

'use strict';

/****************************************************************************
* Initial setup
****************************************************************************/
var servers ={iceServers:[]};
function connSignalServer() {
  // 开启本地音视频设备
  start();
  return true;
}


function start() {
  if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
  	alert("不支持!")
  } else {
    navigator.mediaDevices.getUserMedia(constraints)
        .then(getMediaStream)
        .catch((e) => {
          console.error(e);
        });
  }
}

function getMediaStream(stream) {
  if (localStream) {
    stream.getAudioTracks().forEach((track) => {
      localStream.addTrack(track);
      stream.removeTrack(track);
    });
  } else {
    localStream = stream;
  }

//	localVideo.srcObject = localStream;
	conn();
}

/* 信令部分 */
function conn() {
	
socket.on('joined', (roomid, id) => {
    state = 'joined';
    console.log('receive msg: joined', roomid, id, 'state = ', state);
    createPeerConnection();

});
   
socket.on('otherjoin', (roomid, id) => {
	state = 'joined_unbind';
    if (state === 'joined_unbind') {
      createPeerConnection();
    }

    state = 'joined_conn';
    console.log('receive msg: otherjoin', roomid, id, 'state = ', state);

    call();
});

socket.on('full', (roomid, id) => {
    state = 'leaved';
    console.log('receive msg: full', roomid, id, 'state = ', state);

    socket.disconnect();
    closeLocalMedia();

    console.error('房间已满');
});

socket.on('leaved', (roomid, id) => {
    state = 'leaved';
    console.log('receive msg: leaved', roomid, id, 'state = ', state);
    socket.disconnect();
});

socket.on('bye', (roomid, id) => {
    state = 'joined_unbind';
    closePeerConnection();
    console.log('receive msg: bye', roomid, id, 'state = ', state);
});

socket.on('disconnect', (socket) => {
    console.log('disconnect message', roomid);
    if (!(state === 'leaved')) {
      hangup();
      closeLocalMedia();
    }
    state = 'leaved'
});

socket.on('message', (data) => {
	var data = JSON.parse(data);
    console.log('receive msg: message', data);
//  /* 媒体协商 */
    if (data) {
      if (data.type === 'offer') {
        pc.setRemoteDescription(new RTCSessionDescription(data));

        pc.createAnswer()
            .then(getAnswer)
            .catch((e) => {
              console.error(e);
            });

      } else if (data.type === 'answer') {
        pc.setRemoteDescription(new RTCSessionDescription(data));
      } else if (data.type === 'candidate') {
        var candidate = new RTCIceCandidate({
          sdpMLineIndex: data.label,
          candidate: data.candidate
        });
        pc.addIceCandidate(candidate);

      } else {
        console.error('data type error');
      }
    }
});
	socket.emit('join', roomid);  // 加入房间 111111
}

function hangup() {
  if (pc) {
    pc.close();
    pc = null;
  }
}

/* 退出时关闭 track 流 */
function closeLocalMedia() {
  if (localStream && localStream.getTracks()) {
    localStream.getTracks().forEach((track) => {
      track.stop();
    });
  }
  localStream = null;
}

function leave() {
  if (socket) {
    socket.emit('leave', roomid);  // 离开房间 111111
  }

  /* 释放资源 */
  closePeerConnection();
  closeLocalMedia();
}

function createPeerConnection() {
  console.log('create RTCPeerConnection!');
  if (!pc) {
    pc = new RTCPeerConnection(servers);

    pc.onicecandidate = (e) => {

      if (e.candidate) {
		    sendMessage( JSON.stringify({
          type: 'candidate',
          label: event.candidate.sdpMLineIndex,
          id: event.candidate.sdpMid,
          candidate: event.candidate.candidate
     	  }  )
		    );
		    
        
      } else {
        console.log('this is the end candidate');
      }

    };

    pc.ontrack = getRemoteStream;

  } else {
    console.log('the pc have be created')
  }

  if ((localStream !== null || localStream !== undefined) && (pc !== null || pc !== undefined)) {
    localStream.getTracks().forEach((track) => {
      pc.addTrack(track, localStream);  // 进行添加, 并发送给远端。
    });
  } else {
    console.log('pc or localStream is null or undefined');
  }

}  /* createPeerConnection */

function closePeerConnection() {
  console.log('close RTCPeerConnection!');
  if (pc) {
    pc.close();
    pc = null;
  }
}

function getRemoteStream(e) {
  remoteStream = e.streams[0];
  remoteVideo.srcObject = e.streams[0];
}

function call() {
  if (state === 'joined_conn') {

    var options = {
      offerToReceiveVideo: 1,
      offerToReceiveAudio: 1,
    };
    pc.createOffer(options)
        .then(getOffer)
        .catch((e) => {
          console.error(e);
        });

  }
}

function getOffer(desc) {
  pc.setLocalDescription(desc);
  sendMessage(JSON.stringify(desc));
}

function getAnswer(desc) {
  pc.setLocalDescription(desc);
  sendMessage(JSON.stringify(desc));
}

function sendMessage(data) {
  console.log('send p2p message', data);
  if (socket) {
  	var jsonObject = {
			roomId: roomid,
			data:data 
		};
    socket.emit('message', JSON.stringify(jsonObject));
  }
}


  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于WebRTC实现视频点播可以通过以下步骤进行: 1. 获取视频文件:首先,你需要获取要点播的视频文件。这可以是事先录制好的视频文件,或者从其他来源获取的视频文件。 2. 将视频文件分割成块:将视频文件切分成较小的块,这样可以更好地控制视频的加载和播放过程。你可以使用工具(如FFmpeg)将视频文件切割成适当大小的块。 3. 设置服务器:你需要设置一个服务器来存储和提供视频块。这个服务器可以是你自己搭建的,也可以使用云服务提供商提供的服务器。确保服务器具有足够的带宽和存储空间来处理视频点播请求。 4. 建立WebRTC连接:使用WebRTC建立客户端与服务器之间的连接。客户端可以是基于浏览器的Web应用程序或移动应用程序。在连接建立之前,确保双方都能够互相通信。 5. 加载视频块:客户端通过WebRTC连接从服务器加载视频块。可以使用DataChannel API来传输视频数据块。根据需求,你可以选择一次加载一个块或者预加载多个块来提高播放效果。 6. 解码和播放:客户端接收到视频块后,使用WebRTC提供的API进行解码和播放。你可以使用HTML5的video元素来播放解码后的视频。根据需要,你可以添加进度条、音量控制等功能来提升用户体验。 需要注意的是,WebRTC主要用于实时通信,而不是传统的点播场景。因此,在使用WebRTC实现视频点播时,需要适当调整和扩展WebRTC的功能。另外,也可以考虑使用第三方库或框架来简化开发过程,如MediaElement.js或Video.js等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值