来开发自己的B站直播弹幕姬吧

准备工作

确保电脑上安装了 Electron vue-cli3 并且有一定Vue基础

我是基于Electron+Vue开发的,当然React等也是可行的,这里只展示Vue部分。
为了方便,我提供了一个获取B站弹幕的npm包,目前还不是非常完善,还靠各位一起开发了。

安装

$ npm i bili-danmu-loader-yl --save

使用
import DanmuLoader from 'bili-danmu-loader-yl';
const loader = new DanmuLoader();
//设置房间号
loader.setRoomID(123456);
//弹幕回调
loader.onadd = i => {
  //有新弹幕加入的钩子,i为一个数组,包含了新接收的弹幕
  console.log(i);
}
loader.startLoader( () => {
  //启动加载弹幕成功
  //succeced
} , () => {
  //成功
  //failed
});
//关闭弹幕加载
loader.closeLoader();

进入正题

这件事还要从一只蝙蝠说起(不是,在家里待久了实在没啥事做,那就整个弹幕姬吧?
首先我们开始装prebuilt,如果装了就不用管了
这里推荐用淘宝的镜像

npm install -g cnpm --registry=https://registry.npm.taobao.org

然后是开始装

cnpm install electron-prebuilt -g

然后是初始化项目,后面那个YeuolyDanmu填自己的项目名

vue init simulatedgreg/electron-vue YeuolyDanmu

再安装项目

npm init //安装
npm run dev //开发者模式启动

好了现在项目装好了,我们可以正式开始写代码了

先一通操作删掉原本Electron-Vue的界面,只留下App.vue,界面和路由啥的就大家自己定义叭,反正能用就行,我这里只负责提供最核心的部分

我定义了我自己的路由,如下

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'index',
      component: require('@/views/Index.vue').default,
      meta : {
        title : '默认目录'
      },
      children : [
        {
          name : 'log',
          path : '/index/log',
          component: require('@/components/items/Logger.vue').default,
          meta : {
            title : '日志'
          }
        },
        {
          name : 'task',
          path : '/index/task',
          component: require('@/views/Task.vue').default,
          meta : {
            title : '任务'
          }
        },
        {
          name : 'room-settings',
          path : '/index/room-settings',
          component: require('@/views/RoomSettings.vue').default,
          meta : {
            title : '房间设置'
          }
        }
      ]
    },
   {
      path: '*',
      redirect: '/index/log'
    }
  ]
})

我一共定义了3个界面,一个用来放日志,一个用来当控制台,还有个设置界面,我们来看核心的控制台部分
想一想我们脑子的弹幕姬,应该不止一个窗口叭?应该还有个副窗口用来显示窗口叭?所以我决定在启动获取弹幕后启动第二个窗口,这里咱们用个插件,有个大佬给我们提供了一个快捷创建窗口的插件 链接

npm i -S electron-vue-windows

具体的api就去仔细看看作者的文档吧,这里就不细说了
好了,现在定义我们的打开窗口方法,在任务界面内定义

const win = this.$Win.openWin({
    width: 300,
    height: 700,
    useContentSize: true,
    webPreferences : {
           webSecurity : false
     },
     resizable: true,
     frame: false,
     titleBarStyle: false,

     windowConfig : {
           router : '/danmu',
           name : '弹幕窗口',
     }
 });

这里提一嘴,windowConfig中的router为一个路由的path,有什么用呢?
新建的窗口会使用这个路由的组件作为其的Index,也就是当新窗口已创建它就会显示出这个路由的组件

所以我们要加一条路由

{
      path : '/danmu',
      name : 'danmu',
      component: () => import('@/views/DanmuDialog.vue')
},

好了窗口也建好了,下面来实现窗口通讯,因为无论怎么样都要向弹幕窗口发送弹幕过去的,不然人家也拿不到弹幕,这里使用ipc通讯,这是electron提供的通讯方式,electron允许主进程和渲染进程之间的通讯,而我们的弹幕窗口和主窗口都是渲染进程,所以我们要通过主进程这一个桥梁来实现通讯

这里需要说一下,可能有的人没了解过(完全没了解过electron的
在渲染进程中,也就是 ***.vue啊这些地方,我们通过

const ipc = require('electron').ipcRenderer;

来获取通讯实例,ipc.send发出去的信息都是发到主进程中,但可以用ipc.sendTo来发向指定窗口,我这里用了第一种

在main/index.js中

let mainWindow;
let danmuWindowID;

然后这里就有个很蛋疼的事情,我们手里有主窗口的实例,因为它就建立在main.js里,我们插件建立的窗口我们还没拿到实例呢,这咋整啊,然后我想到同样可以使用通讯,只不过是传个ID的事情

DanmuDialog.vue

<template>
    <div id="handle" class="danmu-dialog">
        <div id="cover" ref="cover">
             <Danmu v-for="(i, key) in danmus" :key="key" :Danmu="i" :parent="$refs.cover"></Danmu>
        </div>
    </div>
</template>

<script>
import Danmu from '../components/items/Danmu';

const drag = require('electron-drag');
const ipc = require('electron').ipcRenderer;
const win_id = require('electron').remote.getCurrentWindow().id;
require('electron').remote.getCurrentWindow().setAlwaysOnTop(true);

//十万的时候清理一下,100000个在沙月的直播间大概要5 * 1000分钟,完全够了
//就算弹幕速度快5倍也能用1000分钟,一天就24 * 60分钟
const danmu_max_len = 100000;
const delete_time = 5;

export default {
    name : 'DanmuDialog',
    components : { Danmu },
    data: () => ({
        danmus : [],
    }),
    methods: {
        //装载弹幕接收钩子
        setupRevDanmu(){
            //由于这个窗口是在渲染进程中进行的,主进程不能直接获取窗口ID,所以在窗口挂载时向主进程发送窗口ID
            ipc.send('danmu-mounted',win_id);
            //用于接收所有传输到弹幕窗口的信息
            ipc.on('to-danmu', ( sender, channel, msg ) => {
                if(channel === 'trans-danmu'){
                    //传过来的弹幕都是数组
                    msg.forEach( e => { 
                        this.danmus.push(e);
                    });
                    if(this.danmus.length > danmu_max_len){
                        this.clear();
                    }
                }else if(channel === 'clear'){
                    this.clear()
                }
            });
            //向主窗口发送成功消息
            ipc.send('to-main','trans-info',
                { block : '[DanmuDialog]', info : '弹幕窗口连接成功', color : 'green'} );
        },
        //当弹幕太多了的时候清除顶部的几个
        clear(){
                this.danmus = []
        }
    },
    mounted() {
        //linux等平台的窗口拖动
        const clear = drag('#cover');
        //windows和mac的窗口拖动
        if(!drag.supported){
            document.querySelector('#cover').style['-webkit-app-region'] = 'drag';
        }
        this.setupRevDanmu();
    },
}
</script>

<style>
    /* 各位自己弄样式叭~ */
</style>

可以看我在窗口加载完之后通过

require('electron').remote.getCurrentWindow().id;

获取了窗口ID,然后通过

ipc.send('danmu-mounted',win_id);

向主进程发送了我们弹幕窗口的ID,那我们就可以开始搭建窗口沟通的桥梁了呀

main/index.js

//获取弹幕窗口
const ipc = require('electron').ipcMain;
ipc.on('danmu-mounted',function(sender,id){
  danmu_win_id = id;
});

以上是获取窗口ID,下面是建立通讯

main/index.js

ipc.on('to-danmu',function(sender,channel,msg){
  const DanmuWin = BrowserWindow.fromId(danmu_win_id);
  if(DanmuWin){
    DanmuWin.webContents.send('to-danmu',channel,msg);
  }
});

我把发向主进程的to-danmu消息直接转发到了弹幕窗口中,通过一个channel参数来分开不同类型的消息(其实就是懒,不想弄一堆转发

我们再回头看DanmuDialog.vue

 ipc.on('to-danmu', ( sender, channel, msg ) => {
                if(channel === 'trans-danmu'){
                    //传过来的弹幕都是数组
                    msg.forEach( e => { 
                        this.danmus.push(e);
                    });
                    if(this.danmus.length > danmu_max_len){
                        this.clear();
                    }
                }else if(channel === 'clear'){
                    this.clear()
                }
            });

我写了这么几行在里面用来收取发来的消息,然后分别处理不同channel的消息
这接消息的搞完了,得发消息了

任务界面中定义一个方法

        transDanmus(danmus){
            //向弹幕窗口发送新弹幕
            ipc.send('to-danmu','trans-danmu',danmus);
        },

这不就把弹幕都发到弹幕窗口里了

好,这通讯的问题都解决了,接下来是获取弹幕了,这又咋整啊,是我的插件登场的时候了(

npm i bili-danmu-loader-yl --save

回到任务界面中
我们可不只需要一个openDanmuDialog来打开窗口,还需要一个来启动弹幕加载
那就来吧

//启动弹幕加载
        startDanmuLoader(){
            //首先打开弹幕窗口,初始化窗口信息
            this.openDanmuDialog();
            const room_id = this.$store.getters.getRoomID;
            //把弹幕传输钩子挂到Loader上
            this.DanmuLoader.onadd = danmus => {
                this.transDanmus(danmus);
            }
            this.DanmuLoader.setRoomID(room_id);
            this.DanmuLoader.startLoader(() => {
                //成功后的回调
            });
        },

好,这就大功告成了,我们只需要调用一下startDanmuLoader()就可以启动弹幕姬了
剩下的无非就是一些美工活了,诸位可以自己定义样式,把弹幕姬弄成自己喜欢的样子,还可以通过onadd接口实现一些弹幕小游戏,统计一段时间内的弹幕信息分析特征啥的。

一些后话

老实说最开始做的时候被B站弹幕传输的数据给整得有点想吐,B站用的是二进制数据,Socket传输数据,拿到数据后我一看,嗯?不是Json,好吧那就分析一下数据吧,花了一段时间分离出来信息的头部和主体,封装了一下数据处理类,结果一拿出来用,嗯?怎么少了几条弹幕?这个时候我还是懵的,一次偶然瞄到数据长度比头部的提供的packet_len要长,嗯?这么长?再把数据拿出来一看发现B站把一堆弹幕都弄到一起去了,也就是说我还要拆数据,把不同的信息给分开,最后总算是做完了,感想就是,老老实实用Json它不想嘛。
后来我搜了一下,发现有挺多人做了B站直播弹幕抓取分析的,但好像都没有提到数据分割这茬。

这就算是做完了,如果各位有兴趣的话可以尝试一下,或者说想白嫖的话可以去我的项目https://github.com/Yeuoly/YeuolyDanmuJi看看,但这里有个蛋疼的事情,我需要弹幕姬拥有获取用户头像的能力,跨域的问题倒不是问题,但因为一个origin的原因,我的请求被B站403了(悲,现在的解决方案是搭建一个本地虚拟服务器,由它作为一个中转站去伪造origin获取用户公共信息,我使用的是php(原生的那种,连websocket都不行,端口号是32867,各位不嫌麻烦就用吧
这就又很蛋疼了,谁平时会开着个php服务器啊,装swoole感觉也不大好,和electron又不好整合,那能咋办啊?我现在的想法是到时候用C++写一个中转站,tcp接electron的请求,然后再去B站拿数据,就不需要搭建php服务器了,但我C菜啊(悲,性能搞不好会很差,这里很需要各位的帮助,实在没有就只能自己写了。

再讲一下为什么要用electron吧,首先一个是跨平台,而且弹幕姬这东西你总不能给它放网页里吧,所以就用electron了。第二个一个是html+css+js对自定义很友好,我到时候准备开放一些接口出来给用户自己定义弹幕样式,通过一些vue可视化编辑插件来完成这个工作,用户只需要拖一拖dom,改一改颜色,调一调大小,加一加图片就可以实现自己理想的弹幕姬了,有能力的用户还可以直接修改js和css。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容