Merge branch 'preview-features' into preview

This commit is contained in:
the1812 2022-09-03 10:56:05 +08:00
commit 9f8ec17797
46 changed files with 693 additions and 272 deletions

View File

@ -1,5 +1,55 @@
# 更新日志 # 更新日志
## v2.4.0
✨新增
<details>
<summary>正式版获得 v2.3.1 ~ v2.3.3 预览版的功能</summary>
- `清爽首页` 的热门视频支持显示弹幕数量.
- `极简首页` 初版已完成.
- 脚本的更新源配置默认值更换为 `GitHub`, 并添加了 `AltCdn`, 表示开发者自定义的其他 CDN 源. `jsDelivr` 将会删除.
- 在 `GitHub` 更新源下, MDI 图标库更换使用 GitHub Pages.
- 新增组件 `UP 主黑名单` by [snowraincloud](https://github.com/snowraincloud). (PR #3537)
- 在无限滚动的场景下, 点击那个 `加载中` 的标识 (ScrollTrigger) 可以手动触发加载下一页. 在遇到没有自动加载下一页的情况时会比较有用.
- `下载视频` 支持 `flac` 音源. (#3497)
- `自定义顶栏` 支持设置顶栏的高度, 设置为 64px 即为原版顶栏的高度. (#3136)
</details>
- `删除视频弹窗` 支持 3.x 播放器的 `关联视频``评分` 弹窗. (#3545)
- `展开弹幕列表` 支持设置 `最大弹幕数量`, 超过此数量不进行展开, 避免展开时卡死页面. (#2972)
- `图片批量导出` 在导出动态的图片时, 可以指定 `originalUser` 作为被转发用户名. (#1208)
- 新增插件 `自定义顶栏 - 频道`, 为自定义顶栏添加一个频道入口. (#3258)
- `自定义顶栏` 的历史弹窗中支持暂停/继续记录历史. (#3303)
🐛修复
- 修复 `默认播放器模式``播放器位置动作` 导致 `夜间模式跟随系统` 未生效. (#3157, PR #3636 by [ChsBuffer](https://github.com/chsbuffer))
- 修复个人空间中的动态无法使用动态相关功能. (#3191)
- 新版播放页 & 3.x 播放器适配: (#3187, #2615)
- 夜间模式 (#3647)
- 播放器投影
- 启用双击控制
- 控制栏触摸优化
- 三连触摸支持
- 隐藏视频标题层
- 展开视频简介
- 启用弹幕空降
- 播放器控制栏背景色
- 强制保留弹幕栏
- 快捷键扩展
- 外置稍后再看
- 启用快速收藏
- 传统连播模式
☕开发者相关 (来自预览版 v2.3.1)
- 重新整理了所有 CDN 调用, 并支持自定义 CDN 配置, 文档见 [doc/cdn.md](https://github.com/the1812/Bilibili-Evolved/blob/preview-features/doc/cdn.md).
- 拆分并整理了 `/src/core/settings` 的代码结构.
- 所有 webpack 配置更换为 TypeScript, 并启用 ESLint 检查.
- 重构了元数据注入功能, 新增了 i18n 和 description 注入, 详细用法可见 `webpack/inject-metadata/i18n.ts``webpack/inject-metadata/description.ts` 中的注释说明.
- 删除了用不到的包 `html-webpack-plugin`.
- 适配了 VSCode File Nesting 功能.
## v2.3.3-preview ## v2.3.3-preview
包含 v2.2.4 的所有更新内容. 包含 v2.2.4 的所有更新内容.

View File

@ -29,6 +29,9 @@ https://afdian.net/@the1812?tab=sponsor
| 时间 | 用户名 | 单号后4位 | 金额 | | 时间 | 用户名 | 单号后4位 | 金额 |
| ------------------- | ---------------- | --------- | ------- | | ------------------- | ---------------- | --------- | ------- |
| 2022.08.31 16:28:40 | 匿名 | 1249 | ¥50.00 |
| 2022.08.28 14:13:22 | 匿名 | 4359 | ¥5.00 |
| 2022.08.27 09:33:37 | 匿名 | 3496 | ¥5.00 |
| 2022.08.23 20:21:47 | *I | 7157 | ¥10.00 | | 2022.08.23 20:21:47 | *I | 7157 | ¥10.00 |
| 2022.08.16 01:11:59 | *生 | 3893 | ¥20.00 | | 2022.08.16 01:11:59 | *生 | 3893 | ¥20.00 |
| 2022.08.09 22:42:25 | H*l | 0339 | ¥5.88 | | 2022.08.09 22:42:25 | H*l | 0339 | ¥5.88 |

View File

@ -75,7 +75,7 @@
"type": "component", "type": "component",
"name": "badgeHelper", "name": "badgeHelper",
"displayName": "直播勋章快速更换", "displayName": "直播勋章快速更换",
"description": "在直播区中, 可从功能面板中直接切换勋章和头衔. 默认加载 256 个 (同时也是上限), 可在选项中修改.", "description": "在直播区中, 可从功能面板中直接切换勋章和头衔. 默认显示 256 个 (同时也是上限), 可在选项中修改.",
"fullRelativePath": "../../registry/dist/components/live/badge-helper.js", "fullRelativePath": "../../registry/dist/components/live/badge-helper.js",
"fullAbsolutePath": "registry/dist/components/live/badge-helper.js" "fullAbsolutePath": "registry/dist/components/live/badge-helper.js"
}, },
@ -163,7 +163,7 @@
"type": "component", "type": "component",
"name": "customNavbar", "name": "customNavbar",
"displayName": "自定义顶栏", "displayName": "自定义顶栏",
"description": "使用脚本提供的顶栏替换原版的主站顶栏 (非主站不替换), 启用后还可以在反广告插件 (例如 ABP) 中配置禁止原版顶栏的消息 iframe 以提升性能:\n- *://message.bilibili.com/pages/nav/index_new_pc_sync\n- *://message.bilibili.com/pages/nav/index_new_sync", "description": "使用脚本提供的顶栏替换原版的主站顶栏 (非主站不替换), 启用后还可以在反广告插件 (例如 ABP) 中配置禁止原版顶栏的消息 iframe 以提升性能:\r\n- *://message.bilibili.com/pages/nav/index_new_pc_sync\r\n- *://message.bilibili.com/pages/nav/index_new_sync\r\n",
"fullRelativePath": "../../registry/dist/components/style/custom-navbar.js", "fullRelativePath": "../../registry/dist/components/style/custom-navbar.js",
"fullAbsolutePath": "registry/dist/components/style/custom-navbar.js" "fullAbsolutePath": "registry/dist/components/style/custom-navbar.js"
}, },
@ -283,7 +283,7 @@
"type": "component", "type": "component",
"name": "sidebarOffset", "name": "sidebarOffset",
"displayName": "侧栏垂直偏移", "displayName": "侧栏垂直偏移",
"description": "给脚本的侧栏设置垂直偏移量, 范围为 -40% ~ 40%", "description": "给脚本的侧栏设置垂直偏移量, 范围为 -35% ~ 40%",
"fullRelativePath": "../../registry/dist/components/style/sidebar-offset.js", "fullRelativePath": "../../registry/dist/components/style/sidebar-offset.js",
"fullAbsolutePath": "registry/dist/components/style/sidebar-offset.js" "fullAbsolutePath": "registry/dist/components/style/sidebar-offset.js"
}, },
@ -371,7 +371,7 @@
"type": "component", "type": "component",
"name": "blackList", "name": "blackList",
"displayName": "屏蔽黑名单up主", "displayName": "屏蔽黑名单up主",
"description": "by [@snowraincloud](https://github.com/snowraincloud)\n\n屏蔽黑名单up主", "description": "by [@snowraincloud](https://github.com/snowraincloud)\n\n屏蔽黑名单up主, 根据up主的名称进行匹配支持精确匹配和正则匹配",
"fullRelativePath": "../../registry/dist/components/utils/black-list.js", "fullRelativePath": "../../registry/dist/components/utils/black-list.js",
"fullAbsolutePath": "registry/dist/components/utils/black-list.js" "fullAbsolutePath": "registry/dist/components/utils/black-list.js"
}, },
@ -427,7 +427,7 @@
"type": "component", "type": "component",
"name": "imageExporter", "name": "imageExporter",
"displayName": "图片批量导出", "displayName": "图片批量导出",
"description": "可以批量导出某个地方的图片, 目前支持动态和专栏.\r\n\r\n动态文件名变量:\r\n- `user`: 用户名\r\n- `id`: 动态 ID\r\n- `n`: 第 n 张图\r\n\r\n专栏文件名变量:\r\n- `title`: 专栏标题\r\n- `n`: 第 n 张图", "description": "可以批量导出某个地方的图片, 目前支持动态和专栏.\r\n\r\n动态文件名变量:\r\n- `user`: 用户名\r\n- `originalUser`: 被转发用户名, 如果不是转发类型的动态则等于 `user`\r\n- `id`: 动态 ID\r\n- `n`: 第 n 张图\r\n\r\n专栏文件名变量:\r\n- `title`: 专栏标题\r\n- `n`: 第 n 张图",
"fullRelativePath": "../../registry/dist/components/utils/image-exporter.js", "fullRelativePath": "../../registry/dist/components/utils/image-exporter.js",
"fullAbsolutePath": "registry/dist/components/utils/image-exporter.js" "fullAbsolutePath": "registry/dist/components/utils/image-exporter.js"
}, },
@ -531,7 +531,7 @@
"type": "component", "type": "component",
"name": "expandDanmakuList", "name": "expandDanmakuList",
"displayName": "展开弹幕列表", "displayName": "展开弹幕列表",
"description": "每次加载视频时自动展开弹幕列表.", "description": "每次加载视频时自动展开弹幕列表, 如果弹幕装载量超过 `最大弹幕数量`, 则不展开以免对页面造成卡顿.",
"fullRelativePath": "../../registry/dist/components/video/danmaku/expand.js", "fullRelativePath": "../../registry/dist/components/video/danmaku/expand.js",
"fullAbsolutePath": "registry/dist/components/video/danmaku/expand.js" "fullAbsolutePath": "registry/dist/components/video/danmaku/expand.js"
}, },
@ -744,6 +744,14 @@
"fullRelativePath": "../../registry/dist/plugins/settings-panel/recent-components.js", "fullRelativePath": "../../registry/dist/plugins/settings-panel/recent-components.js",
"fullAbsolutePath": "registry/dist/plugins/settings-panel/recent-components.js" "fullAbsolutePath": "registry/dist/plugins/settings-panel/recent-components.js"
}, },
{
"type": "plugin",
"name": "customNavbar.items.channel",
"displayName": "自定义顶栏 - 频道",
"description": "为自定义顶栏添加一个频道入口.",
"fullRelativePath": "../../registry/dist/plugins/style/custom-navbar-channel.js",
"fullAbsolutePath": "registry/dist/plugins/style/custom-navbar-channel.js"
},
{ {
"type": "plugin", "type": "plugin",
"name": "customNavbar.items.darkMode", "name": "customNavbar.items.darkMode",

View File

@ -96,7 +96,7 @@
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/live/badge-helper.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/live/badge-helper.js) **GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/live/badge-helper.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/live/badge-helper.js)
在直播区中, 可从功能面板中直接切换勋章和头衔. 默认加载 256 个 (同时也是上限), 可在选项中修改. 在直播区中, 可从功能面板中直接切换勋章和头衔. 默认显示 256 个 (同时也是上限), 可在选项中修改.
### [直播弹幕发送栏](../../registry/dist/components/live/danmaku-sendbar.js) ### [直播弹幕发送栏](../../registry/dist/components/live/danmaku-sendbar.js)
`liveDanmakuSendbar` `liveDanmakuSendbar`
@ -344,7 +344,7 @@
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/style/sidebar-offset.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/style/sidebar-offset.js) **GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/style/sidebar-offset.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/style/sidebar-offset.js)
给脚本的侧栏设置垂直偏移量, 范围为 -40% ~ 40% 给脚本的侧栏设置垂直偏移量, 范围为 -35% ~ 40%
### [简化评论区](../../registry/dist/components/style/simplify/comments.js) ### [简化评论区](../../registry/dist/components/style/simplify/comments.js)
`simplifyComments` `simplifyComments`
@ -460,7 +460,7 @@
by [@snowraincloud](https://github.com/snowraincloud) by [@snowraincloud](https://github.com/snowraincloud)
屏蔽黑名单up主 屏蔽黑名单up主, 根据up主的名称进行匹配支持精确匹配和正则匹配
### [更新链接替换](../../registry/dist/components/utils/change-update-urls.js) ### [更新链接替换](../../registry/dist/components/utils/change-update-urls.js)
`changeUpdateUrls` `changeUpdateUrls`
@ -541,6 +541,7 @@ by [@snowraincloud](https://github.com/snowraincloud)
动态文件名变量: 动态文件名变量:
- `user`: 用户名 - `user`: 用户名
- `originalUser`: 被转发用户名, 如果不是转发类型的动态则等于 `user`
- `id`: 动态 ID - `id`: 动态 ID
- `n`: 第 n 张图 - `n`: 第 n 张图
@ -668,7 +669,7 @@ by [@kdxcxs](https://github.com/kdxcxs)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/video/danmaku/expand.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/video/danmaku/expand.js) **GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/video/danmaku/expand.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/video/danmaku/expand.js)
每次加载视频时自动展开弹幕列表. 每次加载视频时自动展开弹幕列表, 如果弹幕装载量超过 `最大弹幕数量`, 则不展开以免对页面造成卡顿.
### [视频页默认定位](../../registry/dist/components/video/default-location.js) ### [视频页默认定位](../../registry/dist/components/video/default-location.js)
`videoDefaultLocation` `videoDefaultLocation`
@ -975,6 +976,15 @@ by FoundTheWOUT
在设置面板中添加 "最近使用" 类别, 方便检索最近操作过的组件. 在设置面板中添加 "最近使用" 类别, 方便检索最近操作过的组件.
### [自定义顶栏 - 频道](../../registry/dist/plugins/style/custom-navbar-channel.js)
`customNavbar.items.channel`
**AltCdn:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/plugins/style/custom-navbar-channel.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/plugins/style/custom-navbar-channel.js)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/plugins/style/custom-navbar-channel.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/plugins/style/custom-navbar-channel.js)
为自定义顶栏添加一个频道入口.
### [自定义顶栏 - 夜间模式开关](../../registry/dist/plugins/style/custom-navbar-dark-mode.js) ### [自定义顶栏 - 夜间模式开关](../../registry/dist/plugins/style/custom-navbar-dark-mode.js)
`customNavbar.items.darkMode` `customNavbar.items.darkMode`

View File

@ -201,7 +201,7 @@
"type": "component", "type": "component",
"name": "customNavbar", "name": "customNavbar",
"displayName": "自定义顶栏", "displayName": "自定义顶栏",
"description": "使用脚本提供的顶栏替换原版的主站顶栏 (非主站不替换), 启用后还可以在反广告插件 (例如 ABP) 中配置禁止原版顶栏的消息 iframe 以提升性能:\n- *://message.bilibili.com/pages/nav/index_new_pc_sync\n- *://message.bilibili.com/pages/nav/index_new_sync", "description": "使用脚本提供的顶栏替换原版的主站顶栏 (非主站不替换), 启用后还可以在反广告插件 (例如 ABP) 中配置禁止原版顶栏的消息 iframe 以提升性能:\r\n- *://message.bilibili.com/pages/nav/index_new_pc_sync\r\n- *://message.bilibili.com/pages/nav/index_new_sync\r\n",
"fullRelativePath": "../../registry/dist/components/style/custom-navbar.js", "fullRelativePath": "../../registry/dist/components/style/custom-navbar.js",
"fullAbsolutePath": "registry/dist/components/style/custom-navbar.js" "fullAbsolutePath": "registry/dist/components/style/custom-navbar.js"
}, },

View File

@ -6,6 +6,17 @@
<TextBox v-model="search" placeholder="搜索" linear></TextBox> <TextBox v-model="search" placeholder="搜索" linear></TextBox>
</div> </div>
<div class="operations"> <div class="operations">
<div
class="operation"
@click="toggleHistoryPause"
>
<VButton v-if="!paused" title="暂停记录历史" round>
<VIcon icon="mdi-pause" :size="14"></VIcon>
</VButton>
<VButton v-else title="继续记录历史" round>
<VIcon icon="mdi-play" :size="14"></VIcon>
</VButton>
</div>
<a <a
class="operation" class="operation"
target="_blank" target="_blank"
@ -116,6 +127,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { bilibiliApi, getJsonWithCredentials, postTextWithCredentials } from '@/core/ajax'
import { formData, getCsrf } from '@/core/utils'
import { descendingSort } from '@/core/utils/sort' import { descendingSort } from '@/core/utils/sort'
import { import {
VButton, VButton,
@ -151,6 +164,7 @@ export default Vue.extend({
groups: [], groups: [],
loading: true, loading: true,
hasMorePage: true, hasMorePage: true,
paused: false,
} }
}, },
computed: { computed: {
@ -170,7 +184,10 @@ export default Vue.extend({
}, },
async created() { async created() {
try { try {
await this.nextPage() await Promise.all([
this.nextPage(),
this.updateHistoryPauseState(),
])
} finally { } finally {
this.loading = false this.loading = false
} }
@ -210,6 +227,29 @@ export default Vue.extend({
} }
this.hasMorePage = cards.length !== 0 this.hasMorePage = cards.length !== 0
}, },
async updateHistoryPauseState() {
const result = await bilibiliApi(getJsonWithCredentials('https://api.bilibili.com/x/v2/history/shadow'))
/*
result == true: 暂停
result == {}: 没暂停
*/
this.paused = result === true
},
async toggleHistoryPause() {
const targetState = !this.paused
try {
this.paused = targetState
await postTextWithCredentials(
'https://api.bilibili.com/x/v2/history/shadow/set',
formData({
csrf: getCsrf(),
switch: targetState,
}),
)
} catch (error) {
this.paused = !targetState
}
},
}, },
}) })
</script> </script>
@ -236,8 +276,9 @@ export default Vue.extend({
position: absolute; position: absolute;
} }
} }
@mixin round-button($width: 26px) { @mixin round-button($size: 26px) {
width: $width; width: $size;
height: $size;
box-sizing: border-box; box-sizing: border-box;
} }
.header { .header {

View File

@ -31,7 +31,10 @@ export const checkTransparentFill = async (vm: {
} }
return false return false
}, },
).then(() => { ).then(banner => {
if (banner.length === 0) {
return
}
addComponentListener('customNavbar.transparent', value => { addComponentListener('customNavbar.transparent', value => {
if (!getComponentSettings('hideBanner').enabled) { if (!getComponentSettings('hideBanner').enabled) {
vm.toggleStyle(value, 'transparent') vm.toggleStyle(value, 'transparent')

View File

@ -222,6 +222,9 @@ blockquote {
@include foreground-color(); @include foreground-color();
} }
#bilibili-player {
box-shadow: 0 4px 8px 0 #0004 !important;
}
#bilibiliPlayer { #bilibiliPlayer {
box-shadow: 0 4px 8px 0 #0004; box-shadow: 0 4px 8px 0 #0004;
@include background-color('2'); @include background-color('2');

View File

@ -182,6 +182,18 @@
@include color("e"); @include color("e");
} }
} }
#bilibili-player-placeholder-bottom {
@include background-color('3');
&-left,
&-right {
@include background-color('4');
}
}
#bpx-svg-sprite-danmu-on {
path:last-child {
@include theme-fill();
}
}
.bpx-player-container { .bpx-player-container {
box-shadow: 0 4px 8px 0 #0004 !important; box-shadow: 0 4px 8px 0 #0004 !important;
.squirtle-controller { .squirtle-controller {
@ -196,12 +208,16 @@
@include background-color(); @include background-color();
} }
.bpx-player-sending-bar { .bpx-player-sending-bar {
@include background-color(); @include background-color('2');
.bpx-player-dm-setting:hover { .bpx-player-video-btn-dm,
@include theme-fill(); .bpx-player-dm-setting,
.bpx-player-dm-switch {
@include fill('a');
} }
.bpx-player-dm-switch:hover,
.bpx-player-dm-setting:hover,
.bpx-player-video-btn-dm:hover { .bpx-player-video-btn-dm:hover {
@include theme-fill(); @include fill('e');
} }
.bpx-player-dm-input { .bpx-player-dm-input {
@include color("e"); @include color("e");

View File

@ -638,9 +638,21 @@
} }
.share_dropdown { .share_dropdown {
@include color('e'); @include color('e');
@include background-color('3');
.box-right { .box-right {
@include border-color('4'); @include border-color('4');
} }
.capture-bar {
.bar-left {
@include color('a');
}
.bar-right {
.copy-link {
@include theme-background-color();
@include foreground-color();
}
}
}
.share-link { .share-link {
@include background-color('4'); @include background-color('4');
#link_copy { #link_copy {
@ -651,6 +663,12 @@
@include theme-color(); @include theme-color();
} }
} }
.box-top-right {
box-shadow: -0.5px 0 0 #555 !important;
}
.box-bottom {
@include background-color('4');
}
} }
.player-auxiliary-filter-playlist { .player-auxiliary-filter-playlist {
@ -804,3 +822,7 @@
} }
} }
} }
#bilibili-player-placeholder,
.bpx-player-container {
box-shadow: none !important;
}

View File

@ -1,10 +1,17 @@
import { defineComponentMetadata } from '@/components/define' import { defineComponentMetadata } from '@/components/define'
import { toggleStyle } from '@/components/styled-component'
import { playerUrls } from '@/core/utils/urls' import { playerUrls } from '@/core/utils/urls'
const name = 'hideVideoTopMask'
export const component = defineComponentMetadata({ export const component = defineComponentMetadata({
name,
displayName: '隐藏视频标题层', displayName: '隐藏视频标题层',
...toggleStyle('hideVideoTopMask', () => import('./top-mask.scss')), entry: none,
instantStyles: [
{
name,
style: () => import('./top-mask.scss'),
},
],
tags: [ tags: [
componentsTags.style, componentsTags.style,
], ],

View File

@ -1,3 +1,4 @@
.bpx-player-top-wrap,
.bilibili-player-video-top { .bilibili-player-video-top {
display: none !important; display: none !important;
} }

View File

@ -5,3 +5,7 @@
box-shadow: 0px 2px 8px 0px var(--theme-color-20) !important; box-shadow: 0px 2px 8px 0px var(--theme-color-20) !important;
} }
} }
#bilibili-player-placeholder,
.bpx-player-container {
box-shadow: none !important;
}

View File

@ -3,7 +3,7 @@ import { videoUrls } from '@/core/utils/urls'
const entry = async () => { const entry = async () => {
const { select } = await import('@/core/spin-query') const { select } = await import('@/core/spin-query')
const likeButton = await select('.ops span.like') as HTMLElement const likeButton = await select(':is(.ops, .video-toolbar-v1) span.like') as HTMLElement
if (!likeButton) { if (!likeButton) {
return return
} }

View File

@ -8,6 +8,7 @@
visibility: hidden !important; visibility: hidden !important;
} }
} }
.bpx-player-shadow-progress-area,
.bilibili-player-video-control-wrap { .bilibili-player-video-control-wrap {
opacity: 1 !important; opacity: 1 !important;
visibility: visible !important; visibility: visible !important;
@ -19,6 +20,9 @@
bottom: -7px !important; bottom: -7px !important;
visibility: visible !important; visibility: visible !important;
} }
.bpx-player-control-top,
.bpx-player-control-mask,
.bpx-player-control-bottom,
.bpx-player-top-wrap { .bpx-player-top-wrap {
opacity: 0 !important; opacity: 0 !important;
visibility: hidden !important; visibility: hidden !important;

View File

@ -12,7 +12,14 @@
opacity: 1 !important; opacity: 1 !important;
bottom: 44px !important; bottom: 44px !important;
} }
.bpx-player-top-wrap { .bpx-player-shadow-progress-area {
opacity: 0 !important;
visibility: hidden !important;
}
.bpx-player-top-wrap,
.bpx-player-control-mask,
.bpx-player-control-bottom,
.bpx-player-control-top {
opacity: 1 !important; opacity: 1 !important;
visibility: visible !important; visibility: visible !important;
} }

View File

@ -31,6 +31,25 @@
} }
} }
} }
.bpx-player-pbp {
opacity: 0 !important;
bottom: 3px !important;
&.pin {
opacity: 1 !important;
}
.bpx-player-pbp-pin {
opacity: 0 !important;
}
}
&.touch-video-control-show .bpx-player-pbp {
opacity: 1 !important;
bottom: calc(100% - 1px) !important;
.bpx-player-pbp-pin {
opacity: 1 !important;
}
}
&.touch-video-control-show .bilibili-player-video-control { &.touch-video-control-show .bilibili-player-video-control {
#bilibili_pbp, #bilibili_pbp,
#bilibili_pbp_canvas { #bilibili_pbp_canvas {
@ -60,6 +79,9 @@
} }
} }
} }
&.touch-video-control-show .bpx-player-pbp {
bottom: calc(100% + 5px) !important;
}
} }
} }
} }

View File

@ -1,3 +1,5 @@
@import 'common';
@mixin subtitle { @mixin subtitle {
&.bilibili-player-area .subtitle-position.subtitle-position { &.bilibili-player-area .subtitle-position.subtitle-position {
&-bc, &-bc,
@ -13,14 +15,30 @@
bottom: 68px !important; bottom: 68px !important;
} }
} }
.mode-webfullscreen &.touch-video-control-show, .bpx-player-subtitle-panel-position {
.mode-fullscreen &.touch-video-control-show { &[data-position='bottom'] {
.subtitle-position.subtitle-position { bottom: 14px !important;
&-bc, }
&-bl, }
&-br { &.touch-video-control-show .bpx-player-subtitle-panel-position {
bottom: 88px !important; &[data-position='bottom'] {
bottom: 69px !important;
}
}
@include on-fullscreen {
&.touch-video-control-show {
.subtitle-position.subtitle-position {
&-bc,
&-bl,
&-br {
bottom: 88px !important;
}
}
.bpx-player-subtitle-panel-position {
&[data-position='bottom'] {
bottom: 88px !important;
}
} }
} }
} }
} }

View File

@ -1,10 +1,10 @@
@import "common"; @import 'common';
@mixin replace-with-icon() { @mixin replace-with-icon() {
display: block; display: block;
width: 36px; width: 36px;
color: #eee; color: #eee;
font: normal normal normal 20px/1 "Material Design Icons"; font: normal normal normal 20px/1 'Material Design Icons';
text-rendering: auto; text-rendering: auto;
line-height: inherit; line-height: inherit;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
@ -35,7 +35,7 @@
display: none !important; display: none !important;
} }
&::before { &::before {
content: "\F0279"; content: '\F0279';
@include replace-with-icon(); @include replace-with-icon();
} }
} }
@ -45,7 +45,7 @@
display: none !important; display: none !important;
} }
&::before { &::before {
content: "\F08FF"; content: '\F08FF';
@include replace-with-icon(); @include replace-with-icon();
} }
} }
@ -55,7 +55,7 @@
} }
} }
} }
@include on-fullscreen(".touch-player-control") { @include on-fullscreen('.touch-player-control') {
.bilibili-player-area { .bilibili-player-area {
.bilibili-player-video-control-bottom-center { .bilibili-player-video-control-bottom-center {
padding: 0 12px !important; padding: 0 12px !important;
@ -100,7 +100,7 @@
display: none !important; display: none !important;
} }
.squirtle-video-pagelist::before { .squirtle-video-pagelist::before {
content: "\F0279"; content: '\F0279';
@include replace-with-icon(); @include replace-with-icon();
} }
} }
@ -110,7 +110,7 @@
display: none !important; display: none !important;
} }
.squirtle-video-speed::before { .squirtle-video-speed::before {
content: "\F08FF"; content: '\F08FF';
@include replace-with-icon(); @include replace-with-icon();
} }
} }
@ -127,8 +127,28 @@
right: 0 !important; right: 0 !important;
} }
} }
.bpx-player-control-top,
.bpx-player-control-bottom {
padding: 0 !important;
}
.bpx-player-pbp {
padding: 0 !important;
left: 0 !important;
width: 100% !important;
}
.bpx-player-ctrl-btn {
height: 100% !important;
padding-left: 2px !important;
padding-right: 2px !important;
&.bpx-player-ctrl-play {
padding-left: 12px !important;
}
&.bpx-player-ctrl-full {
padding-right: 12px !important;
}
}
} }
@include on-fullscreen(".touch-player-control") { @include on-fullscreen('.touch-player-control') {
.squirtle-controller { .squirtle-controller {
height: 72px !important; height: 72px !important;
.squirtle-controller-wrap { .squirtle-controller-wrap {

View File

@ -1,4 +1,4 @@
import { FeedsCard, addMenuItem, forEachFeedsCard } from '@/components/feeds/api' import { FeedsCard, addMenuItem, forEachFeedsCard, RepostFeedsCard } from '@/components/feeds/api'
import { ComponentEntry } from '@/components/types' import { ComponentEntry } from '@/components/types'
import { getBlob } from '@/core/ajax' import { getBlob } from '@/core/ajax'
import { DownloadPackage } from '@/core/download' import { DownloadPackage } from '@/core/download'
@ -42,6 +42,7 @@ export const setupFeedImageExporter: ComponentEntry = async ({ settings: { optio
const titleData = { const titleData = {
user: card.username, user: card.username,
id: card.id, id: card.id,
originalUser: (card as RepostFeedsCard).repostUsername ?? card.username,
n: (index + 1).toString(), n: (index + 1).toString(),
} }
pack.add(`${formatTitle(feedFormat, false, titleData)}${imageUrls[index].extension}`, blob) pack.add(`${formatTitle(feedFormat, false, titleData)}${imageUrls[index].extension}`, blob)
@ -50,6 +51,7 @@ export const setupFeedImageExporter: ComponentEntry = async ({ settings: { optio
const packTitleData = { const packTitleData = {
user: card.username, user: card.username,
id: card.id, id: card.id,
originalUser: (card as RepostFeedsCard).repostUsername ?? card.username,
n: '', n: '',
} }
await pack.emit(`${formatTitle(feedFormat, false, packTitleData)}.zip`) await pack.emit(`${formatTitle(feedFormat, false, packTitleData)}.zip`)

View File

@ -2,6 +2,7 @@
动态文件名变量: 动态文件名变量:
- `user`: 用户名 - `user`: 用户名
- `originalUser`: 被转发用户名, 如果不是转发类型的动态则等于 `user`
- `id`: 动态 ID - `id`: 动态 ID
- `n`: 第 n 张图 - `n`: 第 n 张图

View File

@ -2,14 +2,10 @@ import { ComponentMetadata } from '@/components/types'
import { matchUrlPattern } from '@/core/utils' import { matchUrlPattern } from '@/core/utils'
import { columnUrls, feedsUrls } from '@/core/utils/urls' import { columnUrls, feedsUrls } from '@/core/utils/urls'
import { setupFeedImageExporter } from './feed' import { setupFeedImageExporter } from './feed'
import desc from './desc.md'
export const component: ComponentMetadata = { export const component: ComponentMetadata = {
name: 'imageExporter', name: 'imageExporter',
displayName: '图片批量导出', displayName: '图片批量导出',
description: {
'zh-CN': desc,
},
tags: [ tags: [
componentsTags.feeds, componentsTags.feeds,
componentsTags.utils, componentsTags.utils,

View File

@ -126,11 +126,11 @@ export const builtInActions: Record<string, KeyBindingAction> = {
}, },
coin: { coin: {
displayName: '投币', displayName: '投币',
run: useClickElement('.video-toolbar .coin,.tool-bar .coin-info, .video-toolbar-module .coin-box, .play-options-ul > li:nth-child(2)'), run: useClickElement('.video-toolbar .coin, .tool-bar .coin-info, .video-toolbar-module .coin-box, .play-options-ul > li:nth-child(2), .video-toolbar-v1 .coin'),
}, },
favorite: { favorite: {
displayName: '收藏', displayName: '收藏',
run: useClickElement('.video-toolbar .collect, .video-toolbar-module .fav-box, .play-options-ul > li:nth-child(3)'), run: useClickElement('.video-toolbar .collect, .video-toolbar-module .fav-box, .play-options-ul > li:nth-child(3), .video-toolbar-v1 .collect'),
}, },
pause: { pause: {
displayName: '暂停/播放', displayName: '暂停/播放',
@ -143,7 +143,7 @@ export const builtInActions: Record<string, KeyBindingAction> = {
let likeClick = true let likeClick = true
return ((context: KeyBindingActionContext) => { return ((context: KeyBindingActionContext) => {
const { event } = context const { event } = context
const likeButton = dq('.video-toolbar .like, .tool-bar .like-info') as HTMLSpanElement const likeButton = dq('.video-toolbar .like, .tool-bar .like-info, .video-toolbar-v1 .like') as HTMLSpanElement
if (!likeButton) { if (!likeButton) {
return false return false
} }

View File

@ -1,9 +1,7 @@
.bilibili-player-video-danmaku { .b-danmaku {
.b-danmaku { &.airborne {
&.airborne { text-decoration: underline;
text-decoration: underline; cursor: pointer;
cursor: pointer; pointer-events: initial;
pointer-events: initial;
}
} }
} }

View File

@ -2,13 +2,39 @@ import { ComponentEntry, ComponentMetadata } from '@/components/types'
import { videoChange } from '@/core/observer' import { videoChange } from '@/core/observer'
import { select } from '@/core/spin-query' import { select } from '@/core/spin-query'
import { matchUrlPattern } from '@/core/utils' import { matchUrlPattern } from '@/core/utils'
import { useScopedConsole } from '@/core/utils/log'
import { mediaListUrls, videoAndBangumiUrls } from '@/core/utils/urls' import { mediaListUrls, videoAndBangumiUrls } from '@/core/utils/urls'
const getDanmakuCount = async () => {
const countElement = await select('.bilibili-player-video-info-danmaku-number, .bpx-player-video-info-dm')
/*
3.x: 已装填 n
2.x: n
*/
const countText = countElement?.textContent ?? ''
const countMatch = countText.match(/\d+/)
if (!countMatch) {
return null
}
const count = parseInt(countMatch[0])
if (Number.isNaN(count)) {
return null
}
return count
}
const entry: ComponentEntry = async ({ settings: { options } }) => { const entry: ComponentEntry = async ({ settings: { options } }) => {
const console = useScopedConsole('展开弹幕列表')
videoChange(async () => { videoChange(async () => {
if (mediaListUrls.some(url => matchUrlPattern(url)) && options.ignoreMediaList) { if (mediaListUrls.some(url => matchUrlPattern(url)) && options.ignoreMediaList) {
return return
} }
const danmakuCount = await getDanmakuCount()
console.log(`当前弹幕量: ${danmakuCount}`)
if (danmakuCount !== null && danmakuCount > options.maxDanmakuCount) {
console.log(`超过了最大弹幕数量 ${options.maxDanmakuCount}, 跳过展开`)
return
}
const danmakuBox = await select('.bui-collapse-wrap') const danmakuBox = await select('.bui-collapse-wrap')
if (danmakuBox && danmakuBox.classList.contains('bui-collapse-wrap-folded')) { if (danmakuBox && danmakuBox.classList.contains('bui-collapse-wrap-folded')) {
const button = await select('.bui-collapse-header') as HTMLDivElement const button = await select('.bui-collapse-header') as HTMLDivElement
@ -23,13 +49,17 @@ export const component: ComponentMetadata = {
entry, entry,
tags: [componentsTags.video], tags: [componentsTags.video],
description: { description: {
'zh-CN': '每次加载视频时自动展开弹幕列表.', 'zh-CN': '每次加载视频时自动展开弹幕列表, 如果弹幕装载量超过 `最大弹幕数量`, 则不展开以免对页面造成卡顿.',
}, },
options: { options: {
ignoreMediaList: { ignoreMediaList: {
defaultValue: true, defaultValue: true,
displayName: '合集类页面不自动展开', displayName: '合集类页面不自动展开',
}, },
maxDanmakuCount: {
defaultValue: 500,
displayName: '最大弹幕数量',
},
}, },
urlInclude: [ urlInclude: [
...videoAndBangumiUrls, ...videoAndBangumiUrls,

View File

@ -1,10 +1,12 @@
.video-desc .info, .video-desc .info,
.video-desc .desc-info, .video-desc .desc-info,
.video-desc-v1 .desc-info,
.play-up-info .play-up-self { .play-up-info .play-up-self {
height: auto !important; height: auto !important;
} }
.video-desc .btn, .video-desc .btn,
.video-desc .toggle-btn, .video-desc .toggle-btn,
.video-desc-v1 .toggle-btn,
.play-up-info .play-up-self-btn { .play-up-info .play-up-self-btn {
display: none !important; display: none !important;
} }

View File

@ -1,7 +1,7 @@
import { ComponentMetadata } from '@/components/types' import { ComponentMetadata } from '@/components/types'
import { videoAndBangumiUrls } from '@/core/utils/urls' import { videoAndBangumiUrls } from '@/core/utils/urls'
import { videoChange } from '@/core/observer' import { videoChange } from '@/core/observer'
import { select } from '@/core/spin-query' import { select, sq } from '@/core/spin-query'
import { addStyle, removeStyle } from '@/core/style' import { addStyle, removeStyle } from '@/core/style'
import style from './full-description.scss' import style from './full-description.scss'
@ -9,11 +9,14 @@ const name = 'fullVideoDescription'
const entry = () => { const entry = () => {
addStyle(style, name) addStyle(style, name)
videoChange(async () => { videoChange(async () => {
const desc = await select('.video-desc') const desc = await select('.video-desc, .video-desc-v1')
if (!desc) { if (!desc) {
return return
} }
const expandButton = await select('.video-desc .btn[report-id="abstract_spread"], .video-desc .toggle-btn') as HTMLElement const expandButton = await sq(
() => dq(desc, '[report-id="abstract_spread"], .toggle-btn') as HTMLElement,
it => it.style.display !== 'none',
)
expandButton?.click() expandButton?.click()
}) })
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<span title="稍后再看" class="watchlater" :class="{ on }" @click="toggle()"> <span title="稍后再看" class="watchlater be-outer-watchlater" :class="{ on }" @click="toggle()">
<VIcon class="icon" :size="28" icon="mdi-timetable"></VIcon> <VIcon class="icon" :size="28" icon="mdi-timetable"></VIcon>
<span class="text">稍后再看</span> <span class="text">稍后再看</span>
<div class="tip" :class="{ show: tipShowing }">{{ tipText }}</div> <div class="tip" :class="{ show: tipShowing }">{{ tipText }}</div>

View File

@ -57,12 +57,12 @@ export const component = defineComponentMetadata({
// urlExclude: watchlaterUrls, // urlExclude: watchlaterUrls,
options, options,
reload: () => { reload: () => {
dqa('.ops .watchlater').forEach((it: HTMLElement) => { dqa('.be-outer-watchlater').forEach((it: HTMLElement) => {
it.style.display = 'inline-block' it.style.display = ''
}) })
}, },
unload: () => { unload: () => {
dqa('.ops .watchlater').forEach((it: HTMLElement) => { dqa('.be-outer-watchlater').forEach((it: HTMLElement) => {
it.style.display = 'none' it.style.display = 'none'
}) })
}, },
@ -74,7 +74,7 @@ export const component = defineComponentMetadata({
displayName: '稍后再看', displayName: '稍后再看',
run: context => { run: context => {
const { clickElement } = context const { clickElement } = context
return clickElement('.video-toolbar .ops .watchlater, .more-ops-list .ops-watch-later, .video-toolbar-module .see-later-box', context) return clickElement('.be-outer-watchlater', context)
}, },
} }
}) })

View File

@ -1,23 +1,57 @@
@import "common"; @import 'common';
.bilibili-player-video-control { .bilibili-player-video-control {
&-mask { &-mask {
background: transparent !important; background: transparent !important;
} }
.bilibili-player-video-control-bottom { .bilibili-player-video-control-bottom {
background: rgba(0, 0, 0, var(--video-control-opacity)) !important; &::before {
margin: 5px 0 0 0 !important; content: '';
padding: 9px 0 0 !important; position: absolute;
height: 30px !important; bottom: 0;
@include on-fullscreen { height: calc(100% - 5px);
margin: -2px 0 0 0 !important; width: 100%;
padding: 13px 0 0 !important; background-color: rgba(0, 0, 0, var(--video-control-opacity));
height: 45px !important; z-index: -1;
@include on-fullscreen {
height: calc(100% + 2px);
}
}
}
}
.bpx-player-control {
&-mask {
background: transparent !important;
}
&-bottom {
&::before {
content: '';
position: absolute;
bottom: 0;
height: calc(100% - 17px);
width: 100%;
background-color: rgba(0, 0, 0, var(--video-control-opacity));
z-index: -1;
@include on-fullscreen {
height: calc(100% - 11px);
}
.video-container-v1 & {
height: calc(100% - 5px);
@include on-fullscreen {
height: calc(100% + 1px);
}
}
} }
} }
} }
body { body {
&.video-control-progress-background { &.video-control-progress-background {
.squirtle-progress-bar:not(.squirtle-progress-buffer):not(.squirtle-progress-timeline):not(.squirtle-progress-dot-container),
.bpx-player-progress-schedule {
background-color: transparent !important;
}
.bpx-player-progress,
.squirtle-progress-dot-container,
.bui-track-video-progress .bui-bar-wrap { .bui-track-video-progress .bui-bar-wrap {
background: rgba(0, 0, 0, var(--video-control-opacity)) !important; background: rgba(0, 0, 0, var(--video-control-opacity)) !important;
} }

View File

@ -22,48 +22,50 @@ const entry: ComponentEntry = async ({ settings: { options } }) => {
} = playerAgent } = playerAgent
await playerReady() await playerReady()
const actions: Map<PlayerModes, () => void | Promise<void>> = new Map([ Promise.resolve().then(async () => {
[PlayerModes.Normal, none], const actions: Map<PlayerModes, () => void | Promise<void>> = new Map([
[PlayerModes.Wide, async () => { [PlayerModes.Normal, none],
await loadLazyPanel(buttons.widescreen.selector) [PlayerModes.Wide, async () => {
disableWindowScroll(() => playerAgent.widescreen()) await loadLazyPanel(buttons.widescreen.selector)
}], disableWindowScroll(() => playerAgent.widescreen())
[PlayerModes.WebFullscreen, async () => { }],
await loadLazyPanel(buttons.webFullscreen.selector) [PlayerModes.WebFullscreen, async () => {
playerAgent.webFullscreen() await loadLazyPanel(buttons.webFullscreen.selector)
}], playerAgent.webFullscreen()
[PlayerModes.Fullscreen, async () => { }],
const video = await sq( [PlayerModes.Fullscreen, async () => {
() => dq(playerAgent.query.video.element.selector), const video = await sq(
(it: HTMLVideoElement) => it !== null && it.readyState === 4 () => dq(playerAgent.query.video.element.selector),
&& document.readyState === 'complete' && document.hasFocus(), (it: HTMLVideoElement) => it !== null && it.readyState === 4
) && document.readyState === 'complete' && document.hasFocus(),
if (video === null) { )
console.warn('[默认播放器模式] 未能应用全屏模式, 等待超时.') if (video === null) {
return console.warn('[默认播放器模式] 未能应用全屏模式, 等待超时.')
} return
// await loadLazyPanel(buttons.fullscreen.selector) }
playerAgent.fullscreen() // await loadLazyPanel(buttons.fullscreen.selector)
}], playerAgent.fullscreen()
]) }],
const video = await playerAgent.query.video.element() as HTMLVideoElement ])
if (!video) { const video = await playerAgent.query.video.element() as HTMLVideoElement
return if (!video) {
} return
const action = actions.get(options.mode) }
// https://github.com/the1812/Bilibili-Evolved/issues/2408 const action = actions.get(options.mode)
// 也许以前切P是会刷新页面但现在(2.7+)的播放器切P是不刷新页面的所以不需要判断 // https://github.com/the1812/Bilibili-Evolved/issues/2408
// const onplay = () => { // 也许以前切P是会刷新页面但现在(2.7+)的播放器切P是不刷新页面的所以不需要判断
// const isNormalMode = !dq('body[class*=player-mode-]') // const onplay = () => {
// if (isNormalMode) { // const isNormalMode = !dq('body[class*=player-mode-]')
// action() // if (isNormalMode) {
// } // action()
// } // }
if (options.applyOnPlay && !playerAgent.isAutoPlay()) { // }
video.addEventListener('play', action, { once: true }) if (options.applyOnPlay && !playerAgent.isAutoPlay()) {
} else { video.addEventListener('play', action, { once: true })
action() } else {
} action()
}
})
} }
export const component: ComponentMetadata = { export const component: ComponentMetadata = {
name: 'defaultPlayerMode', name: 'defaultPlayerMode',

View File

@ -3,6 +3,7 @@ import { playerAgent } from '@/components/video/player-agent'
import { lightOff, lightOn } from '@/components/video/player-light' import { lightOff, lightOn } from '@/components/video/player-light'
import { videoChange } from '@/core/observer' import { videoChange } from '@/core/observer'
import { addComponentListener, getComponentSettings } from '@/core/settings' import { addComponentListener, getComponentSettings } from '@/core/settings'
import { allVideoUrls } from '@/core/utils/urls'
enum IntersectionMode { enum IntersectionMode {
Top = '视频顶部', Top = '视频顶部',
@ -17,115 +18,118 @@ export const component: ComponentMetadata = {
link: 'https://github.com/FoundTheWOUT', link: 'https://github.com/FoundTheWOUT',
}, },
tags: [componentsTags.video], tags: [componentsTags.video],
urlInclude: allVideoUrls,
entry: async ({ settings: { options }, metadata }) => { entry: async ({ settings: { options }, metadata }) => {
const settings = options as { const settings = options as {
triggerLocation: IntersectionMode triggerLocation: IntersectionMode
pause: boolean pause: boolean
light: boolean light: boolean
} }
const { query: { video } } = playerAgent Promise.resolve().then(async () => {
const { query: { video } } = playerAgent
const videoEl = await video.element() as HTMLVideoElement const videoEl = await video.element() as HTMLVideoElement
// const playerWrap = await video.wrap() // const playerWrap = await video.wrap()
// 如果有 video-player 优先的使用该盒子 // 如果有 video-player 优先的使用该盒子
// 因为在稍后再看页面medialist视频也有 player-wrap // 因为在稍后再看页面medialist视频也有 player-wrap
// 选择 player-wrap 会导致闪烁。 // 选择 player-wrap 会导致闪烁。
const playerWrap = ( const playerWrap = (
document.getElementById('video-player') document.getElementById('video-player')
?? (dq('.player-wrap') || dq('.player-module')) ?? (dq('.player-wrap') || dq('.player-module'))
) as HTMLElement ) as HTMLElement
let observer: IntersectionObserver let observer: IntersectionObserver
let intersectionLock = true // Lock intersection action let intersectionLock = true // Lock intersection action
function getToTop(_mode: string): number { function getToTop(_mode: string): number {
switch (_mode) { switch (_mode) {
case IntersectionMode.Top: case IntersectionMode.Top:
return 1 return 1
case IntersectionMode.Medium: case IntersectionMode.Medium:
return 0.5 return 0.5
case IntersectionMode.Bottom: case IntersectionMode.Bottom:
return 0 return 0
default: default:
return 0.5 return 0.5
}
}
function addPlayerOutEvent() {
// window.addEventListener('scroll', onPlayerOutEvent, { passive: true });
observer.observe(playerWrap)
}
function removePlayerOutEvent() {
// window.removeEventListener('scroll', onPlayerOutEvent);
observer.unobserve(playerWrap)
}
const intersectingCall = () => {
if (intersectionLock) {
return
}
intersectionLock = true // relock
if (settings.pause && videoEl.paused) {
videoEl.play()
}
if (
settings.light
&& getComponentSettings('playerAutoLight').enabled
&& !settings.pause
&& !videoEl.paused
) {
lightOff()
}
}
const disIntersectingCall = () => {
// if video is playing, unlock intersecting action
if (!videoEl.paused) {
intersectionLock = false
}
if (settings.pause && !videoEl.paused) {
videoEl.pause()
}
if (
settings.light
&& getComponentSettings('playerAutoLight').enabled
&& !settings.pause
) {
lightOn()
}
}
const createObserver = (mode?: string) => (
new IntersectionObserver(
([e]) => {
e.isIntersecting ? intersectingCall() : disIntersectingCall()
},
{
threshold: getToTop(mode || settings.triggerLocation),
},
)
)
function mountPlayListener() {
videoChange(async () => {
if (playerAgent.isAutoPlay()) {
addPlayerOutEvent()
} }
videoEl.addEventListener('play', addPlayerOutEvent) }
// videoEl.addEventListener('pause', removePlayerOutEvent);
videoEl.addEventListener('ended', removePlayerOutEvent) function addPlayerOutEvent() {
// window.addEventListener('scroll', onPlayerOutEvent, { passive: true });
observer.observe(playerWrap)
}
function removePlayerOutEvent() {
// window.removeEventListener('scroll', onPlayerOutEvent);
observer.unobserve(playerWrap)
}
const intersectingCall = () => {
if (intersectionLock) {
return
}
intersectionLock = true // relock
if (settings.pause && videoEl.paused) {
videoEl.play()
}
if (
settings.light
&& getComponentSettings('playerAutoLight').enabled
&& !settings.pause
&& !videoEl.paused
) {
lightOff()
}
}
const disIntersectingCall = () => {
// if video is playing, unlock intersecting action
if (!videoEl.paused) {
intersectionLock = false
}
if (settings.pause && !videoEl.paused) {
videoEl.pause()
}
if (
settings.light
&& getComponentSettings('playerAutoLight').enabled
&& !settings.pause
) {
lightOn()
}
}
const createObserver = (mode?: string) => (
new IntersectionObserver(
([e]) => {
e.isIntersecting ? intersectingCall() : disIntersectingCall()
},
{
threshold: getToTop(mode || settings.triggerLocation),
},
)
)
function mountPlayListener() {
videoChange(async () => {
if (playerAgent.isAutoPlay()) {
addPlayerOutEvent()
}
videoEl.addEventListener('play', addPlayerOutEvent)
// videoEl.addEventListener('pause', removePlayerOutEvent);
videoEl.addEventListener('ended', removePlayerOutEvent)
})
}
addComponentListener(`${metadata.name}.triggerLocation`, (value: IntersectionMode) => {
removePlayerOutEvent()
observer = createObserver(value)
addPlayerOutEvent()
}) })
}
addComponentListener(`${metadata.name}.triggerLocation`, (value: IntersectionMode) => { observer = createObserver()
removePlayerOutEvent() mountPlayListener()
observer = createObserver(value)
addPlayerOutEvent()
}) })
observer = createObserver()
mountPlayListener()
}, },
displayName: '播放器位置动作', displayName: '播放器位置动作',
description: { description: {

View File

@ -1,4 +1,5 @@
import { ComponentMetadata } from '@/components/types' import { ComponentMetadata } from '@/components/types'
import { playerAgent } from '@/components/video/player-agent'
import { videoChange } from '@/core/observer' import { videoChange } from '@/core/observer'
import { select } from '@/core/spin-query' import { select } from '@/core/spin-query'
import { playerReady } from '@/core/utils' import { playerReady } from '@/core/utils'
@ -12,17 +13,19 @@ export const component: ComponentMetadata = {
urlInclude: videoUrls, urlInclude: videoUrls,
entry: async () => { entry: async () => {
const autoPlayControls = { const autoPlayControls = {
// 命中时应该打开连播: 传统分 P, 合集
enable: [ enable: [
'.base-video-sections .next-button', ':is(.base-video-sections, .base-video-sections-v1) .next-button',
'.multi-page .next-button', ':is(.multi-page, .multi-page-v1) .next-button',
'.player-auxiliary-autoplay-switch input', '.player-auxiliary-autoplay-switch input',
], ],
disable: ['.recommend-list .next-button'], // 命中时应该关闭连播: 关联视频推荐
disable: [':is(.recommend-list, .recommend-list-v1) .next-button'],
} }
// 最后 1P 时不能开启连播 // 最后 1P 时不能开启连播
const disableConditions = [ const disableConditions = [
// 传统分 P // 传统分 P
() => Boolean(dq('.multi-page .list-box li.on:last-child')), () => Boolean(dq(':is(.multi-page, .multi-page-v1) .list-box li.on:last-child')),
// 替代分 P 的合集 // 替代分 P 的合集
// & 可分子合集的分 P 合集, 布局长得比下面那个丑一点 // & 可分子合集的分 P 合集, 布局长得比下面那个丑一点
() => Boolean(dq('.video-sections-item:last-child .video-episode-card:last-child .video-episode-card__info-playing')), () => Boolean(dq('.video-sections-item:last-child .video-episode-card:last-child .video-episode-card__info-playing')),
@ -49,7 +52,7 @@ export const component: ComponentMetadata = {
} }
videoChange(async () => { videoChange(async () => {
checkPlayMode() checkPlayMode()
const video = await select('.bilibili-player-video video') as HTMLVideoElement const video = await playerAgent.query.video.element() as HTMLVideoElement
video?.addEventListener('play', checkPlayMode, { once: true }) video?.addEventListener('play', checkPlayMode, { once: true })
}) })
}, },

View File

@ -1,5 +1,10 @@
@import 'common';
@media screen and (max-width: 1200px) { @media screen and (max-width: 1200px) {
.bilibili-player.mode-webfullscreen .bilibili-player-video-inputbar { .bpx-player-video-inputbar,
display: flex !important; .bilibili-player-video-inputbar {
@include on-fullscreen {
display: flex !important;
}
} }
} }

View File

@ -1,10 +1,17 @@
import { ComponentMetadata } from '@/components/types' import { ComponentMetadata } from '@/components/types'
import { toggleStyle } from '@/components/styled-component'
import { playerUrls } from '@/core/utils/urls' import { playerUrls } from '@/core/utils/urls'
const name = 'preserveDanmakuInput'
export const component: ComponentMetadata = { export const component: ComponentMetadata = {
...toggleStyle('preserveDanmakuInput', () => import('./danmaku-input.scss')), name,
displayName: '强制保留弹幕发送栏', displayName: '强制保留弹幕发送栏',
entry: none,
instantStyles: [
{
name,
style: () => import('./danmaku-input.scss'),
},
],
tags: [ tags: [
componentsTags.style, componentsTags.style,
componentsTags.video, componentsTags.video,

View File

@ -14,7 +14,8 @@ body {
} }
&-related-videos { &-related-videos {
.bilibili-player-video-link, .bilibili-player-video-link,
.bilibili-player-link { .bilibili-player-link,
.bpx-player-link {
display: none !important; display: none !important;
} }
} }
@ -27,7 +28,10 @@ body {
} }
} }
&-rates { &-rates {
.bilibili-player-score { .bilibili-player-score,
.bpx-player-popup-dm-close,
.bpx-player-score-summary-wrap,
.bpx-player-score {
display: none !important; display: none !important;
} }
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<span <span
class="quick-favorite" class="quick-favorite be-quick-favorite"
title="快速收藏" title="快速收藏"
:class="{ on: isFavorite }" :class="{ on: isFavorite }"
@click.left.self="toggle()" @click.left.self="toggle()"

View File

@ -61,10 +61,10 @@ export const component = defineComponentMetadata({
}, },
entry, entry,
unload: () => { unload: () => {
dqa('.ops .quick-favorite').forEach((it: HTMLElement) => (it.style.display = 'none')) dqa('.be-quick-favorite').forEach((it: HTMLElement) => (it.style.display = ''))
}, },
reload: () => { reload: () => {
dqa('.ops .quick-favorite').forEach((it: HTMLElement) => (it.style.display = 'inline-block')) dqa('.be-quick-favorite').forEach((it: HTMLElement) => (it.style.display = 'inline-block'))
}, },
urlInclude: videoUrls, urlInclude: videoUrls,
// urlExclude: favoriteListUrls, // urlExclude: favoriteListUrls,
@ -80,7 +80,7 @@ export const component = defineComponentMetadata({
displayName: '快速收藏', displayName: '快速收藏',
run: context => { run: context => {
const { clickElement } = context const { clickElement } = context
return clickElement('.quick-favorite', context) return clickElement('.be-quick-favorite', context)
}, },
} }
}) })

View File

@ -0,0 +1,24 @@
<template>
<div class="navbar-channel" :data-channel-id="channelId">频道</div>
</template>
<script lang="ts">
import { bilibiliApi, getJsonWithCredentials } from '@/core/ajax'
export default Vue.extend({
data() {
return {
channelId: null,
}
},
async created() {
const { channel_id } = await bilibiliApi(getJsonWithCredentials(
'https://api.bilibili.com/x/web-interface/web/channel/red',
))
if (!channel_id) {
return
}
this.channelId = channel_id
},
})
</script>

View File

@ -0,0 +1,31 @@
import { PluginMetadata } from '@/plugins/plugin'
import { getComponentSettings } from '@/core/settings'
import type { CustomNavbarItemInit } from '../../../components/style/custom-navbar/custom-navbar-item'
export const plugin: PluginMetadata = {
name: 'customNavbar.items.channel',
displayName: '自定义顶栏 - 频道',
description: '为自定义顶栏添加一个频道入口.',
async setup({ addData }) {
addData('customNavbar.items', (items: CustomNavbarItemInit[]) => {
const defaultLink = 'https://www.bilibili.com/v/channel/'
const name = 'channel'
const isOpenInNewTab = () => {
const { options } = getComponentSettings('customNavbar')
if (name in options.openInNewTabOverrides) {
return options.openInNewTabOverrides[name]
}
return options.openInNewTab
}
items.push({
name: 'channel',
displayName: '频道',
content: () => import('./NavbarChannel.vue'),
clickAction: () => {
const channelId = dq('.navbar-channel[data-channel-id]').getAttribute('data-channel-id')
window.open(channelId ? `${defaultLink}${channelId}` : defaultLink, isOpenInNewTab() ? '_blank' : '_self')
},
})
})
},
}

View File

@ -1,5 +1,5 @@
{ {
"version": "2.3.3", "version": "2.4.0",
"author": "Grant Howard, Coulomb-G", "author": "Grant Howard, Coulomb-G",
"copyright": "[year], Grant Howard (https://github.com/the1812) & Coulomb-G (https://github.com/Coulomb-G)", "copyright": "[year], Grant Howard (https://github.com/the1812) & Coulomb-G (https://github.com/Coulomb-G)",
"license": "MIT", "license": "MIT",

View File

@ -5,9 +5,19 @@ import { FeedsCardsManagerV1 } from './v1'
import { FeedsCardsManagerV2 } from './v2' import { FeedsCardsManagerV2 } from './v2'
export * from './base' export * from './base'
export const isV2Feeds = () => {
const hasCookieValue = parseInt(getCookieValue('hit-dyn-v2')) > 0
if (!hasCookieValue) {
return false
}
return [
't.bilibili.com',
'space.bilibili.com',
].some(host => location.host === host)
}
export const feedsCardsManager = (() => { export const feedsCardsManager = (() => {
const isV2Feeds = parseInt(getCookieValue('hit-dyn-v2')) > 0 && location.host === 't.bilibili.com' const isV2 = isV2Feeds()
if (isV2Feeds) { if (isV2) {
return new FeedsCardsManagerV2() return new FeedsCardsManagerV2()
} }
return new FeedsCardsManagerV1() return new FeedsCardsManagerV1()

View File

@ -3,7 +3,12 @@
<div <div
v-for="item of items" v-for="item of items"
:key="item.name" :key="item.name"
class="be-video-control-bar-extend-item bilibili-player-video-btn squirtle-block-wrap" class="
be-video-control-bar-extend-item
bilibili-player-video-btn
squirtle-block-wrap
bpx-player-ctrl-btn
"
:style="{ order: item.order.toString() }" :style="{ order: item.order.toString() }"
:data-name="item.name" :data-name="item.name"
@click="item.action($event)" @click="item.action($event)"
@ -51,7 +56,7 @@ export default Vue.extend({
width: auto; width: auto;
padding-top: 1px; padding-top: 1px;
.be-icon { .be-icon {
transition: transform .2s ease-out; transition: transform 0.2s ease-out;
} }
.be-icon svg, .be-icon svg,
.be-icon { .be-icon {
@ -93,7 +98,7 @@ export default Vue.extend({
align-items: center; align-items: center;
} }
&:hover .be-video-control-tooltip { &:hover .be-video-control-tooltip {
transition: all .3s ease-in-out .3s; transition: all 0.3s ease-in-out 0.3s;
opacity: 1; opacity: 1;
transform: translateX(-50%) translateY(calc(-100% - 19px)); transform: translateX(-50%) translateY(calc(-100% - 19px));
} }
@ -113,4 +118,11 @@ export default Vue.extend({
} }
} }
} }
.bpx-player-control-wrap {
@include on-fullscreen {
& .be-video-control-bar-extend-item button {
padding-top: 2px;
}
}
}
</style> </style>

View File

@ -45,7 +45,7 @@ const idPolyfill = async () => {
}) })
loadPbpData() loadPbpData()
} }
/** , . /** 3.x .
* , , 使. * , , 使.
*/ */
const doubleClickPolyfill = async () => { const doubleClickPolyfill = async () => {
@ -56,10 +56,11 @@ const doubleClickPolyfill = async () => {
preventEvent(layer, 'dblclick') preventEvent(layer, 'dblclick')
} }
export const bpxPlayerPolyfill = lodash.once(async () => { export const bpxPlayerPolyfill = lodash.once(async () => {
// 番剧 + 3.x 播放器通用
playerModePolyfill()
doubleClickPolyfill()
if (!document.URL.startsWith('https://www.bilibili.com/bangumi/play/')) { if (!document.URL.startsWith('https://www.bilibili.com/bangumi/play/')) {
return return
} }
playerModePolyfill()
idPolyfill() idPolyfill()
doubleClickPolyfill()
}) })

View File

@ -250,12 +250,20 @@ export class VideoPlayerV2Agent extends PlayerAgent {
if (!this.nativeApi) { if (!this.nativeApi) {
return null return null
} }
if (this.nativeApi.isMuted) {
return this.nativeApi.isMuted()
}
return this.nativeApi.isMute() return this.nativeApi.isMute()
} }
changeVolume(change: number) { changeVolume(change: number) {
if (!this.nativeApi) { if (!this.nativeApi) {
return null return null
} }
if (this.nativeApi.getVolume) {
const current = this.nativeApi.getVolume()
this.nativeApi.setVolume(current + change / 100)
return Math.round(this.nativeApi.getVolume() * 100)
}
const current = this.nativeApi.volume() const current = this.nativeApi.volume()
this.nativeApi.volume(current + change / 100) this.nativeApi.volume(current + change / 100)
return Math.round(this.nativeApi.volume() * 100) return Math.round(this.nativeApi.volume() * 100)
@ -389,54 +397,54 @@ export class BangumiPlayerAgent extends PlayerAgent {
export class VideoPlayerMixedAgent extends VideoPlayerV2Agent { export class VideoPlayerMixedAgent extends VideoPlayerV2Agent {
query = selectorWrap({ query = selectorWrap({
playerWrap: '.player-wrap', playerWrap: '.player-wrap',
bilibiliPlayer: '.bilibili-player,#bilibili-player', bilibiliPlayer: '.bilibili-player, #bilibili-player',
playerArea: '.bilibili-player-area,.bpx-player-primary-area', playerArea: '.bilibili-player-area, .bpx-player-primary-area',
video: { video: {
element: '.bilibili-player-video video,.bpx-player-video-wrap video', element: '.bilibili-player-video video, .bpx-player-video-wrap video',
wrap: '.bilibili-player-video-wrap,.bpx-player-video-area', wrap: '.bilibili-player-video-wrap, .bpx-player-video-area',
top: '.bilibili-player-video-top,.bpx-player-top-wrap', top: '.bilibili-player-video-top, .bpx-player-top-wrap',
state: '.bilibili-player-video-state,.bpx-player-state-wrap', state: '.bilibili-player-video-state, .bpx-player-state-wrap',
panel: '.bilibili-player-video-panel,.bpx-player-ending-panel', panel: '.bilibili-player-video-panel, .bpx-player-ending-panel',
popup: '.bilibili-player-video-popup,.bpx-player-dialog-wrap', popup: '.bilibili-player-video-popup, .bpx-player-dialog-wrap',
subtitle: '.bilibili-player-video-subtitle,.bpx-player-subtitle-wrap', subtitle: '.bilibili-player-video-subtitle, .bpx-player-subtitle-wrap',
basDanmaku: '.bilibili-player-video-bas-danmaku,.bpx-player-bas-dm-wrap', basDanmaku: '.bilibili-player-video-bas-danmaku, .bpx-player-bas-dm-wrap',
advDanmaku: '.bilibili-player-video-adv-danmaku,.bpx-player-adv-dm-wrap', advDanmaku: '.bilibili-player-video-adv-danmaku, .bpx-player-adv-dm-wrap',
danmaku: '.bilibili-player-video-danmaku,.bpx-player-row-dm-wrap', danmaku: '.bilibili-player-video-danmaku, .bpx-player-row-dm-wrap',
container: '.bilibili-player-video,.bpx-player-video-wrap', container: '.bilibili-player-video, .bpx-player-video-perch',
}, },
control: { control: {
element: '.bilibili-player-control,.bpx-player-control-entity', element: '.bilibili-player-control, .bpx-player-control-entity',
wrap: '.bilibili-player-control-wrap,.bpx-player-control-wrap', wrap: '.bilibili-player-control-wrap, .bpx-player-control-wrap',
mask: '.bilibili-player-control-mask,.bpx-player-control-mask', mask: '.bilibili-player-control-mask, .bpx-player-control-mask',
top: '.bilibili-player-control-top,.bpx-player-control-top', top: '.bilibili-player-control-top, .bpx-player-control-top',
progress: '.bilibili-player-video-progress,.bpx-player-progress', progress: '.bilibili-player-video-progress, .bpx-player-progress',
bottom: '.bilibili-player-control-bottom,.bpx-player-control-bottom', bottom: '.bilibili-player-control-bottom, .bpx-player-control-bottom',
bottomLeft: '.bilibili-player-control-bottom-left,,.bpx-player-control-bottom-left', bottomLeft: '.bilibili-player-control-bottom-left, ,.bpx-player-control-bottom-left',
bottomCenter: '.bilibili-player-control-bottom-center,.bpx-player-control-bottom-center', bottomCenter: '.bilibili-player-control-bottom-center, .bpx-player-control-bottom-center',
bottomRight: '.bilibili-player-control-bottom-right,.bpx-player-control-bottom-right', bottomRight: '.bilibili-player-control-bottom-right, .bpx-player-control-bottom-right',
buttons: { buttons: {
start: '.bilibili-player-video-btn-start,.bpx-player-ctrl-play', start: '.bilibili-player-video-btn-start, .bpx-player-ctrl-play',
next: '.bilibili-player-video-btn-next,.bpx-player-ctrl-btn-next', next: '.bilibili-player-video-btn-next, .bpx-player-ctrl-btn-next',
time: '.bilibili-player-video-time,.bpx-player-ctrl-time', time: '.bilibili-player-video-time, .bpx-player-ctrl-time',
quality: '.bilibili-player-btn-quality,.bpx-player-ctrl-quality', quality: '.bilibili-player-btn-quality, .bpx-player-ctrl-quality',
pageList: '.bilibili-player-video-btn-pagelist,.bpx-player-ctrl-eplist', pageList: '.bilibili-player-video-btn-pagelist, .bpx-player-ctrl-eplist',
speed: '.bilibili-player-video-btn-speed,.bpx-player-ctrl-playbackrate', speed: '.bilibili-player-video-btn-speed, .bpx-player-ctrl-playbackrate',
subtitle: '.bilibili-player-video-btn-subtitle,.bpx-player-ctrl-subtitle', subtitle: '.bilibili-player-video-btn-subtitle, .bpx-player-ctrl-subtitle',
volume: '.bilibili-player-video-btn-volume .bilibili-player-iconfont-volume,.bpx-player-ctrl-volume .bpx-player-ctrl-volume-icon', volume: '.bilibili-player-video-btn-volume .bilibili-player-iconfont-volume, .bpx-player-ctrl-volume .bpx-player-ctrl-volume-icon',
settings: '.bilibili-player-video-btn-setting,.bpx-player-ctrl-setting', settings: '.bilibili-player-video-btn-setting, .bpx-player-ctrl-setting',
pip: '.bilibili-player-video-btn-pip,.bpx-player-ctrl-pip', pip: '.bilibili-player-video-btn-pip, .bpx-player-ctrl-pip',
widescreen: '.bilibili-player-video-btn-widescreen,.bpx-player-ctrl-wide', widescreen: '.bilibili-player-video-btn-widescreen, .bpx-player-ctrl-wide',
webFullscreen: '.bilibili-player-video-web-fullscreen,.bpx-player-ctrl-web', webFullscreen: '.bilibili-player-video-web-fullscreen, .bpx-player-ctrl-web',
fullscreen: '.bilibili-player-video-btn-fullscreen,.bpx-player-ctrl-full', fullscreen: '.bilibili-player-video-btn-fullscreen, .bpx-player-ctrl-full',
}, },
settings: { settings: {
wrap: '.bilibili-player-video-btn-setting-wrap,.bpx-player-ctrl-setting-box', wrap: '.bilibili-player-video-btn-setting-wrap, .bpx-player-ctrl-setting-box',
lightOff: '.bilibili-player-video-btn-setting-right-others-content-lightoff .bui-checkbox-input,.bpx-player-ctrl-setting-lightoff .bui-checkbox-input', lightOff: '.bilibili-player-video-btn-setting-right-others-content-lightoff .bui-checkbox-input, .bpx-player-ctrl-setting-lightoff .bui-checkbox-input',
}, },
}, },
toastWrap: '.bilibili-player-video-toast-wrp,.bpx-player-dialog-wrap', toastWrap: '.bilibili-player-video-toast-wrp, .bpx-player-dialog-wrap',
danmakuTipLayer: '.bilibili-player-dm-tip-wrap,.bpx-player-dm-tip', danmakuTipLayer: '.bilibili-player-dm-tip-wrap, .bpx-player-dm-tip',
danmakuSwitch: '.bilibili-player-video-danmaku-switch input,.bpx-player-dm-switch input', danmakuSwitch: '.bilibili-player-video-danmaku-switch input, .bpx-player-dm-switch input',
}) as PlayerQuery<ElementQuery> }) as PlayerQuery<ElementQuery>
constructor() { constructor() {

View File

@ -362,6 +362,9 @@ export class DoubleClickEvent {
singleClickHandler: (e: MouseEvent) => void = none singleClickHandler: (e: MouseEvent) => void = none
private clickedOnce = false private clickedOnce = false
private readonly stopPropagationHandler = (e: MouseEvent) => {
e.stopImmediatePropagation()
}
private readonly doubleClickHandler = (e: MouseEvent) => { private readonly doubleClickHandler = (e: MouseEvent) => {
if (!this.clickedOnce) { if (!this.clickedOnce) {
this.clickedOnce = true this.clickedOnce = true
@ -400,6 +403,7 @@ export class DoubleClickEvent {
element.addEventListener('click', this.doubleClickHandler, { element.addEventListener('click', this.doubleClickHandler, {
capture: true, capture: true,
}) })
element.addEventListener('dblclick', this.stopPropagationHandler, { capture: true })
} }
} }
/** /**
@ -415,6 +419,7 @@ export class DoubleClickEvent {
element.removeEventListener('click', this.doubleClickHandler, { element.removeEventListener('click', this.doubleClickHandler, {
capture: true, capture: true,
}) })
element.removeEventListener('dblclick', this.stopPropagationHandler, { capture: true })
} }
} }
/** , DOM /** , DOM