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

沙海 2021年8月23日01:05:00Java评论41字数 12715阅读42分23秒阅读模式
摘要

websocket+netty实时视频弹幕交互功能(Java版) Java学习者社区

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

Java学习者社区 文章源自JAVA秀-https://www.javaxiu.com/41765.html

websocket+netty实时视频弹幕交互功能(Java版)文章源自JAVA秀-https://www.javaxiu.com/41765.html

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

1 技术选型

1.1 netty

官方对于netty的描述:文章源自JAVA秀-https://www.javaxiu.com/41765.html

https://netty.io/文章源自JAVA秀-https://www.javaxiu.com/41765.html

主要关键词描述:netty是异步事件驱动网络框架,可做各种协议服务端,并且支持了FTP,SMTP,HTTP等很多协议,并且性能,稳定性,灵活性都很棒。文章源自JAVA秀-https://www.javaxiu.com/41765.html

websocket+netty实时视频弹幕交互功能(Java版)文章源自JAVA秀-https://www.javaxiu.com/41765.html

可以看到netty整体架构上分了三个部分:文章源自JAVA秀-https://www.javaxiu.com/41765.html

  • 以零拷贝,一致性接口,扩展事件模型的底层核心。文章源自JAVA秀-https://www.javaxiu.com/41765.html

  • Socket,Datagram,Pipe,Http Tunnel作为传输媒介。文章源自JAVA秀-https://www.javaxiu.com/41765.html

  • 传输支持的各种协议,HTTP&WebSocket,SSL,大文件,zlib/gzip压缩,文本,二进制,Google Protobuf等各种各种的传输形式。文章源自JAVA秀-https://www.javaxiu.com/41765.html

1.2 WebSocket

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。文章源自JAVA秀-https://www.javaxiu.com/41765.html

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

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

  • 由上述可知,实时直播交互作为互动式是一个双向数据传输过程。所以使用webSocket。文章源自JAVA秀-https://www.javaxiu.com/41765.html

  • netty本身支持了webSocket协议的实现,让实现更加简单方便。文章源自JAVA秀-https://www.javaxiu.com/41765.html

2 实现思路

2.1 服务架构

整体架构是所有客户端都和我的服务端开启一个双向通道的架构。文章源自JAVA秀-https://www.javaxiu.com/41765.html

websocket+netty实时视频弹幕交互功能(Java版)文章源自JAVA秀-https://www.javaxiu.com/41765.html

2.2 传输流程

websocket+netty实时视频弹幕交互功能(Java版)文章源自JAVA秀-https://www.javaxiu.com/41765.html

3 实现效果

3.1 视频展示

先看看效果吧,是不是perfect,接下来就来看具体代码是怎么实现的吧。文章源自JAVA秀-https://www.javaxiu.com/41765.html

websocket+netty实时视频弹幕交互功能(Java版)文章源自JAVA秀-https://www.javaxiu.com/41765.html

视频直播弹幕示例文章源自JAVA秀-https://www.javaxiu.com/41765.html

4 代码实现

4.1 项目结构

一个maven项目,将代码放一个包下就行。文章源自JAVA秀-https://www.javaxiu.com/41765.html

websocket+netty实时视频弹幕交互功能(Java版)文章源自JAVA秀-https://www.javaxiu.com/41765.html

4.2 Java服务端

Java服务端代码,总共三个类,Server,Initailizer和 Handler。文章源自JAVA秀-https://www.javaxiu.com/41765.html

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

一个nio的服务,开启一个tcp端口。文章源自JAVA秀-https://www.javaxiu.com/41765.html

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> {    @Override    protected 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());    }}

后台处理逻辑,接受到消息,写出到所有的客户端:文章源自JAVA秀-https://www.javaxiu.com/41765.html

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> {    // 用于记录和管理所有客户端的channel    public static ChannelGroup channels =            new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);    @Override    protected 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);    }    @Override    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {        channels.add(ctx.channel());    }    @Override    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {        String channelId = ctx.channel().id().asShortText();        System.out.println("客户端被移除,channelId为:" + channelId);        channels.remove(ctx.channel());    }    @Override    public 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 () {        // 关闭 websocket        alert("连接已关闭...");    };    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';  //初始化生成位置x        oBox.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');   //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.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>

这样一个实时的视频弹幕功能就完成啦,是不是很简单,各位小伙伴快来试试吧。文章源自JAVA秀-https://www.javaxiu.com/41765.html

5 小结

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

欢迎一键三连,拒绝做伸手党!文章源自JAVA秀-https://www.javaxiu.com/41765.html

(感谢阅读,希望对你所有帮助)文章源自JAVA秀-https://www.javaxiu.com/41765.html

来源:binhao.blog.csdn.net/article/details/112631642文章源自JAVA秀-https://www.javaxiu.com/41765.html

推荐阅读• 神奇的 SQL 之别样的写法 → 行行比较• YYDS 的 IDEA插件,没装上的安排起来!• 为什么代码规范要求SQL语句不要过多的join?• 红黑树 !!!最近面试BATJ,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。文章源自JAVA秀-https://www.javaxiu.com/41765.html

谢谢支持哟 (*^__^*)文章源自JAVA秀-https://www.javaxiu.com/41765.html

继续阅读
速蛙云 - 极致体验,强烈推荐!!!购买套餐就免费送各大视频网站会员!快速稳定、独家福利社、流媒体稳定解锁!速度快,全球上网、视频、游戏加速、独立IP均支持!基础套餐性价比很高!这里不多说,我一直正在使用,推荐购买:https://www.javaxiu.com/59919.html
weinxin
资源分享QQ群
本站是JAVA秀团队的技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定