From 6a0ecc350611d7248e89a55f99fd3c6794f13fab Mon Sep 17 00:00:00 2001 From: the1812 Date: Mon, 12 Dec 2022 23:15:56 +0800 Subject: [PATCH 1/3] Refactor player agent files --- .../lib/components/utils/keymap/bindings.ts | 2 +- src/components/video/player-agent.ts | 480 ------------------ src/components/video/player-agent/bangumi.ts | 104 ++++ src/components/video/player-agent/base.ts | 97 ++++ src/components/video/player-agent/index.ts | 10 + .../video/player-agent/live-player.ts | 0 src/components/video/player-agent/types.ts | 67 +++ .../video/player-agent/video-player-mixed.ts | 83 +++ .../video/player-agent/video-player-v2.ts | 141 +++++ 9 files changed, 503 insertions(+), 481 deletions(-) delete mode 100644 src/components/video/player-agent.ts create mode 100644 src/components/video/player-agent/bangumi.ts create mode 100644 src/components/video/player-agent/base.ts create mode 100644 src/components/video/player-agent/index.ts create mode 100644 src/components/video/player-agent/live-player.ts create mode 100644 src/components/video/player-agent/types.ts create mode 100644 src/components/video/player-agent/video-player-mixed.ts create mode 100644 src/components/video/player-agent/video-player-v2.ts diff --git a/registry/lib/components/utils/keymap/bindings.ts b/registry/lib/components/utils/keymap/bindings.ts index 2ba47b456..f86b83508 100644 --- a/registry/lib/components/utils/keymap/bindings.ts +++ b/registry/lib/components/utils/keymap/bindings.ts @@ -13,7 +13,7 @@ export interface KeyBindingActionContext { } export interface KeyBindingAction { displayName: string - run: (context: KeyBindingActionContext) => any + run: (context: KeyBindingActionContext) => unknown prevent?: boolean ignoreTyping?: boolean } diff --git a/src/components/video/player-agent.ts b/src/components/video/player-agent.ts deleted file mode 100644 index f259b4426..000000000 --- a/src/components/video/player-agent.ts +++ /dev/null @@ -1,480 +0,0 @@ -import { select } from '@/core/spin-query' -import { isBwpVideo, raiseEvent } from '@/core/utils' -import { bangumiUrls, matchCurrentPage } from '@/core/utils/urls' -import { bpxPlayerPolyfill } from './player-adaptor/bpx' -import { v3PlayerPolyfill } from './player-adaptor/v3' - -/** 元素查询函数, 调用时执行 `SpinQuery.select` 查询, 可访问 `selector` 获取选择器 */ -type ElementQuery = { - (): Promise - sync: () => Target | null - selector: string -} -interface CustomNestedQuery { - [key: string]: QueryResult | CustomNestedQuery -} -interface CustomQuery extends CustomNestedQuery { - [key: string]: QueryResult -} -type AgentType = 'video' | 'bangumi' -type CustomQueryProvider = { - [key in AgentType]?: TargetType -} & { video: TargetType } -interface PlayerQuery extends CustomNestedQuery { - playerWrap: QueryResult - bilibiliPlayer: QueryResult - playerArea: QueryResult - video: { - element: QueryResult - wrap: QueryResult - top: QueryResult - state: QueryResult - panel: QueryResult - popup: QueryResult - subtitle: QueryResult - basDanmaku: QueryResult - advDanmaku: QueryResult - danmaku: QueryResult - container: QueryResult - } - control: { - element: QueryResult - wrap: QueryResult - mask: QueryResult - top: QueryResult - progress: QueryResult - bottom: QueryResult - bottomLeft: QueryResult - bottomCenter: QueryResult - bottomRight: QueryResult - buttons: { - start: QueryResult - next: QueryResult - time: QueryResult - quality: QueryResult - pageList: QueryResult - speed: QueryResult - subtitle: QueryResult - volume: QueryResult - settings: QueryResult - pip: QueryResult - widescreen: QueryResult - webFullscreen: QueryResult - fullscreen: QueryResult - } - settings: { - wrap: QueryResult - lightOff: QueryResult - } - } - toastWrap: QueryResult - danmakuTipLayer: QueryResult - danmakuSwitch: QueryResult -} -const elementQuery = (selector: string): ElementQuery => { - const func = () => select(selector) - func.selector = selector - func.sync = () => dq(selector) as HTMLElement - return func -} -const selectorWrap = (query: CustomNestedQuery): CustomNestedQuery => { - const map = (value: string | Record): ElementQuery | Record => { - if (typeof value !== 'string') { - return lodash.mapValues(value, map) - } - return elementQuery(value) - } - return lodash.mapValues(query, map) as CustomNestedQuery -} -const click = (target: ElementQuery) => { - const button = target.sync() - button?.click() - return button -} -export abstract class PlayerAgent { - abstract type: AgentType - abstract query: PlayerQuery - provideCustomQuery>( - config: CustomQueryProvider, - ) { - const custom = selectorWrap(config[this.type] ?? config.video) as CustomQuery - return { - ...this, - custom, - } as this & { custom: { [key in keyof CustomQueryType]: ElementQuery } } - } - widescreen() { - return click(this.query.control.buttons.widescreen) - } - webFullscreen() { - return click(this.query.control.buttons.webFullscreen) - } - fullscreen() { - return click(this.query.control.buttons.fullscreen) - } - togglePlay() { - return click(this.query.control.buttons.start) - } - togglePip() { - return click(this.query.control.buttons.pip) - } - toggleMute() { - return click(this.query.control.buttons.volume) - } - toggleDanmaku() { - const checkbox = this.query.danmakuSwitch.sync() as HTMLInputElement - if (!checkbox) { - return null - } - checkbox.checked = !checkbox.checked - raiseEvent(checkbox, 'change') - return checkbox.checked - } - - /** true 开灯,false 关灯 */ - async toggleLight(on: boolean) { - const checkbox = (await this.query.control.settings.lightOff()) as HTMLInputElement - // 关灯状态 && 要开灯 -> 开灯 - checkbox.checked && on && checkbox.click() - // 开灯状态 && 要关灯 -> 关灯 - !checkbox.checked && !on && checkbox.click() - } - - // eslint-disable-next-line class-methods-use-this - getPlayerConfig(target: string) { - return lodash.get(JSON.parse(localStorage.getItem('bilibili_player_settings')), target, false) - } - - isAutoPlay() { - return this.getPlayerConfig('video_status.autoplay') - } - - abstract isMute(): boolean - /** 更改音量 (%) */ - abstract changeVolume(change: number): number - /** 跳转到指定时间 */ - abstract seek(time: number): number - /** 更改时间 */ - abstract changeTime(change: number): number -} -export class VideoPlayerV2Agent extends PlayerAgent { - // eslint-disable-next-line class-methods-use-this - get nativeApi() { - return unsafeWindow.player - } - type: AgentType = 'video' - query = selectorWrap({ - playerWrap: '.player-wrap', - bilibiliPlayer: '.bilibili-player', - playerArea: '.bilibili-player-area', - video: { - element: '.bilibili-player-video video', - wrap: '.bilibili-player-video-wrap', - top: '.bilibili-player-video-top', - state: '.bilibili-player-video-state', - panel: '.bilibili-player-video-panel', - popup: '.bilibili-player-video-popup', - subtitle: '.bilibili-player-video-subtitle', - basDanmaku: '.bilibili-player-video-bas-danmaku', - advDanmaku: '.bilibili-player-video-adv-danmaku', - danmaku: '.bilibili-player-video-danmaku', - container: '.bilibili-player-video', - }, - control: { - element: '.bilibili-player-control', - wrap: '.bilibili-player-control-wrap', - mask: '.bilibili-player-control-mask', - top: '.bilibili-player-control-top', - progress: '.bilibili-player-video-progress', - bottom: '.bilibili-player-control-bottom', - bottomLeft: '.bilibili-player-control-bottom-left', - bottomCenter: '.bilibili-player-control-bottom-center', - bottomRight: '.bilibili-player-control-bottom-right', - buttons: { - start: '.bilibili-player-video-btn-start', - next: '.bilibili-player-video-btn-next', - time: '.bilibili-player-video-time', - quality: '.bilibili-player-btn-quality', - pageList: '.bilibili-player-video-btn-pagelist', - speed: '.bilibili-player-video-btn-speed', - subtitle: '.bilibili-player-video-btn-subtitle', - volume: '.bilibili-player-video-btn-volume .bilibili-player-iconfont-volume', - settings: '.bilibili-player-video-btn-setting', - pip: '.bilibili-player-video-btn-pip', - widescreen: '.bilibili-player-video-btn-widescreen', - webFullscreen: '.bilibili-player-video-web-fullscreen', - fullscreen: '.bilibili-player-video-btn-fullscreen', - }, - settings: { - wrap: '.bilibili-player-video-btn-setting-wrap', - lightOff: - '.bilibili-player-video-btn-setting-right-others-content-lightoff .bui-checkbox-input', - }, - }, - toastWrap: '.bilibili-player-video-toast-wrp', - danmakuTipLayer: '.bilibili-player-dm-tip-wrap', - danmakuSwitch: '.bilibili-player-video-danmaku-switch input', - }) as PlayerQuery - - constructor() { - super() - this.checkBwpVideo() - } - checkBwpVideo() { - const videoSelector = this.query.video.element.selector - const bwpSelector = '.bilibili-player-video bwp-video,.bpx-player-video-area bwp-video' - this.query.video.element = (() => { - const func = async () => { - if (await isBwpVideo()) { - return select(bwpSelector) - } - return select(videoSelector) - } - func.selector = videoSelector - func.sync = () => dq(videoSelector) as HTMLElement - isBwpVideo().then(bwp => { - if (!bwp) { - return - } - func.selector = bwpSelector - func.sync = () => dq(bwpSelector) as HTMLElement - }) - return func - })() - } - - isMute() { - 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) - } - seek(time: number) { - if (!this.nativeApi) { - return null - } - this.nativeApi.play() - setTimeout(() => { - this.nativeApi.seek(time) - const toastText = dq( - '.bilibili-player-video-toast-bottom .bilibili-player-video-toast-item:first-child .bilibili-player-video-toast-item-text span:nth-child(2)', - ) - if (toastText) { - toastText.textContent = ' 00:00' - } - }) - return this.nativeApi.getCurrentTime() - } - changeTime(change: number) { - if (!this.nativeApi) { - return null - } - const video = this.query.video.element.sync() as HTMLVideoElement - if (!video) { - return null - } - this.nativeApi.seek(video.currentTime + change, video.paused) - return this.nativeApi.getCurrentTime() - } -} -export class BangumiPlayerAgent extends PlayerAgent { - type: AgentType = 'bangumi' - query = selectorWrap({ - playerWrap: '.player-module', - bilibiliPlayer: '.bpx-player-container', - playerArea: '.bpx-player-primary-area', - video: { - element: '.bpx-player-video-wrap video', - wrap: '.bpx-player-video-area', - top: '.bpx-player-top-wrap', - state: '.bpx-player-state-wrap', - panel: '.bpx-player-ending-panel', - popup: '.bpx-player-dialog-wrap', - subtitle: '.bpx-player-subtitle-wrap', - basDanmaku: '.bpx-player-bas-dm-wrap', - advDanmaku: '.bpx-player-adv-dm-wrap', - danmaku: '.bpx-player-row-dm-wrap', - container: '.bpx-player-video-wrap', - }, - control: { - element: '.squirtle-controller', - wrap: '.bpx-player-control-wrap', - mask: '.bpx-player-control-mask', - top: '.bpx-player-control-top', - progress: '.squirtle-progress-wrap', - bottom: '.squirtle-controller-wrap', - bottomLeft: '.squirtle-controller-wrap-left', - bottomCenter: '.squirtle-controller-wrap-center', - bottomRight: '.squirtle-controller-wrap-right', - buttons: { - start: '.squirtle-video-start', - next: '.squirtle-video-next', - time: '.squirtle-time-wrap', - quality: '.squirtle-video-quality', - pageList: '.squirtle-video-pagelist', - speed: '.squirtle-video-speed', - subtitle: '.squirtle-video-subtitle', - volume: '.squirtle-video-volume .squirtle-volume-icon', - settings: '.squirtle-video-setting', - pip: '.squirtle-video-pip', - widescreen: '.squirtle-video-widescreen', - webFullscreen: '.squirtle-video-pagefullscreen', - fullscreen: '.squirtle-video-fullscreen', - }, - settings: { - wrap: '.squirtle-setting-wrap', - lightOff: '.squirtle-lightoff', - }, - }, - toastWrap: '.bpx-player-tooltip-area', - danmakuTipLayer: '.bpx-player-dialog-wrap', - danmakuSwitch: '.bpx-player-dm-switch input', - }) as PlayerQuery - constructor() { - super() - bpxPlayerPolyfill() - } - isMute() { - const icon = this.query.control.buttons.volume.sync() as HTMLElement - return icon?.classList.contains('squirtle-volume-mute-state') ?? false - } - changeVolume(change: number) { - const video = this.query.video.element.sync() as HTMLVideoElement - if (!video) { - return null - } - video.volume = lodash.clamp(video.volume + change / 100, 0, 1) - return Math.round(video.volume * 100) - } - seek(time: number) { - const video = this.query.video.element.sync() as HTMLVideoElement - if (!video) { - return null - } - video.play() - setTimeout(() => { - video.currentTime = lodash.clamp(time, 0, video.duration) - const toastText = dq('.bpx-player-toast-row .bpx-player-toast-item .bpx-player-toast-text') - if (toastText?.textContent?.startsWith('已为您定位至')) { - toastText.textContent = '已为您定位至00:00' - } - }) - return video.currentTime - } - changeTime(change: number) { - const video = this.query.video.element.sync() as HTMLVideoElement - if (!video) { - return null - } - video.currentTime = lodash.clamp(video.currentTime + change, 0, video.duration) - return video.currentTime - } - async toggleLight(on: boolean) { - const checkbox = this.query.control.settings.lightOff.sync() - // 开灯状态 && 关灯 -> 关灯 - !checkbox.classList.contains('active') && !on && checkbox.click() - // 关灯状态 && 开灯 -> 开灯 - checkbox.classList.contains('active') && on && checkbox.click() - } -} -export class VideoPlayerMixedAgent extends VideoPlayerV2Agent { - query = selectorWrap({ - playerWrap: '.player-wrap', - 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-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', - 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', - }, - 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', - }, - }, - 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 - - constructor() { - super() - this.checkBwpVideo() - v3PlayerPolyfill() - } - - seek(time: number) { - if (!this.nativeApi) { - return null - } - this.nativeApi.play() - setTimeout(() => { - this.nativeApi.seek(time) - const toastText = dq( - '.bilibili-player-video-toast-bottom .bilibili-player-video-toast-item:first-child .bilibili-player-video-toast-item-text span:nth-child(2)', - ) - if (toastText) { - toastText.textContent = ' 00:00' - } - }) - return this.nativeApi.getCurrentTime() - } -} - -export const playerAgent = (() => { - if (matchCurrentPage(bangumiUrls)) { - return new BangumiPlayerAgent() - } - return new VideoPlayerMixedAgent() -})() diff --git a/src/components/video/player-agent/bangumi.ts b/src/components/video/player-agent/bangumi.ts new file mode 100644 index 000000000..fd43c9e08 --- /dev/null +++ b/src/components/video/player-agent/bangumi.ts @@ -0,0 +1,104 @@ +import { bpxPlayerPolyfill } from '../player-adaptor/bpx' +import { PlayerAgent, selectorWrap } from './base' +import { AgentType, PlayerQuery, ElementQuery } from './types' + +export class BangumiPlayerAgent extends PlayerAgent { + type: AgentType = 'bangumi' + query = selectorWrap({ + playerWrap: '.player-module', + bilibiliPlayer: '.bpx-player-container', + playerArea: '.bpx-player-primary-area', + video: { + element: '.bpx-player-video-wrap video', + wrap: '.bpx-player-video-area', + top: '.bpx-player-top-wrap', + state: '.bpx-player-state-wrap', + panel: '.bpx-player-ending-panel', + popup: '.bpx-player-dialog-wrap', + subtitle: '.bpx-player-subtitle-wrap', + basDanmaku: '.bpx-player-bas-dm-wrap', + advDanmaku: '.bpx-player-adv-dm-wrap', + danmaku: '.bpx-player-row-dm-wrap', + container: '.bpx-player-video-wrap', + }, + control: { + element: '.squirtle-controller', + wrap: '.bpx-player-control-wrap', + mask: '.bpx-player-control-mask', + top: '.bpx-player-control-top', + progress: '.squirtle-progress-wrap', + bottom: '.squirtle-controller-wrap', + bottomLeft: '.squirtle-controller-wrap-left', + bottomCenter: '.squirtle-controller-wrap-center', + bottomRight: '.squirtle-controller-wrap-right', + buttons: { + start: '.squirtle-video-start', + next: '.squirtle-video-next', + time: '.squirtle-time-wrap', + quality: '.squirtle-video-quality', + pageList: '.squirtle-video-pagelist', + speed: '.squirtle-video-speed', + subtitle: '.squirtle-video-subtitle', + volume: '.squirtle-video-volume .squirtle-volume-icon', + settings: '.squirtle-video-setting', + pip: '.squirtle-video-pip', + widescreen: '.squirtle-video-widescreen', + webFullscreen: '.squirtle-video-pagefullscreen', + fullscreen: '.squirtle-video-fullscreen', + }, + settings: { + wrap: '.squirtle-setting-wrap', + lightOff: '.squirtle-lightoff', + }, + }, + toastWrap: '.bpx-player-tooltip-area', + danmakuTipLayer: '.bpx-player-dialog-wrap', + danmakuSwitch: '.bpx-player-dm-switch input', + }) as PlayerQuery + constructor() { + super() + bpxPlayerPolyfill() + } + isMute() { + const icon = this.query.control.buttons.volume.sync() as HTMLElement + return icon?.classList.contains('squirtle-volume-mute-state') ?? false + } + changeVolume(change: number) { + const video = this.query.video.element.sync() as HTMLVideoElement + if (!video) { + return null + } + video.volume = lodash.clamp(video.volume + change / 100, 0, 1) + return Math.round(video.volume * 100) + } + seek(time: number) { + const video = this.query.video.element.sync() as HTMLVideoElement + if (!video) { + return null + } + video.play() + setTimeout(() => { + video.currentTime = lodash.clamp(time, 0, video.duration) + const toastText = dq('.bpx-player-toast-row .bpx-player-toast-item .bpx-player-toast-text') + if (toastText?.textContent?.startsWith('已为您定位至')) { + toastText.textContent = '已为您定位至00:00' + } + }) + return video.currentTime + } + changeTime(change: number) { + const video = this.query.video.element.sync() as HTMLVideoElement + if (!video) { + return null + } + video.currentTime = lodash.clamp(video.currentTime + change, 0, video.duration) + return video.currentTime + } + async toggleLight(on: boolean) { + const checkbox = this.query.control.settings.lightOff.sync() + // 开灯状态 && 关灯 -> 关灯 + !checkbox.classList.contains('active') && !on && checkbox.click() + // 关灯状态 && 开灯 -> 开灯 + checkbox.classList.contains('active') && on && checkbox.click() + } +} diff --git a/src/components/video/player-agent/base.ts b/src/components/video/player-agent/base.ts new file mode 100644 index 000000000..7286e4036 --- /dev/null +++ b/src/components/video/player-agent/base.ts @@ -0,0 +1,97 @@ +import { select } from '@/core/spin-query' +import { raiseEvent } from '@/core/utils' +import { + ElementQuery, + CustomNestedQuery, + AgentType, + PlayerQuery, + CustomQuery, + CustomQueryProvider, +} from './types' + +export const elementQuery = (selector: string): ElementQuery => { + const func = () => select(selector) + func.selector = selector + func.sync = () => dq(selector) as HTMLElement + return func +} +export const selectorWrap = (query: CustomNestedQuery): CustomNestedQuery => { + const map = (value: string | Record): ElementQuery | Record => { + if (typeof value !== 'string') { + return lodash.mapValues(value, map) + } + return elementQuery(value) + } + return lodash.mapValues(query, map) as CustomNestedQuery +} +export const click = (target: ElementQuery) => { + const button = target.sync() + button?.click() + return button +} +export abstract class PlayerAgent { + abstract type: AgentType + abstract query: PlayerQuery + provideCustomQuery>( + config: CustomQueryProvider, + ) { + const custom = selectorWrap(config[this.type] ?? config.video) as CustomQuery + return { + ...this, + custom, + } as this & { custom: { [key in keyof CustomQueryType]: ElementQuery } } + } + widescreen() { + return click(this.query.control.buttons.widescreen) + } + webFullscreen() { + return click(this.query.control.buttons.webFullscreen) + } + fullscreen() { + return click(this.query.control.buttons.fullscreen) + } + togglePlay() { + return click(this.query.control.buttons.start) + } + togglePip() { + return click(this.query.control.buttons.pip) + } + toggleMute() { + return click(this.query.control.buttons.volume) + } + toggleDanmaku() { + const checkbox = this.query.danmakuSwitch.sync() as HTMLInputElement + if (!checkbox) { + return null + } + checkbox.checked = !checkbox.checked + raiseEvent(checkbox, 'change') + return checkbox.checked + } + + /** true 开灯,false 关灯 */ + async toggleLight(on: boolean) { + const checkbox = (await this.query.control.settings.lightOff()) as HTMLInputElement + // 关灯状态 && 要开灯 -> 开灯 + checkbox.checked && on && checkbox.click() + // 开灯状态 && 要关灯 -> 关灯 + !checkbox.checked && !on && checkbox.click() + } + + // eslint-disable-next-line class-methods-use-this + getPlayerConfig(target: string) { + return lodash.get(JSON.parse(localStorage.getItem('bilibili_player_settings')), target, false) + } + + isAutoPlay() { + return this.getPlayerConfig('video_status.autoplay') + } + + abstract isMute(): boolean + /** 更改音量 (%) */ + abstract changeVolume(change: number): number + /** 跳转到指定时间 */ + abstract seek(time: number): number + /** 更改时间 */ + abstract changeTime(change: number): number +} diff --git a/src/components/video/player-agent/index.ts b/src/components/video/player-agent/index.ts new file mode 100644 index 000000000..527f8d5ae --- /dev/null +++ b/src/components/video/player-agent/index.ts @@ -0,0 +1,10 @@ +import { bangumiUrls, matchCurrentPage } from '@/core/utils/urls' +import { BangumiPlayerAgent } from './bangumi' +import { VideoPlayerMixedAgent } from './video-player-mixed' + +export const playerAgent = (() => { + if (matchCurrentPage(bangumiUrls)) { + return new BangumiPlayerAgent() + } + return new VideoPlayerMixedAgent() +})() diff --git a/src/components/video/player-agent/live-player.ts b/src/components/video/player-agent/live-player.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/video/player-agent/types.ts b/src/components/video/player-agent/types.ts new file mode 100644 index 000000000..b91edd553 --- /dev/null +++ b/src/components/video/player-agent/types.ts @@ -0,0 +1,67 @@ +/** 元素查询函数, 调用时执行 `SpinQuery.select` 查询, 可访问 `selector` 获取选择器 */ +export type ElementQuery = { + (): Promise + sync: () => Target | null + selector: string +} +export interface CustomNestedQuery { + [key: string]: QueryResult | CustomNestedQuery +} +export interface CustomQuery extends CustomNestedQuery { + [key: string]: QueryResult +} +export type AgentType = 'video' | 'bangumi' | 'live' +export type CustomQueryProvider = { + [key in AgentType]?: TargetType +} & { video: TargetType } +export interface PlayerQuery extends CustomNestedQuery { + playerWrap: QueryResult + bilibiliPlayer: QueryResult + playerArea: QueryResult + video: { + element: QueryResult + wrap: QueryResult + top: QueryResult + state: QueryResult + panel: QueryResult + popup: QueryResult + subtitle: QueryResult + basDanmaku: QueryResult + advDanmaku: QueryResult + danmaku: QueryResult + container: QueryResult + } + control: { + element: QueryResult + wrap: QueryResult + mask: QueryResult + top: QueryResult + progress: QueryResult + bottom: QueryResult + bottomLeft: QueryResult + bottomCenter: QueryResult + bottomRight: QueryResult + buttons: { + start: QueryResult + next: QueryResult + time: QueryResult + quality: QueryResult + pageList: QueryResult + speed: QueryResult + subtitle: QueryResult + volume: QueryResult + settings: QueryResult + pip: QueryResult + widescreen: QueryResult + webFullscreen: QueryResult + fullscreen: QueryResult + } + settings: { + wrap: QueryResult + lightOff: QueryResult + } + } + toastWrap: QueryResult + danmakuTipLayer: QueryResult + danmakuSwitch: QueryResult +} diff --git a/src/components/video/player-agent/video-player-mixed.ts b/src/components/video/player-agent/video-player-mixed.ts new file mode 100644 index 000000000..1f71d7586 --- /dev/null +++ b/src/components/video/player-agent/video-player-mixed.ts @@ -0,0 +1,83 @@ +import { v3PlayerPolyfill } from '../player-adaptor/v3' +import { selectorWrap } from './base' +import { PlayerQuery, ElementQuery } from './types' +import { VideoPlayerV2Agent } from './video-player-v2' + +export class VideoPlayerMixedAgent extends VideoPlayerV2Agent { + query = selectorWrap({ + playerWrap: '.player-wrap', + 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-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', + 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', + }, + 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', + }, + }, + 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 + + constructor() { + super() + this.checkBwpVideo() + v3PlayerPolyfill() + } + + seek(time: number) { + if (!this.nativeApi) { + return null + } + this.nativeApi.play() + setTimeout(() => { + this.nativeApi.seek(time) + const toastText = dq( + '.bilibili-player-video-toast-bottom .bilibili-player-video-toast-item:first-child .bilibili-player-video-toast-item-text span:nth-child(2)', + ) + if (toastText) { + toastText.textContent = ' 00:00' + } + }) + return this.nativeApi.getCurrentTime() + } +} diff --git a/src/components/video/player-agent/video-player-v2.ts b/src/components/video/player-agent/video-player-v2.ts new file mode 100644 index 000000000..e32889683 --- /dev/null +++ b/src/components/video/player-agent/video-player-v2.ts @@ -0,0 +1,141 @@ +import { select } from '@/core/spin-query' +import { isBwpVideo } from '@/core/utils' +import { PlayerAgent, selectorWrap } from './base' +import { AgentType, PlayerQuery, ElementQuery } from './types' + +export class VideoPlayerV2Agent extends PlayerAgent { + // eslint-disable-next-line class-methods-use-this + get nativeApi() { + return unsafeWindow.player + } + type: AgentType = 'video' + query = selectorWrap({ + playerWrap: '.player-wrap', + bilibiliPlayer: '.bilibili-player', + playerArea: '.bilibili-player-area', + video: { + element: '.bilibili-player-video video', + wrap: '.bilibili-player-video-wrap', + top: '.bilibili-player-video-top', + state: '.bilibili-player-video-state', + panel: '.bilibili-player-video-panel', + popup: '.bilibili-player-video-popup', + subtitle: '.bilibili-player-video-subtitle', + basDanmaku: '.bilibili-player-video-bas-danmaku', + advDanmaku: '.bilibili-player-video-adv-danmaku', + danmaku: '.bilibili-player-video-danmaku', + container: '.bilibili-player-video', + }, + control: { + element: '.bilibili-player-control', + wrap: '.bilibili-player-control-wrap', + mask: '.bilibili-player-control-mask', + top: '.bilibili-player-control-top', + progress: '.bilibili-player-video-progress', + bottom: '.bilibili-player-control-bottom', + bottomLeft: '.bilibili-player-control-bottom-left', + bottomCenter: '.bilibili-player-control-bottom-center', + bottomRight: '.bilibili-player-control-bottom-right', + buttons: { + start: '.bilibili-player-video-btn-start', + next: '.bilibili-player-video-btn-next', + time: '.bilibili-player-video-time', + quality: '.bilibili-player-btn-quality', + pageList: '.bilibili-player-video-btn-pagelist', + speed: '.bilibili-player-video-btn-speed', + subtitle: '.bilibili-player-video-btn-subtitle', + volume: '.bilibili-player-video-btn-volume .bilibili-player-iconfont-volume', + settings: '.bilibili-player-video-btn-setting', + pip: '.bilibili-player-video-btn-pip', + widescreen: '.bilibili-player-video-btn-widescreen', + webFullscreen: '.bilibili-player-video-web-fullscreen', + fullscreen: '.bilibili-player-video-btn-fullscreen', + }, + settings: { + wrap: '.bilibili-player-video-btn-setting-wrap', + lightOff: + '.bilibili-player-video-btn-setting-right-others-content-lightoff .bui-checkbox-input', + }, + }, + toastWrap: '.bilibili-player-video-toast-wrp', + danmakuTipLayer: '.bilibili-player-dm-tip-wrap', + danmakuSwitch: '.bilibili-player-video-danmaku-switch input', + }) as PlayerQuery + + constructor() { + super() + this.checkBwpVideo() + } + checkBwpVideo() { + const videoSelector = this.query.video.element.selector + const bwpSelector = '.bilibili-player-video bwp-video,.bpx-player-video-area bwp-video' + this.query.video.element = (() => { + const func = async () => { + if (await isBwpVideo()) { + return select(bwpSelector) + } + return select(videoSelector) + } + func.selector = videoSelector + func.sync = () => dq(videoSelector) as HTMLElement + isBwpVideo().then(bwp => { + if (!bwp) { + return + } + func.selector = bwpSelector + func.sync = () => dq(bwpSelector) as HTMLElement + }) + return func + })() + } + + isMute() { + 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) + } + seek(time: number) { + if (!this.nativeApi) { + return null + } + this.nativeApi.play() + setTimeout(() => { + this.nativeApi.seek(time) + const toastText = dq( + '.bilibili-player-video-toast-bottom .bilibili-player-video-toast-item:first-child .bilibili-player-video-toast-item-text span:nth-child(2)', + ) + if (toastText) { + toastText.textContent = ' 00:00' + } + }) + return this.nativeApi.getCurrentTime() + } + changeTime(change: number) { + if (!this.nativeApi) { + return null + } + const video = this.query.video.element.sync() as HTMLVideoElement + if (!video) { + return null + } + this.nativeApi.seek(video.currentTime + change, video.paused) + return this.nativeApi.getCurrentTime() + } +} From 93f1b8009457c588d32baff1c8e981b1475d939d Mon Sep 17 00:00:00 2001 From: the1812 Date: Mon, 12 Dec 2022 23:43:25 +0800 Subject: [PATCH 2/3] Block ads in search page (fix #3863) --- .../utils/remove-promotions/index.ts | 7 ++++++ .../remove-promotions/remove-promotions.scss | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/registry/lib/components/utils/remove-promotions/index.ts b/registry/lib/components/utils/remove-promotions/index.ts index d910260f4..3854cc28b 100644 --- a/registry/lib/components/utils/remove-promotions/index.ts +++ b/registry/lib/components/utils/remove-promotions/index.ts @@ -67,6 +67,13 @@ const entry: ComponentEntry = async ({ settings, metadata }) => { }, true, ) + addComponentListener( + `${metadata.name}.showPlaceholder`, + (value: boolean) => { + document.body.classList.toggle('promotion-show-placeholder', value) + }, + true, + ) } export const component = defineComponentMetadata({ name: 'removePromotions', diff --git a/registry/lib/components/utils/remove-promotions/remove-promotions.scss b/registry/lib/components/utils/remove-promotions/remove-promotions.scss index 81996d44c..b0be7f671 100644 --- a/registry/lib/components/utils/remove-promotions/remove-promotions.scss +++ b/registry/lib/components/utils/remove-promotions/remove-promotions.scss @@ -1,3 +1,5 @@ +@import 'common'; + #slide_ad, .v-wrap .vcd, .ad-report, @@ -76,3 +78,25 @@ body.dark .blocked-ads.new { pointer-events: none !important; opacity: 0 !important; } +.bili-video-card__wrap { + a[href*="cm.bilibili.com"] { + > * { + visibility: hidden !important; + } + & ~ .bili-video-card__info { + display: none !important; + } + body.promotion-show-placeholder &::before { + content: "🚫已屏蔽广告"; + border-radius: 6px; + @include h-center(); + justify-content: center; + position: absolute; + z-index: 2; + font-size: 16px; + width: 100%; + height: 100%; + background-color: #8882; + } + } +} From 8f144ee9e4bd5f547d6e1547a7f0d5aecec60d7c Mon Sep 17 00:00:00 2001 From: the1812 Date: Tue, 13 Dec 2022 22:59:04 +0800 Subject: [PATCH 3/3] Fix comment styles (fix #3872) --- registry/lib/components/style/simplify/comments/comments-v2.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/registry/lib/components/style/simplify/comments/comments-v2.scss b/registry/lib/components/style/simplify/comments/comments-v2.scss index 34bc438ed..2e5288fd8 100644 --- a/registry/lib/components/style/simplify/comments/comments-v2.scss +++ b/registry/lib/components/style/simplify/comments/comments-v2.scss @@ -45,6 +45,7 @@ $prefix: 'simplifyComments-switch'; .reply-info, .sub-reply-info { display: inline-flex !important; + flex-basis: 100% !important; } .reply-time, .sub-reply-time {