问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

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

发布网友 发布时间:2024-09-30 08:00

我来回答

1个回答

热心网友 时间:2024-10-22 14:34

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

一.技术选型1.1netty

官方对于netty的描述:netty.io/

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

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

以零拷贝,一致性接口,扩展事件模型的底层核心。

Socket,Datagram,Pipe,HttpTunnel作为传输媒介。

传输支持的各种协议,HTTP&WebSocket,SSL,大文件,zlib/gzip压缩,文本,二进制,GoogleProtobuf等各种各种的传输形式。

1.2WebSocket

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

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

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

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

二.实现思路2.1服务架构

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

如下图:

2.2传输流程

如下图:

三.实现效果

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

图片视频直播弹幕示例

四.代码实现4.1项目结构

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

4.2Java服务端

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

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

importio.netty.bootstrap.ServerBootstrap;importio.netty.channel.ChannelFuture;importio.netty.channel.EventLoopGroup;importio.netty.channel.nio.NioEventLoopGroup;importio.netty.channel.socket.nio.NioServerSocketChannel;/***Copyright(c)lbhbinhao@163.com*@authorliubinhao*@date2021/1/14*++++__________________*+++//|//|//|*+/_____/|/_____/|/_____/|*|||||||||*||||||________|||*|||||/|||*|||||/___________|||*|||___________________||____________|||*||//|||||||*||/_________________//||/||/*|_________________________|/b|_____|/|_____|/*/publicenumBulletChatServer{/***Serverinstance*/SERVER;privateBulletChatServer(){EventLoopGroupmainGroup=newNioEventLoopGroup();EventLoopGroupsubGroup=newNioEventLoopGroup();ServerBootstrapserver=newServerBootstrap();server.group(mainGroup,subGroup).channel(NioServerSocketChannel.class).childHandler(newBulletChatInitializer());ChannelFuturefuture=server.bind(9123);}publicstaticvoidmain(String[]args){}}

4.2.2服务端的具体处理逻辑

importio.netty.channel.ChannelInitializer;importio.netty.channel.ChannelPipeline;importio.netty.channel.socket.SocketChannel;importio.netty.handler.codec.http.HttpObjectAggregator;importio.netty.handler.codec.http.HttpServerCodec;importio.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;importio.netty.handler.stream.ChunkedWriteHandler;importio.netty.handler.timeout.IdleStateHandler;/***Copyright(c)lbhbinhao@163.com**@authorliubinhao*@date2021/1/14*++++__________________*+++//|//|//|*+/_____/|/_____/|/_____/|*|||||||||*||||||________|||*|||||/|||*|||||/___________|||*|||___________________||____________|||*||//|||||||*||/_________________//||/||/*|_________________________|/b|_____|/|_____|/*/publicclassBulletChatInitializerextendsChannelInitializer<SocketChannel>{@OverrideprotectedvoidinitChannel(SocketChannelch)throwsException{ChannelPipelinepipeline=ch.pipeline();pipeline.addLast(newHttpServerCodec());pipeline.addLast(newChunkedWriteHandler());pipeline.addLast(newHttpObjectAggregator(1024*64));pipeline.addLast(newIdleStateHandler(8,10,12));pipeline.addLast(newWebSocketServerProtocolHandler("/lbh"));pipeline.addLast(newBulletChatHandler());}}

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

importio.netty.channel.Channel;importio.netty.channel.ChannelHandler;importio.netty.channel.ChannelHandlerContext;importio.netty.channel.SimpleChannelInboundHandler;importio.netty.channel.group.ChannelGroup;importio.netty.channel.group.DefaultChannelGroup;importio.netty.handler.codec.http.websocketx.TextWebSocketFrame;importio.netty.util.concurrent.EventExecutorGroup;importio.netty.util.concurrent.GlobalEventExecutor;/***Copyright(c)lbhbinhao@163.com**@authorliubinhao*@date2021/1/14*++++__________________*+++//|//|//|*+/_____/|/_____/|/_____/|*|||||||||*||||||________|||*|||||/|||*|||||/___________|||*|||___________________||____________|||*||//|||||||*||/_________________//||/||/*|_________________________|/b|_____|/|_____|/*/publicclassBulletChatHandlerextendsSimpleChannelInboundHandler<TextWebSocketFrame>{//用于记录和管理所有客户端的channelpublicstaticChannelGroupchannels=newDefaultChannelGroup(GlobalEventExecutor.INSTANCE);@OverrideprotectedvoidchannelRead0(ChannelHandlerContextctx,TextWebSocketFramemsg)throwsException{//获取客户端传输过来的消息Stringcontent=msg.text();System.err.println("收到消息:"+content);channels.writeAndFlush(newTextWebSocketFrame(content));System.err.println("写出消息完成:"+content);}@OverridepublicvoidhandlerAdded(ChannelHandlerContextctx)throwsException{channels.add(ctx.channel());}@OverridepublicvoidhandlerRemoved(ChannelHandlerContextctx)throwsException{StringchannelId=ctx.channel().id().asShortText();System.out.println("客户端被移除,channelId为:"+channelId);channels.remove(ctx.channel());}@OverridepublicvoidexceptionCaught(ChannelHandlerContextctx,Throwablecause)throwsException{cause.printStackTrace();//发生异常之后关闭连接(关闭channel),随后从ChannelGroup中移除ctx.channel().close();channels.remove(ctx.channel());}}

4.3网页客户端实现

<!DOCTYPEhtml><html><head><metacharset="utf-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><title>Netty视频弹幕实现Author:BinhaoLiu</title><linkrel="stylesheet"href=""><styletype="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:100pxauto;padding:20px;background-color:transparent;box-shadow:009px#222;border-radius:20px;}.wrap.box{position:relative;width:100%;height:90%;background-color:#000000;border-radius:10px}.wrap.boxspan{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.sendinput{width:40%;height:60%;border:0;outline:0;border-radius:5px0px0px5px;box-shadow:0px0px5px#d9d9d9;text-indent:1em}.wrap.send.send-btn{width:100px;height:60%;background-color:#fe943b;color:#FFF;text-align:center;border-radius:0px5px5px0px;line-height:30px;cursor:pointer;}.wrap.send.send-btn:hover{background-color:#4cacdc}</style></head><script>varws=newWebSocket("ws://localhost:9123/lbh");ws.onopen=function(){//WebSocket已连接上,使用send()方法发送数据alert("数据发送中...");};ws.onmessage=function(e){console.log("接受到消息:"+e.data);createEle(e.data);};ws.onclose=function(){//关闭websocketalert("连接已关闭...");};functionsendMsg(msg){ws.send(msg)}</script><body><divclass="wrapflex-column"><div><videosrc="shape.mp4"width="100%"height="100%"controlsautoplay></video></div><divclass="sendflex-row"><inputtype="text"placeholder="弹幕发送[]~(^v^)~*"/><divonclick="javascript:sendMsg(document.querySelector('.con').value)">发送</div></div></div><scriptsrc="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"type="text/javascript"></script><script>//1.获取元素varoBox=document.querySelector('.box');//获取.box元素varcW=oBox.offsetWidth;//获取box的宽度varcH=oBox.offsetHeight;//获取box的高度functioncreateEle(txt){//动态生成span标签varoMessage=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)});}functionroll(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);}functionrandom(start,end){//随机数封装returnstart+~~(Math.random()*(end-start));}varaLi=document.querySelectorAll('li');//10functionforEach(ele,cb){for(vari=0,len=aLi.length;i<len;i++){cb&&cb(ele[i],i);}}forEach(aLi,function(ele,i){ele.style.left=i*100+'px';});//产生闭包varobj={num:1,add:function(){this.num++;//obj.num=2;(function(){console.log(this.num);})}};obj.add();//window</script></body></html>

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

五.小结

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

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

声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
快手极速版怎么绑定不了微信? 本班数学期中测试的优秀率是50%是什么意思 期中测试中全班56名同学有42人达到了优秀标准熊启中是稠剂的优秀率是... 期中测试中,全班56名同学,有49跟达到了优秀标准,求期中测试的优秀率是... 东瓜怎样吃,是减肥的最佳效果呢? 木耳冬瓜瘦肉汤怎么做味道极佳? 黑木耳冬瓜肉末汤美食做法 和前男友分手一年多了,也没联系过,怎么昨晚突然梦到他,而且感觉好真实... 面对分手两年多后的前男友,我应该怎么办 ...了微信但不说话,相亲女加了微信却不聊天,这是什么心态?_百... 如何获取.getfilesdir中的资源id 昨晚梦见,跟孙子去山上越走越黑后来遇见坏人被吓死了后来又遇到一... 梦见别人被坏人枪杀了,自己在胆战心惊的逃,后来回来拿了八万块现金揣... ...被坏人抓住控制起来,后来我跟我妈成功逃脱了,什么意思? 完美国际人界技能出来后,巫师咋样 《神武4》手游特惠庆典开启活跃赐福全服开放 ...总显示“文件被占用,无法发送,请重新选择。”? 怎么解决微信发不出文件的问题? 龙虾怎么洗简单又干净 龙虾怎么清洗的干净 ...中哪里经济最发达,按经济发达程度排名是怎样的? 为什么现在有些女人同时和两个或多个男人谈恋爱? 想出家,希望能找到一位正在修行的师傅,想劝我的师傅别回了,要给我讲... 为什么有的女人同时交往几个男朋友? 为什么有的女性喜欢同时交往多个男朋友? 为什么女人同时交往几个男朋友? 怎么做味道惊艳的排骨玉米汤? 同时交往多个男朋友的女人,心理想的什么? 女人同时交往几个男朋友,是什么心理? 如何烹饪可以提升玉米胡萝卜排骨汤的口感? 为什么有的女人喜欢同时谈好几个恋爱? 卫辉离石家庄辛集市有多远 淄博市到河南省卫辉县多少公里 七大新人须知婚庆公司必备要素 新人考察婚庆公司三大理由 梦见已故的父亲把屋里的水缸和东西扎完了, 太原直达重庆快,还是石家庄直达重庆快呢 三国志14最牛武将:绝对实力排行榜 太原到重庆北途经哪些站? 三国志14最强的武将:强力统帅排行榜 今天梦见男朋友被以前的男朋友砍了好多刀,出了好多血,我好像拉架,我还... 做梦梦见我和以前的对象在一起,却碰见了我现在的男朋友是什么意思 ...和现在男友,梦里前夫老是和我和好,而现在的男朋友老是不要我。我好... 你会要一个和你同床却梦见以前男朋友的,而且在醒来后在日记里写很想他... 学习练瑜伽有哪些好处及注意事项 学习练瑜伽的好处介绍 练习瑜伽有什么好处及作用?看完你就知道,为什么要练瑜伽了 ...是什么?必须今晚交,各位行行好~我会给你们加分的~ 手机现场直播报码的网址可以是什么? 荣耀笔记本怎么关闭锁屏界面 以"发现"为题写一篇作文只需列提纲。谢谢 我找到一把钥匙为题 列作文提纲