WebRTC,即Web Real-Time Communication,web实时通信技术。简单地说就是在web浏览器里面引入实时通信,包括音视频通话等。
本技术需要使用到socket,作为信令服务器,作为俩个浏览器信令交互的工具。
移动端是基于webview做的H5页面。
![]() |
![]() |
![]() |
![]() |
移动端 link 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;"> 拨打中...</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));
}
}