美文网首页
websocket+netty实时视频弹幕交互功能(Java版)

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

作者: 码农突围 | 来源:发表于2021-09-01 17:22 被阅读0次

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

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

    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>
    

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

    5 小结

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

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

    相关文章

      网友评论

          本文标题:websocket+netty实时视频弹幕交互功能(Java版)

          本文链接:https://www.haomeiwen.com/subject/rrxfwltx.html