From 079df626f0bb590f00073541621ccae6574ef82b Mon Sep 17 00:00:00 2001
From: Akari <60416767+MotooriKashin@users.noreply.github.com>
Date: Sun, 29 Sep 2024 17:15:16 +0800
Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=A7=86=E9=A2=91=E4=B8=8B?=
=?UTF-8?q?=E8=BD=BD=E5=8A=9F=E8=83=BD=20(#21)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 初步搭建视频下载框架
* 支持curl命令行
* 支持aria2c命令行
* 更新下载源获取流程
* 实现APP鉴权获取流程
* 视频工具栏分享二维码
* Update README.md
* Bump 1.0.5.0
---
README.md | 56 ++-
package.json | 3 +-
src/assets/checked.png | Bin 0 -> 327 bytes
src/assets/flesh.png | Bin 0 -> 647 bytes
src/dash-player/index.d.ts | 2 +-
.../bilibili/playershared/VideoVod.proto | 7 +-
.../bapis/bilibili/playershared/VideoVod.ts | 7 +-
.../bapis/bilibili/playershared/VodInfo.proto | 7 +-
.../bapis/bilibili/playershared/VodInfo.ts | 7 +-
.../bilibili/api/pgc/player/web/playurl.ts | 2 +-
.../x/passport-tv-login/qrcode/auth_code.ts | 15 +-
.../x/passport-tv-login/qrcode/poll.ts | 2 +-
.../PlayViewUnite.ts | 10 +-
src/main/accessKey/index.css | 164 ++++++++
src/main/accessKey/index.d.css.ts | 2 +
src/main/accessKey/index.ts | 150 ++++++++
src/main/event.ts | 4 +
src/main/html/bofqi/toolbar/index.css | 5 +
src/main/html/bofqi/toolbar/index.ts | 6 +-
src/main/html/header/index.css | 3 +-
src/main/html/header/index.ts | 15 +-
src/main/html/index.ts | 2 -
src/main/index.ts | 10 +-
src/main/player/danmaku/index.ts | 2 +-
src/main/player/download/index.css | 258 +++++++++++++
src/main/player/download/index.d.css.ts | 2 +
src/main/player/download/index.ts | 353 ++++++++++++++++++
src/main/player/heartbeat/index.ts | 40 +-
src/main/player/index.ts | 163 ++++----
src/main/player/nano/index.ts | 26 +-
src/main/player/progress/index.ts | 2 +-
src/main/player/recommend/index.ts | 22 +-
src/main/player/v2/index.ts | 2 +-
src/manifest.json | 6 +-
src/player/auxiliary/info/index.ts | 4 +-
src/player/auxiliary/info/more.ts | 7 +-
36 files changed, 1225 insertions(+), 141 deletions(-)
create mode 100644 src/assets/checked.png
create mode 100644 src/assets/flesh.png
create mode 100644 src/main/accessKey/index.css
create mode 100644 src/main/accessKey/index.d.css.ts
create mode 100644 src/main/accessKey/index.ts
create mode 100644 src/main/player/download/index.css
create mode 100644 src/main/player/download/index.d.css.ts
create mode 100644 src/main/player/download/index.ts
diff --git a/README.md b/README.md
index 53b7a3d..cb62286 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,55 @@
-重构 B 站旧版页面
\ No newline at end of file
+
+
+
+# Bilibili 2019
+恢复 2019 年 12 月 09 日前的部分[B站](https://www.bilibili.com/)页面,为了那些念旧的人
+
+
+
+---
+这是一个[Google Chrome](https://www.google.com/chrome/)的[manifest V3](https://developer.chrome.com/docs/extensions/mv3/manifest/)扩展项目,恢复 2019 年 12 月 09 日前的部分[B站](https://www.bilibili.com/)页面,尤其是那个小电视播放器。
+项目前身是[Bilibili-Old](https://github.com/MotooriKashin/Bilibili-Old)油猴脚本,在B站原始页面的基础上修修补补了四年多,奈何实在老旧,难以为继,于是有了推倒重来的念头。加上[manifest V3](https://developer.chrome.com/docs/extensions/mv3/manifest/)标准的推行,油猴脚本前途未卜,索性转为扩展项目。
+由于 HTML + js + css 都不再复用,从零开始手搓,肯定做不到完全复刻当年的模样,请多见谅!
+
+---
+# 功能
+各种页面正在慢慢搭建中……
+- 播放器
+ + 视频
+ - [x] DASH
+ - [x] flv
+ - [x] 本地视频文件
+ + 弹幕
+ - [x] 普通弹幕(mode1/4/5/6)
+ - [x] 高级弹幕(mode7)
+ - [x] 代码弹幕(mode8)
+ - [x] BAS弹幕(mode9)
+ - [x] 本地弹幕文件
+ + 字幕
+ - [x] 在线 CC 字幕
+ - [x] 本地 vtt 文件
+- 评论
+ + [x] 翻页
+- 页面
+ + [x] B站主页
+ + [x] av
+ + [x] Bangumi
+
+---
+# 安装
+- 欢迎在安装之前访问核心[播放器页面](https://motoorikashin.github.io/Bilibili-2019/)体验一下旧版播放器
+- 若要安装,则要求使用最新的[Google Chrome](https://www.google.com/chrome/)浏览器(当前要求核心版本 **130** 以上,以后只会更高,且暂时无暇理会任何兼容性需求)。
+ 1. 在[RELEASE](https://github.com/MotooriKashin/Bilibili-2019/releases)页面下载最新的版本
+ 2. 解压到本地磁盘任意目录
+ 3. 打开 Chrome [管理扩展程序](chrome://extensions/)页面打开右上角的【开发者模式】
+ 4. 点击【加载已解压的扩展程序】按钮加载刚解压出的文件所在目录
+ 5. 更新版本请重复上述步骤
+
+另外,也可以克隆项目到本地手动构建,参看[代码贡献指南](.github/contributing.md)
+
+# 维护
+欢迎念旧的人一起搭建记忆中的Bilibili,这里有一份简易的[代码贡献指南](.github/contributing.md)供参考
+
+---
+# 开源
+[MIT License](LICENSE)
\ No newline at end of file
diff --git a/package.json b/package.json
index ecf528b..cab1a71 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,7 @@
{
"dependencies": {
- "flv.js": "*"
+ "flv.js": "*",
+ "easyqrcodejs": "*"
},
"devDependencies": {
"@types/fs-extra": "*",
diff --git a/src/assets/checked.png b/src/assets/checked.png
new file mode 100644
index 0000000000000000000000000000000000000000..8e2fc12a65c7aa31fb968ebcd33562d260b0a37e
GIT binary patch
literal 327
zcmeAS@N?(olHy`uVBq!ia0vp^Qa~)k!3HEi(17srqa#;unQ@-`WWuszr>vDn@1!K;HV
z<`Xzmm=Ck%H24I~buqtigyVYOgQ?9STxw1lH})Nvzh}w%+)qm@S(owDzd3N(L059w
zDNAdHPa)<;g$Axs9bCo7>jT*v9S;5}Tqwu#(97V%?CzUN>$csmY%2K8q{`fpbxQIq
zvxiHlWw$qLe@~5~rj)qZwHb^-&JTZeIjO0f5eWQQ%l6OaP-5ZZH+@RqtJBjOwD;0yfhU0B!4OST8HG7V;&u`6;h4qaveVzNmdKI;Vst0I`gCSpWb4
literal 0
HcmV?d00001
diff --git a/src/assets/flesh.png b/src/assets/flesh.png
new file mode 100644
index 0000000000000000000000000000000000000000..4c808594c197048b5de3df474f3b2f96da2469a5
GIT binary patch
literal 647
zcmV;20(kw2P)soFAfy691ql_%rh-s`O$GKqhVYi70`!Q%(y_OD
zk(@Yk6g%IHG}>G3&+Vdc9*fq$Kt4XbUKJfl_@z
zwtw-s0Qu#B9G}6t6y0D^Ee`8l`NW_MvV+ljfbrE3aOW!-ld&5E2tJgb0e~(p!;5%TJr(bEm=+R6v$_NzQAWd2ZWv|p1run
zuJDd^JcCaay+NZoss8gK!+UJTKD%)f}P(await response.json()).data;
+ return await response.json();
}
-interface IAuthCode {
- /** 二维码内容 url */
- url: string;
- /** 扫码登录秘钥 */
- auth_code: string;
+interface IAuthCode extends RestType {
+ data: {
+ /** 二维码内容 url */
+ url: string;
+ /** 扫码登录秘钥 */
+ auth_code: string;
+ }
}
\ No newline at end of file
diff --git a/src/io/com/bilibili/passport/x/passport-tv-login/qrcode/poll.ts b/src/io/com/bilibili/passport/x/passport-tv-login/qrcode/poll.ts
index 73ff2a9..412188a 100644
--- a/src/io/com/bilibili/passport/x/passport-tv-login/qrcode/poll.ts
+++ b/src/io/com/bilibili/passport/x/passport-tv-login/qrcode/poll.ts
@@ -29,7 +29,7 @@ interface IPoll extends RestType {
*/
code: number;
/** 当且仅当 code===0 时登录成功 */
- data?: IPollData;
+ data: IPollData;
}
interface IPollData {
diff --git a/src/io/net/biliapi/grpc/bilibili.app.playerunite.v1.Player/PlayViewUnite.ts b/src/io/net/biliapi/grpc/bilibili.app.playerunite.v1.Player/PlayViewUnite.ts
index dcd17c6..240bd71 100644
--- a/src/io/net/biliapi/grpc/bilibili.app.playerunite.v1.Player/PlayViewUnite.ts
+++ b/src/io/net/biliapi/grpc/bilibili.app.playerunite.v1.Player/PlayViewUnite.ts
@@ -13,11 +13,12 @@ export async function PlayViewUnite(
cid,
qn = QUALITY.P_8K,
extraContent = {},
+ preferCodecType = 0,
mid = 0n,
fnval = FNVAL.DASH_H265 ^ FNVAL.HDR ^ FNVAL.DASH_4K ^ FNVAL.DOLBY_AUDIO ^ FNVAL.DOLBY_VIDEO ^ FNVAL.DASH_8K ^ FNVAL.DASH_AV1
}: IPlayViewUnite) {
const rep = PlayViewUniteReq.encode({
- vod: VideoVod.create({ aid, cid, qn: BigInt(qn), fnval, forceHost: 2, qnTrial: 1 }),
+ vod: VideoVod.create({ aid, cid, qn: BigInt(qn), fnval, forceHost: 2, preferCodecType, qnTrial: 1 }),
extraContent,
}).finish();
const body = new Uint8Array(await new Response(new Blob([rep]).stream().pipeThrough(new CompressionStream('gzip'))).arrayBuffer());
@@ -40,4 +41,11 @@ interface IPlayViewUnite {
mid?: bigint;
/** 视频流格式 */
fnval?: number;
+ /**
+ * 视频编码
+ * | 0 | 1 | 2 | 3 |
+ * | :-: | :-: | :-: | :-: |
+ * | 不指定 | AVC | HEVC | AV1 |
+ */
+ preferCodecType?: number;
}
\ No newline at end of file
diff --git a/src/main/accessKey/index.css b/src/main/accessKey/index.css
new file mode 100644
index 0000000..45e7390
--- /dev/null
+++ b/src/main/accessKey/index.css
@@ -0,0 +1,164 @@
+@scope {
+ :scope {
+ color-scheme: light dark;
+
+ --000000a6: #000000a6;
+ --999: #999;
+ --00a1d6: #00a1d6;
+ --e7e7e7: #e7e7e7;
+ --212121: #212121;
+ --ffffffe6: #ffffffe6;
+ --fff: #fff;
+ --505050: #505050;
+
+ border: 0;
+ border-radius: 8px;
+ box-shadow: 0 0 6px rgba(0, 0, 0, .1);
+ padding: 52px 65px 29px 92px;
+ background-image: url(https://s1.hdslb.com/bfs/seed/jinkela/short/mini-login/img/22_open.72c00877.png), url(https://s1.hdslb.com/bfs/seed/jinkela/short/mini-login/img/33_open.43a09438.png);
+ background-position: 0 100%, 100% 100%;
+ background-repeat: no-repeat, no-repeat;
+ background-size: 14%;
+ }
+
+ &::backdrop {
+ background-color: var(--000000a6);
+ }
+
+ a {
+ text-decoration: none;
+ }
+}
+
+.container {
+ display: flex;
+ flex-direction: column;
+ row-gap: 40px;
+
+ >.wrap {
+ display: flex;
+ column-gap: 40px;
+
+ >.left {
+ display: flex;
+ flex-direction: column;
+ row-gap: 18px;
+
+ >header {
+ text-align: center;
+ font-size: 18px;
+ color: var(--212121);
+ }
+
+ >.panel {
+ inline-size: 173px;
+ block-size: 173px;
+ border-radius: 8px;
+ border: 1px solid var(--e7e7e7);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+
+ >.qrcode {
+ inline-size: 160px;
+ block-size: 160px;
+
+ >canvas {
+ inline-size: inherit;
+ block-size: inherit;
+ }
+ }
+
+ >.qrtip {
+ position: absolute;
+ block-size: 173px;
+ }
+
+ >.progress {
+ position: absolute;
+ inline-size: inherit;
+ block-size: inherit;
+ background-color: var(--ffffffe6);
+ border: 1px solid var(--e7e7e7);
+ border-radius: 8px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ row-gap: 4px;
+
+ &:not(.d) {
+ display: none;
+ }
+
+ >:first-child {
+ inline-size: 56px;
+ block-size: 56px;
+ background-position: center;
+ background-repeat: no-repeat;
+ background-color: var(--fff);
+ border-radius: 50%;
+ }
+
+ &.to>:first-child {
+ background-image: url(../../assets/flesh.png);
+ }
+
+ &.sd>:first-child {
+ background-image: url(../../assets/checked.png);
+ }
+
+ >:not(:first-child) {
+ font-size: 13px;
+ color: var(--505050);
+ }
+
+ }
+
+ &:not(:has(~footer a:hover))>.qrtip {
+ display: none;
+ }
+ }
+
+ >footer {
+ color: var(--212121);
+ font-size: 14px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ a {
+ color: var(--00a1d6);
+ }
+ }
+ }
+
+ >.line {
+ background-color: var(--e7e7e7);
+ inline-size: 1px;
+ }
+
+ >.right {
+ inline-size: 400px;
+
+ >header {
+ text-align: center;
+ font-size: 18px;
+ color: var(--212121);
+ }
+ }
+ }
+
+ >.buttom {
+ font-size: 13px;
+ color: var(--999);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ a {
+ color: var(--00a1d6);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/accessKey/index.d.css.ts b/src/main/accessKey/index.d.css.ts
new file mode 100644
index 0000000..eb9bc4f
--- /dev/null
+++ b/src/main/accessKey/index.d.css.ts
@@ -0,0 +1,2 @@
+declare const stylesheet: CSSStyleSheet;
+export default stylesheet;
\ No newline at end of file
diff --git a/src/main/accessKey/index.ts b/src/main/accessKey/index.ts
new file mode 100644
index 0000000..10ceb1d
--- /dev/null
+++ b/src/main/accessKey/index.ts
@@ -0,0 +1,150 @@
+import { customElement } from "../../utils/Decorator/customElement";
+import { Element } from "../../utils/element";
+import QRCode from "easyqrcodejs";
+import stylesheet from "./index.css" with {type: 'css'};
+import { auth_code } from "../../io/com/bilibili/passport/x/passport-tv-login/qrcode/auth_code";
+import { toastr } from "../../toastr";
+import { poll } from "../../io/com/bilibili/passport/x/passport-tv-login/qrcode/poll";
+
+/** APP 扫码登录 */
+@customElement(undefined, `access-key-${Date.now()}`)
+export class AccessKey extends HTMLElement {
+
+ /**
+ * 需要监听变动的属性。
+ * 与实例方法`attributeChangedCallback`配合使用。
+ * 此字符串序列定义了`attributeChangedCallback`回调时的第一个参数的可能值。
+ */
+ // static observedAttributes = [];
+
+ /**
+ * 在属性更改、添加、移除或替换时调用。
+ * 需要与静态属性`observedAttributes`配合使用。
+ * 此回调的第一个参数在`observedAttributes`数组中定义。
+ */
+ // attributeChangedCallback(name: IobservedAttributes, oldValue: string, newValue: string) {}
+
+ /** 每当元素添加到文档中时调用。 */
+ // connectedCallback() {}
+
+ /** 每当元素从文档中移除时调用。 */
+ // disconnectedCallback() {}
+
+ /** 每当元素被移动到新文档中时调用。 */
+ // adoptedCallback() {}
+
+ #host = this.attachShadow({ mode: 'closed' });
+
+ #container = Element.add('div', {
+ appendTo: this.#host, class: 'container', innerHTML: `
+
未注册过哔哩哔哩的手机号,我们将自动帮你注册账号
+
+
` });
+
+ #body = Element.add('div', { class: 'wrap', insertTo: { target: this.#container, where: 'afterbegin' } });
+
+ #left = Element.add('div', { appendTo: this.#body, class: 'left', innerHTML: '' });
+
+ #line = Element.add('div', { appendTo: this.#body, class: 'line' });
+
+ #right = Element.add('div', {
+ appendTo: this.#body, class: 'right', innerHTML: `
+此功能模拟APP端登录过程来获取身份鉴权
+有了APP鉴权,才能实现一些网页端原本无法实现的操作。这肯定伴随着一定的风险,尤其是您想实现的操作可能会将该鉴权泄露给第三方时。所以
+如非必要,切莫授权
+该鉴权有什么用?问就是没用,建议点击空白处退出→_→
+鉴权都有有效期,怀疑鉴权失效时请重新扫码授权即可~` });
+
+ #codePanel = Element.add('div', { appendTo: this.#left, class: 'panel' });
+
+ #codeTip = Element.add('footer', { appendTo: this.#left, innerHTML: '扫码登录或扫码下载APP
' });
+
+ #qrcode = Element.add('div', { appendTo: this.#codePanel, class: 'qrcode' });
+
+ #qrTip = Element.add('img', { appendTo: this.#codePanel, class: 'qrtip', attribute: { src: '//s1.hdslb.com/bfs/seed/jinkela/short/mini-login/img/qr-tips.51ff2bcf.png' } });
+
+ #timeout = Element.add('div', { appendTo: this.#codePanel, class: ['progress', 'to'], innerHTML: '二维码已过期
请点击刷新
' });
+
+ #scaned = Element.add('div', { appendTo: this.#codePanel, class: ['progress', 'sd'], innerHTML: '扫码成功
请在手机登录
' });
+
+ #QRCode?: QRCode;
+
+ #timer?: ReturnType;
+
+ constructor() {
+ super();
+
+ this.#host.adoptedStyleSheets = [stylesheet];
+
+ this.popover = 'auto';
+
+ this.addEventListener('toggle', () => {
+ if (this.matches(':popover-open')) {
+ this.auth_code();
+ } else {
+ clearInterval(this.#timer);
+ }
+ });
+ this.#timeout.addEventListener('click', this.auth_code);
+ }
+
+ auth_code = () => {
+ clearInterval(this.#timer);
+ auth_code()
+ .then(({ code, message, data }) => {
+ if (code !== 0) throw new ReferenceError(message, { cause: { code, message, data } });
+ const { url, auth_code } = data;
+ this.#QRCode ? this.#QRCode.makeCode(url) : (this.#QRCode = new QRCode(this.#qrcode, { text: url, correctLevel: QRCode.CorrectLevel.H }));
+ this.poll(auth_code);
+ this.#timeout.classList.remove('d');
+ this.#scaned.classList.remove('d');
+ })
+ .catch(e => {
+ toastr.error('获取登录二维码出错', e);
+ console.error(e);
+ })
+ }
+
+ poll(auth_code: string) {
+ this.#timer = setInterval(() => {
+ poll(auth_code)
+ .then(({ code, message, data }) => {
+ switch (code) {
+ case 0: {
+ clearInterval(this.#timer);
+ const { access_token, expires_in } = data;
+ const timeout = Date.now() + expires_in * 1000;
+ localStorage.setItem('access_key', access_token);
+ localStorage.setItem('access_key_expires_in', timeout);
+ toastr.success('获取移动端鉴权成功', `有效期至:${new Date(timeout).toLocaleString()}`)
+ break;
+ }
+ case 86038: {
+ this.#timeout.classList.add('d');
+ clearInterval(this.#timer);
+ break;
+ }
+ case 86039: {
+ break;
+ }
+ case 86090: {
+ this.#scaned.classList.add('d');
+ break;
+ }
+ default: {
+ throw new ReferenceError(message, { cause: { code, message, data } });
+ }
+ }
+ })
+ .catch(e => {
+ toastr.error('二维码状态错误', e);
+ console.error(e);
+ })
+ }, 2e3);
+ }
+
+ showPopover() {
+ document.body.contains(this) || document.body.appendChild(this);
+ super.showPopover();
+ }
+}
\ No newline at end of file
diff --git a/src/main/event.ts b/src/main/event.ts
index 2c7b0d7..5389505 100644
--- a/src/main/event.ts
+++ b/src/main/event.ts
@@ -84,6 +84,9 @@ export enum MAIN_EVENT {
/** 追番 */
ZHUI_FAN,
+
+ /** APP登录 */
+ ACCESS_KEY,
}
/** 播放器事件基类 */
@@ -98,4 +101,5 @@ export interface IPlayerEvent {
7: void;
8: void;
9: boolean;
+ 10: void;
}
\ No newline at end of file
diff --git a/src/main/html/bofqi/toolbar/index.css b/src/main/html/bofqi/toolbar/index.css
index 70ccf13..411ad5d 100644
--- a/src/main/html/bofqi/toolbar/index.css
+++ b/src/main/html/bofqi/toolbar/index.css
@@ -174,6 +174,11 @@
>.or-code-pic {
inline-size: 100px;
block-size: 100px;
+
+ >canvas {
+ inline-size: inherit;
+ block-size: inherit;
+ }
}
}
}
diff --git a/src/main/html/bofqi/toolbar/index.ts b/src/main/html/bofqi/toolbar/index.ts
index feb3468..6b77fea 100644
--- a/src/main/html/bofqi/toolbar/index.ts
+++ b/src/main/html/bofqi/toolbar/index.ts
@@ -1,3 +1,4 @@
+import QRCode from "easyqrcodejs";
import { toviewAdd } from "../../../../io/com/bilibili/api/x/v2/history/toview/add";
import { toviewDel } from "../../../../io/com/bilibili/api/x/v2/history/toview/del";
import { toviewWeb } from "../../../../io/com/bilibili/api/x/v2/history/toview/web";
@@ -73,6 +74,8 @@ export class Toolbar extends HTMLDivElement {
#appBox = Element.add('a', { appendTo: this, class: ['app', 'box'], attribute: { href: '//app.bilibili.com', target: "_blank" }, innerHTML: '用手机看转移阵地~
' });
+ #qrcode?: QRCode;
+
#aid = 0;
get $aid() {
@@ -190,10 +193,11 @@ export class Toolbar extends HTMLDivElement {
const { View } = data;
cid = View.pages[p - 1].cid;
this.#shareText.querySelector('.num')!.textContent = Format.carry(View.stat.share);
- this.#sharePopup.querySelector('#link0')!.value = `https://www.bilibili.com/video/av${aid}/`;
+ this.#sharePopup.querySelector('#link0')!.value = `https://www.bilibili.com/video/av${aid}`;
this.#sharePopup.querySelector('#link2')!.value = ``;
this.#favBox.innerHTML = `收藏${Format.carry(View.stat.favorite)}
`;
this.#coinBox.innerHTML = `投币${Format.carry(View.stat.coin)}
`;
+ this.#qrcode ? this.#qrcode.makeCode(`https://www.bilibili.com/video/av${aid}`) : (this.#qrcode = new QRCode(this.#sharePopup.querySelector('.or-code-pic'), { text: `https://www.bilibili.com/video/av${aid}`, correctLevel: QRCode.CorrectLevel.H }));
this.$aid = aid;
})
.catch(e => {
diff --git a/src/main/html/header/index.css b/src/main/html/header/index.css
index 78e87f5..789539e 100644
--- a/src/main/html/header/index.css
+++ b/src/main/html/header/index.css
@@ -80,7 +80,6 @@
background-color: var(--eee);
aspect-ratio: 32 / 3;
anchor-name: --head-banner;
- container: banner / size;
position: relative;
overflow: clip;
@@ -92,10 +91,10 @@
display: flex;
align-items: center;
justify-content: center;
+ container: layer / size;
>img,
>video {
- inline-size: 100%;
block-size: 100%;
}
}
diff --git a/src/main/html/header/index.ts b/src/main/html/header/index.ts
index f5801aa..dee1220 100644
--- a/src/main/html/header/index.ts
+++ b/src/main/html/header/index.ts
@@ -130,11 +130,10 @@ export class Header extends HTMLElement {
for (const { resources, scale, rotate, translate, blur, opacity } of animatedBannerConfig.layers) {
const div = Element.add('div', { class: 'layer', appendTo: this.#banner });
for (const { id, src } of resources) {
- const $scale = (scale?.initial || 0) + (scale?.offset || 0);
- const $rotate = (rotate?.initial || 0) + (rotate?.offset || 0);
- const $translate = [((translate?.initial?.[0] || 0) + (translate?.offset?.[0] || 0)) * ($scale || 1), (translate?.initial?.[1] || 0) + (translate?.offset?.[1] || 0) * ($scale || 1)];
- const $blur = blur?.wrap === 'alternate' ? Math.abs((blur.initial || 0) + (blur.offset || 0)) : Math.max(0, (blur?.initial || 0) + (blur?.offset || 0));
- const x = (opacity?.initial === undefined ? 1 : opacity.initial) + (opacity?.offset || 0);
+ // const $scale = (scale?.initial || 1);
+ const $translate = [translate?.initial?.[0] || 0, translate?.initial?.[1] || 0];
+ const $blur = blur?.wrap === 'alternate' ? Math.abs(blur.initial || 0) : Math.max(0, blur?.initial || 0);
+ const x = opacity?.initial === undefined ? 1 : opacity.initial;
let y = Math.abs(x % 1);
if (Math.abs(x % 2) >= 1) {
y = 1 - y;
@@ -146,14 +145,14 @@ export class Header extends HTMLElement {
video.loop = true;
video.playsInline = true;
- $rotate && (video.style.rotate = $rotate + 'deg');
+ // $scale === 1 || (video.style.scale = $scale);
($translate[0] || $translate[1]) && (video.style.translate = `calc(100cqb / 155 * (${$translate[0]})) calc(100cqb / 155 * (${$translate[1]}))`);
($blur < 1e-4) || (video.style.filter = `blur(${$blur}px)`);
video.style.opacity = opacity?.wrap === 'alternate' ? y : Math.max(0, Math.min(1, x));
} else {
const img = Element.add('img', { attribute: { src, id: id }, appendTo: div },);
- $rotate && (img.style.rotate = $rotate + 'deg');
- // ($translate[0] || $translate[1]) && (img.style.translate = `calc(100cqb / 155 * (${$translate[0]})) calc(100cqb / 155 * (${$translate[1]}))`);
+ // $scale === 1 || (img.style.scale = $scale);
+ ($translate[0] || $translate[1]) && (img.style.translate = `calc(100cqb / 155 * (${$translate[0]})) calc(100cqb / 155 * (${$translate[1]}))`);
($blur < 1e-4) || (img.style.filter = `blur(${$blur}px)`);
img.style.opacity = opacity?.wrap === 'alternate' ? y : Math.max(0, Math.min(1, x));
}
diff --git a/src/main/html/index.ts b/src/main/html/index.ts
index 52ea393..4181d7b 100644
--- a/src/main/html/index.ts
+++ b/src/main/html/index.ts
@@ -36,8 +36,6 @@ export class Html extends HTMLHtmlElement {
private $body = Element.add('body', { appendTo: this });
- // private $goTop = Element.add('div', { class: 'go-top-m', title: '返回顶部' }, undefined, svg_sent);
-
constructor() {
super();
diff --git a/src/main/index.ts b/src/main/index.ts
index e7a448f..3b32743 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -1,4 +1,6 @@
+import { AccessKey } from "./accessKey";
import { InitComment } from "./comment/initComment";
+import { MAIN_EVENT, mainEv } from "./event";
import { Av } from "./html/av";
import { Bangumi } from "./html/bangumi";
import { BiliHeader } from "./html/header/BiliHeader";
@@ -51,4 +53,10 @@ switch (location.hostname) {
new InitComment();
break;
}
-}
\ No newline at end of file
+}
+
+let accessKey: AccessKey;
+
+mainEv.bind(MAIN_EVENT.ACCESS_KEY, () => {
+ (accessKey || (accessKey = new AccessKey())).showPopover();
+})
diff --git a/src/main/player/danmaku/index.ts b/src/main/player/danmaku/index.ts
index 449ba59..02ad089 100644
--- a/src/main/player/danmaku/index.ts
+++ b/src/main/player/danmaku/index.ts
@@ -56,7 +56,7 @@ export class Broadcast {
}
private get roomid() {
- return `video://${this.player.aid}/${this.player.cid}${this.player.ssid ? `?sid=${this.player.ssid}&epid=${this.player.epid}` : ''}`
+ return `video://${this.player.$aid}/${this.player.$cid}${this.player.$ssid ? `?sid=${this.player.$ssid}&epid=${this.player.$epid}` : ''}`
}
constructor(private player: BilibiliPlayer) {
diff --git a/src/main/player/download/index.css b/src/main/player/download/index.css
new file mode 100644
index 0000000..f1a41af
--- /dev/null
+++ b/src/main/player/download/index.css
@@ -0,0 +1,258 @@
+@scope {
+ :scope {
+ color-scheme: light dark;
+
+ --000000a6: #000000a6;
+ --000: #000;
+ --ccc: #ccc;
+ --00a1d6: #00a1d6;
+ --00b5e5: #00b5e5;
+ --fff: #fff;
+ --777: #777;
+ --bbb: #bbb;
+ --f5f: #f5f;
+ --0dd: #0dd;
+ --07e: #07e;
+ --7ba: #7ba;
+ --e0e: #e0e;
+ --f08: #f08;
+ --d4d: #d4d;
+ --c75: #c75;
+ --00d: #00d;
+ --00a: #00a;
+ --f90: #f90;
+ --d70: #d70;
+ --f00: #f00;
+ --c00: #c00;
+ --c0f: #c0f;
+ --90f: #90f;
+ --ffe42b: #ffe42b;
+ --dfb200: #dfb200;
+ --222: #222;
+ --e2e2e2: #e2e2e2;
+ --f25d8e: #f25d8e;
+
+ padding: 0;
+ border: 0;
+ border-radius: 4px;
+ }
+
+ &::backdrop {
+ background-color: var(--000000a6);
+ }
+
+ a {
+ text-decoration: none;
+ }
+}
+
+.container {
+ >header {
+ margin-block-start: 1em;
+ text-align: center;
+ }
+
+ >form {
+ padding: 1em;
+ color: var(--222);
+ display: flex;
+ flex-direction: column;
+ row-gap: .5em;
+
+ label {
+ display: flex;
+ column-gap: .5em;
+ position: relative;
+
+ &::before {
+ content: attr(data-label);
+ inline-size: 10%;
+ min-inline-size: 6em;
+ flex-shrink: 0;
+ white-space: nowrap;
+ text-align: end;
+ }
+
+ >:last-child {
+ flex: 1;
+ min-inline-size: 0;
+ cursor: pointer;
+ }
+
+ >select,
+ >input {
+ appearance: none;
+ border: 1px solid var(--e2e2e2);
+ border-radius: 4px;
+ white-space: nowrap;
+ outline: none;
+ }
+
+ >button {
+ position: absolute;
+ inset-inline-end: 0;
+ border-radius: 4px;
+ border: 1px solid var(--00a1d6);
+ color: var(--fff);
+ background-color: var(--00a1d6);
+ transition: 0.3s;
+ cursor: pointer;
+
+ &:hover {
+ background-color: var(--00b5e5);
+ border-color: var(--00b5e5)
+ }
+ }
+ }
+
+ >fieldset {
+ display: contents;
+
+ >legend {
+ text-align: center;
+ }
+ }
+
+ >button {
+ padding: 4px;
+ border-radius: 4px;
+ border: 1px solid var(--f25d8e);
+ font-size: 14px;
+ background-color: var(--fff);
+ color: var(--f25d8e);
+ transition: .3s;
+ cursor: pointer;
+
+ &:hover {
+ background-color: var(--f25d8e);
+ color: var(--fff);
+ }
+ }
+
+ >.wrap {
+ padding: 0;
+ border: 0;
+ inline-size: initial;
+ block-size: initial;
+ inset-block-start: initial;
+ inset-block-end: 0;
+ inset-inline: 0;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-items: center;
+ column-gap: 1em;
+
+ >div {
+ display: flex;
+ color: var(--000);
+ border: var(--ccc) 1px solid;
+
+ --background-color: transparent;
+
+ &.i-3gp {
+ --background-color: var(--bbb);
+ }
+
+ &.i-av1,
+ &.i-ac3 {
+ --background-color: var(--f5f);
+ }
+
+ &.i-flv {
+ --background-color: var(--0dd);
+ }
+
+ &.i-m4a,
+ &.i-m4v {
+ --background-color: var(--07e);
+ }
+
+ &.i-mp3 {
+ --background-color: var(--7ba);
+ }
+
+ &.i-mp4 {
+ --background-color: var(--777);
+ }
+
+ &.i-opus,
+ &.i-vor,
+ &.i-vp9,
+ &.i-hevc {
+ --background-color: var(--e0e);
+ }
+
+ &.i-qt {
+ --background-color: var(--f08);
+ }
+
+ &.i-webm {
+ --background-color: var(--d4d);
+ }
+
+ &.i-wmv {
+ --background-color: var(--c75);
+ }
+
+ &::before {
+ content: attr(data-label);
+ min-inline-size: 1.5em;
+ background-color: var(--background-color);
+ align-content: center;
+ }
+
+ >div {
+ padding: 3px;
+ color: var(--00a1d6);
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ row-gap: 2px;
+
+ --background-image: initial;
+
+ &::before {
+ content: attr(data-quality);
+ min-inline-size: 4em;
+ color: var(--fff);
+ background-color: var(--777);
+ background-image: var(--background-image);
+ text-align: center;
+ }
+
+ &::after {
+ content: attr(data-size);
+ font-size: 90%;
+ text-align: center;
+
+ }
+
+ &:hover {
+ color: var(--00b5e5);
+ }
+
+ &.large {
+ --background-image: linear-gradient(to right, var(--00d), var(--00a));
+ }
+
+ &.hd720 {
+ --background-image: linear-gradient(to right, var(--f90), var(--d70));
+ }
+
+ &.hd1080 {
+ --background-image: linear-gradient(to right, var(--f00), var(--c00));
+ }
+
+ &.highres {
+ --background-image: linear-gradient(to right, var(--c0f), var(--90f));
+ }
+
+ &.ultrahighres {
+ --background-image: linear-gradient(to right, var(--ffe42b), var(--dfb200));
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/player/download/index.d.css.ts b/src/main/player/download/index.d.css.ts
new file mode 100644
index 0000000..eb9bc4f
--- /dev/null
+++ b/src/main/player/download/index.d.css.ts
@@ -0,0 +1,2 @@
+declare const stylesheet: CSSStyleSheet;
+export default stylesheet;
\ No newline at end of file
diff --git a/src/main/player/download/index.ts b/src/main/player/download/index.ts
new file mode 100644
index 0000000..efe0dea
--- /dev/null
+++ b/src/main/player/download/index.ts
@@ -0,0 +1,353 @@
+import { pgcPlayurl } from "../../../io/com/bilibili/api/pgc/player/web/playurl";
+import { pugvPlayurl } from "../../../io/com/bilibili/api/pugv/player/web/playurl";
+import { playurl } from "../../../io/com/bilibili/api/x/player/playurl";
+import { PlayViewUnite } from "../../../io/net/biliapi/grpc/bilibili.app.playerunite.v1.Player/PlayViewUnite";
+import { toastr } from "../../../toastr";
+import { customElement } from "../../../utils/Decorator/customElement";
+import { Element } from "../../../utils/element";
+import { Format } from "../../../utils/fomat";
+import { IDM } from "../../IDM";
+import { BilibiliPlayer } from "..";
+import { GroupKind } from "../nano/GroupKind";
+import stylesheet from "./index.css" with {type: 'css'};
+import { MAIN_EVENT, mainEv } from "../../event";
+
+/** 下载功能 */
+@customElement(undefined, `download-${Date.now()}`)
+export class Download extends HTMLElement {
+
+ /**
+ * 需要监听变动的属性。
+ * 与实例方法`attributeChangedCallback`配合使用。
+ * 此字符串序列定义了`attributeChangedCallback`回调时的第一个参数的可能值。
+ */
+ // static observedAttributes = [];
+
+ /**
+ * 在属性更改、添加、移除或替换时调用。
+ * 需要与静态属性`observedAttributes`配合使用。
+ * 此回调的第一个参数在`observedAttributes`数组中定义。
+ */
+ // attributeChangedCallback(name: IobservedAttributes, oldValue: string, newValue: string) {}
+
+ /** 每当元素添加到文档中时调用。 */
+ // connectedCallback() {}
+
+ /** 每当元素从文档中移除时调用。 */
+ // disconnectedCallback() {}
+
+ /** 每当元素被移动到新文档中时调用。 */
+ // adoptedCallback() {}
+
+ #host = this.attachShadow({ mode: 'closed' });
+
+ #container = Element.add('div', {
+ appendTo: this.#host, class: 'container', innerHTML: ''
+ });
+
+ #from = Element.add('form', { appendTo: this.#container });
+
+ #method = Element.add('label', {
+ appendTo: this.#from, data: { label: '下载方式' }, innerHTML: `` });
+
+ #playurl = Element.add('label', {
+ appendTo: this.#from, data: { label: '下载源' }, innerHTML: `` });
+
+ #codeType = Element.add('label', {
+ appendTo: this.#from, data: { label: '视频编码' }, innerHTML: `` });
+
+ #accessKey = Element.add('input', { appendTo: Element.add('label', { appendTo: this.#from, data: { label: 'access_key' }, attribute: { title: '移动端鉴权。\n使用非网页端下载流接口时可能需要。' } }), attribute: { name: 'access_key', placeholder: 'APP端鉴权' } });
+
+ #accessKeyButton = Element.add('button', { insertTo: { target: this.#accessKey, where: 'beforebegin' }, attribute: { type: 'button' }, innerText: '获取' })
+
+ #fileName = Element.add('input', { appendTo: Element.add('label', { appendTo: this.#from, data: { label: '文件名' }, attribute: { title: '指定要保存的文件名(不含拓展名)。\n一般点击【获取】按钮会自动生成,可以在其基础上修改。\n另外,不是所有下载方式都支持指定文件名。' } }), attribute: { name: 'filename', placeholder: '文件名(不含拓展名)' } });
+
+ #referer = Element.add('input', { appendTo: Element.add('label', { appendTo: this.#from, data: { label: 'Referer' }, attribute: { title: '鉴权请求头之一。\n不正确的数值将导致服务器拒绝下载请求,除非你明确知道正确数值,否则不建议修改。\n该请求头包含了当前下载请求的来源页面的地址,即表示当前下载是通过此来源页面里的链接发起的。' } }), attribute: { name: 'referer' } });
+
+ #userAgent = Element.add('input', { appendTo: Element.add('label', { appendTo: this.#from, data: { label: 'User-Agent' }, attribute: { title: '鉴权请求头之一。\n不正确的数值将导致服务器拒绝下载请求,除非你明确知道正确数值,否则不建议修改。\n该请求头是一个特征字符串,使得服务器和对等网络能够识别发出下载请求的用户代理的应用程序、操作系统、供应商或版本信息。' } }), attribute: { name: 'useragent' } });
+
+ #action = Element.add('button', { appendTo: this.#from, innerText: '获取', attribute: { title: '获取或刷新下载流,点击后将在页面底部列出所有获取到的下载流。\n一般在修改【下载源】、【视频编码】等情况下需要点击进行刷新。' } });
+
+ #wrap = Element.add('div', { appendTo: this.#from, class: 'wrap' });
+
+ constructor(private player: BilibiliPlayer) {
+ super();
+
+ this.#host.adoptedStyleSheets = [stylesheet];
+
+ this.popover = 'auto';
+ this.#wrap.popover = 'auto';
+
+ this.#referer.value = location.origin;
+ this.#userAgent.value = 'Bilibili Freedoooooom/MarkII';
+
+ this.#from.addEventListener('submit', e => {
+ e.preventDefault();
+
+ this.callPlayurl();
+ });
+ this.addEventListener('toggle', () => {
+ this.#fileName.value = this.player.$title;
+ const accessKey = localStorage.getItem('access_key');
+ accessKey && (this.#accessKey.value = accessKey);
+ });
+ this.#accessKeyButton.addEventListener('click', () => {
+ mainEv.trigger(MAIN_EVENT.ACCESS_KEY, void 0);
+ });
+ }
+
+ private getp(v: number) {
+ switch (true) {
+ case v >= 2160: return 'ultrahighres';
+ case v > 1080: return 'highres';
+ case v > 720: return 'hd1080';
+ case v > 480: return 'hd720';
+ default: return 'large';
+ }
+ }
+
+ private getq(v: number) {
+ switch (true) {
+ case v >= 120: return 'ultrahighres';
+ case v > 80: return 'highres';
+ case v > 72: return 'hd1080';
+ case v > 32: return 'hd720';
+ default: return 'large';
+ }
+ }
+
+ private callPlayurl() {
+ const form = new FormData(this.#from);
+ const pl = form.get('playurl');
+ switch (pl) {
+ case '0': {
+ if (this.player.$kind === GroupKind.Pugv && this.player.$epid) {
+ pugvPlayurl(this.player.$aid, this.player.$cid, this.player.$epid)
+ .then(({ code, message, data }) => {
+ if (code !== 0) throw new ReferenceError(message, { cause: { code, message, data } });
+ this.decodePlayurl(data);
+ })
+ .catch(e => {
+ toastr.error('获取下载源出错', e);
+ console.error(e);
+ });
+ } else if (this.player.$epid) {
+ pgcPlayurl(this.player.$aid, this.player.$cid, this.player.$epid)
+ .then(({ code, message, result }) => {
+ if (code !== 0) throw new ReferenceError(message, { cause: { code, message, result } });
+ this.decodePlayurl(result);
+ })
+ .catch(e => {
+ toastr.error('获取下载源出错', e);
+ console.error(e);
+ });
+
+ } else {
+ playurl(this.player.$aid, this.player.$cid)
+ .then(({ code, message, data }) => {
+ if (code !== 0) throw new ReferenceError(message, { cause: { code, message, data } });
+ this.decodePlayurl(data);
+ })
+ .catch(e => {
+ toastr.error('获取下载源出错', e);
+ console.error(e);
+ });
+ }
+ break;
+ }
+ case '1': {
+ const accessKey = String(form.get('access_key'));
+ if (!accessKey) throw new ReferenceError('请先获取移动端鉴权~');
+ PlayViewUnite({ accessKey, aid: BigInt(this.player.$aid), cid: BigInt(this.player.$cid), preferCodecType: Number(form.get('codetype')) || 0 })
+ .then(({ vodInfo }) => {
+ if (!vodInfo) throw new ReferenceError('未获取到下载源');
+ this.#wrap.replaceChildren();
+ const { dashAudio, streamList, lossLessItem } = vodInfo;
+ const divs = >{};
+ streamList.forEach(d => {
+ const { dashVideo, streamInfo, segmentVideo } = d;
+ if (dashVideo) {
+ switch (dashVideo.codecid) {
+ case 7: {
+ divs.avc || (divs.avc = Element.add('div', { appendTo: this.#wrap, class: 'i-m4v', data: { label: 'AVC' } }));
+ Element.add('div', { appendTo: divs.avc, class: this.getp(dashVideo.height), data: { quality: streamInfo?.description || `${dashVideo.height}P`, size: Format.fileSize(Number(dashVideo.size)) } }).addEventListener('click', () => {
+ this.callMethod(dashVideo.baseUrl, this.#fileName.value + '.m4v', '', this.#userAgent.value);
+ });
+ break;
+ }
+ case 12: {
+ divs.hevc || (divs.hevc = Element.add('div', { appendTo: this.#wrap, class: 'i-hevc', data: { label: 'HEVC' } }));
+ Element.add('div', { appendTo: divs.hevc, class: this.getp(dashVideo.height), data: { quality: streamInfo?.description || `${dashVideo.height}P`, size: Format.fileSize(Number(dashVideo.size)) } }).addEventListener('click', () => {
+ this.callMethod(dashVideo.baseUrl, this.#fileName.value + '.m4v', '', this.#userAgent.value);
+ });
+ break;
+ }
+ case 13: {
+ divs.av1 || (divs.av1 = Element.add('div', { appendTo: this.#wrap, class: 'i-av1', data: { label: 'AV1' } }));
+ Element.add('div', { appendTo: divs.av1, class: this.getp(dashVideo.height), data: { quality: streamInfo?.description || `${dashVideo.height}P`, size: Format.fileSize(Number(dashVideo.size)) } }).addEventListener('click', () => {
+ this.callMethod(dashVideo.baseUrl, this.#fileName.value + '.m4v', '', this.#userAgent.value);
+ });
+ break;
+ }
+ }
+ } else if (segmentVideo) {
+ const type = segmentVideo.segment[0].url.includes('flv') ? 'flv' : 'mp4';
+ divs[type] || (divs[type] = Element.add('div', { appendTo: this.#wrap, class: `i-${type}`, data: { label: type.toUpperCase() } }));
+ segmentVideo.segment.forEach(d => {
+ Element.add('div', { appendTo: divs[type], class: this.getq(streamInfo?.quality || 0), data: { quality: streamInfo?.description! + '-' + d.order, size: Format.fileSize(Number(d.size)) } }).addEventListener('click', () => {
+ this.callMethod(d.url, this.#fileName.value + `.${type}`, '', this.#userAgent.value);
+ });
+ });
+ }
+ });
+ if (lossLessItem?.audio) {
+ divs.flac = Element.add('div', { appendTo: this.#wrap, class: 'i-opus', data: { label: 'FLAC' } });
+ Element.add('div', { appendTo: divs.flac, class: 'ultrahighres', data: { quality: 'HiRes', size: Format.fileSize(Number(lossLessItem.audio.size)) } }).addEventListener('click', () => {
+ this.callMethod(lossLessItem.audio!.baseUrl, this.#fileName.value + '.flac', '', this.#userAgent.value);
+ });
+ }
+ dashAudio.forEach(d => {
+ divs.aac || (divs.aac = Element.add('div', { appendTo: this.#wrap, class: 'i-m4a', data: { label: 'AAC' } }));
+ Element.add('div', { appendTo: divs.aac, class: 'large', data: { quality: d.id, size: Format.fileSize(Number(d.size)) } }).addEventListener('click', () => {
+ this.callMethod(d.baseUrl, this.#fileName.value + '.m4a', '', this.#userAgent.value);
+ });
+ });
+ this.#wrap.showPopover();
+ })
+ .catch(e => {
+ toastr.error('获取下载源出错', e);
+ console.error(e);
+ });
+ break;
+ }
+ }
+ }
+
+ private decodePlayurl(result: Awaited>['result'] | Awaited>['data'] | Awaited>['data']) {
+ const { durl, dash, support_formats, quality } = result;
+ const divs = >{};
+ if (dash) {
+ const { video, audio, flac, dolby, duration } = dash;
+ video.forEach((d, i) => {
+ switch (d.codecid) {
+ case 7: {
+ divs.avc || (divs.avc = Element.add('div', { appendTo: this.#wrap, class: 'i-m4v', data: { label: 'AVC' } }));
+ Element.add('div', { appendTo: divs.avc, class: this.getp(d.height), data: { quality: support_formats.find(e => e.quality === d.id)?.display_desc || `${d.height}P`, size: Format.fileSize(d.bandwidth * duration / 8) } }).addEventListener('click', () => {
+ this.callMethod(d.baseUrl, this.#fileName.value + '.m4v', this.#referer.value, this.#userAgent.value);
+ });
+ break;
+ }
+ case 12: {
+ divs.hevc || (divs.hevc = Element.add('div', { appendTo: this.#wrap, class: 'i-hevc', data: { label: 'HEVC' } }));
+ Element.add('div', { appendTo: divs.hevc, class: this.getp(d.height), data: { quality: support_formats.find(e => e.quality === d.id)?.display_desc || `${d.height}P`, size: Format.fileSize(d.bandwidth * duration / 8) } }).addEventListener('click', () => {
+ this.callMethod(d.baseUrl, this.#fileName.value + '.m4v', this.#referer.value, this.#userAgent.value);
+ });
+ break;
+ }
+ case 13: {
+ divs.av1 || (divs.av1 = Element.add('div', { appendTo: this.#wrap, class: 'i-av1', data: { label: 'AV1' } }));
+ Element.add('div', { appendTo: divs.av1, class: this.getp(d.height), data: { quality: support_formats.find(e => e.quality === d.id)?.display_desc || `${d.height}P`, size: Format.fileSize(d.bandwidth * duration / 8) } }).addEventListener('click', () => {
+ this.callMethod(d.baseUrl, this.#fileName.value + '.m4v', this.#referer.value, this.#userAgent.value);
+ });
+ break;
+ }
+ }
+ });
+ if (flac) {
+ divs.flac = Element.add('div', { appendTo: this.#wrap, class: 'i-opus', data: { label: 'FLAC' } });
+ Element.add('div', { appendTo: divs.flac, class: 'ultrahighres', data: { quality: 'HiRes', size: Format.fileSize(flac.audio.bandwidth * duration / 8) } }).addEventListener('click', () => {
+ this.callMethod(flac.audio.baseUrl, this.#fileName.value + '.flac', this.#referer.value, this.#userAgent.value);
+ });
+ }
+ dolby.audio?.forEach(d => {
+ divs.dolby = Element.add('div', { appendTo: this.#wrap, class: 'i-ac3', data: { label: '杜比音效' } });
+ Element.add('div', { appendTo: divs.dolby, class: 'ultrahighres', data: { quality: 'HiRes', size: Format.fileSize(d.bandwidth * duration / 8) } }).addEventListener('click', () => {
+ this.callMethod(d.baseUrl, this.#fileName.value + '.flac', this.#referer.value, this.#userAgent.value);
+ });
+ });
+ audio.forEach(d => {
+ divs.aac || (divs.aac = Element.add('div', { appendTo: this.#wrap, class: 'i-m4a', data: { label: 'AAC' } }));
+ Element.add('div', { appendTo: divs.aac, class: 'large', data: { quality: d.id, size: Format.fileSize(d.bandwidth * duration / 8) } }).addEventListener('click', () => {
+ this.callMethod(d.baseUrl, this.#fileName.value + '.m4a', this.#referer.value, this.#userAgent.value);
+ });
+ });
+ } else if (durl) {
+ const type = durl[0].url.includes('flv') ? 'flv' : 'mp4';
+ divs[type] || (divs[type] = Element.add('div', { appendTo: this.#wrap, class: `i-${type}`, data: { label: type.toUpperCase() } }));
+ durl.forEach(d => {
+ Element.add('div', { appendTo: divs[type], class: this.getq(quality), data: { quality: support_formats.find(e => e.quality === quality)?.display_desc + '-' + d.order, size: Format.fileSize(Number(d.size)) } }).addEventListener('click', () => {
+ this.callMethod(d.url, this.#fileName.value + `.${type}`, this.#referer.value, this.#userAgent.value);
+ });
+ })
+ }
+ }
+
+ private callMethod(
+ url: string,
+ fileName?: string,
+ referer?: string,
+ userAgent?: string,
+ ) {
+ const form = new FormData(this.#from);
+ const method = form.get('method');
+
+ switch (method) {
+ case '1': {
+ IDM.download({ url, fileName, userAgent, referer, origin: referer });
+ break;
+ }
+ case '2': {
+ const arr = ['curl', '-C', '-', `"${url}"`];
+ fileName && arr.push('-o', `"${fileName}"`);
+ referer && arr.push('--referer', `"${referer}"`);
+ userAgent && arr.push('--user-agent', `"${userAgent}"`);
+ navigator.clipboard.writeText(arr.join(' '))
+ .then(() => {
+ toastr.success('已复制 curl 命令行到剪切板,可粘贴到终端进行下载', '当然前提是您安装了 curl 程序', 'Windows 下情使用 cmd 而不是 PowerShell');
+ })
+ .catch(e => {
+ toastr.error('已复制 curl 命令行到剪切板失败', e);
+ console.error(e);
+ });
+ break;
+ }
+ case '3': {
+ const arr = ['aria2c', `"${url}"`];
+ fileName && arr.push(`--out="${fileName}"`);
+ referer && arr.push(`--referer="${referer}"`);
+ userAgent && arr.push(`--user-agent="${userAgent}"`);
+ navigator.clipboard.writeText(arr.join(' '))
+ .then(() => {
+ toastr.success('已复制 aria2c 命令行到剪切板,可粘贴到终端进行下载', '当然前提是您安装了 aria2c 程序');
+ })
+ .catch(e => {
+ toastr.error('已复制 aria2c 命令行到剪切板失败', e);
+ console.error(e);
+ });
+ break;
+ }
+ }
+ }
+
+ showPopover() {
+ document.body.contains(this) || document.body.appendChild(this);
+ super.showPopover();
+ }
+
+ identify() {
+ this.#wrap.replaceChildren();
+ this.#fileName.value = '';
+ }
+}
\ No newline at end of file
diff --git a/src/main/player/heartbeat/index.ts b/src/main/player/heartbeat/index.ts
index 853a257..5cd02a6 100644
--- a/src/main/player/heartbeat/index.ts
+++ b/src/main/player/heartbeat/index.ts
@@ -57,57 +57,57 @@ export class HeartBeat {
private onPlay = () => {
this.heartbeatSeek = true;
- this.player.cid && this.heartbeat && this.$csrf && heartbeat(
+ this.player.$cid && this.heartbeat && this.$csrf && heartbeat(
this.$csrf,
- this.player.aid,
- this.player.cid,
+ this.player.$aid,
+ this.player.$cid,
this.player.$video.currentTime,
HEARTBEAT_PLAY_TYPE.CONTINUE,
- this.player.epid ? HEARTBEAT_TYPE.BANGUMI : HEARTBEAT_TYPE.AV
+ this.player.$epid ? HEARTBEAT_TYPE.BANGUMI : HEARTBEAT_TYPE.AV
);
}
private onPause = () => {
- this.player.cid && this.heartbeat && this.$csrf && heartbeat(
+ this.player.$cid && this.heartbeat && this.$csrf && heartbeat(
this.$csrf,
- this.player.aid,
- this.player.cid,
+ this.player.$aid,
+ this.player.$cid,
this.player.$video.currentTime,
HEARTBEAT_PLAY_TYPE.PAUSE,
- this.player.epid ? HEARTBEAT_TYPE.BANGUMI : HEARTBEAT_TYPE.AV
+ this.player.$epid ? HEARTBEAT_TYPE.BANGUMI : HEARTBEAT_TYPE.AV
);
}
private onSeeking = () => {
- this.player.cid && this.heartbeat && this.heartbeatSeek && this.$csrf && heartbeat(
+ this.player.$cid && this.heartbeat && this.heartbeatSeek && this.$csrf && heartbeat(
this.$csrf,
- this.player.aid,
- this.player.cid,
+ this.player.$aid,
+ this.player.$cid,
this.player.$video.currentTime,
HEARTBEAT_PLAY_TYPE.START,
- this.player.epid ? HEARTBEAT_TYPE.BANGUMI : HEARTBEAT_TYPE.AV
+ this.player.$epid ? HEARTBEAT_TYPE.BANGUMI : HEARTBEAT_TYPE.AV
);
}
private onSeeked = () => {
- this.player.cid && this.heartbeat && this.heartbeatSeek && this.$csrf && heartbeat(
+ this.player.$cid && this.heartbeat && this.heartbeatSeek && this.$csrf && heartbeat(
this.$csrf,
- this.player.aid,
- this.player.cid,
+ this.player.$aid,
+ this.player.$cid,
this.player.$video.currentTime,
HEARTBEAT_PLAY_TYPE.PLAYING,
- this.player.epid ? HEARTBEAT_TYPE.BANGUMI : HEARTBEAT_TYPE.AV
+ this.player.$epid ? HEARTBEAT_TYPE.BANGUMI : HEARTBEAT_TYPE.AV
);
}
private onEnded = () => {
- this.player.cid && this.heartbeat && this.$csrf && heartbeat(
+ this.player.$cid && this.heartbeat && this.$csrf && heartbeat(
this.$csrf,
- this.player.aid,
- this.player.cid,
+ this.player.$aid,
+ this.player.$cid,
-1,
HEARTBEAT_PLAY_TYPE.ENDED,
- this.player.epid ? HEARTBEAT_TYPE.BANGUMI : HEARTBEAT_TYPE.AV
+ this.player.$epid ? HEARTBEAT_TYPE.BANGUMI : HEARTBEAT_TYPE.AV
);
}
}
\ No newline at end of file
diff --git a/src/main/player/index.ts b/src/main/player/index.ts
index 73a1472..eb88f27 100644
--- a/src/main/player/index.ts
+++ b/src/main/player/index.ts
@@ -22,6 +22,7 @@ import { AV } from "../../utils/av";
import { cookie } from "../../utils/cookie";
import { customElement } from "../../utils/Decorator/customElement";
import { https } from "../../utils/https";
+import { Download } from "./download";
import { MAIN_EVENT, mainEv } from "../event";
import { mainOptions } from "../option";
import { POLICY } from "../policy";
@@ -38,15 +39,17 @@ import { V2 } from "./v2";
@customElement(undefined, `bilibili-player-${Date.now()}`)
export class BilibiliPlayer extends Player {
- aid = 0;
+ $aid = 0;
- cid = 0;
+ $cid = 0;
- ssid = 0;
+ $ssid = 0;
- epid = 0;
+ $epid = 0;
- kind = GroupKind.Ugc;
+ $kind = GroupKind.Ugc;
+
+ $title = '';
/** 视频推荐组件 */
#recommend = new Recommend(this);
@@ -54,6 +57,9 @@ export class BilibiliPlayer extends Player {
/** 实时弹幕 */
#broadcast = new Broadcast(this);
+ /** 下载组件 */
+ #download = new Download(this);
+
constructor() {
super();
@@ -65,18 +71,26 @@ export class BilibiliPlayer extends Player {
new Progress(this);
// v2 接口
new V2(this);
+
+ this.$auxiliary.$info.$more.add({
+ text: '下载视频',
+ callback: () => {
+ this.#download.showPopover();
+ }
+ })
+
mainEv.trigger(MAIN_EVENT.OPTINOS_CHANGE, mainOptions);
ev.bind(PLAYER_EVENT.LOAD_VIDEO_FILE, () => {
- this.aid = this.cid = this.ssid = this.epid = 0;
- this.kind = GroupKind.Ugc;
+ this.$aid = this.$cid = this.$ssid = this.$epid = 0;
+ this.$kind = GroupKind.Ugc;
});
ev.bind(PLAYER_EVENT.DANMAKU_INPUT, ({ detail }) => {
const csrf = cookie.get('bili_jct');
if (csrf) {
dmPost({
csrf,
- aid: this.aid,
- oid: this.cid,
+ aid: this.$aid,
+ oid: this.$cid,
...detail,
})
.then(({ code, message, data }) => {
@@ -101,32 +115,32 @@ export class BilibiliPlayer extends Player {
const path = url.pathname.split('/');
switch (true) {
case /^av\d+$/i.test(path[2]): {
- this.aid = +path[2].slice(2);
+ this.$aid = +path[2].slice(2);
break;
}
case /^bv1[a-z0-9]{9}$/i.test(path[2]): {
- this.aid = +AV.fromBV(path[2]);
+ this.$aid = +AV.fromBV(path[2]);
break;
}
}
- if (this.aid) {
- Promise.allSettled([cards({ av: this.aid }), pagelist(this.aid)])
+ if (this.$aid) {
+ Promise.allSettled([cards({ av: this.$aid }), pagelist(this.$aid)])
.then(([cards, pagelist]) => {
const card = cards.status === "fulfilled" && cards.value;
const page = pagelist.status === "fulfilled" && pagelist.value;
if (page) {
const p = Number(new URLSearchParams(url.search).get('p')) || 1;
- this.cid = page.data[p - 1].cid;
+ this.$cid = page.data[p - 1].cid;
}
if (card) {
- const d = card.data[`av${this.aid}`];
- this.cid || (this.cid = d.cid);
+ const d = card.data[`av${this.$aid}`];
+ this.$cid || (this.$cid = d.cid);
if (d.redirect_url) {
const path = d.redirect_url.split('/');
- /^ep\d+$/i.test(path[5]) && (this.epid = +path[5].slice(2));
+ /^ep\d+$/i.test(path[5]) && (this.$epid = +path[5].slice(2));
}
navigator.mediaSession.metadata = new MediaMetadata({
- album: d.title,
+ album: this.$title = d.title,
artist: d.owner.name,
artwork: [{
src: d.pic
@@ -134,16 +148,16 @@ export class BilibiliPlayer extends Player {
title: d.title,
});
}
- if (!this.cid) throw new ReferenceError(`cid 无效`, { cause: [cards, pagelist] });
+ if (!this.$cid) throw new ReferenceError(`cid 无效`, { cause: [cards, pagelist] });
this.$connect();
this.#recommend.av();
})
.catch(e => {
- toastr.error('请求 aid 数据错误~', `aid: ${this.aid}`, e);
+ toastr.error('请求 aid 数据错误~', `aid: ${this.$aid}`, e);
console.error(e);
});
} else {
- toastr.error('识别 aid 信息错误~', `aid: ${this.aid}`);
+ toastr.error('识别 aid 信息错误~', `aid: ${this.$aid}`);
}
break;
}
@@ -151,51 +165,67 @@ export class BilibiliPlayer extends Player {
const path = url.pathname.split('/');
switch (true) {
case /^ss\d+$/i.test(path[3]): {
- this.ssid = +path[3].slice(2);
+ this.$ssid = +path[3].slice(2);
break;
}
case /^ep\d+$/i.test(path[3]): {
- this.epid = +path[3].slice(2);
+ this.$epid = +path[3].slice(2);
break;
}
}
- if (this.ssid || this.epid) {
- pgcAppSeason(this.ssid ? { season_id: this.ssid } : { ep_id: this.epid })
+ if (this.$ssid || this.$epid) {
+ pgcAppSeason(this.$ssid ? { season_id: this.$ssid } : { ep_id: this.$epid })
.then(({ code, message, data }) => {
if (code !== 0) throw new ReferenceError(message, { cause: { code, message, data } });
- this.ssid || (this.ssid = data.season_id);
- this.epid || (data.user_status.progress?.last_ep_id && (this.epid = data.user_status.progress?.last_ep_id));
+ this.$ssid || (this.$ssid = data.season_id);
+ this.$epid || (data.user_status.progress?.last_ep_id && (this.$epid = data.user_status.progress?.last_ep_id));
data.modules.forEach(d => {
switch (d.style) {
case "positive":
case "section": {
- this.epid || (this.epid = d.data.episodes[0]?.ep_id);
- if (this.epid) {
- const ep = d.data.episodes.find(d => d.ep_id === this.epid);
+ this.$epid || (this.$epid = d.data.episodes[0]?.ep_id);
+ if (this.$epid) {
+ const ep = d.data.episodes.find(d => d.ep_id === this.$epid);
if (ep) {
- this.aid = ep.aid;
- this.cid = ep.cid;
+ this.$aid = ep.aid;
+ this.$cid = ep.cid;
+ navigator.mediaSession.metadata = new MediaMetadata({
+ album: this.$title = `${data.title}:${isNaN(+ep.title) ? ep.title : `第${ep.title}话`} ${ep.long_title}`,
+ artist: data.title,
+ artwork: [{
+ src: ep.cover
+ }],
+ title: ep.title,
+ });
}
}
break;
}
}
});
- if (this.cid && this.epid) {
+ if (this.$cid && this.$epid) {
this.$connect();
this.#recommend.bangumi();
- } else if (this.ssid) {
- pgcSection(this.ssid)
+ } else if (this.$ssid) {
+ pgcSection(this.$ssid)
.then(({ code, message, result }) => {
if (code !== 0) throw new ReferenceError(message, { cause: { code, message, result } });
const eps = ([]).concat(...(result.main_section?.episodes || []), ...result.section.map(d => d.episodes));
- const ep = this.epid ? eps.find(d => d.id === this.epid) : eps[0];
+ const ep = this.$epid ? eps.find(d => d.id === this.$epid) : eps[0];
if (ep) {
- this.epid = ep.id;
- this.aid = ep.aid;
- this.cid = ep.cid;
+ this.$epid = ep.id;
+ this.$aid = ep.aid;
+ this.$cid = ep.cid;
this.$connect();
this.#recommend.bangumi();
+ navigator.mediaSession.metadata = new MediaMetadata({
+ album: this.$title = `${data.title}:${isNaN(+ep.title) ? ep.title : `第${ep.title}话`} ${ep.long_title}`,
+ artist: data.title,
+ artwork: [{
+ src: ep.cover
+ }],
+ title: ep.title,
+ });
}
})
.catch(e => {
@@ -207,11 +237,11 @@ export class BilibiliPlayer extends Player {
}
})
.catch(e => {
- toastr.error('请求 Bangumi 信息错误~', `ssid: ${this.ssid}`, `epid: ${this.epid}`, e);
+ toastr.error('请求 Bangumi 信息错误~', `ssid: ${this.$ssid}`, `epid: ${this.$epid}`, e);
console.error(e);
});
} else {
- toastr.error('识别 Bangumi 信息错误~', `ssid: ${this.ssid}`, `epid: ${this.epid}`);
+ toastr.error('识别 Bangumi 信息错误~', `ssid: ${this.$ssid}`, `epid: ${this.$epid}`);
}
break;
}
@@ -219,11 +249,11 @@ export class BilibiliPlayer extends Player {
const path = url.hash.split('/');
switch (true) {
case /^av\d+$/i.test(path[1]): {
- this.aid = +path[1].slice(2);
+ this.$aid = +path[1].slice(2);
break;
}
case /^bv1[a-z0-9]{9}$/i.test(path[1]): {
- this.aid = +AV.fromBV(path[1]);
+ this.$aid = +AV.fromBV(path[1]);
break;
}
}
@@ -231,12 +261,12 @@ export class BilibiliPlayer extends Player {
toviewWeb()
.then(({ code, message, data }) => {
if (code !== 0) throw new ReferenceError(message, { cause: { code, message, data } });
- this.aid || (this.aid = data.list[0].aid);
- const { cid, redirect_url, pages } = data.list.find(d => d.aid === this.aid) || data.list[0];
- this.cid = pages[p - 1].cid || cid;
+ this.$aid || (this.$aid = data.list[0].aid);
+ const { cid, redirect_url, pages } = data.list.find(d => d.aid === this.$aid) || data.list[0];
+ this.$cid = pages[p - 1].cid || cid;
if (redirect_url) {
const path = redirect_url.split('/');
- /^ep\d+$/i.test(path[5]) && (this.epid = +path[5].slice(2));
+ /^ep\d+$/i.test(path[5]) && (this.$epid = +path[5].slice(2));
}
if (!cid) throw new ReferenceError(`cid 无效`, { cause: data });
this.$connect();
@@ -258,15 +288,15 @@ export class BilibiliPlayer extends Player {
epid?: number,
kind?: GroupKind,
) {
- aid && (this.aid = aid);
- cid && (this.cid = cid);
- epid && (this.epid = epid);
- kind && (this.kind = kind);
+ aid && (this.$aid = aid);
+ cid && (this.$cid = cid);
+ epid && (this.$epid = epid);
+ kind && (this.$kind = kind);
// 请求 playurl
const qn = +cookie.get('CURRENT_QUALITY') || 0;
- if (this.kind === GroupKind.Pugv && this.epid) {
- pugvPlayurl(this.aid, this.cid, this.epid, qn)
+ if (this.$kind === GroupKind.Pugv && this.$epid) {
+ pugvPlayurl(this.$aid, this.$cid, this.$epid, qn)
.then(({ code, message, data }) => {
if (code !== 0) throw new ReferenceError(message, { cause: { code, message, data } });
data.is_preview && toastr.warn('正在观看预览片段~', '可能需要开通大会员或者付费才能观看全片');
@@ -277,8 +307,8 @@ export class BilibiliPlayer extends Player {
console.error(e);
new FlvAgent(this.$video, { type: 'mp4', url: '//s1.hdslb.com/bfs/static/player/media/error.mp4' });
})
- } else if (this.epid) {
- pgcPlayurl(this.aid, this.cid, this.epid, qn)
+ } else if (this.$epid) {
+ pgcPlayurl(this.$aid, this.$cid, this.$epid, qn)
.then(({ code, message, result }) => {
if (code !== 0) throw new ReferenceError(message, { cause: { code, message, result } });
result.is_preview && toastr.warn('正在观看预览片段~', '可能需要开通大会员或者付费才能观看全片');
@@ -290,7 +320,7 @@ export class BilibiliPlayer extends Player {
new FlvAgent(this.$video, { type: 'mp4', url: '//s1.hdslb.com/bfs/static/player/media/error.mp4' });
});
} else {
- playurl(this.aid, this.cid, qn)
+ playurl(this.$aid, this.$cid, qn)
.then(({ code, message, data }) => {
if (code !== 0) throw new ReferenceError(message, { cause: { code, message, data } });
this.#attachMedia(data);
@@ -302,11 +332,11 @@ export class BilibiliPlayer extends Player {
});
}
// 请求弹幕
- const d = await view(this.cid, this.aid);
+ const d = await view(this.$cid, this.$aid);
if (d.dmSge) {
const { total } = d.dmSge;
for (let i = 1; i <= total; i++) {
- segSo(this.cid, this.aid, i)
+ segSo(this.$cid, this.$aid, i)
.then(d => {
this.addDanmaku(d.elems);
})
@@ -326,7 +356,7 @@ export class BilibiliPlayer extends Player {
// 实时弹幕
this.#broadcast.room();
// 观看人数
- total(this.aid, this.cid)
+ total(this.$aid, this.$cid)
.then(({ code, message, data }) => {
if (code !== 0) throw new ReferenceError(message, { cause: { code, message, data } });
ev.trigger(PLAYER_EVENT.ONLINE_NUMBER, data);
@@ -417,8 +447,8 @@ export class BilibiliPlayer extends Player {
* @param fail 获取对应画质失败回调
*/
updateSource(qn?: number, success?: Function, fail?: Function) {
- if (this.kind === GroupKind.Pugv && this.epid) {
- pugvPlayurl(this.aid, this.cid, this.epid, qn)
+ if (this.$kind === GroupKind.Pugv && this.$epid) {
+ pugvPlayurl(this.$aid, this.$cid, this.$epid, qn)
.then(({ code, message, data }) => {
if (code !== 0) throw new ReferenceError(message, { cause: { code, message, result: data } });
this.#attachMedia(data, this.$video.currentTime);
@@ -434,8 +464,8 @@ export class BilibiliPlayer extends Player {
toastr.error('请求更新画质失败~', e)
fail?.();
});
- } else if (this.epid) {
- pgcPlayurl(this.aid, this.cid, this.epid, qn)
+ } else if (this.$epid) {
+ pgcPlayurl(this.$aid, this.$cid, this.$epid, qn)
.then(({ code, message, result }) => {
if (code !== 0) throw new ReferenceError(message, { cause: { code, message, result } });
this.#attachMedia(result, this.$video.currentTime);
@@ -452,7 +482,7 @@ export class BilibiliPlayer extends Player {
fail?.();
});
} else {
- playurl(this.aid, this.cid, qn)
+ playurl(this.$aid, this.$cid, qn)
.then(({ code, message, data }) => {
if (code !== 0) throw new ReferenceError(message, { cause: { code, message, result: data } });
this.#attachMedia(data, this.$video.currentTime);
@@ -473,9 +503,10 @@ export class BilibiliPlayer extends Player {
/** 重置媒体信息 */
identify() {
- this.aid = this.cid = this.ssid = this.epid = 0;
- this.kind = GroupKind.Ugc;
+ this.$aid = this.$cid = this.$ssid = this.$epid = 0;
+ this.$kind = GroupKind.Ugc;
super.identify();
+ this.#download.identify();
mainEv.trigger(MAIN_EVENT.IDENTIFY, void 0);
ev.trigger(PLAYER_EVENT.CALL_NEXT_REGISTER, void 0);
}
diff --git a/src/main/player/nano/index.ts b/src/main/player/nano/index.ts
index 003db22..5555236 100644
--- a/src/main/player/nano/index.ts
+++ b/src/main/player/nano/index.ts
@@ -138,6 +138,14 @@ export class Nano {
if (ep) {
this.#aid = ep.aid;
this.#cid = ep.cid;
+ navigator.mediaSession.metadata = new MediaMetadata({
+ album: this.#player!.$title = `${data.title}:${isNaN(+ep.title) ? ep.title : `第${ep.title}话`} ${ep.subtitle}`,
+ artist: data.title,
+ artwork: [{
+ src: ep.cover
+ }],
+ title: ep.title,
+ });
}
if (this.#epid && this.#cid) {
this.#player?.$connect(this.#aid, this.#cid, this.#epid, GroupKind.Pugv);
@@ -168,6 +176,14 @@ export class Nano {
if (ep) {
this.#aid = ep.aid;
this.#cid = ep.cid;
+ navigator.mediaSession.metadata = new MediaMetadata({
+ album: this.#player!.$title = `${data.title}:${isNaN(+ep.title) ? ep.title : `第${ep.title}话`} ${ep.long_title}`,
+ artist: data.title,
+ artwork: [{
+ src: ep.cover
+ }],
+ title: ep.title,
+ });
}
}
break;
@@ -188,6 +204,14 @@ export class Nano {
this.#aid = ep.aid;
this.#cid = ep.cid;
this.#player?.$connect(this.#aid, this.#cid, this.#epid);
+ navigator.mediaSession.metadata = new MediaMetadata({
+ album: this.#player!.$title = `${data.title}:${isNaN(+ep.title) ? ep.title : `第${ep.title}话`} ${ep.long_title}`,
+ artist: data.title,
+ artwork: [{
+ src: ep.cover
+ }],
+ title: ep.title,
+ });
}
})
.catch(e => {
@@ -219,7 +243,7 @@ export class Nano {
/^ep\d+$/i.test(path[5]) && (this.#epid = +path[5].slice(2));
}
navigator.mediaSession.metadata = new MediaMetadata({
- album: d.title,
+ album: this.#player!.$title = d.title,
artist: d.owner.name,
artwork: [{
src: d.pic
diff --git a/src/main/player/progress/index.ts b/src/main/player/progress/index.ts
index 5aea7e7..a9fed37 100644
--- a/src/main/player/progress/index.ts
+++ b/src/main/player/progress/index.ts
@@ -21,7 +21,7 @@ export class Progress {
}
#init = () => {
- videoshot(this.player.cid, this.player.aid)
+ videoshot(this.player.$cid, this.player.$aid)
.then(d => {
this.pvData = d;
})
diff --git a/src/main/player/recommend/index.ts b/src/main/player/recommend/index.ts
index a76315a..ce04ddd 100644
--- a/src/main/player/recommend/index.ts
+++ b/src/main/player/recommend/index.ts
@@ -13,7 +13,7 @@ export class Recommend {
constructor(private player: BilibiliPlayer) { }
async av() {
- detail(this.player.aid)
+ detail(this.player.$aid)
.then(({ code, message, data }) => {
if (code !== 0) throw new ReferenceError(message, { cause: { code, message, data } });
if (data.View.ugc_season) {
@@ -31,13 +31,13 @@ export class Recommend {
mainEv.trigger(MAIN_EVENT.NAVIGATE, [ROUTER.AV, url]);
history.replaceState(undefined, '', url);
},
- selected: d.cid === this.player.cid,
+ selected: d.cid === this.player.$cid,
}
}));
this.player.$auxiliary.$filter.$recommend.textContent = '视频合集';
}
} else {
- related(this.player.aid).then(({ code, message, data }) => {
+ related(this.player.$aid).then(({ code, message, data }) => {
if (code !== 0) throw new ReferenceError(message, { cause: { code, message, data } });
this.player.$auxiliary.$recommend.add(data.map(d => {
return {
@@ -58,11 +58,11 @@ export class Recommend {
})
}
if (data.View.pages.length > 1) {
- let ni = data.View.pages.findIndex(d => d.cid === this.player.cid);
+ let ni = data.View.pages.findIndex(d => d.cid === this.player.$cid);
if (ni >= 0 && ni + 1 < data.View.pages.length) {
const ep = data.View.pages[ni + 1];
ep && ev.trigger(PLAYER_EVENT.CALL_NEXT_REGISTER, () => {
- const url = new URL(`https://www.bilibili.com/video/av${this.player.aid}/?p=${ep.page}`);
+ const url = new URL(`https://www.bilibili.com/video/av${this.player.$aid}/?p=${ep.page}`);
mainEv.trigger(MAIN_EVENT.NAVIGATE, [ROUTER.AV, url]);
history.replaceState(undefined, '', url);
});
@@ -75,7 +75,7 @@ export class Recommend {
}
async bangumi() {
- recommend(this.player.ssid)
+ recommend(this.player.$ssid)
.then(({ code, message, data }) => {
if (code !== 0) throw new ReferenceError(message, { cause: { code, message, data } });
this.player.$auxiliary.$recommend.add(data.season.map(d => {
@@ -95,11 +95,11 @@ export class Recommend {
.catch(e => {
console.error('获取推荐数据失败', e);
});
- pgcSection(this.player.ssid)
+ pgcSection(this.player.$ssid)
.then(({ code, message, result }) => {
if (code !== 0) throw new ReferenceError(message, { cause: { code, message, result } });
if (result.main_section) {
- const ni = result.main_section.episodes.findIndex(d => d.id === this.player.epid);
+ const ni = result.main_section.episodes.findIndex(d => d.id === this.player.$epid);
if (ni >= 0 && ni + 1 < result.main_section.episodes.length) {
const ep = result.main_section.episodes[ni + 1];
ep && ev.trigger(PLAYER_EVENT.CALL_NEXT_REGISTER, () => {
@@ -118,7 +118,7 @@ export class Recommend {
async toview(list: Awaited>['data']['list']) {
let ni = -1;
this.player.$auxiliary.$recommend.add(list.map((d, i) => {
- d.aid === this.player.aid && (ni = i);
+ d.aid === this.player.$aid && (ni = i);
return {
src: d.pic + '@.webp',
title: d.title,
@@ -130,14 +130,14 @@ export class Recommend {
mainEv.trigger(MAIN_EVENT.NAVIGATE, [ROUTER.TOVIEW, url]);
history.replaceState(undefined, '', url);
},
- selected: d.aid === this.player.aid,
+ selected: d.aid === this.player.$aid,
}
}));
this.player.$auxiliary.$filter.$recommend.textContent = '稍后再看';
if (ni >= 0) {
const ep = list[ni];
if (ep.pages.length > 1) {
- const ni = ep.pages.findIndex(d => d.cid === this.player.cid);
+ const ni = ep.pages.findIndex(d => d.cid === this.player.$cid);
if (ni >= 0 && ni + 1 < ep.pages.length) {
const part = ep.pages[ni + 1];
part && ev.trigger(PLAYER_EVENT.CALL_NEXT_REGISTER, () => {
diff --git a/src/main/player/v2/index.ts b/src/main/player/v2/index.ts
index d74c658..b5170ec 100644
--- a/src/main/player/v2/index.ts
+++ b/src/main/player/v2/index.ts
@@ -12,7 +12,7 @@ export class V2 {
}
private connect = () => {
- v2(this.player.cid, this.player.aid, this.player.ssid)
+ v2(this.player.$cid, this.player.$aid, this.player.$ssid)
.then(({ code, message, data }) => {
if (code !== 0) throw new ReferenceError(message, { cause: { code, message, data } });
this.subtitle(data.subtitle)
diff --git a/src/manifest.json b/src/manifest.json
index 34c5a85..037cfa8 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -53,16 +53,12 @@
"manifest_version": 3,
"minimum_chrome_version": "125",
"name": "__MSG_name__",
- "options_page": "options/index.html",
"permissions": [
"declarativeNetRequest",
"declarativeNetRequestWithHostAccess",
"sidePanel"
],
- "side_panel": {
- "default_path": "sidebar/index.html"
- },
- "version": "1.0.4.0",
+ "version": "1.0.5.0",
"web_accessible_resources": [
{
"resources": [
diff --git a/src/player/auxiliary/info/index.ts b/src/player/auxiliary/info/index.ts
index f030958..84e474b 100644
--- a/src/player/auxiliary/info/index.ts
+++ b/src/player/auxiliary/info/index.ts
@@ -35,7 +35,7 @@ export class Info extends HTMLDivElement {
#number: Number;
- #more: More;
+ $more: More;
$setting: Setting;
@@ -45,7 +45,7 @@ export class Info extends HTMLDivElement {
this.#player = player;
this.classList.add('bofqi-info');
this.#number = this.appendChild(new Number(player));
- this.#more = this.appendChild(new More(player));
+ this.$more = this.appendChild(new More(player));
this.$setting = this.appendChild(new Setting(player));
}
}
\ No newline at end of file
diff --git a/src/player/auxiliary/info/more.ts b/src/player/auxiliary/info/more.ts
index 6495082..9d6769e 100644
--- a/src/player/auxiliary/info/more.ts
+++ b/src/player/auxiliary/info/more.ts
@@ -51,9 +51,10 @@ export class More extends HTMLButtonElement {
this.#wrap.addEventListener('click', () => this.#wrap.hidePopover());
- this.#add(
+ this.add(
{
- text: '加载文件', callback: () => {
+ text: '加载文件',
+ callback: () => {
toastr.info('请选择要打开的视频、弹幕或字幕文件');
showOpenFilePicker({
multiple: true,
@@ -102,7 +103,7 @@ export class More extends HTMLButtonElement {
}
/** 添加选项 */
- #add(...items: Item[]) {
+ add(...items: Item[]) {
const f = document.createDocumentFragment();
items.forEach(({ text, disable, callback }) => {
const button = document.createElement('button');