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.2.4 的所有更新内容.

View File

@ -29,6 +29,9 @@ https://afdian.net/@the1812?tab=sponsor
| 时间 | 用户名 | 单号后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.16 01:11:59 | *生 | 3893 | ¥20.00 |
| 2022.08.09 22:42:25 | H*l | 0339 | ¥5.88 |

View File

@ -75,7 +75,7 @@
"type": "component",
"name": "badgeHelper",
"displayName": "直播勋章快速更换",
"description": "在直播区中, 可从功能面板中直接切换勋章和头衔. 默认加载 256 个 (同时也是上限), 可在选项中修改.",
"description": "在直播区中, 可从功能面板中直接切换勋章和头衔. 默认显示 256 个 (同时也是上限), 可在选项中修改.",
"fullRelativePath": "../../registry/dist/components/live/badge-helper.js",
"fullAbsolutePath": "registry/dist/components/live/badge-helper.js"
},
@ -163,7 +163,7 @@
"type": "component",
"name": "customNavbar",
"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",
"fullAbsolutePath": "registry/dist/components/style/custom-navbar.js"
},
@ -283,7 +283,7 @@
"type": "component",
"name": "sidebarOffset",
"displayName": "侧栏垂直偏移",
"description": "给脚本的侧栏设置垂直偏移量, 范围为 -40% ~ 40%",
"description": "给脚本的侧栏设置垂直偏移量, 范围为 -35% ~ 40%",
"fullRelativePath": "../../registry/dist/components/style/sidebar-offset.js",
"fullAbsolutePath": "registry/dist/components/style/sidebar-offset.js"
},
@ -371,7 +371,7 @@
"type": "component",
"name": "blackList",
"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",
"fullAbsolutePath": "registry/dist/components/utils/black-list.js"
},
@ -427,7 +427,7 @@
"type": "component",
"name": "imageExporter",
"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",
"fullAbsolutePath": "registry/dist/components/utils/image-exporter.js"
},
@ -531,7 +531,7 @@
"type": "component",
"name": "expandDanmakuList",
"displayName": "展开弹幕列表",
"description": "每次加载视频时自动展开弹幕列表.",
"description": "每次加载视频时自动展开弹幕列表, 如果弹幕装载量超过 `最大弹幕数量`, 则不展开以免对页面造成卡顿.",
"fullRelativePath": "../../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",
"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",
"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)
在直播区中, 可从功能面板中直接切换勋章和头衔. 默认加载 256 个 (同时也是上限), 可在选项中修改.
在直播区中, 可从功能面板中直接切换勋章和头衔. 默认显示 256 个 (同时也是上限), 可在选项中修改.
### [直播弹幕发送栏](../../registry/dist/components/live/danmaku-sendbar.js)
`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)
给脚本的侧栏设置垂直偏移量, 范围为 -40% ~ 40%
给脚本的侧栏设置垂直偏移量, 范围为 -35% ~ 40%
### [简化评论区](../../registry/dist/components/style/simplify/comments.js)
`simplifyComments`
@ -460,7 +460,7 @@
by [@snowraincloud](https://github.com/snowraincloud)
屏蔽黑名单up主
屏蔽黑名单up主, 根据up主的名称进行匹配支持精确匹配和正则匹配
### [更新链接替换](../../registry/dist/components/utils/change-update-urls.js)
`changeUpdateUrls`
@ -541,6 +541,7 @@ by [@snowraincloud](https://github.com/snowraincloud)
动态文件名变量:
- `user`: 用户名
- `originalUser`: 被转发用户名, 如果不是转发类型的动态则等于 `user`
- `id`: 动态 ID
- `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)
每次加载视频时自动展开弹幕列表.
每次加载视频时自动展开弹幕列表, 如果弹幕装载量超过 `最大弹幕数量`, 则不展开以免对页面造成卡顿.
### [视频页默认定位](../../registry/dist/components/video/default-location.js)
`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)
`customNavbar.items.darkMode`

View File

@ -201,7 +201,7 @@
"type": "component",
"name": "customNavbar",
"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",
"fullAbsolutePath": "registry/dist/components/style/custom-navbar.js"
},

View File

@ -6,6 +6,17 @@
<TextBox v-model="search" placeholder="搜索" linear></TextBox>
</div>
<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
class="operation"
target="_blank"
@ -116,6 +127,8 @@
</div>
</template>
<script lang="ts">
import { bilibiliApi, getJsonWithCredentials, postTextWithCredentials } from '@/core/ajax'
import { formData, getCsrf } from '@/core/utils'
import { descendingSort } from '@/core/utils/sort'
import {
VButton,
@ -151,6 +164,7 @@ export default Vue.extend({
groups: [],
loading: true,
hasMorePage: true,
paused: false,
}
},
computed: {
@ -170,7 +184,10 @@ export default Vue.extend({
},
async created() {
try {
await this.nextPage()
await Promise.all([
this.nextPage(),
this.updateHistoryPauseState(),
])
} finally {
this.loading = false
}
@ -210,6 +227,29 @@ export default Vue.extend({
}
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>
@ -236,8 +276,9 @@ export default Vue.extend({
position: absolute;
}
}
@mixin round-button($width: 26px) {
width: $width;
@mixin round-button($size: 26px) {
width: $size;
height: $size;
box-sizing: border-box;
}
.header {

View File

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

View File

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

View File

@ -182,6 +182,18 @@
@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 {
box-shadow: 0 4px 8px 0 #0004 !important;
.squirtle-controller {
@ -196,12 +208,16 @@
@include background-color();
}
.bpx-player-sending-bar {
@include background-color();
.bpx-player-dm-setting:hover {
@include theme-fill();
@include background-color('2');
.bpx-player-video-btn-dm,
.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 {
@include theme-fill();
@include fill('e');
}
.bpx-player-dm-input {
@include color("e");

View File

@ -638,9 +638,21 @@
}
.share_dropdown {
@include color('e');
@include background-color('3');
.box-right {
@include border-color('4');
}
.capture-bar {
.bar-left {
@include color('a');
}
.bar-right {
.copy-link {
@include theme-background-color();
@include foreground-color();
}
}
}
.share-link {
@include background-color('4');
#link_copy {
@ -651,6 +663,12 @@
@include theme-color();
}
}
.box-top-right {
box-shadow: -0.5px 0 0 #555 !important;
}
.box-bottom {
@include background-color('4');
}
}
.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 { toggleStyle } from '@/components/styled-component'
import { playerUrls } from '@/core/utils/urls'
const name = 'hideVideoTopMask'
export const component = defineComponentMetadata({
name,
displayName: '隐藏视频标题层',
...toggleStyle('hideVideoTopMask', () => import('./top-mask.scss')),
entry: none,
instantStyles: [
{
name,
style: () => import('./top-mask.scss'),
},
],
tags: [
componentsTags.style,
],

View File

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

View File

@ -5,3 +5,7 @@
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 { 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) {
return
}

View File

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

View File

@ -12,7 +12,14 @@
opacity: 1 !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;
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 {
#bilibili_pbp,
#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 {
&.bilibili-player-area .subtitle-position.subtitle-position {
&-bc,
@ -13,14 +15,30 @@
bottom: 68px !important;
}
}
.mode-webfullscreen &.touch-video-control-show,
.mode-fullscreen &.touch-video-control-show {
.subtitle-position.subtitle-position {
&-bc,
&-bl,
&-br {
bottom: 88px !important;
.bpx-player-subtitle-panel-position {
&[data-position='bottom'] {
bottom: 14px !important;
}
}
&.touch-video-control-show .bpx-player-subtitle-panel-position {
&[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() {
display: block;
width: 36px;
color: #eee;
font: normal normal normal 20px/1 "Material Design Icons";
font: normal normal normal 20px/1 'Material Design Icons';
text-rendering: auto;
line-height: inherit;
-webkit-font-smoothing: antialiased;
@ -35,7 +35,7 @@
display: none !important;
}
&::before {
content: "\F0279";
content: '\F0279';
@include replace-with-icon();
}
}
@ -45,7 +45,7 @@
display: none !important;
}
&::before {
content: "\F08FF";
content: '\F08FF';
@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-video-control-bottom-center {
padding: 0 12px !important;
@ -100,7 +100,7 @@
display: none !important;
}
.squirtle-video-pagelist::before {
content: "\F0279";
content: '\F0279';
@include replace-with-icon();
}
}
@ -110,7 +110,7 @@
display: none !important;
}
.squirtle-video-speed::before {
content: "\F08FF";
content: '\F08FF';
@include replace-with-icon();
}
}
@ -127,8 +127,28 @@
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 {
height: 72px !important;
.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 { getBlob } from '@/core/ajax'
import { DownloadPackage } from '@/core/download'
@ -42,6 +42,7 @@ export const setupFeedImageExporter: ComponentEntry = async ({ settings: { optio
const titleData = {
user: card.username,
id: card.id,
originalUser: (card as RepostFeedsCard).repostUsername ?? card.username,
n: (index + 1).toString(),
}
pack.add(`${formatTitle(feedFormat, false, titleData)}${imageUrls[index].extension}`, blob)
@ -50,6 +51,7 @@ export const setupFeedImageExporter: ComponentEntry = async ({ settings: { optio
const packTitleData = {
user: card.username,
id: card.id,
originalUser: (card as RepostFeedsCard).repostUsername ?? card.username,
n: '',
}
await pack.emit(`${formatTitle(feedFormat, false, packTitleData)}.zip`)

View File

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

View File

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

View File

@ -126,11 +126,11 @@ export const builtInActions: Record<string, KeyBindingAction> = {
},
coin: {
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: {
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: {
displayName: '暂停/播放',
@ -143,7 +143,7 @@ export const builtInActions: Record<string, KeyBindingAction> = {
let likeClick = true
return ((context: KeyBindingActionContext) => {
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) {
return false
}

View File

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

View File

@ -2,13 +2,39 @@ import { ComponentEntry, ComponentMetadata } from '@/components/types'
import { videoChange } from '@/core/observer'
import { select } from '@/core/spin-query'
import { matchUrlPattern } from '@/core/utils'
import { useScopedConsole } from '@/core/utils/log'
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 console = useScopedConsole('展开弹幕列表')
videoChange(async () => {
if (mediaListUrls.some(url => matchUrlPattern(url)) && options.ignoreMediaList) {
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')
if (danmakuBox && danmakuBox.classList.contains('bui-collapse-wrap-folded')) {
const button = await select('.bui-collapse-header') as HTMLDivElement
@ -23,13 +49,17 @@ export const component: ComponentMetadata = {
entry,
tags: [componentsTags.video],
description: {
'zh-CN': '每次加载视频时自动展开弹幕列表.',
'zh-CN': '每次加载视频时自动展开弹幕列表, 如果弹幕装载量超过 `最大弹幕数量`, 则不展开以免对页面造成卡顿.',
},
options: {
ignoreMediaList: {
defaultValue: true,
displayName: '合集类页面不自动展开',
},
maxDanmakuCount: {
defaultValue: 500,
displayName: '最大弹幕数量',
},
},
urlInclude: [
...videoAndBangumiUrls,

View File

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

View File

@ -1,7 +1,7 @@
import { ComponentMetadata } from '@/components/types'
import { videoAndBangumiUrls } from '@/core/utils/urls'
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 style from './full-description.scss'
@ -9,11 +9,14 @@ const name = 'fullVideoDescription'
const entry = () => {
addStyle(style, name)
videoChange(async () => {
const desc = await select('.video-desc')
const desc = await select('.video-desc, .video-desc-v1')
if (!desc) {
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()
})
}

View File

@ -1,5 +1,5 @@
<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>
<span class="text">稍后再看</span>
<div class="tip" :class="{ show: tipShowing }">{{ tipText }}</div>

View File

@ -57,12 +57,12 @@ export const component = defineComponentMetadata({
// urlExclude: watchlaterUrls,
options,
reload: () => {
dqa('.ops .watchlater').forEach((it: HTMLElement) => {
it.style.display = 'inline-block'
dqa('.be-outer-watchlater').forEach((it: HTMLElement) => {
it.style.display = ''
})
},
unload: () => {
dqa('.ops .watchlater').forEach((it: HTMLElement) => {
dqa('.be-outer-watchlater').forEach((it: HTMLElement) => {
it.style.display = 'none'
})
},
@ -74,7 +74,7 @@ export const component = defineComponentMetadata({
displayName: '稍后再看',
run: 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 {
&-mask {
background: transparent !important;
}
.bilibili-player-video-control-bottom {
background: rgba(0, 0, 0, var(--video-control-opacity)) !important;
margin: 5px 0 0 0 !important;
padding: 9px 0 0 !important;
height: 30px !important;
@include on-fullscreen {
margin: -2px 0 0 0 !important;
padding: 13px 0 0 !important;
height: 45px !important;
&::before {
content: '';
position: absolute;
bottom: 0;
height: calc(100% - 5px);
width: 100%;
background-color: rgba(0, 0, 0, var(--video-control-opacity));
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 {
&.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 {
background: rgba(0, 0, 0, var(--video-control-opacity)) !important;
}

View File

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

View File

@ -3,6 +3,7 @@ import { playerAgent } from '@/components/video/player-agent'
import { lightOff, lightOn } from '@/components/video/player-light'
import { videoChange } from '@/core/observer'
import { addComponentListener, getComponentSettings } from '@/core/settings'
import { allVideoUrls } from '@/core/utils/urls'
enum IntersectionMode {
Top = '视频顶部',
@ -17,115 +18,118 @@ export const component: ComponentMetadata = {
link: 'https://github.com/FoundTheWOUT',
},
tags: [componentsTags.video],
urlInclude: allVideoUrls,
entry: async ({ settings: { options }, metadata }) => {
const settings = options as {
triggerLocation: IntersectionMode
pause: boolean
light: boolean
}
const { query: { video } } = playerAgent
Promise.resolve().then(async () => {
const { query: { video } } = playerAgent
const videoEl = await video.element() as HTMLVideoElement
// const playerWrap = await video.wrap()
// 如果有 video-player 优先的使用该盒子
// 因为在稍后再看页面medialist视频也有 player-wrap
// 选择 player-wrap 会导致闪烁。
const playerWrap = (
document.getElementById('video-player')
const videoEl = await video.element() as HTMLVideoElement
// const playerWrap = await video.wrap()
// 如果有 video-player 优先的使用该盒子
// 因为在稍后再看页面medialist视频也有 player-wrap
// 选择 player-wrap 会导致闪烁。
const playerWrap = (
document.getElementById('video-player')
?? (dq('.player-wrap') || dq('.player-module'))
) as HTMLElement
let observer: IntersectionObserver
let intersectionLock = true // Lock intersection action
let observer: IntersectionObserver
let intersectionLock = true // Lock intersection action
function getToTop(_mode: string): number {
switch (_mode) {
case IntersectionMode.Top:
return 1
case IntersectionMode.Medium:
return 0.5
case IntersectionMode.Bottom:
return 0
default:
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()
function getToTop(_mode: string): number {
switch (_mode) {
case IntersectionMode.Top:
return 1
case IntersectionMode.Medium:
return 0.5
case IntersectionMode.Bottom:
return 0
default:
return 0.5
}
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) => {
removePlayerOutEvent()
observer = createObserver(value)
addPlayerOutEvent()
observer = createObserver()
mountPlayListener()
})
observer = createObserver()
mountPlayListener()
},
displayName: '播放器位置动作',
description: {

View File

@ -1,4 +1,5 @@
import { ComponentMetadata } from '@/components/types'
import { playerAgent } from '@/components/video/player-agent'
import { videoChange } from '@/core/observer'
import { select } from '@/core/spin-query'
import { playerReady } from '@/core/utils'
@ -12,17 +13,19 @@ export const component: ComponentMetadata = {
urlInclude: videoUrls,
entry: async () => {
const autoPlayControls = {
// 命中时应该打开连播: 传统分 P, 合集
enable: [
'.base-video-sections .next-button',
'.multi-page .next-button',
':is(.base-video-sections, .base-video-sections-v1) .next-button',
':is(.multi-page, .multi-page-v1) .next-button',
'.player-auxiliary-autoplay-switch input',
],
disable: ['.recommend-list .next-button'],
// 命中时应该关闭连播: 关联视频推荐
disable: [':is(.recommend-list, .recommend-list-v1) .next-button'],
}
// 最后 1P 时不能开启连播
const disableConditions = [
// 传统分 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 合集, 布局长得比下面那个丑一点
() => 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 () => {
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 })
})
},

View File

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

View File

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

View File

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

View File

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

View File

@ -61,10 +61,10 @@ export const component = defineComponentMetadata({
},
entry,
unload: () => {
dqa('.ops .quick-favorite').forEach((it: HTMLElement) => (it.style.display = 'none'))
dqa('.be-quick-favorite').forEach((it: HTMLElement) => (it.style.display = ''))
},
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,
// urlExclude: favoriteListUrls,
@ -80,7 +80,7 @@ export const component = defineComponentMetadata({
displayName: '快速收藏',
run: 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",
"copyright": "[year], Grant Howard (https://github.com/the1812) & Coulomb-G (https://github.com/Coulomb-G)",
"license": "MIT",

View File

@ -5,9 +5,19 @@ import { FeedsCardsManagerV1 } from './v1'
import { FeedsCardsManagerV2 } from './v2'
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 = (() => {
const isV2Feeds = parseInt(getCookieValue('hit-dyn-v2')) > 0 && location.host === 't.bilibili.com'
if (isV2Feeds) {
const isV2 = isV2Feeds()
if (isV2) {
return new FeedsCardsManagerV2()
}
return new FeedsCardsManagerV1()

View File

@ -3,7 +3,12 @@
<div
v-for="item of items"
: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() }"
:data-name="item.name"
@click="item.action($event)"
@ -51,7 +56,7 @@ export default Vue.extend({
width: auto;
padding-top: 1px;
.be-icon {
transition: transform .2s ease-out;
transition: transform 0.2s ease-out;
}
.be-icon svg,
.be-icon {
@ -93,7 +98,7 @@ export default Vue.extend({
align-items: center;
}
&:hover .be-video-control-tooltip {
transition: all .3s ease-in-out .3s;
transition: all 0.3s ease-in-out 0.3s;
opacity: 1;
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>

View File

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

View File

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

View File

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