作家:刘滨浩

来源:

2021年,还有不支持弹幕的视频网站吗?现在各种弹幕游戏层出不穷,抽奖、PPT都上了弹幕游戏。不能说整个弹幕。今天笔者抽出时间实现了实时视频弹幕交互功能。这种形式为看视频看直播,讲义PPT,抽奖等形式增添了很多乐趣。(威廉莎士比亚、Northern Exposure(美国电视剧)、抽奖等)

1 技术选型

1.1 netty

官方对于netty的描述:

主要关键词描述:netty是异步事件驱动网络框架,可做各种协议服务端,并且支持了FTP,SMTP,HTTP等很多协议,并且性能,稳定性,灵活性都很棒。

可以看到netty整体架构上分了三个部分:

  • 以零拷贝,一致性接口,扩展事件模型的底层核心。
  • Socket,Datagram,Pipe,Http Tunnel作为传输媒介。
  • 传输支持的各种协议,HTTP&WebSocket,SSL,大文件,zlib/gzip压缩,文本,二进制,Google Protobuf等各种各种的传输形式。

1.2 WebSocket

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

1.3 为什么做这样的技术选型。

  • 由上述可知,实时直播交互作为互动式是一个双向数据传输过程。所以使用webSocket。
  • netty本身支持了webSocket协议的实现,让实现更加简单方便。

2 实现思路

2.1 服务架构

整体架构是所有客户端都和我的服务端开启一个双向通道的架构。

2.2 传输流程

3 实现效果

3.1 视频展示

先看看效果吧,是不是perfect,接下来就来看具体代码是怎么实现的吧。

视频直播弹幕示例

4 代码实现

4.1 项目结构

一个maven项目,将代码放一个包下就行。

4.2 Java服务端

Java服务端代码,总共三个类,Server,Initailizer和 Handler。

4.2.1 先做一个netty nio的服务端:

一个nio的服务,开启一个tcp端口。

/**
* Copyright(c)lbhbinhao@163.com
* @author liubinhao
* @date 2021/1/14
* ++++ ______ ______ ______
* +++/ /| / /| / /|
* +/_____/ | /_____/ | /_____/ |
* | | | | | | | | |
* | | | | | |________| | |
* | | | | | / | | |
* | | | | |/___________| | |
* | | |___________________ | |____________| | |
* | | / / | | | | | | |
* | |/ _________________/ / | | / | | /
* |_________________________|/b |_____|/ |_____|/
*/
public enum BulletChatServer {
/**
* Server instance
*/
SERVER;

private BulletChatServer(){
EventLoopGroup mainGroup = new NioEventLoopGroup();
EventLoopGroup subGroup = new NioEventLoopGroup();
ServerBootstrap server = new ServerBootstrap();
(mainGroup,subGroup)
.channel)
.childHandler(new BulletChatInitializer());
ChannelFuture future = (9123);
}

public static void main(String[] args) {

}

}

4.2.2 服务端的具体处理逻辑

/**
* Copyright(c)lbhbinhao@163.com
*
* @author liubinhao
* @date 2021/1/14
* ++++ ______ ______ ______
* +++/ /| / /| / /|
* +/_____/ | /_____/ | /_____/ |
* | | | | | | | | |
* | | | | | |________| | |
* | | | | | / | | |
* | | | | |/___________| | |
* | | |___________________ | |____________| | |
* | | / / | | | | | | |
* | |/ _________________/ / | | / | | /
* |_________________________|/b |_____|/ |_____|/
*/

public class BulletChatInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
(new HttpServerCodec());
(new ChunkedWriteHandler());
(new HttpObjectAggregator(1024*64));
(new IdleStateHandler(8, 10, 12));
(new WebSocketServerProtocolHandler("/lbh"));
(new BulletChatHandler());
}
}

后台处理逻辑,接受到消息,写出到所有的客户端:

/**
* Copyright(c)lbhbinhao@163.com
*
* @author liubinhao
* @date 2021/1/14
* ++++ ______ ______ ______
* +++/ /| / /| / /|
* +/_____/ | /_____/ | /_____/ |
* | | | | | | | | |
* | | | | | |________| | |
* | | | | | / | | |
* | | | | |/___________| | |
* | | |___________________ | |____________| | |
* | | / / | | | | | | |
* | |/ _________________/ / | | / | | /
* |_________________________|/b |_____|/ |_____|/
*/

public class BulletChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
// 用于记录和管理所有客户端的channel
public static ChannelGroup channels =
new DefaultChannelGrou);
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
// 获取客户端传输过来的消息
String content = m();
Sy("收到消息:"+ content);
c(new TextWebSocketFrame(content));
Sy("写出消息完成:"+content);
}

@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
c());
}

@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {

String channelId = c().id().asShortText();
Sy("客户端被移除,channelId为:" + channelId);
c());
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cau();
// 发生异常之后关闭连接(关闭channel),随后从ChannelGroup中移除
c().close();
c());
}

}

4.3 网页客户端实现

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Netty视频弹幕实现 Author:Binhao Liu</title>
<link rel="stylesheet" href="">
<style type="text/css" media="screen">
* {
margin: 0px;
padding: 0px
}

html, body {
height: 100%
}

body {
overflow: hidden;
background-color: #FFF;
text-align: center;
}

.flex-column {
display: flex;
flex-direction: column;
justify-content: space-between;, align-items: center;
}

.flex-row {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}

.wrap {
overflow: hidden;
width: 70%;
height: 600px;
margin: 100px auto;
padding: 20px;
background-color: transparent;
box-shadow: 0 0 9px #222;
border-radius: 20px;
}

.wrap .box {
position: relative;
width: 100%;
height: 90%;
background-color: #000000;
border-radius: 10px
}

.wrap .box span {
position: absolute;
top: 10px;
left: 20px;
display: block;
padding: 10px;
color: #336688
}

.wrap .send {
display: flex;
width: 100%;
height: 10%;
background-color: #000000;
border-radius: 8px
}

.wrap .send input {
width: 40%;
height: 60%;
border: 0;
outline: 0;
border-radius: 5px 0px 0px 5px;
box-shadow: 0px 0px 5px #d9d9d9;
text-indent: 1em
}

.wrap .send .send-btn {
width: 100px;
height: 60%;
background-color: #fe943b;
color: #FFF;
text-align: center;
border-radius: 0px 5px 5px 0px;
line-height: 30px;
cursor: pointer;
}

.wrap .send .send-btn:hover {
background-color: #4cacdc
}
</style>
</head>
<script>
var ws = new WebSocket("ws://localhost:9123/lbh");

ws.onopen = function () {
// Web Socket 已连接上,使用 send() 方法发送数据
alert("数据发送中...");
};
ws.onmessage = function (e) {
con("接受到消息:"+e.data);
createEle);
};
ws.onclose = function () {
// 关闭 websocket
alert("连接已关闭...");
};
function sendMsg(msg) {
ws.send(msg)
}

</script>
<body>
<div class="wrap flex-column">
<div class="box">
<video src="; width="100%" height="100%" controls autoplay></video>
</div>
<div class="send flex-row">

<input type="text" class="con" placeholder="弹幕发送[]~(^v^)~*"/>

<div class="send-btn" onclick="javascript:sendMsg('.con').value)">发送</div>
</div>
</div>
<script src="; type="text/javascript"></script>
<script>
//1.获取元素
var oBox = document.querySelector('.box'); //获取.box元素
var cW = oBox.offsetWidth; //获取box的宽度
var cH = oBox.offsetHeight; //获取box的高度
function createEle(txt) {
//动态生成span标签
var oMessage = document.createElement('span'); //创建标签
oMe = txt; //接收参数txt并且生成替换内容
oMe = cW + 'px'; //初始化生成位置x
oBox.appendChild(oMessage); //把标签塞到oBox里面
roll.call(oMessage, {
//call改变函数内部this的指向
timing: ['linear', 'ease-out'][~~() * 2)],
color: '#' + (~~() * (1 << 24))).toString(16),
top: random(0, cH),
fontSize: random(16, 32)
});
}

function roll(opt) {
//弹幕滚动
//如果对象中不存在timing 初始化
o = o || 'linear';
o = o || '#fff';
o = o || 0;
o = o || 16;
= parseIn); //获取当前left的值
= o; //初始化颜色
= o + 'px';
= o + 'px';
= setInterval(function () {
if ( <= 100) {
clearInterval(); //终止定时器
(this);
return; //终止函数
}
switch (o) {
case 'linear': //如果匀速
+= -2;
break;
case 'ease-out': //
+= (0 - ) * .01;
break;
}
= + 'px';
}.bind(this), 1000 / 60);
}

function random(start, end) {
//随机数封装
return start + ~~() * (end - start));
}

var aLi = document.querySelectorAll('li'); //10

function forEach(ele, cb) {
for (var i = 0, len = aLi.length; i < len; i++) {
cb && cb(ele[i], i);
}
}

forEach(aLi, function (ele, i) {
ele. = i * 100 + 'px';
});
//产生闭包
var obj = {
num: 1,
add: function () {
; = 2;
(function () {
con);
})
}
};
obj.add();//window

</script>
</body>
</html>

这样一个实时的视频弹幕功能就完成啦,是不是很简单,各位小伙伴快来试试吧。

5 小结

上班撸代码,下班继续撸代码写博客,这个还是很简单,笔者写这个的时候一会儿就写完了,不过这也得益于笔者很久以前就写过netty的服务,对于Http,Tcp之类协议也比较熟悉,只有前端会有些难度,问下度娘,也很快能做完,在此分享出来与诸君分享,有问题可找笔者交流。

1.《【unhandled exception caught】老同学十块钱让我帮做个视频网站吧,带弹幕那种》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。

2.《【unhandled exception caught】老同学十块钱让我帮做个视频网站吧,带弹幕那种》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。

3.文章转载时请保留本站内容来源地址,https://www.cxvn.com/gl/djyxgl/229048.html