老板怒吼:今晚整一个B站弹幕交互功能

开发 前端 开发工具
2021 年了,还有不支持弹幕的视频网站吗,现在各种弹幕玩法层出不穷,抽奖,PPT 都上弹幕玩法了,不整个弹幕都说不过去了。

 [[422775]]

图片来自 包图网

今天笔者就抽空做了一个实时视频弹幕交互的小功能,不得不说这样的形式为看视频看直播,讲义 PPT,抽奖等形式增加了许多乐趣。

技术选型

①Netty

官方对于 Netty 的描述:

  1. https://netty.io/ 

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

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

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

②WebSocket

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

WebSocket API 也被 W3C 定为标准。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。

在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

为什么做这样的技术选型:

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

实现思路

①服务架构

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

②传输流程

如下图:

实现效果

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

视频直播弹幕示例

代码实现

①项目结构

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

②Java 服务端

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

先做一个 netty nio 的服务端:一个 nio 的服务,开启一个 tcp 端口。

  1. import io.netty.bootstrap.ServerBootstrap; 
  2. import io.netty.channel.ChannelFuture; 
  3. import io.netty.channel.EventLoopGroup; 
  4. import io.netty.channel.nio.NioEventLoopGroup; 
  5. import io.netty.channel.socket.nio.NioServerSocketChannel; 
  6.  
  7. /** 
  8.  * Copyright(c)lbhbinhao@163.com 
  9.  * @author liubinhao 
  10.  * @date 2021/1/14 
  11.  * ++++ ______                           ______             ______ 
  12.  * +++/     /|                         /     /|           /     /| 
  13.  * +/_____/  |                       /_____/  |         /_____/  | 
  14.  * |     |   |                      |     |   |        |     |   | 
  15.  * |     |   |                      |     |   |________|     |   | 
  16.  * |     |   |                      |     |  /         |     |   | 
  17.  * |     |   |                      |     |/___________|     |   | 
  18.  * |     |   |___________________   |     |____________|     |   | 
  19.  * |     |  /                  / |  |     |   |        |     |   | 
  20.  * |     |/ _________________/  /   |     |  /         |     |  / 
  21.  * |_________________________|/b    |_____|/           |_____|/ 
  22.  */ 
  23. public enum BulletChatServer { 
  24.     /** 
  25.      * Server instance 
  26.      */ 
  27.     SERVER; 
  28.  
  29.     private BulletChatServer(){ 
  30.         EventLoopGroup mainGroup = new NioEventLoopGroup(); 
  31.         EventLoopGroup subGroup  = new NioEventLoopGroup(); 
  32.         ServerBootstrap server = new ServerBootstrap(); 
  33.         server.group(mainGroup,subGroup) 
  34.                 .channel(NioServerSocketChannel.class) 
  35.                 .childHandler(new BulletChatInitializer()); 
  36.         ChannelFuture future = server.bind(9123); 
  37.     } 
  38.  
  39.     public static void main(String[] args) { 
  40.  
  41.     } 
  42.  

服务端的具体处理逻辑:

  1. import io.netty.channel.ChannelInitializer; 
  2. import io.netty.channel.ChannelPipeline; 
  3. import io.netty.channel.socket.SocketChannel; 
  4. import io.netty.handler.codec.http.HttpObjectAggregator; 
  5. import io.netty.handler.codec.http.HttpServerCodec; 
  6. import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; 
  7. import io.netty.handler.stream.ChunkedWriteHandler; 
  8. import io.netty.handler.timeout.IdleStateHandler; 
  9.  
  10. /** 
  11.  * Copyright(c)lbhbinhao@163.com 
  12.  * 
  13.  * @author liubinhao 
  14.  * @date 2021/1/14 
  15.  * ++++ ______                           ______             ______ 
  16.  * +++/     /|                         /     /|           /     /| 
  17.  * +/_____/  |                       /_____/  |         /_____/  | 
  18.  * |     |   |                      |     |   |        |     |   | 
  19.  * |     |   |                      |     |   |________|     |   | 
  20.  * |     |   |                      |     |  /         |     |   | 
  21.  * |     |   |                      |     |/___________|     |   | 
  22.  * |     |   |___________________   |     |____________|     |   | 
  23.  * |     |  /                  / |  |     |   |        |     |   | 
  24.  * |     |/ _________________/  /   |     |  /         |     |  / 
  25.  * |_________________________|/b    |_____|/           |_____|/ 
  26.  */ 
  27.  
  28. public class BulletChatInitializer extends ChannelInitializer<SocketChannel> { 
  29.     @Override 
  30.     protected void initChannel(SocketChannel ch) throws Exception { 
  31.         ChannelPipeline pipeline = ch.pipeline(); 
  32.         pipeline.addLast(new HttpServerCodec()); 
  33.         pipeline.addLast(new ChunkedWriteHandler()); 
  34.         pipeline.addLast(new HttpObjectAggregator(1024*64)); 
  35.         pipeline.addLast(new IdleStateHandler(8, 10, 12)); 
  36.         pipeline.addLast(new WebSocketServerProtocolHandler("/lbh")); 
  37.         pipeline.addLast(new BulletChatHandler()); 
  38.     } 

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

  1. import io.netty.channel.Channel; 
  2. import io.netty.channel.ChannelHandler; 
  3. import io.netty.channel.ChannelHandlerContext; 
  4. import io.netty.channel.SimpleChannelInboundHandler; 
  5. import io.netty.channel.group.ChannelGroup; 
  6. import io.netty.channel.group.DefaultChannelGroup; 
  7. import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 
  8. import io.netty.util.concurrent.EventExecutorGroup; 
  9. import io.netty.util.concurrent.GlobalEventExecutor; 
  10.  
  11. /** 
  12.  * Copyright(c)lbhbinhao@163.com 
  13.  * 
  14.  * @author liubinhao 
  15.  * @date 2021/1/14 
  16.  * ++++ ______                           ______             ______ 
  17.  * +++/     /|                         /     /|           /     /| 
  18.  * +/_____/  |                       /_____/  |         /_____/  | 
  19.  * |     |   |                      |     |   |        |     |   | 
  20.  * |     |   |                      |     |   |________|     |   | 
  21.  * |     |   |                      |     |  /         |     |   | 
  22.  * |     |   |                      |     |/___________|     |   | 
  23.  * |     |   |___________________   |     |____________|     |   | 
  24.  * |     |  /                  / |  |     |   |        |     |   | 
  25.  * |     |/ _________________/  /   |     |  /         |     |  / 
  26.  * |_________________________|/b    |_____|/           |_____|/ 
  27.  */ 
  28.  
  29. public class BulletChatHandler  extends SimpleChannelInboundHandler<TextWebSocketFrame> { 
  30.     // 用于记录和管理所有客户端的channel 
  31.     public static ChannelGroup channels = 
  32.             new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); 
  33.     @Override 
  34.     protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { 
  35.         // 获取客户端传输过来的消息 
  36.         String content = msg.text(); 
  37.         System.err.println("收到消息:"+ content); 
  38.         channels.writeAndFlush(new TextWebSocketFrame(content)); 
  39.         System.err.println("写出消息完成:"+content); 
  40.     } 
  41.  
  42.     @Override 
  43.     public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 
  44.         channels.add(ctx.channel()); 
  45.     } 
  46.  
  47.     @Override 
  48.     public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { 
  49.  
  50.         String channelId = ctx.channel().id().asShortText(); 
  51.         System.out.println("客户端被移除,channelId为:" + channelId); 
  52.         channels.remove(ctx.channel()); 
  53.     } 
  54.  
  55.     @Override 
  56.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 
  57.         cause.printStackTrace(); 
  58.         // 发生异常之后关闭连接(关闭channel),随后从ChannelGroup中移除 
  59.         ctx.channel().close(); 
  60.         channels.remove(ctx.channel()); 
  61.     } 
  62.  

③网页客户端实现

代码如下:

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3. <head> 
  4.     <meta charset="utf-8"
  5.     <meta http-equiv="X-UA-Compatible" content="IE=edge"
  6.     <title>Netty视频弹幕实现 Author:Binhao Liu</title> 
  7.     <link rel="stylesheet" href=""
  8.     <style type="text/css" media="screen"
  9.         * { 
  10.             margin: 0px; 
  11.             padding: 0px 
  12.         } 
  13.  
  14.         html, body { 
  15.             height: 100% 
  16.         } 
  17.  
  18.         body { 
  19.             overflow: hidden; 
  20.             background-color: #FFF; 
  21.             text-align: center; 
  22.         } 
  23.  
  24.         .flex-column { 
  25.             display: flex; 
  26.             flex-direction: column
  27.             justify-content: space-between;, align-items: center; 
  28.         } 
  29.  
  30.         .flex-row { 
  31.             display: flex; 
  32.             flex-direction: row; 
  33.             justify-content: center; 
  34.             align-items: center; 
  35.         } 
  36.  
  37.         .wrap { 
  38.             overflow: hidden; 
  39.             width: 70%; 
  40.             height: 600px; 
  41.             margin: 100px auto; 
  42.             padding: 20px; 
  43.             background-color: transparent; 
  44.             box-shadow: 0 0 9px #222; 
  45.             border-radius: 20px; 
  46.         } 
  47.  
  48.         .wrap .box { 
  49.             position: relative
  50.             width: 100%; 
  51.             height: 90%; 
  52.             background-color: #000000; 
  53.             border-radius: 10px 
  54.         } 
  55.  
  56.         .wrap .box span { 
  57.             position: absolute
  58.             top: 10px; 
  59.             left: 20px; 
  60.             display: block; 
  61.             padding: 10px; 
  62.             color: #336688 
  63.         } 
  64.  
  65.         .wrap .send { 
  66.             display: flex; 
  67.             width: 100%; 
  68.             height: 10%; 
  69.             background-color: #000000; 
  70.             border-radius: 8px 
  71.         } 
  72.  
  73.         .wrap .send input { 
  74.             width: 40%; 
  75.             height: 60%; 
  76.             border: 0; 
  77.             outline: 0; 
  78.             border-radius: 5px 0px 0px 5px; 
  79.             box-shadow: 0px 0px 5px #d9d9d9; 
  80.             text-indent: 1em 
  81.         } 
  82.  
  83.         .wrap .send .send-btn { 
  84.             width: 100px; 
  85.             height: 60%; 
  86.             background-color: #fe943b; 
  87.             color: #FFF; 
  88.             text-align: center; 
  89.             border-radius: 0px 5px 5px 0px; 
  90.             line-height: 30px; 
  91.             cursor: pointer; 
  92.         } 
  93.  
  94.         .wrap .send .send-btn:hover { 
  95.             background-color: #4cacdc 
  96.         } 
  97.     </style> 
  98. </head> 
  99. <script> 
  100.     var ws = new WebSocket("ws://localhost:9123/lbh"); 
  101.  
  102.     ws.onopen = function () { 
  103.         // Web Socket 已连接上,使用 send() 方法发送数据 
  104.         alert("数据发送中..."); 
  105.     }; 
  106.     ws.onmessage = function (e) { 
  107.         console.log("接受到消息:"+e.data); 
  108.         createEle(e.data); 
  109.     }; 
  110.     ws.onclose = function () { 
  111.         // 关闭 websocket 
  112.         alert("连接已关闭..."); 
  113.     }; 
  114.     function sendMsg(msg) { 
  115.         ws.send(msg) 
  116.     } 
  117.  
  118.  
  119. </script> 
  120. <body> 
  121. <div class="wrap flex-column"
  122.     <div class="box"
  123.         <video src="shape.mp4" width="100%" height="100%" controls autoplay></video> 
  124.     </div> 
  125.     <div class="send flex-row"
  126.  
  127.         <input type="text" class="con" placeholder="弹幕发送[]~(^v^)~*"/> 
  128.  
  129.         <div class="send-btn" onclick="javascript:sendMsg(document.querySelector('.con').value)">发送</div> 
  130.     </div> 
  131. </div> 
  132. <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js" type="text/javascript"></script> 
  133. <script> 
  134.     //1.获取元素 
  135.     var oBox = document.querySelector('.box');   //获取.box元素 
  136.     var cW = oBox.offsetWidth;   //获取box的宽度 
  137.     var cH = oBox.offsetHeight;   //获取box的高度 
  138.     function createEle(txt) { 
  139.         //动态生成span标签 
  140.         var oMessage = document.createElement('span');   //创建标签 
  141.         oMessage.innerHTML = txt;   //接收参数txt并且生成替换内容 
  142.         oMessage.style.left = cW + 'px';  //初始化生成位置x 
  143.         oBox.appendChild(oMessage);   //把标签塞到oBox里面 
  144.         roll.call(oMessage, { 
  145.             //call改变函数内部this的指向 
  146.             timing: ['linear''ease-out'][~~(Math.random() * 2)], 
  147.             color: '#' + (~~(Math.random() * (1 << 24))).toString(16), 
  148.             top: random(0, cH), 
  149.             fontSize: random(16, 32) 
  150.         }); 
  151.     } 
  152.  
  153.     function roll(opt) { 
  154.         //弹幕滚动 
  155.         //如果对象中不存在timing 初始化 
  156.         opt.timing = opt.timing || 'linear'
  157.         opt.color = opt.color || '#fff'
  158.         opt.top = opt.top || 0; 
  159.         opt.fontSize = opt.fontSize || 16; 
  160.         this._left = parseInt(this.offsetLeft);   //获取当前left的值 
  161.         this.style.color = opt.color;   //初始化颜色 
  162.         this.style.top = opt.top + 'px'
  163.         this.style.fontSize = opt.fontSize + 'px'
  164.         this.timer = setInterval(function () { 
  165.             if (this._left <= 100) { 
  166.                 clearInterval(this.timer);   //终止定时器 
  167.                 this.parentNode.removeChild(this); 
  168.                 return;   //终止函数 
  169.             } 
  170.             switch (opt.timing) { 
  171.                 case 'linear':   //如果匀速 
  172.                     this._left += -2; 
  173.                     break; 
  174.                 case 'ease-out':   // 
  175.                     this._left += (0 - this._left) * .01; 
  176.                     break; 
  177.             } 
  178.             this.style.left = this._left + 'px'
  179.         }.bind(this), 1000 / 60); 
  180.     } 
  181.  
  182.     function random(start, end) { 
  183.         //随机数封装 
  184.         return start + ~~(Math.random() * (end - start)); 
  185.     } 
  186.  
  187.     var aLi = document.querySelectorAll('li');   //10 
  188.  
  189.     function forEach(ele, cb) { 
  190.         for (var i = 0, len = aLi.length; i < len; i++) { 
  191.             cb && cb(ele[i], i); 
  192.         } 
  193.     } 
  194.  
  195.     forEach(aLi, function (ele, i) { 
  196.         ele.style.left = i * 100 + 'px'
  197.     }); 
  198.     //产生闭包 
  199.     var obj = { 
  200.         num: 1, 
  201.         addfunction () { 
  202.             this.num++;   //obj.num = 2; 
  203.             (function () { 
  204.                 console.log(this.num); 
  205.             }) 
  206.         } 
  207.     }; 
  208.     obj.add();//window 
  209.  
  210. </script> 
  211. </body> 
  212. </html> 

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

小结

这个还是很简单,笔者写这个的时候一会儿就写完了。不过这也得益于笔者很久以前就写过 Netty 的服务,对于 HTTP,TCP 之类协议也比较熟悉。

只有前端会有些难度,问下度娘,也很快能做完,在此分享出来与诸君分享,有问题可找笔者交流。

作者:兴趣使然的程序猿

编辑:陶家龙

出处:http://adkx.net/w71wf

 

责任编辑:武晓燕 来源: adkx.net
相关推荐

2022-04-26 10:47:53

分配权限vuejs

2018-01-04 09:20:55

python爬虫视频弹幕

2024-08-06 10:16:52

Java AgentJava

2020-10-20 14:12:54

B站开源弹幕

2021-10-29 07:49:23

Python弹幕播放

2021-04-01 07:44:45

排名调整Java Java基础

2020-03-26 09:36:06

AB Test平台的流量

2009-08-26 17:05:55

.NET委托

2022-12-26 00:00:05

Python爬虫B站弹幕

2012-12-07 10:04:58

管理项目管理日常管理

2021-07-15 09:49:08

B站宕机黑客

2012-04-24 09:22:16

软件测试

2021-04-09 08:13:14

API网关互联网

2013-08-13 10:07:57

移动互联网移动开发转行创业

2021-12-14 17:19:15

存储数据

2020-04-06 20:47:42

FishShellLinux

2022-06-07 07:38:43

威士忌气味测试

2021-03-05 07:47:07

工作流引擎节点

2015-06-03 16:58:01

创业创业干货
点赞
收藏

51CTO技术栈公众号