Skip to content
forked from liqi0816/bilitwin

bilibili merged flv+mp4+ass+enhance / 哔哩哔哩: 超清FLV下载, FLV合并, 原生MP4下载, 弹幕ASS下载, MKV打包, 播放体验增强, 原生appsecret, 不借助其他网站

License

Notifications You must be signed in to change notification settings

hhl-db/bilitwin

 
 

Repository files navigation

正在施工。大约需要一个星期更新脚本。届时将放出可用版本。

围观群众看得懂的东西

  • Chrome更新了一个超好用的东西,现在我们可以几乎0内存合并FLV了

  • 之前我得了“不重构就会死”的病,不过终于找到药了!

  • 为了获得真·项目的经验,打算上Vue全家桶了。

  • 再拖更也不好意思了。

  • #help-wanted#

目前的状态

下载组件已完工。万岁!

功能增强组件(src/service/bilipolyfill.ts)正在进行中。

我本来是不看番剧的,但是最近居然也沉迷了,准备加一个没有灵魂的空降指挥部(src/codec/introskip)

SVM空降指挥部性能压力山大,思考之后还是用以图搜图的算法来做比较平衡

目录结构

  • util:工具 可复用的函数

  • codec:媒体文件(flv/ass/mkv)生成

  • service:与B站页面交互 从B站抓数据 在页面上模拟用户操作 监控页面状态

  • store:Vuex 转发service的数据到视图层

  • components:视图层

开发指南

  • util:旧文件有JSDoc,新文件是TypeScript,易懂

  • codec:纯函数,易懂

  • service:重头戏来了

    作为插件,页面本身的状态管理是不会管我们的,但是插件又依赖于页面,状态同步成了一团乱麻。

    仔细思考了现有的技术后,我总结出,一个能独立存在的模块与外界交互的方式主要有以下几种

    • 向模块输入信息:设置对象属性,调用方法传参数,在输入上设置事件监听器/回调
    • 模块向外输出信息:获取对象属性,调用方法返回同步值,调用方法返回Promise,发射事件/调用回调

    搜索了一番之后,我发现我想要的其实是一个Promise.all的事件版,或者Observable.merge的所有事件版。问了一圈,没找到。

    所以只能自己造轮子。

    util/event-duplex.js就是成果。解释一下:

    OnEventDuplexFactory<InputEventMap, OutputEventMap, OutOnEventMap>(init?)

    InputEventMap定义这个模块接受哪些事件作为输入,OutputEventMap定义这个模块发射哪些事件作为输出。这前两个类型虽然有默认值,但是还是强烈建议写上,至少可以作为文档。

    与此同时,我想要给模块加上onevent这种比较方便的监听器添加方法。因为TypeScript的类型映射不能改属性名,所以只能用OutOnEventMap再次指明事件类型,内容和OutputEventMap一样,只不过属性名前面要加上on。因为JavaScript不能读取TypeScript类型,所以要额外再传递一次哪些事件要加上onevent属性,参数init接受Iterable<事件名>。这两个倒是可选的,只不过加上以后方便一些。

    示例:

    OnEventDuplexFactory<
      { click: MouseEvent },
      { load: ProgressEvent },
      { onload: ProgressEvent }
      >(['load'])

    会生成一个类,实现了(一个合理简化了的)addEL/removeEL/dispatchE,而且有onload属性,addEL('load')的时候也可以正确提示事件类型。那事件输入呢?

    OnEventDuplex实现了[inputSocketSymbol]: EventSocket接口,这个inputSocketSymbol也从util/event-duplex.js里导出了。EventSocket的目的就是提供Promise.all的事件版。因此,所有事件输入都应该通过this[inputSocketSymbol].addEL/removeEL实现。

    但这不是转了一圈,更麻烦了呀?

    接下来就是神奇之处了:util/event-duplex.js还导出了一个工具函数pipeEventsThrough。望文生义,它接受两个参数,第一个是事件源,第二个是实现了[inputSocketSymbol]: EventSocket接口的 接盘侠 事件目的地。

    pipeEventsThrough(button, eventDuplex);

    这个函数会从eventDuplex[inputSocketSymbol]获取所有eventDuplex订阅过的事件,然后在button发射这些事件的时候,转发一份给eventDuplex

    注意,“获取所有eventDuplex订阅过的事件”这个行为是一次性的,所以我推荐把所有事件输入都提前到constructor里绑定好,或者至少调用eventDuplex[inputSocketSymbol].addEventType显式注册。如果pipe之后再扩充事件列表,新事件并不会被转发。这个时候需要重新pipeEventsThrough——别担心,重复地注册监听器也只会触发一次

    所以现在我们可以有各种舒服的用法了:

    pipeEventsThrough(button1, eventDuplex1);
    pipeEventsThrough(button2, eventDuplex1);
    pipeEventsThrough(button1, eventDuplex2);
    pipeEventsThrough(button2, eventDuplex2);

    想要取消?

    eventDuplex1[inputSocketSymbol].disconnect(button1);

    如果上游是EventDuplex,还可以链式调用

    pipeEventsThrough(button1, eventDuplex1)
      .pipeEventsThrough(eventDuplex2)
      .pipeEventsThrough(eventDuplex3)
      .addEventListener('load', console.log)

    很像RxJS,是吧?我承认,可能最主要的差别仅仅是我实现了一个fromAllEvents

    可能还有一个差别,RxJS与函数式结合得最好,如果管道有状态就很坑爹,更不要说管道上的方法了——当然,我喜欢函数式,学Haskell几乎应该是我大学最快乐的一门课了。但是函数式处理IO真的会变得很奇怪,以至于出了do这个语法糖。EventDuplex并不会偏向哪一种,设置属性或者调用方法也不会很奇怪,毕竟一开始就是new出来的,明示了这个模块是有状态的。所以

    eventDuplex2.pause()
    console.log(eventDuplex2.currentState)
    console.log(await eventDuplex2.setState('buffer-empty'))  
    eventDuplex2.resume()

    这样的代码也OK。

    至于垃圾收集,很遗憾,我没有找到完美的解决方案,所以理论上和原生监听器一样,事件源保留监听器的引用,如果是事件源先被垃圾收集,监听器也会被收集,但如果事件源还在,想要先删掉监听器,需要显式.removeEL。不过我还是找到了一个妥协方案:

    eventDuplex[inputSocketSymbol].close()

    这会把EventSocket标记为已关闭(其实就是delete了所有属性)。事件源下一次发射事件的时候,将会移除监听器。在此之前,将会泄露一个空对象(EventSocket)+一些很短小的函数(监听器)。

    如果在EventSocket保留上游的引用,确实可以在close的时候自动清理,但是上游可以被收集了的时候并不会通知下游,下游保留上游的引用会导致上游泄露。考虑再三,还是决定与原生行为看齐。如果有更好的解决方案,请告诉我。

    现在有了用着顺手的模块,我们可以开始解耦了。

    • BiliUserjs:监控页面状态(aid/cid/video/播放控制条/右键菜单ul),输入无,输出页面状态改变事件
    • BiliMonkey:抓取网络请求,输入cid/播放控制条,输出视频地址/下载进度/模块是否需要重建
    • BiliPolyfill:简化用户操作,输入aid/cid/video,输出待定/模块是否需要重建
    • bilitwin-keeper:监控模块状态,输入Monkey/Polyfill,适时重建模块,不输出
    • bilitwin-options:负责持久化用户设置,CRUD,可能返回Promise,不使用事件
    • bilitwin-store:vuex中介,输入Monkey/Polyfill,筛选需要的事件,然后写入vuex
    • bilitwin-ui:监控UI,如果UI被源页面一句$.html()清掉了负责补上,输入BiliUserjs,输出待定
    • bilitwin:负责启动上面一大堆,并且安装合适的pipe,同时充当IoC容器
  • components:解耦UI与服务,这样以后UI可以单独放出去自定义,方便人民群众fork

About

bilibili merged flv+mp4+ass+enhance / 哔哩哔哩: 超清FLV下载, FLV合并, 原生MP4下载, 弹幕ASS下载, MKV打包, 播放体验增强, 原生appsecret, 不借助其他网站

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 74.0%
  • JavaScript 23.5%
  • Vue 1.1%
  • Other 1.4%