js实现bilibili弹幕列表随视频播放滚动

以下是一些写脚ben时获得的数据记录,最后面是脚ben代码。

抓包加上查资料拿到的弹幕数据接口:

  • "https://api.bilibili.com/x/v1/dm/list.so?oid=" + cid 返回数据类型为XML(旧接口)。
  • https://api.bilibili.com/x/v2/dm/web/seg.so?oid=" + cid + "&type=1&segment_index=1 返回二进制数据(新接口)

二进制数据处理比较麻烦,我就用了XML的接口。

cid是视频的id,也叫oid,不是AV号,获取方式有两种:

  • 在控制台输入cid获取当前视频的cid
  • 通过方法:window.__INITIAL_STATE__.videoData.pages

接口测试时获取的数据如下:
在这里插入图片描述
弹幕的返回数据含义如下:

<d p="1611.18000,1,25,16777215,1631635549,0,ab0d3321,54811347826209280,10">来看八爷</d>

有9个参数,经过查询并且测试获得了部分参数的含义:

  • 参数1: 1611.18000 弹幕出现的时间,以秒数为单位
  • 参数2: 1 弹幕的模式,1-3 滚动弹幕,4 底端弹幕,5顶端弹幕,6 逆向弹幕,7 精准定位,8 高级弹幕
  • 参数3: 25 字号 (12非常小,16特小,18小,25中,36大,45很大,64特别大)
  • 参数4: 16777215 字体的颜色,这串数字是十进制表示
  • 参数5: 1631635549 unix时间戳
  • 参数6: 0 弹幕池类型,0普通池,1字幕池,2特殊池 【目前特殊池为高级弹幕专用】
  • 参数7: ab0d3321 发送者的Hash,用于“屏蔽此弹幕的发送者”功能
  • 参数8: 54811347826209280 弹幕在弹幕数据库中rowID 用于“历史弹幕”功能。
  • 参数9: 10 未知,貌似是新增的,查阅的资料里都没有这个参数

这里只有第一个参数用得到,剩下的参数会在我的另一个修改视频弹幕的脚ben里用到。

最终效果如下:
在这里插入图片描述

代码如下:

// 允许跨域请求,防止浏览器拦截
var meta = document.createElement("meta");
meta.httpEquiv = "Access-Control-Allow-Origin";
meta.content = "*";
$("head")[0].appendChild(meta);

// 初始化若干变量
var page_loc = location.href;
var page_start = page_loc.indexOf("?p=");
var page_p;

// 获取视频cid
if (page_start == -1 || page_start == 1) page_p = 0;
else page_p = page_loc.slice(page_start + 3) - 1;
var page_cid = window.__INITIAL_STATE__.videoData.pages[page_p].cid;

// 发起请求,拿到弹幕数据
$.ajax({
    url: "https://api.bilibili.com/x/v1/dm/list.so?oid=" + page_cid,
    type: "GET",
    dataType: "XML",
    success: function (xml) {
        // 处理返回数据
        data_deal(xml.all);
    },
    error: function (err) {
        console.log(err);
        alert("弹幕列表滚动脚本失效,可尝试更新最新版本脚本解决问题!\n如果有一定编程基础可以打开控制台查看报错信息。");
    }
});

// 存储数据
function data_deal(data) {
    var data_list = new Array(); // 存信息
    var data_arr = new Array(); // 存内容
    var data_length = data.length;
    for (var i = 8; i < data_length; i++) {
        var data_array = data[i].attributes.p.value.split(",");
        $.each(data_array, function (i) {
            data_array[i] = parseFloat(data_array[i]); // 转换类型,便于排序
        })
        data_list.push(data_array);
        data_arr.push(data[i].innerHTML);
    }

    // 执行排序
    data_sort(data_list, 0, data_list.length - 1, data_arr);

    // 修改弹幕列表
    $(".bui-collapse-header").bind("click", function () {
        var clearInter = setInterval(function () {
            if ($(".player-auxiliary-danmaku-load-status").css("display") == "none") {
                list_change(data_list, data_arr);
                clearInterval(clearInter);
            }
        }, 100);
    });
}

// 数据排序(快排算法)
function data_sort(data_list, l, r, data_arr) {
    if (l < r) {
        var i = l,
            j = r,
            temp = data_list[l],
            t1, t2, t3;
        while (i < j) {
            while (i < j && data_list[j][0] >= temp[0]) j--;
            while (i < j && data_list[i][0] <= temp[0]) i++;
            if (i < j) {
                t1 = data_list[i];
                data_list[i] = data_list[j];
                data_list[j] = t1;
                t2 = data_arr[i];
                data_arr[i] = data_arr[j];
                data_arr[j] = t2;
            }
        }

        data_list[l] = data_list[i];
        data_list[i] = temp;
        t3 = data_arr[l];
        data_arr[l] = data_arr[i];
        data_arr[i] = t3;
        data_sort(data_list, l, i - 1, data_arr);
        data_sort(data_list, i + 1, r, data_arr);
    }
}

// 时间格式转换(分转秒)
function time_m_s(a) {
    if (a.length == 2) return a[0] * 60 + a[1];
    else return a[0] * 3600 + a[1] * 60 + a[2];
}

// 时间格式转换(秒转分)
function time_s_m(a) {
    a = Math.floor(a);
    var m = Math.floor(a / 60);
    var s = (a - Math.floor(a / 60) * 60);
    if (m < 0) m = "00";
    else if (m < 10) m = "0" + String(m);
    if (s < 10) s = "0" + String(s);
    return m + ":" + s;
}

// 发送日期格式转换
function data_s_d(a) {
    var date = new Date(a * 1000);
    var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
    var D = date.getDate();
    if (D < 10) D = "0" + String(D) + ' ';
    else D = D + ' ';
    var h = date.getHours();
    if (h < 10) h = "0" + String(h) + ":";
    else if (h == 0) h = "00" + ":";
    else h = h + ":";
    var m = date.getMinutes();
    if (m < 10) m = "0" + String(m);
    else if (m == 0) m = "00";
    return M + D + h + m;
}

// 获取当前视频播放秒数
function get_now_s() {
    var time_now = $('.bilibili-player-video-time-now')[0].innerHTML.split(":");
    $.each(time_now, function (i) {
        time_now[i] = parseInt(time_now[i]);
    })
    return time_m_s(time_now);
}

function list_change(data_list, data_arr) {
    // 修改页面控制
    $(".player-auxiliary-danmaku-btn-time").removeAttr("orderby");
    $(".player-auxiliary-danmaku-btn-danmaku").removeAttr("orderby");
    $(".player-auxiliary-danmaku-btn-date").removeAttr("orderby");
    $(".player-auxiliary-danmaku-function > .player-auxiliary-danmaku-btn-danmaku")[0].innerHTML = "滚动弹幕(共" + data_arr.length + "条)";

    // 弹幕排序
    list_sort(data_list, data_arr);
}

function list_sort(data_list, data_arr) {
    // 破坏初始列表结构
    $(".player-auxiliary-danmaku-wrap > div > ul").empty();
    $(".player-auxiliary-danmaku-wrap > .player-auxiliary-danmaku-contaner").removeClass("player-auxiliary-danmaku-contaner player-auxiliary-bscrollbar");
    $(".player-auxiliary-danmaku-wrap").off();
    $(".player-auxiliary-danmaku-wrap > div > ul").off();

    // 清除BFC
    var div = document.createElement("div");
    var nothing = document.createTextNode(".");
    div.appendChild(nothing);
    $(".player-auxiliary-danmaku-wrap > div > ul")[0].appendChild(div);
    $(div).addClass("BFC-nothing");
    $(".BFC-nothing").css("opacity", "0");

    // 构造有序列表
    $(data_arr).each(function (i, v) {
        var li = document.createElement("li");
        var span1 = document.createElement("span");
        var span2 = document.createElement("span");
        var span3 = document.createElement("span");
        var span4 = document.createElement("span");
        var text1 = document.createTextNode(time_s_m(data_list[i][0]));
        var text2 = document.createTextNode(v);
        var text3 = document.createTextNode(data_s_d(data_list[i][4]));
        var text4 = document.createTextNode(">");
        span1.appendChild(text1);
        span2.appendChild(text2);
        span3.appendChild(text3);
        span4.appendChild(text4);
        $(li).attr("dmno", i);
        $(span2).attr("title", v);
        $(li).addClass("danmaku-info-row");
        $(span1).addClass("danmaku-info-time");
        $(span2).addClass("danmaku-info-danmaku");
        $(span3).addClass("danmaku-info-date");
        $(span4).addClass("danmaku-info-animate");
        li.appendChild(span1);
        li.appendChild(span2);
        li.appendChild(span3);
        li.appendChild(span4);
        $(".player-auxiliary-danmaku-wrap > div > ul")[0].appendChild(li);
        $('.danmaku-info-animate').css({
            'position': 'absolute',
            'width': '15px',
            'height': '100%',
            'padding': '0',
            'left': '0px',
            'font-size': '15px',
            'color': 'rgb(1,185,245)',
            'transition': '1s ease-in-out'
        });
    });

    // 播放动画(监听视频播放进度)
    var isChange = $(".bilibili-player-video-time-now")[0].innerHTML;
    player_animate(data_list, get_now_s());
    setInterval(function () {
        if (isChange != $(".bilibili-player-video-time-now")[0].innerHTML) {
            player_animate(data_list);  // 动画播放
            isChange = $(".bilibili-player-video-time-now")[0].innerHTML;
        }
    }, 1000);
}

// 播放动画
function player_animate(data_list) {
    var time_now_s = get_now_s();
    var length = data_list.length;
    var begin = -1, end = -1;
    for (var i = 0; i < length; i++) {
        if (Math.floor(data_list[i][0]) < time_now_s) continue;
        if (Math.floor(data_list[i][0]) > time_now_s) break;
        if (begin != -1) end++;
        if (begin == -1) begin = end = i;
    }
    if (end == -1) return;
    var li_height = parseFloat($(".player-auxiliary-danmaku-wrap > div > ul > li").css("height").replace("px", ""));
    var li_width = parseFloat($(".player-auxiliary-danmaku-wrap > div > ul > li").css("width").replace("px", ""));
    for (var i = begin; i <= end; i++) {
        if (length > 15 && i > 11) $(".player-auxiliary-danmaku-wrap > div > ul > li:first").css("margin-top", "-" + (i - 11) * li_height + "px");  // 列表滚动
        $($('.player-auxiliary-danmaku-wrap > div > ul > li')[i]).find(".danmaku-info-animate").css("left", li_width);  // 播放动画
    }
}

如果有兴趣了解更多相关内容,欢迎来访我的个人网站:eyes++的个人空间

如果对该类脚本感兴趣的话,欢迎来到我的github看看,我开源了一个script仓库,里面有很多我写的和正在写的脚本,大佬们有兴趣可以一起贡献代码,仓库地址

------------------分割线(2021/10/7)-------------------
之前那个脚本效率太低了,所以没有投入使用,我给它优化了一下逻辑,现在可以投入使用了,但是我实在是不会用js注入动画,所以这次的我取消了动画。。。。。。有知道的大佬可以教教我,下载地址:bilibili弹幕列表实时滚动播放

代码如下:

// ==UserScript==
// @name         bilibili弹幕列表实时滚动播放
// @namespace    https://eyesblog.gitee.io
// @version      1.0
// @description  在B站看视频时弹幕一多就会遮住内容,可我又想看视频又想看弹幕,就写了这个脚本,安装后右侧的弹幕列表会随视频播放而滚动,并且会有弹幕出现提示
// @author       eyes++
// @match        https://www.bilibili.com/video/*
// @require      https://cdn.staticfile.org/jquery/3.5.0/jquery.min.js
// @grant        none
// ==/UserScript==

(function () {
    'use strict';
    // 允许跨域请求,防止浏览器拦截
    let meta = document.createElement("meta");
    meta.httpEquiv = "Access-Control-Allow-Origin";
    meta.content = "*";
    $("head")[0].appendChild(meta);

    // 初始化若干变量
    let page_loc = location.href;
    let page_start = page_loc.indexOf("?p=");
    let page_p;

    // 获取视频cid
    if (page_start == -1 || page_start == 1) page_p = 0;
    else page_p = page_loc.slice(page_start + 3) - 1;
    let page_cid = window.__INITIAL_STATE__.videoData.pages[page_p].cid;

    // 发起请求,拿到弹幕数据
    $.ajax({
        url: "https://api.bilibili.com/x/v1/dm/list.so?oid=" + page_cid,
        type: "GET",
        dataType: "XML",
        success: function (xml) {
            // 处理返回数据
            data_deal(xml.all);
        },
        error: function (err) {
            console.log("弹幕列表滚动脚本报错:", err);
            alert("弹幕列表滚动脚本失效,可尝试更新最新版本脚本解决问题!\n如果有一定编程基础可以打开控制台查看报错信息。");
        }
    });

    // 存储数据
    let data_deal = data => {
        let data_list = new Array(); // 存时间信息
        let data_arr = new Array(); // 存内容
        let data_length = data.length;
        for (var i = 8; i < data_length; i++) {
            let data_array = data[i].attributes.p.value.split(",");
            data_array.splice(5, 4);
            data_array.splice(1, 3);
            $.each(data_array, function (i) {
                data_array[i] = parseFloat(data_array[i]); // 转换类型,便于排序
            })
            data_list.push(data_array);
            data_arr.push(data[i].innerHTML);
        }

        // 执行排序
        data_sort(data_list, 0, data_length - 9, data_arr);

        // 修改弹幕列表
        let clear = setInterval(() => {
            if ($(".bui-collapse-header")[0]) { // 等待弹幕列表加载出来
                clearInterval(clear);
                $(".bui-collapse-header").click(() => {
                    let clearInter = setInterval(function () {
                        if ($(".player-auxiliary-danmaku-load-status").css("display") == "none") { // 等待弹幕数据加载出来
                            clearInterval(clearInter);
                            list_change(data_list, data_arr); // 然后开始修改列表
                        }
                    }, 100);
                })
            }
        }, 100)
    }

    // 数据排序(快排算法)
    let data_sort = (data_list, l, r, data_arr) => {
        if (l < r) {
            let i = l,
                j = r,
                temp = data_list[l],
                t1, t2, t3;
            while (i < j) {
                while (i < j && data_list[j][0] >= temp[0]) j--;
                while (i < j && data_list[i][0] <= temp[0]) i++;
                if (i < j) {
                    t1 = data_list[i];
                    data_list[i] = data_list[j];
                    data_list[j] = t1;
                    t2 = data_arr[i];
                    data_arr[i] = data_arr[j];
                    data_arr[j] = t2;
                }
            }

            data_list[l] = data_list[i];
            data_list[i] = temp;
            t3 = data_arr[l];
            data_arr[l] = data_arr[i];
            data_arr[i] = t3;
            data_sort(data_list, l, i - 1, data_arr);
            data_sort(data_list, i + 1, r, data_arr);
        }
    }

    // 查找
    let danmaku_search = (arr, time) => {
        try {
            arr.forEach((v, i) => {
                if (v[0] - time >= 0 && v[0] - time < 1) throw new Error(String(i));
                if (v[0] - time > 1) throw new Error(i);
            });
        } catch (e) {
            return e.message;
        }
        return -1;
    }

    // 时间格式转换(分转秒)
    let time_m_s = a => {
        if (a.length == 2) return a[0] * 60 + a[1];
        else return a[0] * 3600 + a[1] * 60 + a[2];
    }

    // 时间格式转换(秒转分)
    let time_s_m = a => {
        a = Math.floor(a);
        let m = Math.floor(a / 60);
        let s = (a - Math.floor(a / 60) * 60);
        if (m < 0) m = "00";
        else if (m < 10) m = "0" + String(m);
        if (s < 10) s = "0" + String(s);
        return m + ":" + s;
    }

    // 发送日期格式转换
    let data_s_d = a => {
        let date = new Date(a * 1000);
        let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
        let D = date.getDate();
        if (D < 10) D = "0" + String(D) + ' ';
        else D = D + ' ';
        let h = date.getHours();
        if (h < 10) h = "0" + String(h) + ":";
        else if (h == 0) h = "00" + ":";
        else h = h + ":";
        let m = date.getMinutes();
        if (m < 10) m = "0" + String(m);
        else if (m == 0) m = "00";
        return M + D + h + m;
    }

    // 获取当前视频播放秒数
    let get_now_s = () => {
        let time_now = $('.bilibili-player-video-time-now')[0].innerHTML.split(":");
        $.each(time_now, function (i) {
            time_now[i] = parseInt(time_now[i]);
        })
        return time_m_s(time_now);
    }

    let list_change = (data_list, data_arr) => {
        // 修改页面控制
        $(".player-auxiliary-danmaku-btn-time").removeAttr("orderby");
        $(".player-auxiliary-danmaku-btn-danmaku").removeAttr("orderby");
        $(".player-auxiliary-danmaku-btn-date").removeAttr("orderby");
        $(".player-auxiliary-danmaku-function > .player-auxiliary-danmaku-btn-danmaku")[0].innerHTML = "滚动弹幕(共" + data_arr.length + "条)";

        // 破坏初始列表结构
        $(".player-auxiliary-danmaku-wrap > .player-auxiliary-danmaku-contaner").removeClass("player-auxiliary-danmaku-contaner player-auxiliary-bscrollbar");
        $(".player-auxiliary-danmaku-wrap").off();
        $(".player-auxiliary-danmaku-wrap > div > ul").off();

        // 监听视频播放
        let danmaku_length = $(".player-auxiliary-danmaku-wrap > div > ul > li").length; // 获取当前展示的弹幕数量
        let li_width = $(".player-auxiliary-danmaku-wrap > div > ul > li").css("width"); // 列表宽度
        let isChange = $(".bilibili-player-video-time-now")[0].innerHTML;
        let data_length = data_list.length;

        // 开场进行一次排序
        if (parseFloat($(".player-auxiliary-danmaku-wrap").css("height").replace("px", "")) >= parseFloat($(".player-auxiliary-danmaku-wrap > div > ul").css("height").replace("px", ""))) {
            setInterval(() => {
                if (isChange != $(".bilibili-player-video-time-now")[0].innerHTML) {
                    let now_i = danmaku_search(data_list, get_now_s()); // 获取即将播放的弹幕
                    if (now_i != -1) list_structure(data_list, data_arr, 0, danmaku_length - 1, now_i, li_width);
                    isChange = $(".bilibili-player-video-time-now")[0].innerHTML;
                }
            }, 1000);
        } else {
            setInterval(() => {
                if (isChange != $(".bilibili-player-video-time-now")[0].innerHTML) {
                    // 获取列表首尾
                    let now_i = danmaku_search(data_list, get_now_s());
                    // 判断是否需要更新
                    if (now_i != -1 && parseInt(now_i) + danmaku_length - 15 >= data_length) list_structure(data_list, data_arr, data_length - danmaku_length, data_length - 1);
                    else if (now_i != -1) list_structure(data_list, data_arr, parseInt(now_i) - 7, parseInt(now_i) + danmaku_length - 8);
                    isChange = $(".bilibili-player-video-time-now")[0].innerHTML;
                }
            }, 1000);
        }

        // 开启与停止滚动控制
    }

    // 构建弹幕列表
    let list_structure = (data_list, data_arr, start, end) => {
        $(".player-auxiliary-danmaku-wrap > div > ul").empty(); // 清除先前的弹幕,重新构建
        for (let i = start; i <= end; i++) {
            let li = document.createElement("li");
            let span1 = document.createElement("span");
            let span2 = document.createElement("span");
            let span3 = document.createElement("span");
            let text1 = document.createTextNode(time_s_m(data_list[i][0]));
            let text2 = document.createTextNode(data_arr[i]);
            let text3 = document.createTextNode(data_s_d(data_list[i][1]));
            span1.appendChild(text1);
            span2.appendChild(text2);
            span3.appendChild(text3);
            $(span1).addClass("danmaku-info-time");
            $(span2).addClass("danmaku-info-danmaku");
            $(span3).addClass("danmaku-info-date");
            li.appendChild(span1);
            li.appendChild(span2);
            li.appendChild(span3);
            $(li).addClass("danmaku-info-row");
            $(".player-auxiliary-danmaku-wrap > div > ul")[0].appendChild(li);
        }
    }
})();

安装脚本后只要打开右边的弹幕列表,弹幕就会随视频播放而滚动了。
在这里插入图片描述

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值