基于videojs 实现javascript弹幕功能

 基础库 framework.barrage.js

class Barrage {
    constructor(canvas) {
        this.canvas = document.getElementById(canvas);
        let rect = this.canvas.getBoundingClientRect();
        this.w = rect.right - rect.left;
        this.h = rect.bottom - rect.top;
        this.ctx = this.canvas.getContext('2d');
        this.ctx.font = '16px Microsoft YaHei';
        this.barrageList = [];
    }
    //添加弹幕列表
    shoot(value) {
        let top = this.getTop();
        let color = this.getColor();
        let offset = this.getOffset();
        let width = Math.ceil(this.ctx.measureText(value).width);
        let barrage = {
            value: value,
            top: top,
            left: this.w,
            color: color,
            offset: offset,
            width: width
        }
        this.barrageList.push(barrage);
    }
    //开始绘制
    draw() {
        if (this.barrageList.length) {
            this.ctx.clearRect(0, 0, this.w, this.h);
            for (let i = 0; i < this.barrageList.length; i++) {
                let b = this.barrageList[i];
                if (b.left + b.width <= 0) {
                    this.barrageList.splice(i, 1);
                    i--;
                    continue;
                }
                b.left -= b.offset;
                this.drawText(b);
            }
        }
        requestAnimationFrame(this.draw.bind(this));
    }
    //绘制文字
    drawText(barrage) {
        this.ctx.fillStyle = barrage.color;
        this.ctx.fillText(barrage.value, barrage.left, barrage.top);
    }
    //获取随机颜色
    getColor() {
        return '#ffffff';
    	//return '#' + Math.floor(Math.random() * 0xffffff).toString(16);
    }
    //获取随机top
    getTop() {
        //canvas绘制文字x,y坐标是按文字左下角计算,预留30px
        return Math.floor(Math.random() * (this.h - 30)) + 30;
    }
    //获取偏移量
    getOffset() {
    	return +(Math.random() * 4).toFixed(1) + 1;
    }
}

//    let barrage = new Barrage('canvas');
//    barrage.draw();
//    const textList = ['弹幕', '666', '233333333',
//        'javascript', 'html', 'css', '前端框架', 'Vue', 'React',
//        'Angular', '测试弹幕效果', '弹幕', '666', '233333333',
//        'javascript', 'html', 'css', '前端框架', 'Vue', 'React',
//        'Angular', '测试弹幕效果', '弹幕', '666', '233333333',
//        'javascript', 'html', 'css', '前端框架', 'Vue', 'React',
//        'Angular', '测试弹幕效果'
//    ];
//    textList.forEach((t) => {
//        barrage.shoot(t);
//    });
//    let i = 0;
//    setInterval(function () {
//        i++;
//        barrage.shoot("测试弹幕" + i);
//    }, 100);

 

 拓展 danmu.js

/*!
 * DanMu Javascript Library
 * Version 0.1
 * Date 2020年8月6日
 * Author DQC
 */
(function () {
    var DanMu = function (options) {
        this.options = $.extend({}, this.defaults, options);
        this._init();
    };

    DanMu.prototype = {
        defaults: {
            // 宿主元素ID
            hostContainer: null,
            // 弹幕画板参数
            canvas: {
                id: null,
                width: null,
                height: null
            },
            // 是否启用弹幕插件
            isEnable: false,
            // 是否显示log
            displayLog: true,
            // 发送到弹幕参数
            sendData: {
                //发送弹幕间隔时间
                interval: null,
                intervalCallBack: null,
                // 发送弹幕ajax url
                url: null,
                // ajax 参数
                params: {},
                // 成功后回调
                successCallBack: function () {
                },
                // 失败后回调
                errorCallBack: function () {
                },
            },
            // 加载弹幕列表
            loadData: {
                url: null,
                params: {},
                successCallBack: function () {
                },
                errorCallBack: function () {
                },
            },
            // 宿主弹幕类型、暂时只有video
            host: {
                type: "VIDEO",
                object: null
            },
        },
        // 弹幕列表数据
        // 字段名限制
        // type=video [content:'这是一条弹幕', anchor: 10000]
        // type: other 暂无
        items: new Array(),
        // 总条数
        total: 0,
        // 当前时间、用于触发弹幕
        currentPlayDate: null,
        // 画板对象
        canvas: null,
        // 弹幕插件对象
        barrage: null,
        // 存储弹幕复制数据
        tmpItem: [],
        isRun: true,
        isSend: false,
        _hostType: function () {
            let _this = this;
            if (_this.options.host != null) {
                let type = _this.options.host.type.toUpperCase();
                switch (type) {
                    case "VIDEO":
                        _this._videoDanMuMethods.init(_this);
                        break;
                }
            }
        },
        _videoDanMuMethods: {
            parent: null,
            vplayer: null,
            isFirst: true,
            isSeeking: false,
            tmpPlayCurrent: [],
            init: function (parent) {
                try {
                    this.parent = parent;
                    this.vplayer = this.parent.options.host.object;
                    this.parent._createDanMuElement();
                    this.eventBinding();
                } catch (error) {
                    parent.errorLogHandle(error);
                }

            },
            eventBinding: function () {
                let _this = this;
                _this.vplayer.on('firstplay', function (event) {
                    _this.isFirst = false;
                    // 复制数据
                    _this.copyData();
                });
                _this.vplayer.on('seeking', function (event) {
                    _this.isSeeking = true;
                });
                _this.vplayer.on('timeupdate', function (event) {
                    _this.timeupdate();
                });
                _this.vplayer.on('seeked', function (event) {
                    _this.seeked();
                });

            },
            seeked: function () {
                let _this = this;
                _this.isSeeking = false;
                _this.tmpPlayCurrent = [];
                _this.copyData();
            },
            copyData: function () {
                let _this = this;
                _this.parent.tmpItem = _this.parent.items.slice(0);
            },
            timeupdate: function () {
                let _this = this;
                _this.parent.currentDate = Math.floor(this.vplayer.currentTime());
                if (!_this.isFirst && !_this.isSeeking) {
                    // 判断是否当前时间已经调用,因为timeupdate方法有可能会调用1-N次
                    if (!_this.tmpPlayCurrent.includes(_this.parent.currentDate)) {
                        _this.parent.infoLogHandle("vplay timeupdate event,当前播放进度时间:" + _this.parent.currentDate);
                        _this.tmpPlayCurrent.push(_this.parent.currentDate);
                        // 循环取出弹幕
                        if (_this.parent.isRun) {
                            for (let i = 0; i < _this.parent.tmpItem.length; i++) {
                                let sendTime = _this.parent.tmpItem[i].anchor;
                                let content = _this.parent.tmpItem[i].content;
                                if ((sendTime / 1000) == _this.parent.currentDate) {
                                    _this.parent.infoLogHandle("显示一个弹幕:" + content + ",当前播放时间:" + _this.parent.currentDate);
                                    _this.parent.barrage.shoot(content);
                                }
                            }
                        }
                    }
                }
            }
        },
        _send: function (data) {
            let _this = this;
            _this.barrage.shoot(data);
        },
        start: function () {
            this.isRun = true;
            $("#" + this.options.canvas.id).show();
        },
        stop: function () {
            this.isRun = false;
            $("#" + this.options.canvas.id).hide();
        },
        send: function (content) {
            let _this = this;
            if (_this.options.sendData.interval != null || _this.options.sendData.interval > 0) {
                if (_this.isSend) {
                    if (typeof (_this.options.sendData.intervalCallBack) != "undefined" && typeof (_this.options.sendData.intervalCallBack) == "function") {
                        _this.options.sendData.intervalCallBack();
                    }
                } else {
                    _this.options.sendData.params.content = content;
                    _this.options.sendData.params.anchor = _this.currentDate * 1000;
                    _this._sendDanMuData();
                    setTimeout(function () {
                        _this.isSend = false;
                    }, 10000);
                }
            }

        },
        _createDanMuElement: function () {
            let _this = this;
            $(_this.options.hostContainer).css("position", "relative");
            $(_this.options.hostContainer).append('<canvas id="' + _this.options.canvas.id + '" width="' + _this.options.canvas.width + '" height="' + _this.options.canvas.height + '" style="z-index:0;position:absolute;top:0;left:0;pointer-events: none;">您的浏览器不支持canvas标签。</canvas>');
            _this.barrage = new Barrage(_this.options.canvas.id);
            _this.barrage.draw();
        },
        _init: function () {
            this.infoLogHandle("_init method!!!");
            let _this = this;
            _this._checkOptions().then(function () {
                _this._hostType();
                _this._loadDanMuPoolData();
            }).catch(function (error) {
                _this.errorLogHandle("不满足danmu.js要求!!!" + error);
            });
        },
        _sendDanMuData: function () {
            let _this = this;
            _this.isSend = true;
            new Promise(function (resolve, reject) {
                ajaxSubmit({
                    url: _this.options.sendData.url,
                    params: _this.options.sendData.params,
                    async: true,
                    onSuccess: function (data, textStatus) {
                        _this.infoLogHandle("发送了成功一条弹幕:" + data);
                        _this._send(data);
                        //将新发送成功的弹幕放到弹幕列表中
                        let newData = {
                            anchor: _this.currentDate * 1000,
                            content: data
                        }
                        _this.items.push(newData);
                        _this.options.sendData.successCallBack(data, textStatus);
                        resolve();
                    },
                    onError: function (error) {
                        if (typeof (_this.options.sendData.errorCallBack) != "undefined" && typeof (_this.options.sendData.errorCallBack) == "function") {
                            _this.options.sendData.errorCallBack(error);
                            reject();
                        }
                    }
                });
            });

        },
        _loadDanMuPoolData: function () {
            let _this = this;
            _this.infoLogHandle("_loadDanMuPoolData method!!!");
            ajaxSubmit({
                url: _this.options.loadData.url,
                async: true,
                params: _this.options.loadData.params,
                onSuccess: function (data, textStatus) {
                    //避免返回结构不同,所以返回给前台处理完数据后在返回给插件显示弹幕
                    let loadData = _this.options.loadData.successCallBack(data, textStatus);
                    if (typeof (loadData) != "undefined") {
                        _this.items = loadData;
                    }
                    _this.total = _this.items.length;
                },
                onError: function (error) {
                    if (typeof (_this.options.loadData.errorCallBack) != "undefined" && typeof (_this.options.loadData.errorCallBack) == "function") {
                        _this.options.loadData.errorCallBack(error);
                    }
                }
            });
        },
        _checkOptions: function () {
            let _this = this;
            return (new Promise(function (resolve, reject) {
                if (_this.options.isEnable == false) {
                    _this.errorLogHandle("弹幕插件未启用!!!");
                    reject();
                }
                if (typeof (Barrage) == "undefined" || typeof (Barrage) != "function") {
                    _this.errorLogHandle("请在danmu.js之前加载jquery、jquery.ui.js、framework.barrage.js、inserttest.js才能使用danmu.js!!!");
                    reject();
                }
                if (_this.options.hostContainer == null) {
                    _this.errorLogHandle("请配置参数[options > hostContainer]!!!");
                    reject();
                }
                if (_this.options.host.object == null) {
                    _this.errorLogHandle("请配置参数[options host > object]!!!");
                    reject();
                }
                resolve();
            }));
        },
        infoLogHandle: function (logMsg) {
            let _this = this;
            if (_this.options.displayLog) {
                console.log("DANMU INFO LOG:", logMsg, new Date());
            }
        },
        errorLogHandle: function (logMsg) {
            console.error("DANMU ERROR LOG:", logMsg, new Date());
        },
    }

    window.DanMu = DanMu;
}());

使用方法

var danMu = new DanMu({
                isEnable: true,
            displayLog: false,
            hostContainer: ".videobox",
            canvas: {
                id: "barrage",
                width: $("#video_div").width(),
                height: (vh / 3)
            },
            host: {
                type: "video",
                object: vplayer
            },
            sendData: {
                interval: 10,
                url: "",
                params: {
                    
                },
                successCallBack: function (data, textStatus) {
                    
                },
                errorCallBack: function (error) {
                    
                }
            },
            loadData: {
                url: "",
                params: {
                    
                },
                successCallBack: function (data, textStatus) {
                    return null;
                }
            }
        })

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值