本文使用webtrc实现了一个简单的语音视频聊天室、支持多人音视频聊天、屏幕共享。
音视频功能需要在有Https协议的域名下才能获取到设备信息,
测试环境搭建Https服务参考Windows下Nginx配置SSL实现Https访问(包含openssl证书生成)_殷长庆的博客-CSDN博客
正式环境可以申请一个免费的证书
复杂网络环境下需要自己搭建turnserver,网络上搜索大多是使用coturn来搭建turn服务
turn默认监听端口3478,可以使用webrtc.github.io 测试服务是否可用
本文在局域网内测试,不必要部署turn,使用的谷歌的stun:stun.l.google.com:19302
webrtc参考文章
WebRTC技术简介 - 知乎 (zhihu.com)
服务端使用netty构建一个websocket服务,用来完成为音视频传递ICE信息等工作。
4.0.0
com.luck.cc
cc-im
1.0-SNAPSHOT
cc-im
http://maven.apache.org
${env.JAVA_HOME}
UTF-8
1.8
io.netty
netty-all
4.1.74.Final
cn.hutool
hutool-all
5.5.7
maven-compiler-plugin
1.8
1.8
maven-assembly-plugin
3.0.0
com.luck.im.ServerStart
jar-with-dependencies
make-assembly
package
single
聊天室服务
package com.luck.im;
import java.util.List;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.MessageToMessageCodec;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
public class ChatSocket {
private static EventLoopGroup bossGroup = new NioEventLoopGroup();
private static EventLoopGroup workerGroup = new NioEventLoopGroup();
private static ChannelFuture channelFuture;
/**
* 启动服务代理
*
* @throws Exception
*/
public static void startServer() throws Exception {
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(
new WebSocketServerProtocolHandler("/myim", null, true, Integer.MAX_VALUE, false));
pipeline.addLast(new MessageToMessageCodec() {
@Override
protected void decode(ChannelHandlerContext ctx, TextWebSocketFrame frame,
List
聊天室业务
package com.luck.im;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.AttributeKey;
import io.netty.util.internal.StringUtil;
public class ChatHandler extends SimpleChannelInboundHandler {
/** 用户集合 */
private static Map umap = new ConcurrentHashMap<>();
/** channel绑定自己的用户ID */
public static final AttributeKey UID = AttributeKey.newInstance("uid");
@Override
public void channelRead0(ChannelHandlerContext ctx, String msg) {
JSONObject parseObj = JSONUtil.parseObj(msg);
Integer type = parseObj.getInt("t");
String uid = parseObj.getStr("uid");
String tid = parseObj.getStr("tid");
switch (type) {
case 0:
// 心跳
break;
case 1:
// 用户加入聊天室
umap.put(uid, ctx.channel());
ctx.channel().attr(UID).set(uid);
umap.forEach((x, y) -> {
if (!x.equals(uid)) {
JSONObject json = new JSONObject();
json.set("t", 2);
json.set("uid", uid);
json.set("type", "join");
y.writeAndFlush(json.toString());
}
});
break;
case 2:
Channel uc = umap.get(tid);
if (null != uc) {
uc.writeAndFlush(msg);
}
break;
case 9:
// 用户退出聊天室
umap.remove(uid);
leave(ctx, uid);
ctx.close();
break;
default:
break;
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
String uid = ctx.channel().attr(UID).get();
if (StringUtil.isNullOrEmpty(uid)) {
super.channelInactive(ctx);
return;
}
ctx.channel().attr(UID).set(null);
umap.remove(uid);
leave(ctx, uid);
super.channelInactive(ctx);
}
/**
* 用户退出
*
* @param ctx
* @param uid
*/
private void leave(ChannelHandlerContext ctx, String uid) {
umap.forEach((x, y) -> {
if (!x.equals(uid)) {
// 把数据转到用户服务
JSONObject json = new JSONObject();
json.set("t", 9);
json.set("uid", uid);
y.writeAndFlush(json.toString());
}
});
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
启动类
package com.luck.im;
public class ServerStart {
public static void main(String[] args) throws Exception {
// 启动聊天室
ChatSocket.startServer();
}
}
网页主要使用了adapter-latest.js,下载地址webrtc.github.io
github访问不了可以用webrtc/adapter-latest.js-Javascript文档类资源-CSDN文库
index.html
聊天室
上面的index.html文件放到D盘根目录下了,然后配置一下websocket
server {
listen 443 ssl;
server_name mytest.com;
ssl_certificate lee/lee.crt;
ssl_certificate_key lee/lee.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
root d:/;
index index.html index.htm index.php;
}
location /myim {
proxy_pass http://127.0.0.1:8321/myim;
}
}
java启动
java -jar cc-im.jar
网页访问
https://127.0.0.1/index.html