欢迎关注方志朋的博客,回复”666“获面试宝典

2021年了,还有不支持弹幕的视频网站吗,现在各种弹幕玩法层出不穷,抽奖,ppt都上弹幕玩法了,不整个弹幕都说不过去了,今天笔者就抽空做了一个实时视频弹幕交互功能的实现,不得不说这样的形式为看视频看直播,讲义PPT,抽奖等形式增加了许多乐趣。

1 技术选型

1.1 netty

官方对于netty的描述:

https://netty.io/

主要关键词描述: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端口。

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;/*** 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();server.group(mainGroup,subGroup).channel(NioServerSocketChannel.class).childHandler(new BulletChatInitializer());ChannelFuture future = server.bind(9123);}public static void main(String[] args) {}}

4.2.2 服务端的具体处理逻辑

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;/*** Copyright(c)lbhbinhao@163.com** @author liubinhao* @date 2021/1/14* ++++ ______                           ______             ______* +++/     /|                         /     /|           /     /|* +/_____/  |                       /_____/  |         /_____/  |* |     |   |                      |     |   |        |     |   |* |     |   |                      |     |   |________|     |   |* |     |   |                      |     |  /         |     |   |* |     |   |                      |     |/___________|     |   |* |     |   |___________________   |     |____________|     |   |* |     |  /                  / |  |     |   |        |     |   |* |     |/ _________________/  /   |     |  /         |     |  /* |_________________________|/b    |_____|/           |_____|/*/public class BulletChatInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new HttpServerCodec());pipeline.addLast(new ChunkedWriteHandler());pipeline.addLast(new HttpObjectAggregator(1024*64));pipeline.addLast(new IdleStateHandler(8, 10, 12));pipeline.addLast(new WebSocketServerProtocolHandler("/lbh"));pipeline.addLast(new BulletChatHandler());}
}

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

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.GlobalEventExecutor;/*** Copyright(c)lbhbinhao@163.com** @author liubinhao* @date 2021/1/14* ++++ ______                           ______             ______* +++/     /|                         /     /|           /     /|* +/_____/  |                       /_____/  |         /_____/  |* |     |   |                      |     |   |        |     |   |* |     |   |                      |     |   |________|     |   |* |     |   |                      |     |  /         |     |   |* |     |   |                      |     |/___________|     |   |* |     |   |___________________   |     |____________|     |   |* |     |  /                  / |  |     |   |        |     |   |* |     |/ _________________/  /   |     |  /         |     |  /* |_________________________|/b    |_____|/           |_____|/*/public class BulletChatHandler  extends SimpleChannelInboundHandler<TextWebSocketFrame> {// 用于记录和管理所有客户端的channelpublic static ChannelGroup channels =new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);@Overrideprotected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {// 获取客户端传输过来的消息String content = msg.text();System.err.println("收到消息:"+ content);channels.writeAndFlush(new TextWebSocketFrame(content));System.err.println("写出消息完成:"+content);}@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {channels.add(ctx.channel());}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {String channelId = ctx.channel().id().asShortText();System.out.println("客户端被移除,channelId为:" + channelId);channels.remove(ctx.channel());}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();// 发生异常之后关闭连接(关闭channel),随后从ChannelGroup中移除ctx.channel().close();channels.remove(ctx.channel());}}

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) {console.log("接受到消息:"+e.data);createEle(e.data);};ws.onclose = function () {// 关闭 websocketalert("连接已关闭...");};function sendMsg(msg) {ws.send(msg)}</script>
<body>
<div class="wrap flex-column"><div class="box"><video src="shape.mp4" 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(document.querySelector('.con').value)">发送</div></div>
</div>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js" 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');   //创建标签oMessage.innerHTML = txt;   //接收参数txt并且生成替换内容oMessage.style.left = cW + 'px';  //初始化生成位置xoBox.appendChild(oMessage);   //把标签塞到oBox里面roll.call(oMessage, {//call改变函数内部this的指向timing: ['linear', 'ease-out'][~~(Math.random() * 2)],color: '#' + (~~(Math.random() * (1 << 24))).toString(16),top: random(0, cH),fontSize: random(16, 32)});}function roll(opt) {//弹幕滚动//如果对象中不存在timing 初始化opt.timing = opt.timing || 'linear';opt.color = opt.color || '#fff';opt.top = opt.top || 0;opt.fontSize = opt.fontSize || 16;this._left = parseInt(this.offsetLeft);   //获取当前left的值this.style.color = opt.color;   //初始化颜色this.style.top = opt.top + 'px';this.style.fontSize = opt.fontSize + 'px';this.timer = setInterval(function () {if (this._left <= 100) {clearInterval(this.timer);   //终止定时器this.parentNode.removeChild(this);return;   //终止函数}switch (opt.timing) {case 'linear':   //如果匀速this._left += -2;break;case 'ease-out':   //this._left += (0 - this._left) * .01;break;}this.style.left = this._left + 'px';}.bind(this), 1000 / 60);}function random(start, end) {//随机数封装return start + ~~(Math.random() * (end - start));}var aLi = document.querySelectorAll('li');   //10function forEach(ele, cb) {for (var i = 0, len = aLi.length; i < len; i++) {cb && cb(ele[i], i);}}forEach(aLi, function (ele, i) {ele.style.left = i * 100 + 'px';});//产生闭包var obj = {num: 1,add: function () {this.num++;   //obj.num = 2;(function () {console.log(this.num);})}};obj.add();//window</script>
</body>
</html>

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

5 小结

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

(感谢阅读,希望对你所有帮助)

来源:binhao.blog.csdn.net/article/details/112631642

热门内容:干掉SQL中的like,我用es后运营小姐姐们都说好快!假如 IDEA 也加入防沉迷功能...当 Docker 遇到 Intellij IDEA,再次解放了生产力~你真的会写for循环吗?来看看这些常见的for循环优化方式最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

明天见(。・ω・。)ノ♡

websocket+netty实时视频弹幕交互功能(Java版)相关推荐

  1. 乐视直播api编写java,GitHub - czp3009/bilibili-live-api: bilibili 直播弹幕协议 API Java 版(deprecated)...

    Deprecated 本仓库已废弃. 本项目使用 Bilibili Web 协议, 随着 Bilibili 前端的更新, 已经无法正常使用. 本项目仅提供 Bilibili 直播间的弹幕获取有关 AP ...

  2. 微信小程序+服务器 利用Swoole的WebSocket实现简单的交互功能

    1.服务端  2.文件内写入(切记:监听的端口号,一定要提前放行  我的是9501) <?php//创建WebSocket Server对象,监听0.0.0.0:9501端口$ws = new ...

  3. 阿衰java免费版_flutter plugin之路:flutter与原生交互传值OC/java版(一)

    说在前头:flutter虽然很牛掰,但是目前很多功能或者插件的实现其实还是需要通过原生来实现,然后通过flutter和原生的交互传值实现数据的交换. -本篇是flutter调用原生方法,然后原生执行完 ...

  4. java版mosquitto客户端使用SSL功能的具体操作总结

    在开发java版mosquitto客户端程序时需要使用paho库,如果开发的java客户端要用ssl功能,则需要Bouncy Castle库:在使用ssl功能时,需要证书文件进行进行身份认证,但在测试 ...

  5. mc用什么版本的java_MC版本Java版5大功能优势

    众所周知Java开发应用广泛,Java版本被很多人认为是最好的MC版本,MC版本Java版5大功能优势有什么呢?Java版开发优势:支持更多的模组.各种节日彩蛋.官方更高的重视性.游戏画面色彩更好.操 ...

  6. java 版百度网盘功能

    java 版百度网盘功能,目前已经实现:  1:百度网盘登录  2:列出百度网盘文件  3: 切换目录 4: 多线程下载文件 速度有待优化.思路已经成型. 源码地址:https://gitee.com ...

  7. 微信开放平台分账功能实战(Java版)

    ####近期为了接入微信支付以及微信分账等功能,开发了微信类的一系列接口,下面就本着开发的目标,再次记录回顾一下微信开放的步骤.. ####目标:通过微信支付,实现分账到运营商的功能. ####根据实 ...

  8. Java版WebSocket消息推送系统搭建

    Java版WebSocket消息推送系统搭建 最近在做消息推送,网上查了一些资料,开始想的是用MQ来做,后面发现用WebSocket来做的话感觉应该要简单点,话不多说,准备撸代码. 后端核心代码 /* ...

  9. [开源]java版QQ机器人集成小黄鸡功能

    项目采用开源程序IQQ.内置webQQ协议.并且使用十分方便.直接调用即可.      项目采用webService接口调用可以不用重新登录QQ即可实现项目的修改.测试期间 多次登录QQ导致QQ被检测 ...

最新文章

  1. 马斯克Neuralink联合创始人宣布离职,此前尚未推出上市产品
  2. python子类初始化父类_Python实现子类调用父类的初始化实例
  3. FPGA RAM存储器设计
  4. ViewData 和 echarts用法
  5. Sharepoin学习笔记—架构系列--Sharepoint的网页(Page),网页解析(Parsing)与解析安全处理(Security)...
  6. 【EMNLP2020】一种多层对多层的BERT蒸馏方法
  7. 【设计模式】实现线程安全单例模式的五种方式
  8. wordpress iDowns-v1.8.3下载类主题无缝对接erphpdown会员中心+在线充值+VIP开通+卡密插件
  9. ERROR:此文件包含病毒,已删除
  10. iOS打包静态库(完整篇)
  11. 优矿-获取商品期权数据
  12. [BZOJ1776] [Usaco2010 Hol]cowpol 奶牛政坛
  13. 翁恺c语言程序设计入门期末,程序设计入门-C语言 翁恺.doc
  14. java实现关键字查询_SpringData关键字查询实现方法详解
  15. python3.4勾股定理代码_用于解答算法题目的Python3代码框架
  16. 直方图的计算,绘制与分析
  17. windows下的ubuntu盘符问题
  18. ajaxfileupload IE10 拒绝访问
  19. 大数据分析就业培训课程大纲分享
  20. 基于javaweb+springboot的水果商城在线商城(java+Springboot+ssm+mysql+jsp+maven)

热门文章

  1. 常见基本不等式的几何解释
  2. cmd中实现scott的解锁和开锁以及授予dba权限
  3. LaxTex-----参考文献中同名作者被默认缺省的问题
  4. CORS在Spring中的实现
  5. 复习心得 JAVA异常处理
  6. jquery仿邮箱文本输入框自动加载邮箱后缀
  7. Android拷贝工程不覆盖原工程的配置方法
  8. ORM查询语言(OQL)简介--概念篇
  9. Linux的文件系统
  10. 如何用javascript控制上传文件的大小